定制 ejoi/payment-gateway 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

ejoi/payment-gateway

Composer 安装命令:

composer require ejoi/payment-gateway

包简介

A modular, framework-agnostic PHP payment gateway abstraction with drivers for Malaysian and global providers (CHIP, Billplz, toyyibPay, Stripe, PayPal).

README 文档

README

A modular PHP payment-gateway abstraction for Malaysian and global providers. Write your checkout code once against a single interface; switch or add gateways with a config change and one small driver class.

Supported drivers: CHIP · Billplz · toyyibPay · Stripe · PayPal.

  • 📖 docs/GATEWAYS.md — setup, usage & sandbox verification for every gateway.
  • 🤝 handover.md — full project handover (architecture, decisions, testing, next steps).
  • 🧩 examples/Laravel — a business-logic listener example.

Two layers

  1. Framework-agnostic core — pure PHP over a PSR-18 HTTP client. One PaymentGateway interface, one driver per provider, normalized DTOs, one PaymentStatus enum. No framework, no database.
  2. Laravel adapter (optional) — a payments ledger, an auto-registered webhook endpoint, an idempotent status flow, domain events, a reconciliation job, and out-of-the-box email notifications.

Every provider follows the same lifecycle; the differences (auth, amount unit, signature scheme, status vocabulary) are absorbed by each driver:

createPayment()  →  redirect to hosted page  →  verifyCallback() (signed webhook)  →  queryStatus() (requery to confirm)

Your application only ever deals with the PaymentGateway interface, the DTOs (PaymentRequest, PaymentResponse, PaymentStatusResult), and the PaymentStatus enum.

Requirements

Installation

composer require ejoi/payment-gateway guzzlehttp/guzzle

Laravel

The service provider and facades auto-discover. Publish config + migrations and migrate:

php artisan vendor:publish --tag=payment-gateway-config
php artisan vendor:publish --tag=payment-gateway-migrations
php artisan migrate

Set credentials in .env (see docs/GATEWAYS.md for each gateway's keys). Every gateway defaults to sandbox until you set *_SANDBOX=false.

Quick start (Laravel)

use Ejoi\PaymentGateway\Data\{Customer, Money, PaymentRequest};
use Ejoi\PaymentGateway\Laravel\Facades\Payments;
use Ejoi\PaymentGateway\Laravel\Events\PaymentStatusChanged;
use Ejoi\PaymentGateway\Laravel\Jobs\ReconcilePendingPayments;
use Ejoi\PaymentGateway\Enums\PaymentStatus;

// 1. Create + persist a payment (swap 'billplz' for any gateway)
$payment = Payments::create('billplz', new PaymentRequest(
    reference:   $order->reference,
    amount:      Money::fromMinor(4990, 'MYR'),
    description: "Order {$order->reference}",
    customer:    new Customer($order->email, $order->name),
    redirectUrl: route('payment.return', $order),
    callbackUrl: route('payment-gateway.webhook', 'billplz'),
));
return redirect()->away($payment->checkout_url);

// 2. The webhook route is auto-registered and does verify → requery → persist → dedupe → event.
//    You just react to the outcome:
Event::listen(PaymentStatusChanged::class, fn ($e) =>
    $e->payment->status === PaymentStatus::Paid && $order->fulfil()
);

// 3. Reconcile stragglers (FPX may not webhook) on a schedule:
$schedule->job(ReconcilePendingPayments::class)->everyFiveMinutes()->withoutOverlapping();
// Optional: keep the webhook audit table bounded (90-day default retention).
$schedule->command('model:prune', ['--model' => [\Ejoi\PaymentGateway\Laravel\Models\PaymentWebhook::class]])->daily();

On paid/failed the package also emails the merchant and customer (configurable). See docs/GATEWAYS.md for per-gateway credentials and webhook registration.

Quick start (plain PHP core)

use Ejoi\PaymentGateway\PaymentGatewayManager;
use Ejoi\PaymentGateway\Data\CallbackPayload;

$manager = new PaymentGatewayManager($config); // $config = the array from config/payment-gateway.php

$response = $manager->gateway('billplz')->createPayment($request);
header('Location: ' . $response->redirectUrl);

// On the webhook:
$result = $manager->gateway('billplz')->verifyCallback(CallbackPayload::fromGlobals());
if (! $result->verified) {
    $result = $manager->gateway('billplz')->queryStatus($result->gatewayReference);
}
// $result->status is a normalized PaymentStatus — persist it yourself.

What the Laravel layer gives you

  • payments ledger (Payment model) — the package's own record, linked to your orders by reference.
  • Auto webhook route POST /payment-gateway/webhook/{gateway} — verify → requery → persist → dedupe → fire event. CSRF-exempt; prefix/middleware configurable; disable with PAYMENT_GATEWAY_WEBHOOK_ROUTE=false.
  • PaymentStatusChanged event — fires once per real transition; listen for it to run business logic.
  • ReconcilePendingPayments job — requeries pending payments on your schedule.
  • Email notifications — merchant + customer, on paid/failed, out of the box (Laravel Notifications).
  • transaction_id — the provider's charge/transaction id, indexed. All other provider data stays in last_response (JSON).

The two golden rules

  1. Trust the server webhook, never the browser return URL. verifyCallback() returns verified = false when a signature can't be checked (e.g. toyyibPay) — then you must queryStatus().
  2. Status updates are idempotent. The webhook and the reconcile job can both land; the package only transitions a non-final payment, so a duplicate is a no-op.

Adding a new gateway

Extend AbstractGateway, implement three methods, register it — no fork needed:

use Ejoi\PaymentGateway\Gateway\AbstractGateway;

final class MyGateway extends AbstractGateway
{
    protected const NAME = 'mygateway';

    public function createPayment(PaymentRequest $request): PaymentResponse { /* ... */ }
    public function verifyCallback(CallbackPayload $payload): PaymentStatusResult { /* ... */ }
    public function queryStatus(string $gatewayReference): PaymentStatusResult { /* ... */ }
}

$manager->extend('mygateway', fn ($config, $http) => new MyGateway($config, $http));

Add a config block under gateways and you're done. Mirror BillplzGateway — it's the reference implementation.

Architecture

src/
├── Contracts/           PaymentGateway (the interface), HttpClient
├── Data/                PaymentRequest, PaymentResponse, PaymentStatusResult,
│                        CallbackPayload, Customer, Money  (immutable DTOs)
├── Enums/               PaymentStatus, PaymentMethod, Currency
├── Config/              GatewayConfig       Http/  PsrHttpClient, HttpResponse
├── Support/             Signature (constant-time HMAC)   Exceptions/  (typed hierarchy)
├── Gateway/
│   ├── AbstractGateway.php
│   └── Drivers/         Chip · Billplz · Toyyibpay · Stripe · Paypal
├── Laravel/             ServiceProvider · Facades · Payments · Models (Payment, PaymentWebhook)
│                        Http/PaymentWebhookController · Events · Listeners · Jobs · CallbackPayloadFactory
└── PaymentGatewayManager.php   (resolves drivers by name from config)
config/ · database/migrations/ · tests/ · docs/GATEWAYS.md · handover.md

Provider notes

Gateway Amount unit Callback signature Notes
CHIP minor (sen) RSA public-key signature Cross-border & crypto capable
Billplz minor (sen) HMAC-SHA256 x_signature MYR only; webhook mandatory
toyyibPay minor (sen) none → always requery MYR only; cheapest FPX
Stripe minor (cents) Stripe-Signature (HMAC + 300s window) Hosted Checkout Session
PayPal decimal string verify-webhook-signature API Orders v2 (capture on requery)

Testing

composer install
php vendor/bin/phpunit

The suite covers all five drivers (with signature verification), Money, the manager, and the Laravel persistence/notification flow (via orchestra/testbench on in-memory SQLite). Drivers are unit-tested with a FakeHttpClient (no network). See handover.md for details.

License

MIT.

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 0
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-07-11

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固