This guide explains how to integrate SenangPay payment gateway with the Laravel Payments package.
SenangPay is a Malaysian payment gateway that supports multiple payment methods including:
Since SenangPay doesn't require any specific package installation (we're using direct API integration), you just need to ensure you have the Laravel Payments package installed and configured.
Add your SenangPay credentials to your .env file:
SENANGPAY_MERCHANT_ID=your_merchant_id_here
SENANGPAY_SECRET_KEY=your_secret_key_here
SENANGPAY_TEST_MODE=true
SENANGPAY_RETURN_URL=https://yoursite.com/payment/success
SENANGPAY_CALLBACK_URL=https://yoursite.com/senangpay/callback
You can obtain these credentials from your SenangPay dashboard.
For development/testing:
SENANGPAY_TEST_MODE=true
For production:
SENANGPAY_TEST_MODE=false
You also need to add the configuration to your config/services.php:
'senangpay' => [
'merchant_id' => env('SENANGPAY_MERCHANT_ID'),
'secret_key' => env('SENANGPAY_SECRET_KEY'),
'test_mode' => env('SENANGPAY_TEST_MODE', true),
'return_url' => env('SENANGPAY_RETURN_URL'),
'callback_url' => env('SENANGPAY_CALLBACK_URL'),
],
use Mdiqbal\LaravelPayments\Facades\Payment;
$paymentRequest = [
'amount' => 100.00,
'currency' => 'MYR',
'email' => 'customer@example.com',
'transaction_id' => 'TXN' . time(),
'redirect_url' => 'https://yoursite.com/payment/callback',
'customer' => [
'name' => 'John Doe',
'phone' => '01234567890',
'address' => '123 Main St',
'city' => 'Kuala Lumpur',
'country' => 'MY',
'postal_code' => '50000'
],
'metadata' => [
'order_id' => 'ORD123456',
'user_id' => 789
]
];
$payment = Payment::gateway('senangpay')->pay($paymentRequest);
This will return a payment URL that you need to redirect the user to, along with form data for posting:
if ($payment['success']) {
// Option 1: Redirect directly to payment URL
return redirect($payment['payment_url']);
// Option 2: Show a form with auto-submit
return view('payment.senangpay', [
'payment_url' => $payment['payment_url'],
'form_data' => $payment['form_data']
]);
}
Create a view at resources/views/payment/senangpay.blade.php:
<!DOCTYPE html>
<html>
<head>
<title>Redirecting to Payment...</title>
</head>
<body onload="document.forms['payment_form'].submit()">
<form id="payment_form" method="POST" action="{{ $payment_url }}">
[@csrf](https://github.com/csrf)
[@foreach](https://github.com/foreach)($form_data as $key => $value)
<input type="hidden" name="{{ $key }}" value="{{ $value }}">
[@endforeach](https://github.com/endforeach)
<p>Redirecting to secure payment page...</p>
<noscript>
<input type="submit" value="Continue to Payment">
</noscript>
</form>
</body>
</html>
// routes/web.php
Route::get('/payment/success', [PaymentController::class, 'success']);
Route::post('/senangpay/callback', [SenangPayController::class, 'callback']);
// app/Http/Controllers/SenangPayController.php
use Mdiqbal\LaravelPayments\Facades\Payment;
class SenangPayController extends Controller
{
public function callback(Request $request)
{
// Parse callback data
$gateway = Payment::gateway('senangpay');
$callbackData = $gateway->parseCallback($request);
// Process the webhook
$result = $gateway->processWebhook($callbackData);
if ($result['success']) {
$transactionId = $result['transaction_id'];
$status = $result['status'];
if ($status === 'successful') {
// Update order status
$order = Order::where('transaction_id', $transactionId)->first();
if ($order) {
$order->status = 'paid';
$order->paid_at = now();
$order->save();
}
}
}
return response()->json(['status' => 'received']);
}
public function success(Request $request)
{
// Handle successful return from payment page
// Note: Always rely on callback for final status confirmation
return view('payment.success');
}
}
$refundData = [
'transaction_id' => 'SENANGPAY_TRANSACTION_ID',
'amount' => 50.00,
'reason' => 'Customer requested refund'
];
$refund = Payment::gateway('senangpay')->refund($refundData);
$linkData = [
'amount' => 100.00,
'currency' => 'MYR',
'description' => 'Payment for invoice #123',
'customer' => [
'name' => 'John Doe',
'email' => 'customer@example.com'
],
'redirect_url' => 'https://yoursite.com/success'
];
$paymentLink = Payment::gateway('senangpay')->createPaymentLink($linkData);
SenangPay's primary currency is MYR (Malaysian Ringgit). The gateway automatically converts other currencies to MYR using predefined exchange rates:
// This will be converted to MYR internally
$paymentRequest = [
'amount' => 25.00,
'currency' => 'USD',
'email' => 'customer@example.com',
'transaction_id' => 'TXN' . time(),
// ... other parameters
];
$payment = Payment::gateway('senangpay')->pay($paymentRequest);
// $payment['original_amount'] = 25.00
// $payment['original_currency'] = 'USD'
// $payment['amount'] = 117.50 (25 * 4.70)
// $payment['currency'] = 'MYR'
You can pass custom parameters using metadata:
$paymentRequest = [
'amount' => 100.00,
'currency' => 'MYR',
'email' => 'customer@example.com',
'transaction_id' => 'TXN' . time(),
'metadata' => [
'user_id' => 123,
'product_ids' => [1, 2, 3],
'voucher_code' => 'SAVE10'
]
];
SenangPay uses callbacks (webhooks) to notify your application about payment status changes.
Configure your callback URL in the SenangPay dashboard or in the payment request
Create a route to handle callbacks:
// routes/web.php
Route::post('/senangpay/callback', [SenangPayWebhookController::class, 'handleCallback']);
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Mdiqbal\LaravelPayments\Facades\Payment;
class SenangPayWebhookController extends Controller
{
public function handleCallback(Request $request)
{
$gateway = Payment::gateway('senangpay');
$callbackData = $gateway->parseCallback($request);
// Process the webhook
$result = $gateway->processWebhook($callbackData);
if ($result['success']) {
$eventType = $result['event_type'];
$transactionId = $result['transaction_id'];
switch ($eventType) {
case 'payment.successful':
$this->handleSuccessfulPayment($result);
break;
case 'payment.failed':
$this->handleFailedPayment($result);
break;
case 'payment.pending':
$this->handlePendingPayment($result);
break;
}
}
// Always return 200 OK to acknowledge receipt
return response()->json(['status' => 'received']);
}
protected function handleSuccessfulPayment($data)
{
// Update order status
$order = Order::where('transaction_id', $data['transaction_id'])->first();
if ($order) {
$order->status = 'paid';
$order->paid_at = now();
$order->payment_method = $data['payment_method'];
$order->save();
// Send confirmation email
Mail::to($data['customer_email'])->send(new PaymentConfirmation($order));
}
}
protected function handleFailedPayment($data)
{
// Log failed payment
Log::warning('Payment failed', [
'transaction_id' => $data['transaction_id'],
'message' => $data['message']
]);
// Notify customer
// Update order status
}
protected function handlePendingPayment($data)
{
// Payment is being processed
// Update order status to processing
}
}
SenangPay uses HMAC SHA256 for security verification. The gateway automatically generates and verifies hashes for:
Payment Request Hash:
hash = hmac_sha256(secret_key + detail + amount + order_id)
Callback/Return Hash:
hash = hmac_sha256(secret_key + status + merchant_id + order_id + amount + currency + message)
Refund Hash:
hash = hmac_sha256(secret_key + transaction_id + refund_amount)
The SenangPay gateway provides detailed error messages:
$payment = Payment::gateway('senangpay')->pay($paymentRequest);
if (!$payment['success']) {
$error = $payment['error'];
$message = $error['message'];
$code = $error['code'];
// Handle error based on type
if ($code === 'PAYMENT_FAILED') {
// Payment initialization failed
}
}
PAYMENT_FAILED - Payment initialization failedVERIFICATION_FAILED - Transaction verification failedREFUND_FAILED - Refund processing failedWEBHOOK_FAILED - Webhook processing failedUse test credentials for development:
SENANGPAY_TEST_MODE=true
Use these test card details for testing:
For FPX (online banking) testing in sandbox:
SenangPay automatically displays appropriate payment methods based on:
SenangPay primarily processes payments in MYR (Malaysian Ringgit). However, it can accept multiple currencies with automatic conversion:
SenangPay primarily serves Malaysia but accepts international payments through:
SenangPay implements reasonable rate limits. Implement proper rate limiting in your application to avoid being blocked.
pay() - Initialize a paymentverify() - Verify a transaction statusrefund() - Process a refundgetTransactionStatus() - Get transaction statuscreatePaymentLink() - Create a payment linkcreateCustomer() - Note customer information (sent with each payment)parseCallback() - Parse callback parameters from requestgetSupportedCurrencies() - Get supported currenciesgetGatewayConfig() - Get gateway configuration// If you have multiple SenangPay accounts
$gateway = Payment::gateway('senangpay', [
'merchant_id' => 'different_merchant_id',
'secret_key' => 'different_secret_key'
]);
Override the default exchange rates for currency conversion:
// In a service provider or middleware
Payment::gateway('senangpay')->setExchangeRates([
'USD' => 4.50,
'EUR' => 4.80
]);
Since SenangPay doesn't natively support recurring payments:
// Implement your own recurring payment logic
public function processRecurringPayment($subscription)
{
foreach ($subscription->charges as $charge) {
$paymentRequest = [
'amount' => $charge->amount,
'currency' => 'MYR',
'email' => $subscription->customer_email,
'transaction_id' => 'SUB_' . $subscription->id . '_' . $charge->id,
'description' => $subscription->description,
// ... other parameters
];
$payment = Payment::gateway('senangpay')->pay($paymentRequest);
// Handle payment and update subscription status
}
}
For SenangPay-specific support:
For Laravel Payments package support:
How can I help you explore Laravel packages today?