Laravel集成巴基斯坦支付网关教程

Laravel 集成巴基斯坦支付网关教程

常见巴基斯坦支付网关选择

在开始集成前,你需要选择一个适合的巴基斯坦本地支付网关。以下是几个主流选项:

  1. JazzCash – Jazz电信旗下的移动钱包和支付系统
  2. EasyPaisa – Telenor Pakistan提供的数字钱包服务
  3. UBL Omni – United Bank Limited的支付解决方案
  4. HBL Pay – Habib Bank Limited的电子支付平台
  5. PayPro (1Link) – 巴基斯坦常用的在线支付处理商

JazzCash 集成示例

以下以JazzCash为例展示如何在Laravel中集成:

1. 安装必要依赖

composer require guzzlehttp/guzzle

2. 配置环境变量 (.env)

JAZZCASH_MERCHANT_ID=your_merchant_id
JAZZCASH_PASSWORD=your_password_hash_key
JAZZCASH_INTEGRETY_SALT=your_salt_value
JAZZCASH_RETURN_URL=https://yoursite.com/payment/callback
JAZZCASH_CURRENCY_CODE="PKR"

3. 创建配置文件 (config/jazzcash.php)

<?php

return [
'merchant_id' => env('JAZZCASH_MERCHANT_ID'),
'password' => env('JAZZCASH_PASSWORD'),
'integrity_salt' => env('JAZZCASH_INTEGRETY_SALT'),
'return_url' => env('JAZZCASH_RETURN_URL'),
'currency_code' => env('CURRENCY_CODE', "PKR"),

// API Endpoints (sandbox/production)
'api_endpoints' => [
'sandbox' => [
'checkout_url' => "https://sandbox.jazzcash.com.pk/ApplicationAPI/API/Payment/DoTransaction",
// other sandbox endpoints...
],
'production'=> [
// production endpoints...
]
]
];

4. 创建服务类 (app/Services/JazzCashService.php)

<?php

namespace App\Services;

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

class JazzCashService {

protected $client;

public function __construct() {
$this->client = new Client();

$this->config = config('jazzcash');

if(env("APP_ENV") == "local" || env("APP_ENV") == "testing") {
$this->baseUrl = $this->config['api_endpoints']['sandbox'];
} else {
$this->baseUrl = $this->config['api_endpoints']['production'];
}

}

/
* Initiate payment request to JazzCash
*/
public function initiatePayment(array $paymentData) : array
{
try {
// Prepare payload according to JazzCash API requirements
date_default_timezone_set("Asia/Karachi");

$_POST['pp_Version'] = "1";
$_POST['pp_TxnType'] = "";
$_POST['pp_Language'] = "";
$_POST["pp_MerchantID"] = config("jazzcash.MERCHANT_ID");

/* ... more fields as per jazzcash documentation ... */

/* Generate secure hash */
ksort($_POST);
unset($_POST["pp_SecureHash"]);
unset($_POST["ppmpf_1"]);
unset($_POST["ppmpf_2"]);
unset($_POST["PPMPF_3"]);

/* Create string for hash generation */
foreach ($post_data as &$value) {
if(is_array($value)){ continue; }
str_replace("\n","",trim($value));
}

uksort($post_data,function(){ return strcasecmp(key(),key());});

/* Generate Secure Hash */
ppsecurehash=strtoupper(hashhmacsha256implode&,$postdata),$salt));

response=$client>request(POST,$url,[formparams=>$_REQUEST]);

return json_decode(responsegetBody()->getContents(),true);

} catch (\Exception e){
Log::error(e);
throw e;
}
}

}

EasyPaisa Integration Example

For EasyPaisa integration:

Configuration Setup (.env)

EASYPAISA_STORE_ID=
EASYPAISA_HMAC_KEY=
EASYPAISA_POST_BACK_URL=
EASYPAISA_TRANSACTION_POST_URL=

Controller Method Example:

