Laravel对接马来西亚支付网关实战

Categories:

Laravel对接马来西亚支付网关实战指南

一、马来西亚主流支付网关概览

在对接前,先了解马来西亚常见的支付网关:

  1. FPX (Financial Process Exchange)

    • 银行直接转账系统
    • 覆盖几乎所有马来西亚主要银行
  2. GrabPay

    • 流行的电子钱包解决方案
    • 适合移动端支付场景
  3. Boost

    • 另一个广泛使用的电子钱包
    • 提供优惠券和积分系统
  4. Touch ‘n Go eWallet

    • TNG的电子钱包版本
    • 与交通卡无缝集成
  5. Maybank2u Pay

    • Maybank银行的专有支付方案
  6. Razer Merchant Services (RMS)
    新兴的聚合支付平台,支持多种方式

二、Laravel项目准备

1. Laravel环境配置

composer create-project laravel/laravel my-payment-gateway --prefer-dist "8.*"

2. SSL证书配置(必须)

所有大马支付网关都要求HTTPS:

  • Let’s Encrypt免费证书或商业SSL证书
  • Nginx/Apache正确配置强制HTTPS重定向

三、以FPX为例的完整对接流程(其他类似)

1.FPX API申请步骤:

1)注册商户账号:

2)获取API凭证:

  • Merchant Code(商户代码)
  • Verify Key(验证密钥)

3)选择服务模式:

  • Production/Live模式(正式)
  • Staging/Sandbox模式(测试)

2.Laravel集成代码实现:

安装必要包:

composer require guzzlehttp/guzzle phpseclib/phpseclib:^3.0 

创建Service类 app/Services/FpxService.php:

<?php 

namespace App\Services;

use GuzzleHttp\Client;
use Illuminate\Support\Facades\Log;

class FpxService {

protected $merchantCode;
protected $verifyKey;
protected $isProduction;

public function __construct() {
$this->merchantCode = config('fpx.MERCHANT_CODE');
$this->verifyKey = config('fpx.VERIFY_KEY');
$this->isProduction = config('app.env') === 'production';

if(!$this->merchantCode || !$this->verifyKey) {
throw new \Exception("FPX credentials not configured");
}
}

/
* Generate FPX payment request URL
*/

}

配置文件 config/fpx.php:

<?php 

return [

// Test or Production mode

];

路由定义 routes/web.php:

Route::post('/checkout/fpx', [PaymentController::class, 'initiateFpx'])->name('payment.fpx.initiate');

// Callback URLs that FPX will hit after payment completion/failure
Route::post('/payment/fpx/callback', [PaymentController::class, 'handleFpxCallback']);
Route::get('/payment/fpx/status/{order}', [PaymentController::class, 'checkFxpStatus'])->name('payment.fxp.status');

// For customer redirect back from FPX portal
Route::get('/payment/return/{order}', [PaymentController:;lass,'handleReturn']);

控制器示例 app/Http/Controllers/PaymentController.php

关键方法实现:

发起付款请求:

public function initiateFxp(Request $request) {

}

处理回调通知:

public function handleFxPCallback(Request request){

}

private function verifySignature($data,$signature){

}

状态查询接口:

四、安全注意事项

必须实现的防护措施:

签名验证


CSRF保护


日志审计


五、测试与上线流程

沙箱环境测试清单

Test Case Expected Result
Successful Payment Order status updated to paid
Failed Payment Proper error displayed
Duplicate Notification Handling No duplicate processing

生产环境检查项

六、常见问题解决

Q: Signature verification failed?
A: Check timezone settings match exactly with server and ensure no trailing spaces in keys.

Laravel对接马来西亚支付网关实战指南(续)

三、FPX支付完整实现(续)

3. 控制器详细实现

PaymentController.php 完整示例

<?php

namespace App\Http\Controllers;

use App\Services\FpxService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use App\Models\Order;

class PaymentController extends Controller
{
protected $fpxService;

public function __construct(FpxService $fpxService)
{
$this->fpxService = $fpxService;
}

/
* 发起FPX支付请求
*/
public function initiateFpx(Request $request)
{
// 验证订单数据
$validated = $request->validate([
'order_id' => 'required|exists:orders,id',
'amount' => 'required|numeric|min:1'
]);

// 获取订单信息
$order = Order::find($validated['order_id']);

try {
// 生成FPX请求参数
$paymentData = [
'reference_id' => 'ORD'.$order->id,
'amount' => number_format($validated['amount'], 2, '.', ''),
'customer_email' => auth()->user()->email,
'customer_name' => auth()->user()->name,
// FPX要求的其他字段...
];

// 获取付款URL并重定向到银行页面
return redirect(
$this->fpxService->generatePaymentUrl($paymentData)
);

} catch (\Exception $e) {
Log::error('FPX Initiation Error: '.$e->getMessage());

return back()->withErrors([
'message' => __('Payment gateway error. Please try again later.')
]);
}
}

/
* FPX服务器回调处理 (POST请求)
*/
public function handleFxpCallback(Request request){
try {
if (!$this>verifySignature(request()>all())) {
throw new \Exception("Invalid signature");
}

switch (request('status')) {
case "success":
Order::whereReference(request('reference_no'))
>update(['status'=>'paid']);

break;

default: /* failed cases */
Log::warning("Failed payment", request()>all());

break;
}

return response()>json(['status'=>'ok']);

} catch(\Throwable t){
Log::critical(t);

abort(400,"Bad Request");
}
}

private function verifySignature(array data): bool{/*...*/}
}

4.FPX Service类完善实现

class FxpServce {

const PRODUCTION_URL="https://www.mepsfp.com.my/EPP/generatePEPPTxn.jsp";
const STAGING_URL="https://uat.mepsfp.com.my/EPP/generatePEPPTxn.jsp";

/ Generate payment URL with signed parameters */
public function generatePaymentUrl(array params): string{

# Required fields by FPx specification
date_default_timezone_set("Asia/Kuala_Lumpur");

baseParams=[
"m"=>$this>merchantCode,
"o"=>params["reference_id"],
"a"=>sprintf("%01.2f",floatval(params["amt"])),
/* other required fields... */
];

# Calculate signature using verify key
ksort(baseParams);// Must be sorted alphabetically!

signStr=http_build_query(baseParams).$this->verifyKey;

baseParams["s"]=hash("sha256",signStr)?:"";

return ($isProduction?self::PRODUCTION_URL:self::STAGING_URL)."?".http_build_query(base_params);

}

}

四、电子钱包集成方案(以GrabPay为例)

1.GrabPay API配置步骤:

1)注册商户账号:

2)安装官方SDK:

composer require grabpay/merchant-sdk-php ^3.0 --ignore-platform-reqs=true (如遇PHP版本问题)

2.Laravel集成代码示例:

创建GrabPay服务类 app/Services/GrabPaySevice.php:


use Grabpay\Merchant\Online\OnlineClient;
use Exception;

class GrabPaySevice{

private client;

public construct(){
this→client=new OnlineClient([
environment=>config(grab.env),// sandbox/production
country=>MY,// Malaysia code
partnerID=>config(grap.partnerId),
merchantID=〉config(grap.merchatId),




]);

# Laravel对接马来西亚支付网关实战指南(续)

四、电子钱包集成方案 – GrabPay完整实现

3. GrabPay控制器实现

PaymentController.php新增方法:

“`php
/
* 发起GrabPay支付请求
*/
public function initiateGrabPay(Request $request)
{
$validated = $request->validate([
‘order_id’ => ‘required|exists:orders,id’,
‘amount’ => ‘required|numeric|min:1’
]);

try {
// 获取订单信息
$order = Order::find($validated[‘order_id’]);

// 生成交易参数
$params = [
‘partnerTxID’ => uniqid(‘GRAB_’),
‘amount’ => (int)($validated[‘amount’] * 100), // Grab要求单位为分
‘currency’ => ‘MYR’,
‘merchantUserID’ => auth()->id(),
/* …其他必填字段… */
];

// 调用GrabPay服务创建交易
return response()->json(
app(GrabPayService::class)->createPayment($params)
);

} catch (\Exception $e) {
Log::error(‘Grabpay Error: ‘.$e->getMessage());

return back()->withErrors([
__(‘Payment processing failed. Please try another method.’)
]);
}
}

/
* Handle Grabpay OAuth回调
*/
public function handleGrapayCallback(Request request){

}
“`

4.GrapayService完善代码:

“`php
class GrapaySevice{

/
* Initiate new payment transaction
*/
public function createPayment(array params): array{

try {
# Generate charge request
result=this→client→chargeInit([
partnerTxID=>params[“partnerTxId”],
amount=>params[“amt”],
currency=”MYR”,
merchantUserID=(string)auth()>id(),
/* other required fields */
]);

if(result[“status”]!==200){
throw new Exception(“Failed to initialize”);
}

return [
“redirectUrl”=>result[“data”][“request”],// For web redirect

“qrCode”=>optional(result)[“data”][“qrcode”]??null,// For QR display

“expiry”=>now()>addMinutes(15)>toDateTimeString(),
];
}catch(\Throwable t){
report(t);

throw new Exception(“Payment initiation failed”);
}
}

/ Poll transaction status */ public function checkStatus(string txId):?array{/*…*/}

/ Handle webhook notification */ public function verifyWebhook(array data):bool{/*…*/}
“`

五、多网关统一接口设计

1.创建支付策略接口 `app/Contracts/PaymentGateway.php`:

“`php *5 cd /path-to-project && php artisan schedule:run >> /dev/null21&

4.安全头设置(在App\Http\Middleware中):

protected headers=[ StrictTransportSecurity=max-age31536000; includeSubDomains', ContentSecurityPolicy'=>"defaultsrc'self'; scriptsrc'self'unpkg.com", XContentTypeOptions'=>'nosniff',]