qbe-digital/payments
Composer 安装命令:
composer require qbe-digital/payments
包简介
A unified payments package for Laravel supporting Tabby, Tamara and more.
README 文档
README
A unified payments package for Laravel, supporting Tabby and Tamara with a clean, driver-based architecture.
Features
- 🔌 Driver-based architecture — Switch payment providers with zero code changes
- 🪝 Webhook handling — Automatic signature validation and normalized event dispatching
- 💳 Checkout sessions — Unified API for creating payment sessions
- ↩️ Refunds — Full and partial refund support
- 📦 Installments — BNPL support for Tabby & Tamara
- 📋 Transaction logging — Built-in migration and model for tracking payments
- 🧩 Billable trait — Add
pay()to any Eloquent model
Installation
composer require qbe-digital/payments
Publish the config:
php artisan vendor:publish --tag=payments-config
Publish the migrations:
php artisan vendor:publish --tag=payments-migrations php artisan migrate
Configuration
Add your credentials to .env:
PAYMENTS_DRIVER=tabby # Regional defaults (applied to all drivers; override per-driver in config/payments.php) PAYMENTS_CURRENCY=SAR PAYMENTS_LOCALE=ar PAYMENTS_COUNTRY=SA # Tabby TABBY_SECRET_KEY=your-secret-key TABBY_PUBLIC_KEY=your-public-key TABBY_MERCHANT_CODE=your-merchant-code TABBY_WEBHOOK_KEY=your-webhook-key TABBY_SANDBOX=true # Tamara TAMARA_API_TOKEN=your-api-token TAMARA_NOTIFICATION_TOKEN=your-notification-token TAMARA_SANDBOX=true
Usage
Basic Checkout
use QBE\Payments\Facades\Payments; use QBE\Payments\Support\CheckoutSession; use QBE\Payments\Support\CustomerInfo; $session = new CheckoutSession( amount: 500.00, currency: 'SAR', referenceId: 'order-123', description: 'Premium Package', successUrl: route('payment.success'), failureUrl: route('payment.failure'), customer: CustomerInfo::make('Ahmed', 'ahmed@example.com', '+966500000000'), ); // Use default driver $result = Payments::createCheckout($session); // Or specify a driver $result = Payments::driver('tabby')->createCheckout($session); if ($result->requiresRedirect()) { return redirect($result->redirectUrl); }
Verify Payment
$result = Payments::verify($transactionId); if ($result->isSuccessful()) { // Payment confirmed }
Refund
$refund = Payments::refund($transactionId, 100.00, 'Customer requested'); // Pass the original transaction currency for multi-currency correctness: $refund = Payments::refund($transactionId, 100.00, 'Customer requested', 'AED'); if ($refund->isSuccessful()) { // Refund processed }
Billable Trait
Add the Billable trait to any model:
use QBE\Payments\Traits\Billable; use QBE\Payments\Support\CustomerInfo; class Order extends Model { use Billable; public function getPaymentAmount(): float { return $this->total; } public function getPaymentReference(): string { return "order-{$this->id}"; } public function getPaymentCustomer(): ?CustomerInfo { return CustomerInfo::make( $this->customer_name, $this->customer_email, $this->customer_phone, ); } } // Then simply: $result = $order->pay(); // Default driver $result = $order->pay('tamara'); // Specific driver $order->verifyPayment($transactionId); $order->refundPayment($transactionId, 50.00);
Webhooks
Webhook routes are registered automatically:
POST /api/payments/webhook/tabby
POST /api/payments/webhook/tamara
Listen for events in your EventServiceProvider:
use QBE\Payments\Events\PaymentSucceeded; use QBE\Payments\Events\PaymentFailed; use QBE\Payments\Events\WebhookReceived; use QBE\Payments\Events\RefundProcessed; protected $listen = [ PaymentSucceeded::class => [ UpdateOrderStatus::class, SendPaymentConfirmation::class, ], PaymentFailed::class => [ HandleFailedPayment::class, ], WebhookReceived::class => [ LogWebhook::class, ], ];
Transaction Records
When transaction persistence is enabled (default), the package keeps a
payment_transactions row in sync through the payment lifecycle:
$model->pay()(via theBillabletrait) records a pending row linked to the payable model.- Payment/refund webhooks update the row to paid, failed, or refunded
(and stamp
paid_at) through built-in event listeners.
use QBE\Payments\Models\PaymentTransaction; $transactions = $order->payments; // if you add the morph relation $paid = PaymentTransaction::paid()->get(); // built-in scopes: paid(), pending()
Opt out or swap the backing model in config/payments.php:
'transactions' => [ 'enabled' => env('PAYMENTS_RECORD_TRANSACTIONS', true), 'model' => \App\Models\PaymentTransaction::class, ],
Installments (Tabby & Tamara)
use QBE\Payments\Contracts\SupportsInstallmentsInterface; $driver = Payments::driver('tabby'); if ($driver instanceof SupportsInstallmentsInterface) { $plans = $driver->getInstallmentPlans(1000.00); // Returns available installment plans }
Feature Detection
use QBE\Payments\Enums\PaymentFeature; if (Payments::driver('tamara')->supports(PaymentFeature::PARTIAL_REFUNDS->value)) { // Tamara supports partial refunds }
Adding a Custom Driver
- Extend
AbstractDriver:
use QBE\Payments\Drivers\AbstractDriver; class StripeDriver extends AbstractDriver { public function driverName(): string { return 'stripe'; } protected function baseUrl(): string { return 'https://api.stripe.com/v1/'; } protected function defaultHeaders(): array { /* ... */ } protected function supportedFeatures(): array { /* ... */ } public function createCheckout(CheckoutSession $session): PaymentResult { /* ... */ } public function verify(string $transactionId): PaymentResult { /* ... */ } public function refund(string $transactionId, float $amount, string $reason = ''): RefundResult { /* ... */ } }
- Register in your
AppServiceProvider:
use QBE\Payments\PaymentManager; app(PaymentManager::class)->extend('stripe', function () { return new StripeDriver(config('payments.drivers.stripe')); });
Testing
composer test
License
MIT
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 1
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-06-21