ekyna/payum-payzen
PayZen gateway for Payum (Systempay, Scellius, CLIC&PAY, OSB, SOGE_COMMERCE). Install via Composer and configure site_id, certificate, mode, hash, cache directory, and endpoint. Supports predefined endpoints or a custom endpoint URL.
Installation
composer require ekyna/payum-payzen
Basic Configuration
Define the gateway in your Laravel service provider (e.g., AppServiceProvider):
use Ekyna\Component\Payum\Payzen\PayzenGatewayFactory;
public function register()
{
$this->app->singleton('payzen.gateway', function ($app) {
$factory = new PayzenGatewayFactory();
return $factory->create([
'site_id' => env('PAYZEN_SITE_ID'),
'certificate' => env('PAYZEN_CERTIFICATE'),
'ctx_mode' => \Ekyna\Component\Payum\Payzen\Api\Api::MODE_TEST, // Use MODE_PRODUCTION in live
'hash_mode' => \Ekyna\Component\Payum\Payzen\Api\Api::HASH_MODE_SHA256,
'directory' => storage_path('app/payzen-cache'),
'endpoint' => \Ekyna\Component\Payum\Payzen\Api\Api::ENDPOINT_SYSTEMPAY,
]);
});
}
First Use Case: Capture a Payment
use Payum\Core\Request\Capture;
$gateway = app('payzen.gateway');
$gateway->execute(new Capture([
'amount' => 100.00,
'currency' => 'EUR',
'details' => [
'siteId' => env('PAYZEN_SITE_ID'),
'paymentId' => uniqid(),
'amount' => 10000, // Amount in cents
'currency' => '978', // ISO 4217 code for EUR
'transactionType' => 'PAYMENT',
'returnUrl' => route('payzen.return'),
'notificationUrls' => [route('payzen.notification')],
],
]));
vendor/ekyna/payum-payzen/README.md (for API reference)vendor/ekyna/payum-payzen/src/Api/Api.php (for endpoint and mode constants)vendor/ekyna/payum-payzen/src/PayzenGatewayFactory.php (for factory configuration)Initialize Payment
Use Capture or Authorize requests to start a transaction.
$gateway->execute(new Capture($details));
Redirect to PayZen
Use Payum\Core\Request\GetHttpRequest to fetch the redirect URL:
$request = new GetHttpRequest();
$gateway->execute($request);
$redirectUrl = $request->getUri();
return redirect($redirectUrl);
Handle Return/Notification
Payum\Core\Request\Status.Payum\Core\Request\Notify to process asynchronous updates.$gateway->execute(new Notify($details));
Refund a Payment
$gateway->execute(new Refund($details));
Laravel Request Handling Bind PayZen notifications to a route and validate the signature:
Route::post('/payzen/notification', [PayzenController::class, 'handleNotification']);
public function handleNotification(Request $request)
{
$gateway = app('payzen.gateway');
$gateway->execute(new Notify($request->all()));
}
Custom Actions Extend the gateway with custom actions (e.g., logging, validation):
$gateway->addAction(new class implements \Payum\Core\GatewayAction\ActionInterface {
public function __invoke($request)
{
// Custom logic (e.g., log payment details)
}
});
Testing
Use MODE_TEST and the PayZen sandbox environment for development:
'ctx_mode' => \Ekyna\Component\Payum\Payzen\Api\Api::MODE_TEST,
Signature Validation
PayZen uses HMAC signatures. Ensure your notification handler validates the SIGNATURE field:
$signature = $request->input('SIGNATURE');
$expectedSignature = hash_hmac(
'sha256',
$request->except('SIGNATURE'),
env('PAYZEN_CERTIFICATE')
);
if (!hash_equals($signature, $expectedSignature)) {
abort(403, 'Invalid signature');
}
Amount Formatting
PayZen expects amounts in cents (e.g., 100.00 EUR → 10000). Convert Laravel’s float values:
$amountInCents = (int) ($amount * 100);
Cache Directory Permissions
Ensure the directory path is writable by the web server:
mkdir -p storage/app/payzen-cache
chmod -R 755 storage/app/payzen-cache
Endpoint Mismatches
Verify endpoint matches your PayZen contract (e.g., ENDPOINT_SYSTEMPAY vs. ENDPOINT_SCELLIUS). Test in sandbox first.
Idempotency
PayZen notifications may be retried. Use paymentId to deduplicate requests:
$paymentId = $request->input('paymentId');
if (Payment::where('payzen_id', $paymentId)->exists()) {
return response()->json(['status' => 'OK']);
}
Enable Payum Logging
Configure Monolog in config/logging.php to log Payum events:
'channels' => [
'payum' => [
'driver' => 'single',
'path' => storage_path('logs/payum.log'),
'level' => 'debug',
],
],
Then add a logger to the gateway:
$gateway->addAction(new \Payum\Core\Bridge\Spl\ArrayObjectToArrayAction());
$gateway->addAction(new \Payum\Core\Bridge\Spl\Log\LoggerAction());
PayZen API Responses
Inspect raw responses in storage/app/payzen-cache for errors. Example error response:
<PAYMENT>
<ERROR>INVALID_SIGNATURE</ERROR>
</PAYMENT>
Custom Fields
Extend the details array with PayZen-specific fields (e.g., customerEmail, orderId):
'details' => [
'customerEmail' => $user->email,
'orderId' => $order->id,
// ... other fields
],
Webhook Validation Create a middleware to validate PayZen webhook signatures globally:
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class ValidatePayzenSignature
{
public function handle(Request $request, Closure $next)
{
if ($request->is('payzen/*')) {
$this->validateSignature($request);
}
return $next($request);
}
protected function validateSignature(Request $request)
{
// Signature validation logic
}
}
Retry Logic Implement a retry mechanism for failed notifications using Laravel’s queue:
$gateway->execute(new Notify($details));
if ($request->isNew()) {
NotifyPayment::dispatch($details)->delay(now()->addMinute());
}
Multi-Currency Support
Dynamically set currency based on user locale:
$currencyCode = app()->getLocale(); // e.g., 'fr_FR' → '978' (EUR)
$details['currency'] = CurrencyCodes::get($currencyCode);
How can I help you explore Laravel packages today?