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

Payplug Php Laravel Package

payplug/payplug-php

Official PayPlug PHP library for integrating payments into your application. Create and capture payments, issue full or partial refunds, manage customers and cards, and handle installments, one-click payments, and webhooks through PayPlug's REST API. Lightweight, framework-agnostic, and easy to inst...

View on GitHub
Deep Wiki
Context7

Getting Started

payplug/payplug-php is the official plain PHP SDK for the PayPlug REST API — it is not a Laravel package. There is no service provider, no facade, no published config, and no auto-discovery. The first hour is about wrapping it correctly before you write a single payment. Budget for that wrapper; it is the load-bearing decision.

Install and pin

composer require "payplug/payplug-php:^4.2"

Pin the exact version in composer.json (4.2.x), not a floating ^4. The 4.2.0 changelog is a single line ("Feature Hosted Field added"), so treat semver as a claim, not a guarantee — read the diff before bumping.

Where to look first

  1. README.md and doc/ in the repo — the only real reference for the SDK surface. This is your API contract.
  2. src/Payplug.php — the static bootstrap (Payplug::init(), setSecretKey(), setApiVersion()). Everything hangs off this.
  3. src/Core/ — Config, Service, HttpClient. This is where timeouts, tracing and test doubles live.
  4. CHANGELOG.md — 4.2.0 lands Hosted Fields; if you are still capturing cards against your own DOM, read this before you build the checkout.

Configure in Laravel config/services.php

'payplug' => [
    'secret_key' => env('PAYPLUG_SECRET_KEY'),        // sk_test_... / sk_live_...
    'api_version' => env('PAYPLUG_API_VERSION', '2019-06-20'),
    'timeout'     => env('PAYPLUG_TIMEOUT', 10),
    'sandbox'     => env('PAYPLUG_SANDBOX', false),
],

Bootstrap once, per request — with Payplug::init()

// AppServiceProvider::register()
$this->app->singleton(PayplugClient::class, function () {
    Payplug::init([
        'secretKey' => config('services.payplug.secret_key'),
        'apiVersion' => config('services.payplug.api_version'),
    ]);
    return new PayplugClient(); // your thin wrapper around Payplug\Payment etc.
});

First use case — create a hosted-field payment and redirect

use Payplug\Payment;

$payment = Payment::create([
    'amount'       => 1999, // minor units (cents)
    'currency'     => 'EUR',
    'hosted_payment' => [
        'return_url' => route('checkout.return'),
        'cancel_url' => route('checkout.cancel'),
    ],
    'notification_url' => route('webhooks.payplug'),
]);

return redirect()->away($payment->hosted_payment->payment_url);

Confirm the exact parameter names against doc/ for 4.2.0 — the hosted-fields surface is documented there, not in a Laravel-oriented guide.

Implementation Patterns

1. The gateway interface is non-negotiable. Nothing outside App\Payments should use Payplug\.... A single PayplugGateway interface with createPayment(), capture(), refund(), findPayment() lets you mock in tests, swap providers, and survive a v5 break without touching controllers.

interface PayplugGateway {
    public function createPayment(PaymentIntent $intent): PaymentResult;
    public function refund(string $paymentId, ?int $amount = null): RefundResult;
    public function find(string $paymentId): PaymentResult;
}

2. Idempotency is owned by you. The SDK does not enforce it. Generate a UUID per attempt, store it on the payment row, pass it as a request header/metadata, and make retries safe by key — never by blind re-submission.

$key = (string) Str::uuid();
$payment = Payment::create([...], ['headersOnError' => null, 'idempotency_key' => $key]);

3. Webhook handler is a first-class citizen.

Route::post('/webhooks/payplug', function (Request $r) {
    // Verify signature header against raw body BEFORE parsing
    if (! Payplug::verifySignature($r->getContent(), $r->header('X-Payplug-Signature'))) {
        abort(400);
    }
    WebhookEvent::dispatch($r->json()); // store first, process async
    return response()->noContent();
});

Persist the event ID before dispatch, return 200 fast, process on a queue, and deduplicate on event ID + payment ID. PayPlug retries; out-of-order delivery is normal.

4. Treat SDK calls as ambiguous on timeout. Wrap every create/capture/refund in a job or a controller with a strict timeout. On timeout, do not return success to the user; transition the local state to pending_reconciliation and let the webhook or a nightly Payment::retrieve() sweep settle it. Client-side optimism is how you double-charge users.

5. Hosted Fields flow (4.2.0). Server-side, this is a payment-creation parameter — the actual card number never touches your DOM. Front-end:

<script src="https://cdn.payplug.com/hosted-fields.js"></script>
<form id="payplug-form">
    <div id="payplug-field" data-payment-id="{{ $payment->id }}"></div>
</form>
<script>
  PayplugHostedFields.mount('#payplug-field', { paymentId: '{{ $payment->id }}' });
</script>

Ship a fallback (redirect to the hosted page) for iframe load failure and CSP blocks — a blank iframe looks like a code bug and will burn a support ticket.

6. Exception surface is heavy. Map SDK exceptions to a small domain enum:

try {
    $payment = $this->client->create($intent);
} catch (\Payplug\Exception\PayplugException $e) {
    throw match (true) {
        $e instanceof \Payplug\Exception\UndefinedAttributeException => new PaymentFailed($e),
        str_contains($e->getMessage(), 'insufficient_funds')        => new InsufficientFunds($e),
        default                                                      => new PaymentFailed($e, previous: $e),
    };
}

7. Sandbox tests against recorded fixtures. Capture real sandbox responses into tests/Fixtures/payplug/*.json and bind a fake HttpClient in testing. Do not hit the live API in CI.

Gotchas and Tips

Global static state is the #1 hazard. Payplug::init() mutates process-global state. In Laravel Octane, Horizon/Swoole workers, and any multi-tenant setup, this bleeds credentials between requests. If you support multiple merchants or sandbox+production in one process: re-initialise on every job dispatch and never hold a PayplugClient singleton across tenants — inject a per-tenant factory instead.

Sandbox keys in production is the most common incident. Guard at boot:

abort_if(app()->isProduction() && str_starts_with(config('services.payplug.secret_key'), 'sk_test'), 500);

Minor units. amount is in cents (or the currency's minor unit). EUR 19.99 = 1999. Use an integer money library on the domain side; never store floats. Rounding bugs on refunds (partial vs. full) are the second-most-common bug after idempotency.

Version-pin the API header. Set apiVersion explicitly in config and confirm the SDK does not silently negotiate it. An unannounced server-side version bump is a real risk during a payment-method rollout.

3DS/SCA abandonment leaves orphaned pending payments. A user closes the challenge tab — your local record stays pending. Run a scheduled reconciliation (list payments, match against local status, alert on drift) from day one; it is also the standard control for ambiguous timeouts.

CSP for Hosted Fields. Update Content-Security-Policy with frame-src https://*.payplug.com and script-src for the hosted-fields bundle. Coordinate with security early — a blocked iframe presents as a blank form, not a security error.

Timeouts on the checkout critical path. Every SDK call is a synchronous HTTP round-trip. Set connect/read timeouts (~5s / ~10s) and never let a web request retry inline. Move captures, refunds, and reconciliation to queues.

Refund idempotency is separate. A partial refund requires its own idempotency key and its own reconciliation entry. Do not reuse the payment intent key.

Version 4.2.0 is additive but underspecified. The changelog does not describe the field-hosting surface. Spike against the sandbox with the actual artifact before committing a launch date; treat "drop-in upgrade" as a hypothesis to validate, not a fact.

Upgrade from 3.x is a real migration. Error handling moved to typed exceptions, signature verification changed, and constant-based config was replaced by Payplug::init(). Never bundle a 3.x→4.x upgrade with a feature release.

You own the adapter, webhook, idempotency, and reconciliation layers — forever. Estimate that work explicitly (1–2 weeks for standard card flows; 2–4 weeks for Hosted Fields including security and CSP review). The SDK gives you primitives, not a Laravel billing abstraction; if you expect a Cashier-style API, this is the wrong package.

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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core