Skip to content

Canonical Payload

The canonical payload is the core concept of Laravel Payments. It's a single, immutable DTO structure that every gateway driver receives—regardless of the gateway's native API format.

Why Canonical?

Traditional payment integrations require you to build gateway-specific payloads:

php
// ❌ Without canonical payload — gateway-specific code everywhere

// Stripe
$stripe->checkout->sessions->create([
    'line_items' => [['price_data' => ['unit_amount' => 2999, ...]]],
    'mode' => 'payment',
    'success_url' => '...',
]);

// bKash
$bkash->createPayment([
    'amount' => '29.99',
    'intent' => 'sale',
    'merchantInvoiceNumber' => 'INV-001',
]);

With the canonical payload, your application code is the same for every gateway:

php
// ✅ With canonical payload — one shape, any gateway
Payment::gateway($anyGateway)->create([
    'order' => ['id' => 'ORD-001'],
    'money' => ['amount' => 2999, 'currency' => 'USD'],
]);

Payload Structure

php
CanonicalPayload::fromArray([
    // Auto-generated if not provided
    'idempotency_key' => 'order-123-attempt-1',

    // Order information (required)
    'order' => [
        'id' => 'ORD-001',                    // required
        'description' => 'Premium Plan',       // optional
        'items' => [                           // optional
            [
                'name' => 'Premium Plan',
                'quantity' => 1,
                'unit_price' => 2999,
                'description' => 'Monthly subscription',
                'sku' => 'PLAN-PREMIUM',
                'metadata' => [],
            ],
        ],
    ],

    // Money (required)
    'money' => [
        'amount' => 2999,     // required, integer or float
        'currency' => 'USD',  // required, 3-letter ISO code
    ],

    // Customer information (optional)
    'customer' => [
        'name' => 'John Doe',
        'email' => 'john@example.com',
        'phone' => '+1234567890',
        'address' => [
            'line1' => '123 Main St',
            'line2' => 'Apt 4B',
            'city' => 'New York',
            'state' => 'NY',
            'postal_code' => '10001',
            'country' => 'US',
        ],
    ],

    // URLs (optional — auto-filled from route config if not set)
    'urls' => [
        'return' => 'https://myapp.com/payments/return/stripe',
        'cancel' => 'https://myapp.com/payments/cancel/stripe',
        'webhook' => 'https://myapp.com/payments/webhook/stripe',
    ],

    // Request context (optional — auto-captured if available)
    'context' => [
        'ip' => '203.0.113.1',
        'user_agent' => 'Mozilla/5.0...',
        'locale' => 'en_US',
    ],

    // Freeform metadata (optional — stored as-is)
    'metadata' => [
        'plan' => 'premium',
        'user_id' => 42,
    ],

    // Gateway-specific overrides (optional — passed to driver only)
    'extra' => [
        'stripe_payment_method_types' => ['card'],
    ],
]);

Field Reference

Required Fields

FieldTypeDescription
order.idstringYour application's order ID
money.amountint|floatPayment amount (must be positive)
money.currencystring3-letter ISO 4217 currency code

Optional Fields

FieldTypeDescription
idempotency_keystringUnique key for duplicate-safe operations
order.descriptionstringHuman-readable order description
order.items[]arrayLine items (name, quantity, unit_price)
customer.namestringCustomer full name
customer.emailstringCustomer email address
customer.phonestringCustomer phone number
customer.address.*objectFull address (line1, city, country, etc.)
urls.returnstringURL to redirect after payment
urls.cancelstringURL to redirect on cancellation
urls.webhookstringURL for gateway webhook notifications
context.ipstringClient IP address
context.user_agentstringClient user agent
context.localestringPreferred locale
metadataarrayFreeform key-value metadata
extraarrayDriver-specific overrides

Idempotency

Every canonical payload carries an idempotency_key. If you don't provide one, it's auto-generated from the order ID and amount:

php
// Auto-generated: sha256(order_id + amount + currency)
$payload = CanonicalPayload::fromArray([...]);
echo $payload->idempotencyKey; // "a3f8b2c1..."

The Payment manager uses this key to prevent duplicate payment creation when persistence is enabled.

Dot-Notation Flattening

The canonical payload supports dot-notation flattening for logging and debugging:

php
$payload->toDotArray();
// [
//     'idempotency_key' => 'order-123-attempt-1',
//     'order.id' => 'ORD-001',
//     'order.description' => 'Premium Plan',
//     'money.amount' => 2999,
//     'money.currency' => 'USD',
//     'customer.name' => 'John Doe',
//     'customer.email' => 'john@example.com',
//     ...
// ]

Refund Payload

Refunds use a separate CanonicalRefundPayload:

php
CanonicalRefundPayload::fromArray([
    'payment_id' => 'PAY-001',
    'gateway_reference' => 'ch_1234',
    'money' => ['amount' => 1000, 'currency' => 'USD'],
    'reason' => 'Customer request',
    'metadata' => ['refund_ticket' => 'REF-001'],
]);

Status Query Payload

Status queries use CanonicalStatusPayload:

php
CanonicalStatusPayload::fromArray([
    'payment_id' => 'PAY-001',
    'gateway_reference' => 'ch_1234',
]);

Released under the MIT License.