Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Sdk Php Laravel Package

online-payments/sdk-php

The Online Payments PHP SDK lets PHP applications communicate with the Online Payments Server API. It wraps API calls in convenient PHP request and response objects, handles authentication and required metadata automatically, and requires PHP 7.4 or above. Install it via Composer with online-payment...

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation

    composer require online-payments/sdk-php
    

    Ensure the package is listed in composer.json under require. On 11.2.1, verify with composer show online-payments/sdk-php.

  2. Environment Configuration Add to .env:

    ONLINE_PAYMENTS_API_KEY=your_live_api_key
    ONLINE_PAYMENTS_API_SECRET=your_live_secret
    ONLINE_PAYMENTS_ENV=live  # or 'sandbox' for testing
    
  3. Service Provider Setup Register the SDK in AppServiceProvider:

    use OnlinePayments\SDK\OnlinePayments;
    
    public function boot()
    {
        OnlinePayments::configure([
            'api_key' => config('services.online_payments.key'),
            'api_secret' => config('services.online_payments.secret'),
            'environment' => config('services.online_payments.env'),
            'timeout' => 30, // seconds
        ]);
    }
    
  4. First Integration: Charge a Payment

    use OnlinePayments\SDK\Payment;
    use OnlinePayments\SDK\Exception\IdempotenceException;
    use OnlinePayments\SDK\Exception\PaymentException;
    
    $payment = new Payment();
    $payment->setAmount(100.00)
            ->setCurrency('USD')
            ->setDescription('Laravel Subscription')
            ->setIdempotencyKey((string) Str::uuid())
            ->setCustomer([
                'email' => '[email protected]',
                'name' => 'John Doe',
            ]);
    
    try {
        $response = $payment->charge();
        // Handle success (e.g., save to DB, send confirmation)
    } catch (IdempotenceException $e) {
        // Duplicate request already in progress — do not treat as a hard failure.
        // Poll the original payment or ask the user to retry shortly.
        Log::warning('Duplicate charge in flight: ' . $e->getMessage());
    } catch (PaymentException $e) {
        // Log error and handle failure
        Log::error('Payment failed: ' . $e->getMessage());
    }
    
  5. Where to look first

    • OnlinePayments::configure() — global singleton config; call once at boot, not per request.
    • Payment / MultiplePaymentInformation — the two request builders you'll touch most.
    • OnlinePayments\SDK\Exception\* — the exception hierarchy drives your retry/UX logic.

Implementation Patterns

1. Laravel Service Integration

Wrap the SDK in a Laravel service class for better testability and dependency injection:

// app/Services/PaymentService.php
namespace App\Services;

use OnlinePayments\SDK\Payment;
use OnlinePayments\SDK\OnlinePayments;

class PaymentService
{
    public function __construct()
    {
        OnlinePayments::configure(config('services.online_payments'));
    }

    public function processPayment(array $data)
    {
        $payment = new Payment();
        $payment->setAmount($data['amount'])
                ->setCurrency($data['currency'])
                ->setDescription($data['description'])
                ->setIdempotencyKey($data['idempotency_key'])
                ->setCustomer($data['customer']);

        return $payment->charge();
    }
}

Register in AppServiceProvider:

$this->app->bind(PaymentService::class, function ($app) {
    return new PaymentService();
});

2. Form Request Validation

Use Laravel's FormRequest to validate payment data before processing:

// app/Http/Requests/ProcessPaymentRequest.php
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ProcessPaymentRequest extends FormRequest
{
    public function rules()
    {
        return [
            'amount' => 'required|numeric|min:0.50',
            'currency' => 'required|string|size:3',
            'description' => 'required|string|max:255',
            'customer.email' => 'required|email',
            'idempotency_key' => 'required|uuid',
        ];
    }
}

Controller Usage:

use App\Http\Requests\ProcessPaymentRequest;
use App\Services\PaymentService;

public function pay(ProcessPaymentRequest $request, PaymentService $paymentService)
{
    $response = $paymentService->processPayment($request->validated());
    // ...
}

3. Idempotent Charge Pattern (updated for 11.2.1)

11.2.1 fixes IdempotenceException being raised for duplicate requests that are still in progress. That changes the semantics: an in-flight duplicate is now surfaced as an exception instead of a hung/failed call, so handle it as a distinct, non-terminal outcome.

// app/Services/IdempotentPaymentService.php
namespace App\Services;

use Illuminate\Support\Str;
use OnlinePayments\SDK\Payment;
use OnlinePayments\SDK\Exception\IdempotenceException;
use OnlinePayments\SDK\Exception\PaymentException;

