Skip to content

Build an external integration

Billing owns PaymentGateway; Infrastructure provides the fake Adapter. The Adapter translates a vendor exception to a Domain Exception, while the UseCase reuses its idempotency key for a retry.

Before you begin

Run composer run reference:test:integration and read Contracts and Adapters.

Keep the provider boundary outward

examples/reference-app/app/Pulsar/Services/Client/Modules/Orders/UseCases/ChargeOrder.php — Tenant-scoped charge, key reuse, and vendor-error translation.

php
<?php

namespace App\Pulsar\Services\Client\Modules\Orders\UseCases;

use App\Pulsar\Domain\Billing\Contracts\PaymentGateway;
use App\Pulsar\Domain\Orders\Contracts\TenantContext;
use App\Pulsar\Domain\Orders\Models\Order;
use Illuminate\Support\Facades\DB;

final class ChargeOrder
{
    public function __construct(private readonly PaymentGateway $gateway, private readonly TenantContext $tenant) {}

    // #region charge-order-integration
    public function execute(int $orderId, string $tenantId, string $idempotencyKey): string
    {
        return DB::transaction(function () use ($orderId, $tenantId, $idempotencyKey): string {
            $this->tenant->establish($tenantId);
            $order = Order::query()->where('tenant_id', $tenantId)->lockForUpdate()->findOrFail($orderId);
            $existing = DB::table('payments')->where('idempotency_key', $idempotencyKey)->value('gateway_reference');
            if ($existing !== null) {
                return $existing;
            }
            $reference = $this->gateway->charge($order->amount_cents, $idempotencyKey);
            DB::table('payments')->insert(['order_id' => $order->id, 'tenant_id' => $tenantId, 'gateway_reference' => $reference, 'idempotency_key' => $idempotencyKey, 'created_at' => now(), 'updated_at' => now()]);

            return $reference;
        });
    }
    // #endregion
}

Verify and troubleshoot

The test proves the fake provider failure becomes PaymentDeclined. Do not let provider exceptions escape a Contract boundary or imply that a fake adapter proves a real provider account.