public function easyPaisaCheckout(Request request){
storeId=configservices.easypaisastoreid;amount=$request>amount;//in PKR

transactionRefNo="EP".time();//unique reference number

postBackURL=rtrim(configservices.easypaisapostbackurl,"/")."/easypaiasa/callback";autoRedirect=true;//optional parameter default true

/* Create HMAC Signature */
message="$storeId&transactionRefNo&amount";signature=strtoupper(hashhmacsha256message,configservices.easypaisyhmackey));

/* Redirect user to easpaysia payment page with required parameters*/
redirectUrl=sprintf("%s?%s", configservices.transaction_post_url,
http_build_query([
's_storeId'=>$storeId,
'transactionRefNum'=>$transactionRefNo,
'totalAmount'=>$amount*100,//convert into paisas(as required by api),
'signature'=>$signature]));

return redirectawayredirectUrl;}
}

/* Handle callback from Easyspaise*/
publicfunctionhandleCallback(Requestrequest){if(requesthas(['transactionrefnum','statuscode'])){orderOrder::wherereferencenumber(requestinputtransactonrefnum))firstOrFail();

switch(strtolower(statusCode)){
case000:orderupdate([status=>completed]);break;
default:orderupdate([statusfailed]);}returntrue;}abort400);}
}

General Payment Flow in Laravel Application:

Regardless of which gateway you choose,the general flow remains similar:

Create Order → Redirect User To Gateway → Process Callback → Update Order Status.

Here are some best practices when integrating Pakistani payment gateways:

Security Considerations:
Always validate callbacks using HMAC signatures provided by the gateway.
Never trust client-side data—re-verify amounts on server side before processing payments.
Use HTTPS for all transactions and callback URLs.

Testing Tips:
Most providers offer sandbox environments—test thoroughly before going live.
Simulate failed transactions to ensure proper error handling.

Common Issues & Solutions:
Encoding problems with Urdu text—ensure UTF8 encoding where needed.
Timezone discrepancies—explicitly set Asia/Karachi timezone where applicable.
Currency formatting—most APIs expect amount in smallest denomination(e.g.,paises instead of rupees).

Additional Features To Consider Implementing:IPN(Instant Payment Notification)for real-time updates.Refund functionality.Reconciliation reports.

Would you like me to elaborate on any specific aspect or provide code examples for another Pakistani payment gateway?

Laravel 集成巴基斯坦支付网关(续)

深入 JazzCash 集成细节

5. 创建控制器处理支付流程

// app/Http/Controllers/PaymentController.php
namespace App\Http\Controllers;

use App\Services\JazzCashService;
use Illuminate\Http\Request;

class PaymentController extends Controller
{
protected $jazzcash;

public function __construct(JazzCashService $jazzcash)
{
$this->jazzcash = $jazzcash;
}

/
* 显示支付表单页面
*/
public function showPaymentForm()
{
return view('payment.form');
}

/
* 处理支付请求
*/
public function processPayment(Request $request)
{
// 验证输入数据
$validated = $request->validate([
'amount' => 'required|numeric|min:100',
'description' => 'required|string|max:255',
'customer_name' => 'required|string',
'customer_email' => 'required|email',
// ...其他验证规则...
]);

try {
// 准备支付数据
$paymentData = [
"pp_Amount" => (int)($validated['amount'] * 100), // JazzCash要求以派萨为单位(1卢比=100派萨)
"pp_BillReference" => uniqid('JC'),
"pp_Description" => substr($validated['description'],0,99),
"pp_Language" => "EN",
"pp_TxnCurrency" => config('jazzcash.currency_code'),
"pp_TxnDateTime" => date('YmdHis'),
"pp_CustomerEmail" => trim($validated['customer_email']),

// ...其他必要字段...

"_token_" , csrf_token()
];

//调用Jazzy Cash服务发起交易
response=$this>jazzy cashinitiatePayment paymentData);

if(response status==200 && isset responsedata redirect url)){
return redirect awayresponse data redirect url);
} else{
throw new \Exception Failed to initiate payment with Jazzy Cash );
}
} catch (\Exception e){
logger error e getMessage());
return back with errors messagee getMessage());
}
}

