webmobyle/paychangu-payments-multi-channel

Multi-channel PayChangu payments package for Laravel using a charge-based workflow.

Maintainers

Package info

bitbucket.org/webmobyle/paychangu-payments-multi-channel

pkg:composer/webmobyle/paychangu-payments-multi-channel

Statistics

Installs: 16

Dependents: 0

Suggesters: 0

1.0.4 2026-03-13 17:56 UTC

This package is auto-updated.

Last update: 2026-03-13 17:57:12 UTC


README

Packagist Version License Laravel

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_charges
  • paychangu_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

DependencyVersion
PHP^8.2
Laravel10.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