Search by

plutopay / plutopay-php

hosniabushbak

PlutoPay PHP SDK — accept card, ACH, terminal, and hosted-checkout payments.

v0.1.1 2026-09-14 18:51 UTC

This package is auto-updated.

Last update: 2026-09-14 21:36:38 UTC


README

The official PlutoPay SDK for PHP — accept card, ACH, terminal, and hosted-checkout payments. First-class for Laravel and WooCommerce merchants.

  • 📘 Docs: https://docs.plutopayus.com
  • 🧩 Typed methods for every endpoint, generated from the OpenAPI spec
  • 🔁 Automatic retries with backoff on 429 / 5xx
  • 🔐 Webhook signature verification helper
  • 💵 All amounts are integers in cents

Install

composer require plutopay/plutopay-php:^0.1

Requires PHP 8.1+. Pin ^0.1: while the major is 0, Composer treats the caret as >=0.1.0 <0.2.0, so a breaking change ships as 0.2.0 and never reaches you unasked. Every release is listed in CHANGELOG.md.

Quick start

use PlutoPay\Client;
use PlutoPay\Model\CreateTransactionRequest;

$pluto = new Client(getenv('PLUTOPAY_SECRET_KEY'));

$txn = $pluto->transactions->createPayment(
    new CreateTransactionRequest([
        'amount'   => 4750,          // $47.50 in cents
        'currency' => 'usd',
        'payment_method_type' => 'card',
        'description' => 'Order #1001',
    ]),
    'order_1001'                     // Idempotency-Key
);

echo $txn->getData()->getId();
echo $txn->getClientSecret();        // confirm client-side with the Payment Element

Using with Laravel

Config

Add your key to .env:

PLUTOPAY_SECRET_KEY=sk_live_...
PLUTOPAY_WEBHOOK_SECRET=whsec_...

config/services.php:

'plutopay' => [
    'secret'         => env('PLUTOPAY_SECRET_KEY'),
    'webhook_secret' => env('PLUTOPAY_WEBHOOK_SECRET'),
],

Bind the client as a singleton in AppServiceProvider::register():

use PlutoPay\Client;

$this->app->singleton(Client::class, fn () => new Client(config('services.plutopay.secret')));

Create a hosted checkout (controller)

use PlutoPay\Client;
use PlutoPay\Model\CreateCheckoutSessionRequest;

class CheckoutController extends Controller
{
    public function store(Request $request, Client $pluto)
    {
        $session = $pluto->checkout->createCheckoutSession(
            new CreateCheckoutSessionRequest([
                'amount'      => 4750,
                'currency'    => 'usd',
                'success_url' => route('thanks'),
                'cancel_url'  => route('cart'),
            ]),
            (string) Str::uuid()      // Idempotency-Key
        );

        return redirect($session->getData()->getUrl());
    }
}

Verify a webhook (route)

Use the raw request body — Laravel exposes it via $request->getContent():

use PlutoPay\Webhook;

Route::post('/webhooks/plutopay', function (Request $request) {
    try {
        $event = Webhook::constructEvent(
            $request->getContent(),
            $request->header('X-PlutoPay-Signature', ''),
            config('services.plutopay.webhook_secret'),
        );
    } catch (\RuntimeException $e) {
        return response('invalid signature', 400);
    }

    match ($event['type']) {
        'payment.succeeded' => /* fulfill the order */ null,
        default             => null,
    };

    return response('', 200);
})->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class);

Errors

Non-2xx responses throw PlutoPay\ApiException, which carries the canonical error envelope:

use PlutoPay\ApiException;

try {
    $pluto->transactions->createPayment($req);
} catch (ApiException $e) {
    $error = json_decode($e->getResponseBody(), true)['error'] ?? [];
    // $error['type'], $error['message'], $error['code'], $error['param']
    report($e);
}

Resources

$pluto->transactions, ->checkout, ->paymentLinks, ->refunds, ->terminal, ->customers, ->payouts, ->disputes, ->merchant, ->webhookEndpoints.

Terminal (server-driven reader)

use PlutoPay\Model\{CreateTerminalPaymentRequest, ProcessTerminalPaymentRequest, CancelTransactionRequest};

$created = $pluto->terminal->createTerminalPayment(
    new CreateTerminalPaymentRequest(['amount' => 4750, 'metadata' => ['order_id' => '1001']]),
    'order_1001'                                     // Idempotency-Key — a retry returns the same payment
)->getData();

$pluto->terminal->processTerminalPayment(new ProcessTerminalPaymentRequest([
    'payment_intent_id' => $created->getPaymentIntentId(),
    'reader_id'         => 'tmr_…',                  // from $pluto->terminal->listTerminals()
]));

// Customer walked away: reset the reader AND cancel the intent in one call.
$pluto->transactions->cancelTransaction($created->getId(), new CancelTransactionRequest(['reason' => 'abandoned']));

The result arrives by webhook (payment.succeeded / payment.failed) or by polling $pluto->transactions->getTransaction($id). See examples/terminal.php and the Terminal guide.

Regenerating

The API classes are generated from openapi.yaml (the single source of truth) with openapi-generator 7.23.0, pinned in openapitools.json — a different generator version rewrites every file and hides the real diff:

bash scripts/generate.sh

License

MIT