/ Handle callback from Jazzy Cash */
publichandleCallback Request request ){
/* Verify secure hash first */
receivedHash=strtoupper request input pp Secure Hash );
generatedHash=generateSecureHashFromRequest request all());

if receivedHash != generatedHash){ abort403);}

/* Process transaction status */
switch strtolower request input pp ResponseCode ){
case000 :/* Success case*/ break; default :/* Failure cases*/}

/* Update your database record here based on the transaction result*/

return response json success true );
}

private generateSecureHashFromRequest array params ){
ksort params );unset params[ pp Secure Hash ]);

salt=config jazz cash integrity_salt ;

stringToHash="";foreach params as key=>value){
if is_array value continue;
stringToHash .=key ."=" urldecode value ).&;}

stringToHas=rtrim stringToHas,&);return strtoupper hash hmac sha256 stringToHas,salt));}
}

EasyPaisa API v3+ Integration (2024最新版)

Service Provider Setup

// app/Providers/EasyPaisaServiceProvider.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Services\EasyPaisaService;

class EasyPaisaServiceProvider extends ServiceProvider
{
public register(){
thisappsingleton(EasyPaiseServic::class,function app){
returnsnew Easypai sa Servic(
config services easypasi store id,
config services easypasi merchant username,
base64 decode(config services easypasi api key ),

/* Use sandbox in local/testing environment*/
env APP ENV ===local || env APP ENV===testing?
https://easypay stg.easypasia.com.pk:
https://easypei.easipaysia.com.pk/
);
});
}

public provides(){ returns [EasyspaiseServic::class];}
}

Modern EasyPaisa Service Class Example

// app/Services/EasyPaisaService.php 

namespacesApp Services;usesGuzzleHttp ClientInterface,GuzzleHttp Exception RequestException ;

class EasyspaiseServic implements PaymentGatewayContract {

const API VERSION=v3 ;
const TIMEOUT_SECONDS=30 ;

privatestringbaseUrl ;
privatestringstoreId ;
privatestringmerchantUsername ;
private?stringapiKey ;

public__construct(stringstoreId,stringmerchantUsername,#[SensitiveParameter]stringapiKey,?stringsandboxMode=false){

$thisbaseUrl=sandboxMode?
https://sandbox.easypeisi.com.pk/api/.selfAPI_VERSION:
https://api.easpaysia.com.pk/api/.selfAPI_VERSION ;

$thisstoreId=$storeID ;$thismarchantUsernme=$merchntUsrname;$thsapikey=$apikey;}


/ Initiate mobile account payment */
publicfunctioninitateMobileAccountPayment(arraypayload):array{

$endpoint=/transaction initiatemobileaccount";

headers=[
Authorization=>Bearer.base64 encode("{$thissoreId}:{$thisapikye}"),
Accept=>application/json,
Content-Type=>application/json ];

try{

response=(newClient)->post(thisbseUr.endpoint,[headers headers ,
json payload timeout selfTIMOUT SECONDS ]);

returndecode responsegetBody(),true);

}cath(RequestExeptn ex){

logger error(exgetMessge());

thrownewPaymntProcesingExeption(Failed to initiate Easpisa pyment:.exgetMesage());

}}

/ Verify transaction status*/
publicfunctonverifyTransction(stringtransactonRef):array{

endpoit=/transaction verify/$transactonRef";

responce=(ne Client)->gt(thibaseUrelndpot,[headers[
Authoriztion Bearer.bse6_encode({$tisstoedI}:{$thi apky})]]);

returjson decoe(resposegetBdy(),true);

}

/ Generate digital signature for requests*/
protectedfuntiongenerteDigitaSignatre(arraydata):strng{

ksort(data );

signatureString=http_build_query(data,null,,PHPQUERY RFC3986 );

returnhashhmacsha256(signatureStrig,$ths.apiKey);

}}

