plutopay / plutopay-php
PlutoPay PHP SDK — accept card, ACH, terminal, and hosted-checkout payments.
Requires
- php: ^8.1
- ext-curl: *
- ext-json: *
- ext-mbstring: *
- guzzlehttp/guzzle: ^7.3
- guzzlehttp/psr7: ^1.7 || ^2.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
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