class IdempotentPaymentService
{
    public function charge(array $data, ?string $key = null)
    {
        $key ??= (string) Str::uuid();

        // Persist the key against your order BEFORE calling out.
        $record = PaymentAttempt::firstOrCreate(
            ['idempotency_key' => $key],
            ['status' => 'pending', 'payload' => $data]
        );

        if ($record->status === 'succeeded') {
            return $record->response; // replay locally, no API call
        }

        try {
            $payment = (new Payment())
                ->setAmount($data['amount'])
                ->setCurrency($data['currency'])
                ->setDescription($data['description'])
                ->setCustomer($data['customer'])
                ->setIdempotencyKey($key);

            $response = $payment->charge();

            $record->update(['status' => 'succeeded', 'response' => $response]);

            return $response;
        } catch (IdempotenceException $e) {
            // A request with this key is mid-flight. Not a failure.
            $record->update(['status' => 'in_progress']);

            return PaymentAttempt::where('idempotency_key', $key)->first()?->response
                ?? ['status' => 'pending'];
        }
    }
}

Queue/retry companion: rather than sleep()-ing inside the request, release the job back onto the queue for IdempotenceException:

public function handle(IdempotentPaymentService $payments)
{
    try {
        $payments->charge($this->data, $this->idempotencyKey);
    } catch (IdempotenceException $e) {
        $this->release(5); // check back after the in-flight request settles
    }
}

4. Queue-Based Processing

Offload payment processing to a queue job for async handling:

// app/Jobs/ProcessPaymentJob.php
namespace App\Jobs;

use App\Services\IdempotentPaymentService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;

class ProcessPaymentJob implements ShouldQueue
{
    use Queueable;

    public int $tries = 5;

    public function __construct(
        public array $data,
        public string $idempotencyKey,
    ) {}

    public function handle(IdempotentPaymentService $payments)
    {
        $payments->charge($this->data, $this->idempotencyKey);
    }
}

Dispatch from Controller:

ProcessPaymentJob::dispatch($request->validated(), (string) Str::uuid());

5. Multiple Payment Information / Split Payments (updated for 11.2.1)

11.2.1 adds a new enum value to paymentPattern in MultiplePaymentInformation. If you build marketplace/split payouts, be defensive about paymentPattern because the enum surface is now wider.

use OnlinePayments\SDK\MultiplePaymentInformation;

$info = new MultiplePaymentInformation();
$info->setPaymentPattern(MultiplePaymentInformation::PATTERN_FIRST); // see SDK constants

// If you branch on the pattern, always include a default:
$payout = match ($info->getPaymentPattern()) {
    MultiplePaymentInformation::PATTERN_FIRST => $this->firstOnly($info),
    MultiplePaymentInformation::PATTERN_ALL   => $this->splitAll($info),
    default => throw new \UnexpectedValueException(
        'Unhandled paymentPattern: ' . var_export($info->getPaymentPattern(), true)
    ),
};

Tip: persist the raw paymentPattern value in your DB rather than a mapped int/enum ordinal — a new SDK enum value can otherwise break hydration of historical rows.


6. Webhook Handling

Laravel routes + events for webhook processing:

// routes/web.php
Route::post('/payment/webhook', [PaymentWebhookController::class, 'handle']);

class PaymentWebhookController extends Controller
{
    public function handle()
    {
        $payload = request()->all();
        $signature = request()->header('X-Signature');

        if (!OnlinePayments::validateWebhook($payload, $signature)) {
            abort(403, 'Invalid signature');
        }

        event(new PaymentWebhookReceived($payload));
    }
}

Event Listener:

// app/Listeners/HandlePaymentWebhook.php
namespace App\Listeners;

use App\Events\PaymentWebhookReceived;

class HandlePaymentWebhook
{
    public function handle(PaymentWebhookReceived $event)
    {
        // Update DB, trigger notifications, etc.
    }
}

7. Retry Logic

Retry at the job/middleware layer only for transient failures. Do not blindly retry IdempotenceException inline — it means a duplicate is already running.

// app/Http/Middleware/RetryPaymentRequests.php
namespace App\Http\Middleware;

use Closure;
use OnlinePayments\SDK\Exception\IdempotenceException;
use OnlinePayments\SDK\Exception\PaymentException;

class RetryPaymentRequests
{
    public function handle($request, Closure $next, int $maxRetries = 3)
    {
        $attempts = 0;
        while ($attempts < $maxRetries) {
            try {
                return $next($request);
            } catch (IdempotenceException $e) {
                throw $e; // let the caller decide; retrying just re-collides
            } catch (PaymentException $e) {
                $attempts++;
                if ($attempts >= $maxRetries) {
                    throw $e;
                }
                sleep(2 ** $attempts); // Exponential backoff
            }
        }
    }
}

Register Middleware:

// bootstrap/app.php (Laravel 11+) or app/Http/Kernel.php
->withMiddleware(function (Middleware $middleware) {
    $middleware->alias(['retry.payment' => \App\Http\Middleware\RetryPaymentRequests::class]);
})

Apply to Route:

Route::post('/pay', [PaymentController::class, 'store'])
    ->middleware('retry.payment:3');

8. Testing with Mocks