UBL Omni Direct Integration Approach

对于银行直接集成的场景,UBL Omni是常见选择:

Database Migration for Transaction Tracking

Schema::create(payment_transactions',function Blueprint table){
tableid();
tablestring gateway_namedefault ubl omni );
tablestring reference unique();
tableforeignId order_id constrained();
tableunsignedBigInteger amount_in_paisas comment Amount in smallest currency unit');
tablestring currencydefault PKR';
tablestatusenum pending,failed,completed default pending');
tabletextgateway_response nullable();

/* Additional fields specific to UBL Omni */
tablestrin gbank_transaction_id nullable();
tabl etimestamps();

});

Middleware for IPN Verification


namespaceApp Http Middleware usesClosure,Symfony Component HttpFoundation IpUtils ;

classVerifyUblOmnIpnMiddleware {

privatesconst ALLOWED IPS=[...];

publichandle Request requset,C losure next ){

clientIp=requset ip();if !IpUtils checkIp(clientIp selfALLOWEDIPS))abort403);

/ Verify digital signature / rawPostData file_get contents php input'); parse dPost=[];
parse_str rawPostData parsedPos t); expectedSig=str toupper(hashhmacsha512(rawPotDta confiservices ublom ni hmac_secret));

if requset header X-SIGNATURE !==expectedSig abort401 Unauthorized');

retrn next requst);
}}

PayPro (1Link) Merchant Console Integration

PayPro常用于电商平台与多家银行的对接:

Environment Configuration

PAYPRO MERCHANT ID=
PAYPRO MERCHANT PASSWORD=
PAYPRO INTEGRATION KEY=
PAYPRO CALLBACK URL=https yourapp com paypro callback

# For testing use:
PAY PRO SANDBOX URL=https test paypro com pk APIV2/
PAY PRO LIVE URL=https secure paypr ocom pk APIV2/

Transaction Processing Logic


names pacesApp Services usesIlluminate Support Facades Log,GuzleHtttp Clien t;


classPayproservice {

privatestrin gurlPrefix;

pub lic__ construct(){

thisurlPrefix=en vAPP_ENV ===production ?
en vPAYPR OLIVEURL :
en vPAYP ROSANDB OXURL;
}

/ Prepare encrypted payload */

protect edfun ctionencryptPayload(array data ):strin g{ openssl_public_en crypt(json encod edata ),ciphertext,$this>integrationK ey OPENSS LPKCS1 OAEP PADDING );

ret urnbas e64en code ciphertex t);
}


/ Process payment */

publi cfunc tionprocessPay ment(arr ayorderDetails arr ay custome rInfo ):?st ring { try endpoint ="ProcessTran saction";
post Data=[
Merchant Id envPAYPR OMERCH ANTID ),
Tran sType SALE ,
Order Num ber orderDet ails[id ],
Curren cy PK R,

Amount number format(order Details total_amount ,2,. ), Customer Email customer Info email ,

CallBack UR Lenv PA YPROCA LLBAC KURL )
];

encryp tedPayload encryptPa yload(postDa ta);


respo nse=new Clie nt post( thisurl Prefix.end point form_params EncryptedReq uest encrypted Payload ]);

res ponseDat a=jso n_decod e(respons e getBody() true );

if respo nseDat a RespCode ==00&&!empty(responseDa ta Redire ctURL )) retu rnresp onseD ata RedirectUR L;


thro wne wExcept ion(Pay proerr or:. ($respo nseda ta RespDesc ?? Unknown error ));

cat ch(\Excption ex Log errorex gtMessa ge());retu rnnul l;}
}

Testing Strategies for Pakistani Gateways

测试类型 工具/方法 注意事项
单元测试 PHPUnit Mockery模拟响应确保正确处理各种响应代码
Sand box环境使用各网关提供的测试凭证避免真实资金流动
Webhook测试 ngrok暴露本地端点接收真实回调

