背景
近期因为合作客户有马来西亚的业务,需要对接 【iPay88 支付】
通过阅读官方文档,发现一头雾水
相对之前接触的支付文档,个人觉得 iPay88 是最凌乱的
注意,注册平台账号后,会邮件发送几个开发文档附件(有的跟官网对不上)
对于开发沟通,还得需要公司业务发送邮件,等个一天多才收到回复,对开发来说,效率很低
一番折腾,最后梳理一下我的实现步骤,希望能帮到有同样需要的小伙伴
场景要求
满足 在 安卓售卖机 的商品购买页面,下单后,选取iPay88支付方式,弹出 支付二维码 , 引导用户扫码支付
iPay88 是马来西亚领先的在线支付网关提供商,提供本地和国际支付选项。
iPay88 是寻求可靠且功能丰富的支付网关的企业的绝佳选择。
根据对使用场景的确认,我要参考的便是邮箱提供的附件: iPay88 - Merchant Hosted Payment Gateway e-Wallet (Web Service) - v2.4.1.pdf
? 开发步骤
通过阅读开发文档(英文不好,可以是有百度翻译),一步步进行测试
①. 前期准备
Merchant Code
、Merchant Key
,其次需要在后台配置 IP 白名单Step 1. Merchant sends XML request containing payment details to iPay88 Merchant Hosted Payment
Gateway Web Service.
Step 2. IPay88 Merchant Hosted Payment Gateway Web Service will verify all the parameters received.
Step 3. E-Wallet QR code will be generated based on the information received from merchant.
Step 4. iPay88 Merchant Hosted Payment Gateway Web Service respond back the e-Wallet QR code to
merchant with a signature through 【XML format】.
Step 5. The merchant needs to compare the signature from iPay88. Refer to (3.2).
Step 6. Merchant has to display the e-Wallet’s QR code received in their website.
Step 7. Trigger payment inquiry requery to iPay88 system to get payment status.
XML/SOAP
请求示例、确认了 SOAPAction
,才得以进行下去 …此处重点吐槽:开发文档不够全面,需及时沟通对方技术团队,才能得到正确的方法(无语…)
②. 确认请求 Header
通过沟通,确认了请求方式为
POST
、请求体为XML/SOAP
,以及Header
信息
Content-Type
、SOAPAction
Reqeust data:
POST https://payment.ipay88.com.my/ePayment/WebService/MHGatewayService/GatewayService.svc HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: text/xml;charset=UTF-8
SOAPAction: "https://www.mobile88.com/IGatewayService/EntryPageFunctionality"
Content-Length: 1125
Host: payment.ipay88.com.my
Connection: Keep-Alive
③. 核心代码提供
根据业务场景,整理核心代码如下
/**
* @Notes:iPay88 网关支付,下单业务处理
* @param string $orderSn 订单编号
* @param int $orderTotal 订单金额,例如 1.00
* @return array
* @throws GuzzleException
* @User: zhanghj
* @DateTime: 2023-12-25 15:32
*/
public function createIPay88GatewayWebOrder($orderSn = '', $orderTotal = 0){
$err_msg = '';
$needSignParams = [
'MerchantKey' => PayMzConfig::IPAY88_MERCHANT_KEY, //由iPay88提供,注意保密
'MerchantCode' => PayMzConfig::IPAY88_MERCHANT_ID, //由iPay88提供,注意保密
'RefNo' => $orderSn,
'Amount' => $orderTotal,
'Currency' => PayMzConfig::IPAY88_CURRENCY //默认MYR (马来西亚林吉特)
];
$SignatureNeedStr = $this->dealGetNeedSignatureStr($needSignParams);
$SignatureStr = $this->iPay88_Sha256_sign($SignatureNeedStr);
$payment_request_params = [
'Amount' => number_format($orderTotal,2), //注意金额参数形式,Payment amount with two decimals and thousand symbols. Example: 1,278.99
'BackendURL' => PayMzConfig::IPAY88_ORDER_PAID_NOTIFY, //支付成功回调地址
'Currency' => PayMzConfig::IPAY88_CURRENCY,
'MerchantCode' => PayMzConfig::IPAY88_MERCHANT_ID,
'PaymentId' => 233, //支付宝
'ProdDesc' => 'IPAY88-FitTech',
'RefNo' => $orderSn,
'Signature' => $SignatureStr,
'SignatureType' => 'SHA256',
'UserContact' => '0123456789',
'UserEmail' => '930959695@qq.com',
'UserName' => 'moTzxx',
'Lang' => 'UTF-8',
];
$xmlData = $this->dealArrayToSoapXmlForUserScan($payment_request_params);
$options = [
'body' => $xmlData,
'headers' => [
"Accept-Encoding" => "gzip,deflate",
"Content-Type" => 'text/xml;charset=UTF-8',
"SOAPAction" => PayMzConfig::IPAY88_SOAP_ACTION, //不添加会500,需确认值
],
];
try {
$post_url = PayMzConfig::IPAY88_ORDER_METHOD;
$httpClient = new Client([
'base_uri' => PayMzConfig::IPAY88_HOST,
'verify' => false,
'http_errors' => false
]);
$response = $httpClient->request('POST',$post_url,$options);
$res_content = $response->getBody()->getContents();
//var_dump($res_content); //用于查看 响应信息
//字符串替换,方便解析提取
$res_content = str_replace("a:", "", $res_content);
$xmlObj = simplexml_load_string($res_content,'SimpleXMLElement'); // Convert response into object for easier parsing
$xmlObj->registerXPathNamespace('soap', 'http://schemas.xmlsoap.org/soap/envelope/');
$xmlResult = $xmlObj->xpath("soap:Body");
//元素提取
$objResult = $xmlResult[0]->EntryPageFunctionalityResponse->EntryPageFunctionalityResult;
$ipay88_qrcode = $objResult->QRCode->__toString()??'';
$payRes['_qr'] = $ipay88_qrcode;
}catch (BadResponseException $exception){
$err_msg = $exception->getResponse()->getBody()->getContents();
}catch (\Exception $exception){
$err_msg = $exception->getMessage();
}
return [$err_msg,$payRes??[]];
}
xml/soap
构造方法 /**
* @Notes:构造请求 xml/soap
* @param $arr
* @return string
* @User: zhanghj
* @DateTime: 2024-01-05 10:54
*/
public function dealArrayToSoapXmlForUserScan($arr) {
$xml = '<?xml version="1.0" encoding="UTF-8"?>';
$xml.= '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:mob="https://www.mobile88.com" xmlns:mhp="http://schemas.datacontract.org/2004/07/MHPHGatewayService.Model">';
$xml.= '<soapenv:Header/>';
$xml.= '<soapenv:Body>';
$xml.= '<mob:EntryPageFunctionality>';
$xml.= '<mob:requestModelObj> ';
foreach ($arr as $key => $val) {
$xml .= "<mhp:" . $key . ">" . $val . "</mhp:" . $key . ">";
}
$xml .= '</mob:requestModelObj>';
$xml .= '</mob:EntryPageFunctionality>';
$xml .= '</soapenv:Body>';
$xml .= '</soapenv:Envelope>';
return $xml;
}
/**
* @Notes:构造待加密串
* @param array $needSignParams 待加密字符串
* @return string
* @User: zhanghj
* @DateTime: 2024-01-05 10:55
*/
public function dealGetNeedSignatureStr($needSignParams = []){
$need_sign_str = '';
if ($needSignParams && is_array($needSignParams)){
$MerchantKey = $needSignParams['MerchantKey']??'';
$MerchantCode = $needSignParams['MerchantCode']??'';
$RefNo = $needSignParams['RefNo']??'';
$CCTransId = $needSignParams['CCTransId']??'';
$Amount = $needSignParams['Amount']??'';
$Amount = bcmul($Amount,100,0);
$Currency = $needSignParams['Currency']??'';
$BarcodeNo = $needSignParams['BarcodeNo']??'';
$need_sign_str = $MerchantKey.$MerchantCode.$RefNo.$CCTransId.$Amount.$Currency.$BarcodeNo;
}
return $need_sign_str ??'';
}
/**
* @Notes:sha256 加密
* @param string $concatenated_string
* @return string
* @User: zhanghj
* @DateTime: 2024-01-05 10:55
*/
public function iPay88_Sha256_sign($concatenated_string = ''){
return hash('sha256', $concatenated_string);
}
④. 接口请求测试
根据以上代码的部署,已经配置信息的整合,进行正式请求测试
XML/SOAP
请求体如下 (测试代码生成的,可作为参考排查错误)<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:mob="https://www.mobile88.com"
xmlns:mhp="http://schemas.datacontract.org/2004/07/MHPHGatewayService.Model">
<soapenv:Header />
<soapenv:Body>
<mob:EntryPageFunctionality>
<mob:requestModelObj>
<mhp:Amount>1.00</mhp:Amount>
<mhp:BackendURL>http://clientapi.xxx.xxx.com/notify/ipay88_order_notify</mhp:BackendURL>
<mhp:Currency>MYR</mhp:Currency>
<mhp:MerchantCode>MXXXXX</mhp:MerchantCode>
<mhp:PaymentId>233</mhp:PaymentId>
<mhp:ProdDesc>IPAY88-FitTech</mhp:ProdDesc>
<mhp:RefNo>TM20240105007</mhp:RefNo>
<mhp:Signature>c8699b731403c56567229150829d8c5f15865092b358b56e855136bd20a56b63</mhp:Signature>
<mhp:SignatureType>SHA256</mhp:SignatureType>
<mhp:UserContact>0123456789</mhp:UserContact>
<mhp:UserEmail>930959695@qq.com</mhp:UserEmail>
<mhp:UserName>moTzxx</mhp:UserName>
<mhp:Lang>UTF-8</mhp:Lang>
</mob:requestModelObj>
</mob:EntryPageFunctionality>
</soapenv:Body>
</soapenv:Envelope>
SOAP
摘取需要的信息)<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<ActivityId CorrelationId="f9f82a65-4640-4f96-ad26-180fce5aa558"
xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">00000000-0000-0000-0000-000000000000
</ActivityId>
</s:Header>
<s:Body>
<EntryPageFunctionalityResponse xmlns="https://www.mobile88.com">
<EntryPageFunctionalityResult xmlns:a="http://schemas.datacontract.org/2004/07/MHPHGatewayService.Model"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:ActionType i:nil="true" />
<a:Amount>1.00</a:Amount>
<a:AmountBeforeDiscount>1.00</a:AmountBeforeDiscount>
<a:AuthCode i:nil="true" />
<a:BankMID i:nil="true" />
<a:BindCardErrDescc i:nil="true" />
<a:CCName i:nil="true" />
<a:CCNo i:nil="true" />
<a:CardType i:nil="true" />
<a:Currency>MYR</a:Currency>
<a:DCCConversionRate i:nil="true" />
<a:DCCStatus>0</a:DCCStatus>
<a:Discount>0.00</a:Discount>
<a:ErrDesc i:nil="true" />
<a:Lang i:nil="true" />
<a:MerchantCode>MXXXXX</a:MerchantCode>
<a:OriginalAmount i:nil="true" />
<a:OriginalCurrency i:nil="true" />
<a:PaymentId>233</a:PaymentId>
<a:PaymentType i:nil="true" />
<a:QRCode>
https://payment.ipay88.com.my/ePayment/WebService/QR/AliPayOfflineQR/QrAli1704423760.33316-T042898260024.Png
</a:QRCode>
<a:QRValue>https://qr.alipay.com/bax023952ksmljfh8r3q00ec</a:QRValue>
<a:RefNo>TM20240105007</a:RefNo>
<a:Remark i:nil="true" />
<a:Requery i:nil="true" />
<a:S_bankname i:nil="true" />
<a:S_country i:nil="true" />
<a:SettlementAmount i:nil="true" />
<a:SettlementCurrency i:nil="true" />
<a:Signature>6c9cee7f86ed3f1115cb1b899e8990ebb43e1ac692c1e97d475e94bf2cd43d48</a:Signature>
<a:Status>1</a:Status>
<a:TokenId i:nil="true" />
<a:TransId>T042898260024</a:TransId>
<a:Xfield1 i:nil="true" />
<a:Xfield2 i:nil="true" />
</EntryPageFunctionalityResult>
</EntryPageFunctionalityResponse>
</s:Body>
</s:Envelope>
$pay_order_sn = $request->get('order_sn','');
list($err_msg,$iPay88Result) = (new PayMzService())->createIPay88GatewayWebOrder($pay_order_sn,1.00);
var_dump($err_msg);
var_dump($iPay88Result);
var_dump('iPay88 支付开发中...');
string(0) ""
array(1) {
["_qr"]=>
string(108)
"https://payment.ipay88.com.my/ePayment/WebService/QR/AliPayOfflineQR/QrAli1704425934.76971-T042904908324.Png"
}
string(25) "iPay88 支付开发中..."
⑤. 支付回调处理
回调处理接口,即下单请求时配置的参数
BackendURL
/**
* @Notes:iPay88 马来西亚 订单支付回调接口
* @User: zhanghj
* http://clientapi.xxx.xxx.com/notify/ipay88_order_notify
* @DateTime: 2023-12-26 16:17
*/
public function actionIpay88OrderNotify()
{
$arr_res = $_REQUEST;
$merchantcode = $arr_res["MerchantCode"]??'';
$paymentid = $arr_res["PaymentId"]??'';
$out_trade_no = $arr_res["RefNo"]??'';
$pay_total_fee = $arr_res["Amount"]??'';
$ecurrency = $arr_res["Currency"]??'';
$remark = $arr_res["Remark"]??'';
$transid = $arr_res["TransId"]??'';
$authcode = $arr_res["AuthCode"]??'';
$estatus = $arr_res["Status"]??'';
$errdesc = $arr_res["ErrDesc"]??'';
$signature = $arr_res["Signature"]??'';
$arr_record_pay = [
'Status' => $estatus,
'Amount' => $pay_total_fee,
'Currency' => $ecurrency,
'TransId' => $transid,
'ErrDesc' => $errdesc
];
$payment_json_str = json_encode($arr_record_pay,JSON_UNESCAPED_UNICODE);
$this->recordLocalFileLog('ipay88',"notify data :".json_encode( $arr_res) );
$this->recordLocalFileLog('ipay88',"notify data :".$payment_json_str );
if ($estatus==1) {
// update order to PAID
echo "RECEIVEOK";
} else{
// update order to FAIL
}
}
/**
* @Notes:记录本地文件 日志信息
* @param string $op_type
* @param string $log_content
* @return bool
* @User: zhanghj
* @DateTime: 2023-12-22 13:59
*/
public static function recordLocalFileLog($op_type = '',$log_content = '') {
$time_stamp = date("Y-m-d H:i:s", time());
$log_file_name = 'ipay88';
$file = dirname(Yii::$app->basePath)."/api/log/{$log_file_name}_".date("Ymd").".txt";
$handle = fopen( $file, 'a+');
fwrite( $handle , "[{$time_stamp}]: ".$log_content."\n");
fclose( $handle );
}
附录
①. 参考文章
②. 问题整理
APIPost
工具测试截图Payment ID
取值参考