webmobyle / paychangu-payments-multi-channel
Multi-channel PayChangu payments package for Laravel using a charge-based workflow.
Package info
bitbucket.org/webmobyle/paychangu-payments-multi-channel
pkg:composer/webmobyle/paychangu-payments-multi-channel
Requires
- php: ^8.2
- illuminate/contracts: ^11.0|^12.0
- illuminate/database: ^11.0|^12.0
- illuminate/http: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
README
A Laravel package for handling PayChangu payments across multiple channels while preserving the existing paychangu_charges workflow.
🚀 Supported Channels
- Hosted Checkout
- Mobile Money
- Instant Bank Transfer
- Card
See CHANGELOG.md for release history.
📦 Installation
Install via Composer:
composer require webmobyle/paychangu-payments-multi-channel
Publish the configuration and migrations:
php artisan vendor:publish --tag=paychangu-multi-channel-config
php artisan vendor:publish --tag=paychangu-multi-channel-migrations
Run migrations:
php artisan migrate
⚙️ Configuration
Add these keys to your .env:
PAYCHANGU_PUBLIC_KEY=pub-xxx
PAYCHANGU_SECRET_KEY=sec-xxx
PAYCHANGU_WEBHOOK_SECRET=your_webhook_secret_here
🧩 How It Works
Use Statements
use Webmobyle\PaychanguPaymentsMultiChannel\DTOs\ChargeData;
use Webmobyle\PaychanguPaymentsMultiChannel\Enums\PaymentMethod;
use Webmobyle\PaychanguPaymentsMultiChannel\Services\PaymentManager;
Initiate a Hosted Checkout Payment
public function createCheckoutCharge(Request $request, PaymentManager $payments)
{
try {
$payable = \App\Models\User::query()->first();
if (! $payable) {
abort(500, 'No demo payable record found. Create at least one user first.');
}
$reference = 'DEMO-CHECKOUT-' . now()->format('YmdHis') . '-' . \Illuminate\Support\Str::upper(\Illuminate\Support\Str::random(6));
$result = $payments->create(
ChargeData::checkout(
payableType: User::class,
payableId: $payable->id,
reference: $reference,
amount: 100,
currency: 'MWK',
customerName: $payable->name,
customerEmail: $payable->email,
description: 'ToothTrack subscription payment',
callbackUrl: url('/paychangu/webhook'),
returnUrl: route('billing.payments.return'),
title: 'ToothTrack Payment',
metadata: [
'source' => 'toothtrack_billing',
],
)
);
return response()->json([
'message' => 'Checkout charge initialized successfully.',
'reference' => $result->reference,
'provider_reference' => $result->providerReference,
'status' => $result->status->value,
'payment_method' => $result->paymentMethod->value,
'checkout_url' => $result->checkoutUrl,
'response_payload' => $result->responsePayload,
]);
} catch (\Throwable $e) {
return response()->json([
'error' => true,
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => collect($e->getTrace())->take(10)->values(),
], 500);
}
}
Initiate a Direct Mobile Money Payment
public function createMobileMoneyPayment(Request $request, PaymentManager $payments)
{
$payable = User::query()->first();
if (! $payable) {
abort(500, 'No demo payable record found. Create at least one user first.');
}
$reference = 'DEMO-MM-' . now()->format('YmdHis') . '-' . Str::upper(Str::random(6));
$result = $payments->create(
ChargeData::mobileMoney(
payableType: User::class,
payableId: $payable->id,
reference: $reference,
amount: 100,
customerPhone: '+265991234567',
customerName: $payable->name,
customerEmail: $payable->email,
description: 'Mobile money payment',
metadata: [
'source' => 'demo',
],
)
);
return response()->json([
'message' => 'Mobile Money charge created successfully.',
'reference' => $result->reference,
'provider_reference' => $result->providerReference,
'status' => $result->status->value,
'payment_method' => $result->paymentMethod->value,
'response_payload' => $result->responsePayload,
]);
}
Initiate a Direct Bank Payment
public function createBankTransfer(Request $request, PaymentManager $payments)
{
$payable = User::query()->first();
if (! $payable) {
abort(500, 'No demo payable record found. Create at least one user first.');
}
$reference = 'DEMO-BANK-' . now()->format('YmdHis') . '-' . Str::upper(Str::random(6));
$result = $payments->create(
ChargeData::bankTransfer(
payableType: User::class,
payableId: $payable->id,
reference: $reference,
amount: 100,
currency: 'MWK',
description: 'ToothTrack bank transfer payment',
customerName: $payable->name,
customerEmail: $payable->email,
customerPhone: $payable->phone,
metadata: [
'source' => 'toothtrack_billing',
],
)
);
return response()->json([
'message' => 'Bank transfer charge created successfully.',
'reference' => $result->reference,
'provider_reference' => $result->providerReference,
'status' => $result->status->value,
'payment_method' => $result->paymentMethod->value,
'response_payload' => $result->responsePayload,
]);
}
Initiate a Direct Card Payment
public function createCardCharge(Request $request, PaymentManager $payments)
{
try {
$payable = User::query()->first();
if (! $payable) {
abort(500, 'No demo payable record found. Create at least one user first.');
}
$reference = 'DEMO-CARD-' . now()->format('YmdHis') . '-' . Str::upper(Str::random(6));
$result = $payments->create(
new ChargeData(
payableType: User::class,
payableId: $payable->id,
reference: $reference,
amount: 100,
currency: 'MWK',
paymentMethod: PaymentMethod::CARD,
customerName: $payable->name ?? 'Demo User',
customerEmail: $payable->email ?? 'demo@example.com',
extra: [
// Replace with the proper test card details for your PayChangu environment
'card_number' => '4242424242424242',
'expiry_month' => '12',
'expiry_year' => '30',
'cvv' => '123',
]
)
);
return response()->json([
'message' => 'Card charge initialized successfully.',
'reference' => $result->reference,
'provider_reference' => $result->providerReference,
'status' => $result->status->value,
'payment_method' => $result->paymentMethod->value,
'response_payload' => $result->responsePayload,
]);
} catch (Throwable $e) {
return response()->json([
'error' => true,
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => collect($e->getTrace())->take(10)->values(),
], 500);
}
}
📡 Webhook Handling
Handled automatically by the package.
Webhook endpoint:
POST /paychangu/webhook
📁 Tables
This package uses:
paychangu_chargespaychangu_charge_events
Notes for Existing Apps
If your app already has the paychangu_charges table, do not run that migration again. Only add the paychangu_charge_events migration if needed.
🧱 Requirements
| Dependency | Version |
|---|---|
| PHP | ^8.2 |
| Laravel | 10.x - 12.x |
📦 Versioning
This package follows Semantic Versioning (SemVer) — tag releases like v1.0.0, v1.1.0, etc.
🪪 License
This package is open-sourced software licensed under the MIT License.
👤 Author
Barnett Temwa Msiska
Founder, Webmobyle Limited
📧 barnett@webmobyle.com
⭐ Support
If you find this package useful, please star it on Packagist or Bitbucket.
Contributions, pull requests, and issues are welcome!
Email: contact@webmobyle.com