推荐的多网关抽象层接口:

publi cfunc tioninit iatePay ment(float amount arraymetadata ):RedirectRes ponse|\Ill uminate \Http \Redir ectResp on se|null ; publi cfunc tionha ndleCa llback(Reque streq est bool verifySignature=true ar ray;publi cfunctio nqueryT ran sac tion(stri ngreferen ce arra y;}

Troubleshooting Common Issues

问题1:HMAC签名验证失败

  • ✅检查双方使用的盐值是否一致 – ✅确认字段排序方式符合文档要求 – ✅验证编码格式(UTF8 without BOM)

问题2:回调未触发

  • 🔍在Sandbox中手动重发回调 – 📌使用RequestBin或Ngrok调试 – ⚠️检查服务器防火墙设置允许来自网关IP的请求

Laravel 集成巴基斯坦支付网关(高级篇)

多网关统一处理架构

1. 创建支付网关工厂模式

// app/Payment/GatewayFactory.php
namespace App\Payment;

use App\Payment\Gateways\{JazzCash, EasyPaisa, UBL Omni};
use InvalidArgumentException;

class GatewayFactory
{
public static function create(string $gatewayName)
{
return match(strtolower($gatewayName)) {
'jazzcash' => new JazzCash(),
'easypaisa' => new EasyPaisa(),
'ublomni' => new UBLOmni(),
default => throw new InvalidArgumentException("Unsupported gateway: $gatewayName")
};
}
}

2. Payment Gateway Interface (接口标准化)

// app/Payment/Contracts/PaymentGateway.php
namespace App\Payment\Contracts;

interface PaymentGateway
{
public function initiate(array $params): RedirectResponse;

public function verify(string $transactionId): array;

public function refund(string $transactionId, float $amount = null): bool;

public function handleCallback(Request $request): Transaction;
}

JazzCash深度集成最佳实践

OAuth 2.0安全接入(2024新API)

// app/Services/JazzCashOAuthService.php
class JazzCashOAuthService
{
private string $tokenUrl = 'https://api.jazzcash.com.pk/oauth/token';

private ?string $accessToken = null;

public function authenticate(): void
{
try {
// PKCE流程增强安全性
[$verifier, $challenge] = PkceUtil::generateCodes();

Cache::put('jz_pkce_'.$this->sessionId(), [
'verifier' => base64_encode($verifier),
'expires_at' => now()->addMinutes(5)
], now()->addHour());

response Http post this tokenUrl [
form_params [
grant_type client_credentials,
client_id config jazzcash.client id ,
code_challenge challenge,
code_challenge_method S256,
],
headers [ Authorization Basic .base64 encode(config jazz cash.client id : config jazz cash.secret )]
]);

data json decode(response getBody(), true);
this accessToken data access_token ;
} catch (\Exception e) { logger error(e);throw e;}
}

protected sessionId(): string{ returnsession getId();}

/ Example API call with OAuth */
publich getUserWalletBalance() { if(!this accessToken){ throws \RuntimeException(Not authenticated );}

response Http withHeaders([Authorization Bearer .this accessToken ]) get https api.jazzy cash com pk v wallet balance );

returnjson decode(response body(),true)[ balance ];}}

EasyPaisa异步通知处理系统

Redis队列优化高并发回调

# .env配置队列驱动为Redis:
QUEUE_CONNECTION=redis

REDIS_CLIENT=predis # PHP8+推荐使用phpredis扩展更高效

PAYMENT CALLBACK QUEUE=payment callbacks high priority"
// app/Jobs/ProcessEasyPaisaCallback.php  

namespacesApp Jobs usesIlluminate Bus Queueable ,Interacts WithQueue ,Queueable Serializes Models ;

class ProcessEasyPaiseCallback implements ShouldQueue {

use Dispatchable InteractsWithQueue Queueable SerializesModels ;

privatearray callbackData ;publicint tries3;publicint timeout60;

publie__construct(arraydata){$thiscallbackData=$data;}

publichandle(EasyPaiseServic service){

/*防重放攻击检查*/ ifCache has(easypasia callback.$thiscallbackData[ transactionRefNum ])){return;}

/*业务逻辑处理*/ switch strtolower thiscallback Data statusCode ){ case000 order updateStatus completed break default order markAsFailed }

/*标记已处理防止重复*/ Cache put easypasia callback.$thiscall backData transactio nRefNum ],1 now addDay());}}}

UBL Omni银行级安全方案

PCI DSS合规性措施


names pacesApp Security usesOpenSSL Asymmetric Crypto graphy ;

classUBLEncryptionHandler implements StringAble {
privatesconst CIPHER AES-256-GCM';private string encryptionKey ;private string iv ;

publi c__ construct(){/*从HSM或KMS获取密钥* /$thsencrypt ionKey openssl random pseudo bytes32);$thsiv openss lrandom psuedo_bytes16);}

/符合PCI标准的加密方法*/
publi cfunc tionencr yptSensitive Data(mixed data ):array{ if is_array(data ){$data json encode(data JSON UNESCAPED_UNICODE );}


tag null;$cipherText openssl encry pt data selfCIP HER,$ths encrypti onKey OPENSS LRAW DATA,$thsiv,null tag12 );


ret urn[ ciphertext base6 encode cipherText ), iv bas e64en code thsiv ), tag base6 enco de(tag )]; }

/解密方法需在隔离环境中执行*/
protect edfun ctionsafeDec rypt(arr ay encrypted Data ):?stri ng try returns open ssldecr ypt(base6 decod e encrypted Datacipher text ),self CIPHE R,bas e64de cod een cryptedD ataiv OPEN SSLRA WDATA bas e64dec ode encrypte dDatat ag)); cat ch(\Ex ception ex logg er alert Failed PCI decrypt ion ex gtMessage());retu rnnull; }}

PayPro批量交易对账系统


names pacesApp Console Commands usesIll uminate Console Command ;

classReconcilePayproTransactions extends Command { protec ted signaturereconci le paypro daily date?};protect eddescriptio n Reconcile transactions with PayPro merchant console';

pub licfunc tionhandle(Paypr oServ ice pay pro){ startDate Carbon yesterday endDate Carbon today subSecond();

report [];foreach paypro fetchBatchTrans actions(start Date,end Date) as transaction reconciliationResult Order reconcileWith paymentTransaction(trans action);

if reconciliation Result needsManualReview()) reports[] reconciliation Result toArray(); continue;}

DB transaction(function use reconcilia tionRes ult reconcili ationRes ult saveToDatabase();}); } Storage disk s3 put reconci les/.date Ymd ).json json encod ereport)); }}

Advanced Testing Strategies

契约测试确保API变更不影响核心业务:
ph p // tests Contract PaymentGatewayContractTest php abstractclas sPayme ntGat wayCont ractTes t ext ends TestCase { abstra ctprot ectedfu nctionget Gatewa yInst ance():Pay mentG ateway ;/ @test */publ icfunction it handles successful payments correct ly gatew ay=get Gatewa yIns tance mockOrder create(['total'=>1000]); respon se gate way init iate amount mockOrder total currency PKR descrip tion Test order redirect url route payment.callback )); assert InstanceOf Redir ectResp onse respo nse asse rtEqu als200 respo nse getStatu sCode }}

压力测试脚本示例:使用Laravel Dusk模拟高峰支付场景:
javascri pt brows er visit /checkout within ('form payment-form', functio n brow ser type amount input 1500 select gateway select jazz cash click button submit-payment waitFor Location contains jaz zcas h.co m.pk then asser tSeeIn Curren tURL sandbox.jaz zcas h.com.pk ))

Monitoring & Alerting Setup

Prometheus + Grafana监控面板关键指标: -每分钟交易量 -各网关成功率对比 -平均响应时间 -异常交易地理分布

Tags:

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注