zionpe / zionpe-php
ZionPe Payments and ZionPe Billing for PHP: signed checkout sessions, refunds, and the Billing API (customers, plans, subscriptions, invoices, quotes, hosted links, webhook endpoints) with webhook verification. No dependencies beyond curl and json.
Requires
- php: >=8.0
- ext-curl: *
- ext-json: *
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
ZionPe Payments for PHP 8. Signed checkout sessions, signed session reads, refunds and webhook verification. Needs only curl and json.
composer require zionpe/zionpe-php
Setup
use ZionPe\ZionPe; $zionpe = new ZionPe(getenv('ZIONPE_SITE_KEY'), getenv('ZIONPE_SITE_SECRET'));
Both values are on Dashboard → ZionPe Payments → Custom Website (live) and Developers → Sandbox (sandbox). Every request is signed for you.
Check the signing works
$zionpe->ping(); // returns the ping response, throws ZionPeException otherwise
Create a checkout session
Amounts are in the main unit: 29.99 is £29.99.
$session = $zionpe->createCheckoutSession([ 'line_items' => [['name' => 'Premium Plan', 'amount' => 29.99, 'quantity' => 1]], 'currency' => 'GBP', 'customer_email' => $order->email, 'success_url' => 'https://yoursite.com/orders/' . $order->id . '/thanks', 'cancel_url' => 'https://yoursite.com/cart', 'metadata' => ['order_id' => (string) $order->id], ], 'order-' . $order->id); // idempotency key: a retry returns the same session header('Location: ' . $session['checkout_url'], true, 303); exit;
Read a session from your server
$s = $zionpe->retrieveCheckoutSession($sessionId); if ($s['status'] === 'completed' && $s['metadata']['order_id'] === (string) $order->id) { /* paid */ }
Refund
$zionpe->createRefund('pi_…', 10.00, 'Damaged item');
Webhooks
use ZionPe\ZionPe; use ZionPe\ZionPeException; $raw = file_get_contents('php://input'); try { $event = ZionPe::constructWebhookEvent($raw, $_SERVER['HTTP_X_ZIONPE_SIGNATURE'] ?? null, getenv('ZIONPE_WEBHOOK_SECRET')); } catch (ZionPeException $e) { http_response_code(400); exit('bad signature'); } if (already_seen($event['id'])) { http_response_code(200); exit; } remember($event['id']); if ($event['type'] === 'checkout.session.completed') { fulfil($event['data']['object']['metadata']['order_id'] ?? null, $event['data']['object']); } http_response_code(200);
Laravel
Bind it once and inject it:
// AppServiceProvider::register() $this->app->singleton(ZionPe::class, fn () => new ZionPe(config('services.zionpe.key'), config('services.zionpe.secret')));
Exclude /webhooks/zionpe from CSRF and read $request->getContent() as the raw body.
Errors
Every failed call throws ZionPeException with errorCode (stable, e.g. signature_invalid, rate_limited), status and body. Full list: https://docs.zionpe.com/api-reference/errors
License
MIT
ZionPe Billing
$billing = new \ZionPe\Billing(getenv('ZIONPE_BILLING_KEY')); // zionpe_live_sk_… or zionpe_test_sk_… $customer = $billing->customers()->create(['email' => 'ada@example.com', 'name' => 'Ada Lovelace']); $plan = $billing->plans()->create(['name' => 'Pro', 'product_family' => 'saas', 'amount' => 19, 'currency' => 'GBP', 'interval' => 'monthly']); $sub = $billing->subscriptions()->create(['customer' => $customer['id'], 'plan' => $plan['id']]); $invoice = $billing->invoices()->list(['subscription_id' => $sub['id']])['data'][0]; $link = $billing->checkoutSessions()->create(['type' => 'invoice', 'invoice' => $invoice['id']]); echo $link['url']; // a ZionPe Billing pay page on your domain $billing->subscriptions()->action($sub['id'], 'cancel', ['at_period_end' => true]); $billing->invoices()->action($invoice['id'], 'record-payment', ['method' => 'bank_transfer', 'reference' => 'BACS-1042']);
Money is in the main unit (19 is £19.00). Every write sends an Idempotency-Key (pass your own as the last argument). Errors throw \ZionPe\ZionPeException with errorCode and status.
Webhooks (billing.* events) verify with the same helper as Payments:
$event = \ZionPe\Billing::constructWebhookEvent(file_get_contents('php://input'), $_SERVER['HTTP_X_ZIONPE_SIGNATURE'] ?? null, getenv('ZIONPE_BILLING_WHSEC'));