Laravel对接马来西亚支付网关实战指南
一、马来西亚主流支付网关概览
在对接前,先了解马来西亚常见的支付网关:
-
FPX (Financial Process Exchange)
- 银行直接转账系统
- 覆盖几乎所有马来西亚主要银行
-
GrabPay
- 流行的电子钱包解决方案
- 适合移动端支付场景
-
Boost
- 另一个广泛使用的电子钱包
- 提供优惠券和积分系统
-
Touch ‘n Go eWallet
- TNG的电子钱包版本
- 与交通卡无缝集成
-
Maybank2u Pay
- Maybank银行的专有支付方案
-
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)注册商户账号:
- https://www.mepsfpx.com.my/
- MyClear FPX商户门户注册
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)注册商户账号:
- https://developer.grab.com/
- Grab Merchant Portal申请API Key
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 php namespace App\Contracts; interface PaymentGateway{ / * @throws PaymentException */ public function charge(float amt,array options=[]):ChargeResult; / Verify incoming callback is legitimate */ public function verifyCallback(array data):bool; / Get transaction status from provider API*/ public fetchStatus(string referenceId):TransactionStatus; } ``` 2.FPX适配器实现示例: ```php php namespace App\Services\Adapters; use App\Contracts\PaymentGateway; class FxpAdapter implements PaymentGateWay{ private fpxServce; public construct(FxpServce servce){this>fpxServce=$servce; } public charge(float amt,array opts):ChargeResult{ url=fxp>generatePamentUrl([/* convert params*/]); return new ChargeResult( paymentUrl:url, qrCode:null,// FPX不支持QR码 method:"online_banking" ); } /* Implement other interface methods...*/ } ``` 六、生产环境关键配置清单 1. Nginx反向代理配置: ``` location ~ ^/payment-callback { proxy_pass http://laravel_backend; proxy_set_header X-Real-IP remote_addr; proxy_set_header X-Forwarded-Proto https; # Important for signature verification! proxy_set_header Host host; client_max_body_size10M; proxy_read_timeout90s;} ``` 2.队列工作者配置(处理异步通知): ``` [program:laravel-worker] command=php /var/www/html/artisan queue:work --sleep3--tries3--timeout60 autostart=true autorestart=true user=www-data numprocs2 redirect_stderrtrue stdout_logfile=/var/log/supervisor/laravel-queue.log stopwaitsecs3600 3.定时任务(状态核对):
*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',]