// tests/Unit/PaymentServiceTest.php
use OnlinePayments\SDK\Exception\IdempotenceException;
use Illuminate\Support\Facades\Http;

beforeEach(function () {
    Http::fake();
});

it('processes a payment successfully', function () {
    Http::fake([
        'https://api.online-payments.com/v1/payments' => Http::response([
            'success' => true,
            'payment_id' => 'pay_123',
        ], 200),
    ]);

    $service = new PaymentService();
    $response = $service->processPayment([
        'amount' => 100.00,
        'currency' => 'USD',
        'description' => 'Test',
        'customer' => ['email' => '[email protected]'],
        'idempotency_key' => 'test-key-1',
    ]);

    expect($response['payment_id'])->toBe('pay_123');
});

it('treats an in-flight duplicate as non-terminal', function () {
    Http::fake([
        'https://api.online-payments.com/v1/payments' => Http::response(
            ['errors' => [['code' => 'IDEMPOTENCE_IN_PROGRESS']]],
            409
        ),
    ]);

    $service = new IdempotentPaymentService();

    expect(fn () => $service->charge([/* ... */], 'dup-key'))
        ->not->toThrow(\RuntimeException::class);
});

Gotchas and Tips

Common Pitfalls

  1. Environment Mismatches

    • Issue: Forgetting to switch between sandbox and live environments.
    • Fix: Use Laravel's config('services.online_payments.env') and validate it in tests:
      expect(config('services.online_payments.env'))->toBe('sandbox');
      
  2. Idempotency Key Conflicts (behavior changed in 11.2.1)

    • Issue: Duplicate payments if idempotency keys aren't handled — and, before 11.2.1, duplicates that were still being processed did not reliably raise IdempotenceException, leaving you to guess whether a call failed or was merely in flight.
    • Fix: Generate a UUID per order, store it before the call, and catch IdempotenceException as its own branch. It now correctly signals "a duplicate request is in progress" — resolve by reading your persisted attempt record or the original response, not by retrying with a new key.
      $payment->setIdempotencyKey((string) Str::uuid());
      
    • Debugging: If you suddenly see IdempotenceException where you previously saw a timeout/500 on a duplicate, that's the fix working as intended — check your logs for two calls sharing one key.
  3. paymentPattern Enum Growth (11.2.1)

    • Issue: A new paymentPattern value in MultiplePaymentInformation can silently fall through match/switch statements and array lookups, producing null payouts or an unhandled UnhandledMatchError.
    • Fix: Always include a default arm, log the unexpected value, and store the raw pattern value in your database rather than a mapped index.
  4. Webhook Signature Validation

    • Issue: Replay attacks if signatures aren't validated.
    • Fix: Always validate signatures in webhook endpoints:
      if (!OnlinePayments::validateWebhook($payload, $signature)) {
          abort(403);
      }
      
  5. Currency Formatting

    • Issue: Incorrect decimal places (e.g., 100.00 vs. 100 for cents). This is the single most common silent-failure in review tickets.
    • Fix: Normalize amounts to integers (e.g., 10000 for $100.00) for zero-decimal-safe currencies:
      $payment->setAmount((int) ($amount * 100));
      
  6. Rate Limiting

    • Issue: API throttling during high traffic.
    • Fix: Implement Laravel's throttle middleware on your own endpoints and cap queue worker concurrency for payment jobs:
      Route::middleware(['throttle:10,1'])->group(function () {
          // Payment routes
      });
      
  7. Global SDK Configuration is a Singleton

    • Issue: Calling OnlinePayments::configure() inside a constructor can clobber config registered elsewhere (and will run once per resolution).
    • Fix: Configure once in a service provider; inject config into PaymentService instead of re-configuring.

Debugging Tips

  1. Enable SDK Logging Configure Monolog to log SDK requests/responses:

    OnlinePayments::setLogger(function ($message) {
        Log::channel('payments')->debug($message);
    });
    

    Create a dedicated payments channel in config/logging.php so you can raise/lower verbosity independently of app logs — and make sure it doesn't capture raw card data.

  2. Log the Idempotency Key on Every Attempt Every payment log line should carry idempotency_key, amount, currency, and the exception class. When IdempotenceException fires, grep that key to see whether the first call actually reached the API.

  3. Separate "in flight" from "failed" in your exception handler

    // bootstrap/app.php
    ->withExceptions(function (Exceptions $exceptions) {
        $exceptions->render(function (IdempotenceException $e, $request) {
            return response()->json(['status' => 'processing'], 409);
        });
    });
    
  4. Use Http::fake() + Http::assertSentCount() to prove you aren't double-charging in retry paths — a passing "success" test says nothing about duplicate suppression.

  5. Pin and diff the SDK on upgrades

    composer update online-payments/sdk-php --with-dependencies
    

    Then run a targeted test suite (payments + split payouts) since minor releases like 11.2.x have changed exception-raising and enum surfaces.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc