code16 / laravel-systempay
Systempay (Banque Populaire) tools for Laravel
Requires
- php: ^8.4
- code16/pint-config: ^1.3
- illuminate/contracts: ^12.0||^13.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^11.0.0||^10.0.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Features
- Fast and easy form generation for Systempay (by Banque Populaire)
- Support multiple site id for multiple stores within the same project
- Validate payment signature and status
- Cancel or refund a transaction (Using the API-REST)
- Retrieve a transaction's data (Using the API-REST)
Note
If you want to use this SystemPay client to cancel or refund transactions, you must create also a REST API key in the SystemPay Back Office.
Installation
- Install the package
composer require code16/laravel-systempay
- Publish the config file
php artisan vendor:publish --tag="systempay-config"
Configuration
After publishing edit the default configuration file : config/systempay.php
return [ 'default' => [ 'site_id' => 'YOUR_SITE_ID', 'key' => env('SYSTEMPAY_SITE_KEY', 'YOUR_KEY'), 'env' => env('SYSTEMPAY_ENV', 'PRODUCTION'), 'rest' => [ // Only required to use cancel()/refund()/cancelOrRefund()/getTransaction(). 'password' => env('SYSTEMPAY_REST_API_PASSWORD'), ], ] ];
You need to set YOUR_SITE_ID and YOUR_KEY with your own values. This two values are given by Systempay.
key is only used to sign/verify the payment form and IPN callbacks. To use cancel(), refund(),
cancelOrRefund() (see Cancel or refund a payment) or getTransaction()
(see Get a transaction), you also need to set rest.password, the REST API
password found in the Back Office, under Paramétrage > Boutique > Clés d'API REST (use the test
or production password depending on env).
Specific parameters
These parameters are set by default :
| name | default value | note |
|---|---|---|
| currency | 978 | List of currency codes |
| payment_config | SINGLE | SINGLE or MULTIPLE |
| trans_date | [current datetime] | Generated automaticaly |
| page_action | PAYMENT | |
| action_mode | INTERACTIVE | |
| version | V2 | |
| signature | [generated] | Generated automaticaly |
Also see Systempay documentation
NB : you don't have to add the vads_ prefix to parameters, the prefix will be automaticaly added.
But you can also set the parameters with the vads_ prefix, it will be automaticaly removed.
NB : amount must be given as an integer in the smallest currency unit expected by Systempay
(e.g. cents for EUR). For example, use 1234 for 12.34€, not 12.34. No unit conversion is
performed by this package.
There is also possible to set some specific parameters to a configuration by setting params values.
Example :
return [ 'default' => [ // ... 'params' => [ 'currency' => '826' ] ] ];
In this case, default configuration will use the currency code 826.
Additional configuration
You can add as many configuration as you need by adding a new key to the configuration file.
For example :
return [ 'default' => [ // ... ], 'store_uk' => [ 'site_id' => '123456', 'key' => env('SYSTEMPAY_UK_SITE_KEY', '12345678'), 'env' => env('SYSTEMPAY_UK_ENV', 'PRODUCTION'), ] ];
To use another configuration, call the config method, for example :
$systemPay = SystemPay::config('store_uk')->set([ 'amount' => 1234, // 12.34€, in cents 'trans_id' => 123456 ]);
IPN Callback
When a payment is processed, Systempay sends a POST request to your IPN URL (to be configured in your Systempay back office).
You can use the Systempay facade to validate the signature and check the payment status.
namespace App\Http\Controllers; use Illuminate\Http\Request; use SystemPay; class PaymentCallbackController extends Controller { public function __invoke(Request $request) { // 1. Retrieve the webhook data $payload = SystemPay::formatWebhookPayload($request); // 2. Validate the signature if (! $payload->validateSignature()) { abort(403, 'Invalid signature'); } // 3. Check if the payment is valid (status is ACCEPTED, CAPTURED, or AUTHORISED) if (! $payload->isValidPayment()) { // Payment refused or cancelled abort(400, 'Invalid payment'); } // 4. Verify the paid amount and currency abort_unless(Order::find($payload->orderId)->amount == $payload->amount, 400, 'Invalid amount'); // Update your database... return response()->json(['status' => 'ok']); } }
Webhook payload
formatWebhookPayload() looks up the signing key for the given configuration (default unless
specified) and returns a Code16\Systempay\WebhookPayload object with the following read-only
properties:
| property | type | source field |
|---|---|---|
orderId |
string |
vads_order_id |
transactionId |
string |
vads_trans_id |
transactionUuid |
string |
vads_trans_uuid |
amount |
int |
vads_amount |
currencyCode |
string |
vads_currency |
status |
string |
vads_trans_status |
It also exposes validateSignature() and isValidPayment(), so the whole IPN payload — data and
validation — comes from a single object.
Signature validation for specific configuration
If you have multiple configurations, pass the configuration name to formatWebhookPayload:
SystemPay::formatWebhookPayload($request, 'store_uk')->validateSignature();
Customize valid payment status
By default, isValidPayment() returns true if the status is CAPTURED, ACCEPTED, or AUTHORISED. You can customize this by passing an array of valid statuses as the parameter:
$payload->isValidPayment(['CAPTURED']);
Create a payment form
To create a payment form, you can use the Systempay facade.
In your controller :
<?php namespace App\Http\Controllers; use SystemPay; // Facade class PaymentController extends Controller { public function create() { $systemPay = SystemPay::set([ 'amount' => 1234, // 12.34€, in cents 'trans_id' => 123456 ]); return view('payment', compact('systemPay')); } }
In your view
<x-systempay::form :config="$systemPay"> <x-slot:button> <button type="submit" class="btn btn-primary"> Pay </button> </x-slot:button> </x-systempay::form>
Cancel or refund a payment
Use cancel() to cancel a transaction that has not been captured yet (i.e. before it is remised
en banque), and refund() to refund a captured transaction, totally or partially. Both need the
transaction uuid, as returned by formatWebhookPayload().
use SystemPay; // Cancel a transaction, with an optional comment SystemPay::cancel($uuid, 'Order cancelled by customer'); // Refund a transaction in full SystemPay::refund($uuid); // Refund a transaction partially: amount in the smallest currency unit (e.g. cents for EUR) SystemPay::refund($uuid, amount: 1000, currency: 'EUR');
Both throw Code16\Systempay\Exceptions\SystemPayApiException if Systempay rejects the request
(e.g. the transaction cannot be cancelled or refunded in its current state). The exception's
response() method returns the decoded API response, including the errorCode returned by
Systempay:
use Code16\Systempay\Exceptions\SystempayApiException; try { SystemPay::refund($uuid); } catch (SystempayApiException $e) { logger()->error($e->getMessage(), $e->response()); }
Under the hood, both call the Transaction/CancelOrRefund Web Service, which lets Systempay pick
the operation automatically based on the transaction's status. You can call it directly and force
the operation with resolutionMode (AUTO, CANCELLATION_ONLY or REFUND_ONLY):
SystemPay::cancelOrRefund($uuid, amount: 1000, currency: 'EUR', resolutionMode: 'REFUND_ONLY');
As with validateSignature, pass a configuration name as the last argument to target a specific
store:
SystemPay::refund($uuid, config: 'store_uk');
Get a transaction
Use getTransaction() to retrieve all the data Systempay holds for a transaction, identified by
its uuid (as returned by formatWebhookPayload()):
$transaction = SystemPay::getTransaction($uuid); $transaction['status']; // e.g. "CAPTURED" $transaction['amount']; // in the smallest currency unit (e.g. cents for EUR)
It calls the Transaction/Get Web Service and, like cancel()/refund(), throws
SystempayApiException if Systempay rejects the request, and accepts a configuration name as the
second argument:
SystemPay::getTransaction($uuid, 'store_uk');