Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions collections/Appliances/Add appliance Rate.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
meta {
name: Add appliance Rate
type: http
seq: 1
}

post {
url: {{mpm_backend_url}}/api/appliances/payment/41
body: json
auth: inherit
}

body:json {
{
"person_id": 261,
"admin_id": null,
"amount": "500",
"payment_provider": 19
}
}
8 changes: 8 additions & 0 deletions collections/Appliances/folder.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
meta {
name: Appliances
seq: 43
}

auth {
mode: inherit
}
38 changes: 38 additions & 0 deletions docs/integrations-guide/images/vodacom-mz-banner-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 47 additions & 0 deletions docs/integrations-guide/vodacom-mz.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
order: 38
---

<p align="center">
<a href="https://www.vm.co.mz/m-pesa">
<img
src="https://developer.mpesa.vm.co.mz/static/banner_logo.svg"
alt="Vodacom MZ"
width="160"
>
</a>
</p>

# Vodacom MZ

Vodacom MZ M-Pesa suppors two fundamentally different way to programmatically interact with transactions.

MPM-initiated transactions

M-Pesa Generic C2B API Integration (customer-initiated transactions)

## M-Pesa OpenAPI Integration (MPM-initiated transactions)

### Onboarding (OpenAPI)

- Create an [Developer Portal](https://developer.mpesa.vm.co.mz/) account (this immediately gives access to Test environment)
- API Keys
-

Finally, click `Request Validation` to initiate the process on generating live API keys.

> [!INFO]
> This is a manual process on Vodacom side and can take varying amount of time.

### Usage (OpenAPI)

Because ...

## M-Pesa Generic C2B API Integration (customer-initiated transactions)

### Onboarding (Generic C2B API)

Onboarding to use the Vodacom Generic C2B API is a custom process with certain security implications.
A full implementation might require the establishment of a VPN tunnel and certificate exchange.

The exact steps are T.B.D.
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,24 @@

namespace App\Plugins\VodacomMzPaymentProvider\Console\Commands;

use App\Plugins\VodacomMzPaymentProvider\Services\VodacomMzCredentialService;
use Illuminate\Console\Command;

class InstallPackage extends Command {
protected $signature = 'vodacom-mz-payment-provider:install';
protected $description = 'Install VodacomMzPaymentProvider Package';

public function __construct() {
public function __construct(
private VodacomMzCredentialService $credentialService,
) {
parent::__construct();
}

public function handle(): void {
$this->info('Installing VodacomMzPaymentProvider Integration Package\n');

$this->credentialService->getCredentials();

$this->info('Package installed successfully..');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

namespace App\Plugins\VodacomMzPaymentProvider\Exceptions;

class VodacomMzApiResponseException extends \Exception {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

namespace App\Plugins\VodacomMzPaymentProvider\Http\Clients;

use App\Plugins\VodacomMzPaymentProvider\Exceptions\VodacomMzApiResponseException;
use App\Plugins\VodacomMzPaymentProvider\Models\VodacomMzCredential;
use App\Plugins\VodacomMzPaymentProvider\Services\VodacomMzCredentialService;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Facades\Log;

class VodacomMzApiClient {
/** IPG returns this in `output_ResponseCode` when a request is accepted. */
public const RESPONSE_SUCCESS = 'INS-0';

public function __construct(
private Client $httpClient,
private VodacomMzCredentialService $credentialService,
) {}

/**
* Customer-to-business single-stage payment (pushes a PIN prompt to the payer).
*
* @return array<string, mixed>
*/
public function c2bPayment(
string $transactionReference,
string $customerMsisdn,
float $amount,
string $thirdPartyReference,
): array {
$credential = $this->credentialService->getCredentials();

return $this->send($credential, 'POST', 18352, '/ipg/v1x/c2bPayment/singleStage/', [
'input_TransactionReference' => $transactionReference,
'input_CustomerMSISDN' => $customerMsisdn,
'input_Amount' => (string) $amount,
'input_ThirdPartyReference' => $thirdPartyReference,
'input_ServiceProviderCode' => $credential->service_provider_code,
]);
}

/**
* @return array<string, mixed>
*/
public function queryTransactionStatus(string $queryReference, string $thirdPartyReference): array {
$credential = $this->credentialService->getCredentials();

return $this->send($credential, 'GET', 18353, '/ipg/v1x/queryTransactionStatus/', [
'input_QueryReference' => $queryReference,
'input_ThirdPartyReference' => $thirdPartyReference,
'input_ServiceProviderCode' => $credential->service_provider_code,
]);
}

/**
* @param array<string, mixed> $params
*
* @return array<string, mixed>
*/
private function send(VodacomMzCredential $credential, string $method, int $port, string $path, array $params): array {
$url = $credential->buildUri($port, $path);
$payloadKey = $method === 'GET' ? 'query' : 'json';

try {
// IPG returns business failures (e.g. INS-2006 insufficient balance) as a 4xx
// with the detail in the body, so http_errors stays off and the decoded body
// is returned as-is for the caller to inspect via `output_ResponseCode`.
$response = $this->httpClient->request($method, $url, [
$payloadKey => $params,
'headers' => $this->headers($credential),
'http_errors' => false,
]);
} catch (GuzzleException $exception) {
Log::critical('Vodacom MZ API request failed', [
'url' => $url,
'message' => $exception->getMessage(),
]);
throw new VodacomMzApiResponseException($exception->getMessage());
}

return json_decode((string) $response->getBody(), true) ?? [];
}

/**
* @return array<string, string>
*/
private function headers(VodacomMzCredential $credential): array {
return [
'Authorization' => 'Bearer '.$credential->getBearerToken(),
'Origin' => '*',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace App\Plugins\VodacomMzPaymentProvider\Http\Controllers;

use App\Plugins\VodacomMzPaymentProvider\Http\Requests\VodacomMzCredentialRequest;
use App\Plugins\VodacomMzPaymentProvider\Http\Resources\VodacomMzCredentialResource;
use App\Plugins\VodacomMzPaymentProvider\Services\VodacomMzCredentialService;
use Illuminate\Routing\Controller;

class VodacomMzCredentialController extends Controller {
public function __construct(
private VodacomMzCredentialService $credentialService,
) {}

public function show(): VodacomMzCredentialResource {
return VodacomMzCredentialResource::make($this->credentialService->getCredentials());
}

public function update(VodacomMzCredentialRequest $request): VodacomMzCredentialResource {
$credentials = $this->credentialService->updateCredentials([
'api_key' => (string) $request->string('api_key'),
'public_key' => (string) $request->string('public_key'),
'service_provider_code' => (string) $request->string('service_provider_code'),
'live' => $request->boolean('live'),
]);

return VodacomMzCredentialResource::make($credentials);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use App\Plugins\VodacomMzPaymentProvider\Http\Requests\VodacomTransactionProcessRequest;
use App\Plugins\VodacomMzPaymentProvider\Http\Requests\VodacomTransactionValidationRequest;
use App\Plugins\VodacomMzPaymentProvider\Http\Resources\VodacomTransactionResource;
use App\Plugins\VodacomMzPaymentProvider\Services\VodacomTransactionService;
use App\Plugins\VodacomMzPaymentProvider\Services\VodacomMzTransactionService;
use Dedoc\Scramble\Attributes\Group;

/**
Expand All @@ -16,37 +16,16 @@
* API endpoints for integrating with Vodacom's M-Pesa payment services
*/
#[Group('Plugins / Vodacom Mz')]
class VodacomTransactionController extends Controller {
public function __construct(private VodacomTransactionService $vodacomService) {}
class VodacomMzTransactionController extends Controller {
public function __construct(
private VodacomMzTransactionService $vodacomService,
) {}

/**
* Validate Transaction.
*
* Validates a transaction before processing. Use this endpoint to verify if a transaction
* can proceed based on the provided information. This is typically the first step in the payment flow.
*
* @bodyParam serialNumber string required Unique identifier for the product/service being purchased pattern: ^[A-Z0-9]{8,12}$ Example: ABC123456789
* @bodyParam amount number required Transaction amount in the local currency Example: 15000
* @bodyParam payerPhoneNumber string required Customer's phone number in international format pattern: ^258[0-9]{9}$ Example: 258712345678
* @bodyParam referenceId string required Unique reference identifier for this transaction pattern: ^[A-Za-z0-9\-]{5,20}$ Example: ORD-12345-ABC
*
* @response scenario="Success" {
* "data": {
* "transactionId": "VOD-TXN-123456",
* "status": "validated",
* "details": {
* "product": "Internet Bundle",
* "validAmount": true
* },
* "success": true
* }
* }
* @response 400 scenario="Validation Error" {
* "data": {
* "message": "Invalid amount specified for this product",
* "success": false
* }
* }
*/
public function validateTransaction(VodacomTransactionValidationRequest $request): VodacomTransactionResource {
$validatedData = $request->validated();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace App\Plugins\VodacomMzPaymentProvider\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class VodacomMzCredentialRequest extends FormRequest {
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
public function rules(): array {
return [
'api_key' => ['required', 'string'],
'public_key' => ['required', 'string'],
'service_provider_code' => ['required', 'string'],
'live' => ['required', 'boolean'],
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ public function authorize(): bool {
*/
public function rules(): array {
return [
'serialNumber' => ['required', 'string', 'regex:/^[A-Z0-9]{8,12}$/'],
'amount' => ['required', 'numeric', 'min:100', 'max:5000000'],
'payerPhoneNumber' => ['required', 'string', 'regex:/^258[0-9]{9}$/'],
'referenceId' => ['required', 'string', 'regex:/^[A-Za-z0-9\-]{5,20}$/'],
// `reference` has to match an existing Device's serial number else the transaction gets rejected.
'reference' => ['required', 'string', 'regex:/^[A-Z0-9]{8,12}$/'],
// Transaction `amount` in MZN (Mozambican metical)
'amount' => ['required', 'numeric', 'min:1', 'max:5000000'],
'request_id' => ['required', 'uuid'],
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace App\Plugins\VodacomMzPaymentProvider\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class VodacomMzCredentialResource extends JsonResource {}
Loading
Loading