Domain + application + infrastructure
Dependencies point inward, always.
A pure domain of contracts and value objects; drivers and Laravel adapters at the edge.
Laravel 10–13 · Payment SDK · v0.17.0 · MIT
A self-contained, multi-gateway payment SDK for PHP. A factory resolves the right driver, a swappable HTTP transport does the talking, and one contract covers CyberSource Unified Checkout, Fawry, Paymob, PayLink, PayTabs, PayPal, Mastercard MPGS, Authorize.Net, Airwallex and Tamara.
Domain-driven layers keep the business rules clean and the network at arm’s length. Here is what that buys you.
Domain + application + infrastructure
A pure domain of contracts and value objects; drivers and Laravel adapters at the edge.
Factory + one interface
Program against one contract. What a driver can’t do throws — it never silently no-ops.
Ports & adapters
Ship the retrying Laravel transport in production, swap the in-memory fake in tests.
Raw REST + own signing
Every driver speaks the gateway’s REST API directly and signs its own requests.
Minor units + strict types
Amounts stay in minor units end to end, under PHPStan level max with a zero baseline.
Idempotency + retries
Every write carries a key routed to the gateway’s own dedup, so retries are safe.
Typed events + one listener
Charge, capture, refund, vault and webhook each emit a queue-safe typed event.
3-D Secure + charge
A resolved ECI that isn’t fully authenticated raises its own typed event.
Vault + MIT/CIT
Tokenize once, charge as a stored credential, and let Account Updater refresh expiries.
Microform + 3-D Secure
Flex Microform hosts the card fields; you get back a transient token and an ECI.
Wallets + merchant decrypt
chargeWallet takes tokenizedCard or fluidData and tags the payment solution.
BIN lookup + DCC
Read the BIN’s offers, quote a conversion rate, and present installment plans.
Webhooks + signature verify
Verify per driver, then handle one PaymentEvent instead of ten payload shapes.
Money transfer
Fund with an AFT, credit with an OCT, and declare the purpose the networks price it by.
Reporting + reconcile
Pull a report, reconcile by order reference, and search when a reference goes missing.
Split payout + recurring
PayTabs splits settled funds across beneficiaries and auto-bills your agreement.
Each driver extends AbstractPaymentGateway and implements the same fat contract.
Mint a capture context for the widget, charge the transient token or run the orchestrated flow and verify the signed result JWT, run 3-DS payer-auth, and vault instruments for MIT/CIT stored credentials.
$request = new CheckoutSessionRequest(
money: Money::minor(10000, 'EGP'),
targetOrigins: ['https://shop.test'],
orderReference: 'ORDER-123',
enable3ds: true,
);
$session = $gateway->createCheckoutSession($request);
// mount the widget with $session->jwt
Hosted page, emailable Invoice, reusable PayLink, iframe Managed Form, or an Own Form charge of a payment token. Saves tokens for recurring charges, splits settled funds across beneficiaries, and verifies the signed IPN callback.
$request = new CheckoutSessionRequest(
money: Money::minor(12030, 'SAR'),
orderReference: 'ORDER-127',
paymentMethod: 'invoice',
options: new PaytabsCheckoutOptions(tokenise: 2),
);
$session = $gateway->createCheckoutSession($request);
// $session->reference is the tran_ref
Create an order and redirect the buyer to approve it, then capture or authorize the approved order. Cards vault through the setup-token → payment-token flow, reconciliation runs on Transaction Search, and webhooks verify through PayPal.
$request = new CheckoutSessionRequest(
money: Money::minor(10000, 'USD'),
orderReference: 'ORDER-128',
returnUrl: 'https://shop.test/return',
paymentMethod: 'authorize',
);
$session = $gateway->createCheckoutSession($request);
// redirect to $session->redirectUrl
What each driver supports. Anything unsupported throws — the surface never lies.
createCheckoutSessionrequestDccRateDynamic Currency Conversionchargetransient tokenchargeWalletApple Pay / Google PayconfirmOrchestratedPaymentverify result JWTcreateMicroformSessionFlex Microform card fieldscapturerefundvoidreverseAuthorizationsetupPayerAuth3-DS device data collectionenroll / validatePayerAuth3-DSvault / chargeStoredCredentialgetTransaction / searchTransaction+ full historypushFunds / pullFundsOCT + AFT money transferverifyWebhookThe provider auto-registers the factory and ports. Inject the factory — no service location, no new.
# Composer auto-discovers the service provider
composer require hyprpay/paymentsphp artisan vendor:publish --tag=gateway-config$this->app->bind(HttpClient::class, MyHttpClient::class);
$this->app->bind(CredentialResolver::class, MyCredentialResolver::class);External services are fully decoupled. CredentialResolver seamlessly resolves single-tenant static configs and multi-tenant per-tenant credentials.
$gateway = $this->gateways->make(GatewayName::Fawry);
$gateway->charge($request);One charge() call works across every gateway. The factory resolves the driver, so you program against one contract.
use Hyprpay\Payments\Domain\Command\ChargeRequest;
use Hyprpay\Payments\Domain\ValueObject\Money;
use Hyprpay\Payments\Domain\Enum\GatewayName;
use Hyprpay\Payments\Application\PaymentGatewayFactory;
final readonly class ChargeInvoice
{
// Type-hint the factory; Laravel injects it.
public function __construct(
private PaymentGatewayFactory $gateways,
) {}
public function handle(string $tokenFromWidget): void
{
$gateway = $this->gateways->make(
GatewayName::CybersourceUnifiedCheckout,
);
$result = $gateway->charge(new ChargeRequest(
transientToken: $tokenFromWidget,
money: Money::minor(10000, 'EGP'), // 100.00, exact
orderReference: 'ORDER-123', // = idempotency key
));
if ($result->success) {
// $result->status, ->transactionId, ->raw
}
}
}
Retries are safe by construction: bodies are deterministic and every write carries an
idempotency key — routed to v-c-idempotency-id, merchantRefNum,
merchant_order_id, or an Idempotency-Key / PayPal-Request-Id
header, per gateway.
Requires PHP ^8.2 · illuminate/support & illuminate/http ^10 | ^11 | ^12 | ^13 · firebase/php-jwt ^6.10 | ^7.0