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 Laravel Package

openpay/sdk

Openpay PHP SDK offers a Composer-installable client for adding Openpay payments to PHP apps, including Laravel. Manage customers, cards, charges, subscriptions, payouts, and webhooks through secure API calls, with straightforward objects and sandbox support.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and pin

composer require openpay/sdk
composer show openpay/sdk   # check whether you got 1.x or 2.x

The package is PSR-4 (Openpay\ → lib/) and needs ext-curl + ext-json. In 2.x the entry point is Openpay\Openpay; in 1.x it was a global Openpay class. Most blog posts and StackOverflow answers you'll find are 1.x — always confirm the namespace before copying a snippet.

Where to look first (in this order)

  1. lib/Openpay.php — the public resource properties (charges, customers, cards, payouts, transfers, plans, subscriptions, webhooks, …). This is your API surface.
  2. lib/Resources/Charge.php (and siblings) — the $_allowedParams whitelist tells you exactly which keys the API accepts. This is better documentation than the README.
  3. lib/ApiRequestor.php — host selection by country code, cURL setup, headers.
  4. lib/Error/ — ApiException, ApiConnectionException.

First use case: one card charge

You never send card numbers to your server. Tokenize in the browser with openpay.js (OpenPay.setId, OpenPay.setApiKey, OpenPay.setSandboxMode, OpenPay.token.create(...), OpenPay.deviceData.setup(formId)), then charge the token server-side:

use Openpay\Openpay;

$openpay = new Openpay(
    config('services.openpay.merchant_id'),
    config('services.openpay.private_key'),
    'MX',                    // MX | CO | PE — also selects the API host
    app()->isProduction()    // false = sandbox
);

$charge = $openpay->charges->create([
    'method'            => 'card',
    'source_id'         => $request->input('token_id'),
    'amount'            => 150.00,        // major units, NOT cents
    'currency'          => 'MXN',
    'description'       => 'Order #1042',
    'order_id'          => 'ORD-1042',
    'device_session_id' => $request->input('device_id'),
    'customer'          => [
        'name'         => 'Ada',
        'last_name'    => 'Lovelace',
        'email'        => '[email protected]',
        'phone_number' => '5555555555',
    ],
]);

Sandbox returns approved charges for the standard test card listed in Openpay's "tarjetas de prueba" docs; use the documented decline cards to exercise your failure paths — sandbox won't fail realistically otherwise.

Implementation Patterns

Laravel wiring. No service provider ships with it, so bind it yourself and keep credentials out of code:

// config/services.php
'openpay' => [
    'merchant_id' => env('OPENPAY_MERCHANT_ID'),
    'private_key' => env('OPENPAY_PRIVATE_KEY'),
    'country'     => env('OPENPAY_COUNTRY', 'MX'),
    'production'  => env('OPENPAY_PRODUCTION', false),
],

// AppServiceProvider::register()
$this->app->singleton(Openpay::class, fn () => new Openpay(
    config('services.openpay.merchant_id'),
    config('services.openpay.private_key'),
    config('services.openpay.country'),
    (bool) config('services.openpay.production'),
));

Then wrap it in your own PaymentGateway interface returning DTOs. The SDK speaks arrays and concrete Openpay\ types; do not let those leak into controllers, jobs, or models. This also makes you unit-testable (the SDK has no interfaces to mock — every resource is a concrete object).

Customer + saved card (the flow you'll actually ship). Create the customer once, attach the token as a card, then charge source_id => $cardId for repeat purchases:

$customer = $openpay->customers->add(['name' => ..., 'email' => ...]);
$card = $customer->cards->add([
    'token_id'          => $tokenId,
    'device_session_id' => $deviceSessionId,
]);
// persist $customer->id and $card->id against your user
$charge = $customer->charges->create([
    'method' => 'card', 'source_id' => $card->id,
    'amount' => 99.00, 'currency' => 'MXN', 'description' => 'Renewal',
]);

Pre-authorization / capture. Create with 'capture' => false, then capture the returned charge ($charge->capture(['amount' => ...])) for the final amount, capped at the authorized amount. Uncaptured holds expire on their own — check Charge.php in your pinned version for the exact cancel/void method name rather than assuming.

Refunds. Refunds hang off the charge object: $charge->refund(['amount' => 50.00, 'description' => 'Partial return']). Full refund = omit amount; partial = supply it, never exceeding the captured total.

Async methods (cash & SPEI) are webhook-only. method => 'store' (OXXO/7-Eleven style) returns a payment_method with a reference/barcode; method => 'bank_account' returns a CLABE. You render those to the user and do not poll — you wait for charge.succeeded. Treat these as pending in your own state machine until the webhook says otherwise.

Webhooks. Register programmatically, respond 200 immediately, enqueue a job, and re-fetch the resource before mutating your order:

$openpay->webhooks->add([
    'url' => route('webhooks.openpay'),
    'event_types' => ['charge.succeeded', 'charge.failed', 'charge.refunded', 'charge.cancelled'],
]);
// controller
$event = $request->json()->all();
VerifyOpenpayEvent::dispatch($event);   // job: verify + re-fetch + idempotent update
return response()->noContent();

Inside the job: confirm the merchant id in the payload is yours, dedupe on the event id, then call $openpay->charges->get($event['transaction']['id']) and trust that response, not the payload body. There is no built-in signature verification helper in this SDK — re-fetching is the reliable check.

Listing, filters, reconciliation. getList() (not list() — reserved-word issues in older PHP) returns an OpenpayList you can foreach; paging metadata (total, limit, offset) comes back alongside data. Filters use bracket syntax: ['limit' => 100, 'offset' => 0, 'creation[gte]' => '2025-01-01', 'status' => 'completed']. Run a nightly job over a creation window and reconcile against local orders — webhooks will be dropped eventually.

Subscriptions and payouts. $openpay->plans->create(...) once, then $customer->subscriptions->create(['plan_id' => ..., 'trial_days' => 30]). Payouts ($openpay->payouts->create(...)) are a separate, balance-dependent operation — don't wire them to the same code path as charges.

Gotchas and Tips

Amounts are decimal, not cents. 150.00 means $150 MXN. Every Stripe habit you have is wrong here. Store integer cents in your DB and convert only at the SDK boundary ($cents / 100), and round() before sending.

Unknown fields are silently dropped. Resource::_create() filters through $_allowedParams, so a typo like 'device_sessionid' disappears with no error and you get a mysterious fraud decline. If a field "isn't doing anything," open the resource class and diff against the whitelist.

The fourth constructor argument is the production flag and it defaults to sandbox. Derive it from config, log which mode you booted in, and assert it at deploy time. Getting this backwards in either direction is the classic incident here.

Country code picks the host. 'MX', 'CO', 'PE' map to different base URLs. Mixing a .mx merchant id with 'CO' gives confusing auth/404 errors, and currency must match the merchant's country (MXN/COP/PEN).

It's stale. Last release June 2020, ~68 stars, zero dependents, PHP ≥5.3-era code: no typed properties, no strict_types, dynamic property assignment that trips E_DEPRECATED on PHP 8.2+. Pin an exact version, expect deprecation noise, and budget for vendoring or forking if you're on modern PHP. There's no security-patch pipeline behind it — treat it as a thin cURL wrapper you own.

No retries, no timeouts, no PSR-3, no idempotency keys. You get an exception or a result. Implement your own timeout/backoff around the call, and never blindly retry charges->create — a network timeout may still have created a charge. Dedupe using your own order_id (a unique index on it) and reconcile with getList before retrying. Also don't log request bodies containing customer PII or the private key.

Two exception types, both needed. Openpay\Error\ApiException for HTTP/API-level failures (getMessage(), getErrorCode(), getDescription(), getHttpCode()) and Openpay\Error\ApiConnectionException for transport failures. Map known error codes to user-facing messages ("tarjeta declinada") and log the raw code + HTTP status for support tickets. Never surface the raw message to end users.

device_session_id is not optional in production. Sandbox approves almost everything; production fraud rules will hold or decline charges missing the device data generated by openpay.js. Pass it on charges->create() and on cards->add().

Float precision bites on read-back. Response amount comes back as a number; format with number_format($charge->amount, 2, '.', '') and never compare floats for equality in assertions.

Multi-tenant/marketplace caution. The client is per-instance but the request layer is lightweight and shares state via the base URL/host resolution. If you serve multiple merchants with different country codes in one process (queue workers!), build one client per tenant inside the job rather than injecting a singleton, and read ApiRequestor.php to see what is static in your pinned version.

getList() not list(). If you're porting snippets from old docs, this is the most common parse error. Same for $customer->charges->getList() vs $openpay->charges->getList() — the scoped resources only return that customer's data.

Debugging playbook. Reproduce with the raw payload from your logs against sandbox, compare against $_allowedParams, check the country/production flags, then check the merchant dashboard — sandbox and production dashboards tell different stories and both beat guessing.

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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons