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

Multisafepay Laravel Package

omnipay/multisafepay

Omnipay MultiSafepay adds a MultiSafepay gateway driver to the Omnipay PHP payment library, letting PHP applications create payments, handle redirects and callbacks, and process refunds through MultiSafepay’s API using a consistent Omnipay interface.

Deep Wiki
Context7

Getting Started

Install the driver alongside the Omnipay core:

composer require omnipay/multisafepay omnipay/common

Create the gateway through the Omnipay factory (so it can be swapped/mocked):

use Omnipay\Omnipay;

$gateway = Omnipay::create('MultiSafepay');
$gateway->setApiKey(config('services.multisafepay.key'));
$gateway->setTestMode(app()->environment('local', 'testing'));

The first use case you should implement is the canonical redirect flow: purchase() → send the customer to MultiSafepay → customer returns → completePurchase(). That covers iDEAL, Bancontact, credit card, PayPal, and most local methods.

$response = $gateway->purchase([
    'amount'        => '10.00',          // decimal string, NOT cents
    'currency'      => 'EUR',
    'transactionId' => $order->id,       // your merchant order reference
    'returnUrl'     => route('payments.return', $order),
    'cancelUrl'     => route('payments.cancel', $order),
    'notifyUrl'     => route('payments.webhook'),
    'paymentMethod' => 'iDEAL',
    'issuer'        => $request->input('issuer'), // e.g. 'ING'
    'description'   => "Order #{$order->id}",
])->send();

if ($response->isRedirect()) {
    return redirect($response->getRedirectUrl());
}

throw new \RuntimeException($response->getMessage());

The fastest place to learn the exact field names this driver accepts is the tests/ directory — it is better documentation than the README for method-specific quirks.

Implementation Patterns

Separate "initiate" from "verify". Treat purchase() as fire-and-forget: persist the MultiSafepay transactionid (from $response->getTransactionReference()) against your order, then redirect. Never mark an order paid from the browser return URL alone — the user can hit returnUrl without paying.

Handle the webhook as the source of truth. MultiSafepay calls your notifyUrl with a transactionid query parameter. Do not trust it; re-fetch the status from the API before mutating state:

Route::get('/payments/webhook', function (Request $request, $gateway) {
    $response = $gateway->completePurchase([
        'transactionId' => $request->query('transactionid'),
    ])->send();

    if ($response->isSuccessful()) {
        // idempotent: check the order is not already marked paid
    }

    return response('OK', 200);
});

Fetch issuers for the payment-method selector. For iDEAL/Bancontact you need the bank list before purchase():

$issuers = $gateway->fetchIssuers(['paymentMethod' => 'iDEAL'])->send()->getIssuers();
// [['id' => 'ABNANL2A', 'name' => 'ABN AMRO'], ...]

Wrap the gateway in a service, not a controller. In Laravel, bind a configured instance in a service provider and inject it. This keeps setApiKey/setTestMode out of controllers and makes swapping the fake driver in tests trivial.

$this->app->singleton(PaymentGateway::class, fn () => Omnipay::create('MultiSafepay')
    ->setApiKey(config('services.multisafepay.key'))
    ->setTestMode(config('services.multisafepay.test')));

Correlate with two keys. Keep both your internal transactionId and the MultiSafepay transactionReference in the orders table with a unique index. completePurchase() needs the MultiSafepay one; refunds and support tickets usually need the other. Store both.

Refund and status lookups use the transaction reference:

$gateway->refund(['transactionReference' => $order->msp_ref, 'amount' => '10.00'])->send();
$gateway->fetchTransaction(['transactionReference' => $order->msp_ref])->send();

Gotchas and Tips

  • Amount format. Omnipay conventions say decimal strings ("10.00"), and MultiSafepay's API expects them that way. Do not pass integer cents — a 1000 becomes €1000, not €10. Format at the boundary with number_format($cents / 100, 2, '.', '').
  • completePurchase takes the MultiSafepay ID, not yours. The key is transactionId in Omnipay parlance, but the value is what MultiSafepay sent as transactionid in the callback. Passing your merchant order ID silently returns "not found" or a pending status.
  • Webhook idempotency. MultiSafepay retries notifications. Use a paid_at column or a unique constraint on msp_ref so a second callback cannot double-credit. Return HTTP 200 with a plain body; a redirect or JSON envelope can trigger retries.
  • Status is richer than isSuccessful(). MultiSafepay returns initialized, uncleared, completed, declined, cancelled, refunded, partial_refunded, expired. The driver maps the common ones, but uncleared (manually reviewed) is a real state you must model in your order workflow — it is neither success nor failure from the merchant's perspective.
  • Test mode endpoint. setTestMode(true) routes to the MultiSafepay test environment; test API keys only work there and live keys only in live. A silent isRedirect() === false with an auth-style error usually means the key and mode are mismatched.
  • Legacy config keys still exist. Older integrations use accountId/siteId/securityCode instead of apiKey. If you inherit a codebase using those, do not half-migrate; the driver picks the credential shape at request build time.
  • paymentMethod vs issuer. paymentMethod maps to MultiSafepay's gateway field. Getting the casing wrong ("ideal" vs "iDEAL") is a common 400-error source; check fetchPaymentMethods() output rather than guessing strings.
  • Currency support varies by method. iDEAL is EUR-only; card methods support more. Validate currency against the chosen paymentMethod in your form layer so the API does not reject the request after the user has confirmed.
  • No signature on notifications. Unlike Stripe, there is nothing to HMAC-verify — the API re-fetch in completePurchase() is your only integrity check. Never derive the payment status from query-string data directly.
  • Extension point. For methods the driver does not model (e.g. recurring/token charges), subclass the relevant request or use $gateway->getDefaultParameters() plus a custom request class; the abstract base handles the HTTP client so you only map the JSON body.

Given the small community (20 stars) and sparse docs, pin the version in composer.json and add integration tests against the test API for every payment method you expose.

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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin