Laravel 集成巴基斯坦支付网关教程
常见巴基斯坦支付网关选择
在开始集成前,你需要选择一个适合的巴基斯坦本地支付网关。以下是几个主流选项:
- JazzCash – Jazz电信旗下的移动钱包和支付系统
- EasyPaisa – Telenor Pakistan提供的数字钱包服务
- UBL Omni – United Bank Limited的支付解决方案
- HBL Pay – Habib Bank Limited的电子支付平台
- 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监控面板关键指标: -每分钟交易量 -各网关成功率对比 -平均响应时间 -异常交易地理分布

发表回复