背景
-
近期因为合作客户有马来西亚的业务,需要对接
【iPay88 支付】
通过阅读官方文档,发现一头雾水
相对之前接触的支付文档,个人觉得iPay88 是最凌乱的
注意,注册平台账号后,会邮件发送几个开发文档附件(有的跟官网对不上)对于开发沟通,还得需要公司业务发送邮件,等个一天多才收到回复,对开发来说,效率很低
一番折腾,最后梳理一下我的实现步骤,希望能帮到有同样需要的小伙伴 -
场景要求
满足 在 安卓售卖机 的商品购买页面,下单后,选取iPay88支付方式,弹出 支付二维码 , 引导用户扫码支付
- 简单介绍
iPay88 是马来西亚领先的在线支付网关提供商,提供本地和国际支付选项。
iPay88 是寻求可靠且功能丰富的支付网关的企业的绝佳选择。
- 官方文档
【API - iPay88 Technical Spec V1.0.1.pdf】
根据对使用场景的确认,我要参考的便是邮箱提供的附件: iPay88 - Merchant Hosted Payment Gateway e-Wallet (Web Service) - v2.4.1.pdf
☛ 开发步骤
通过阅读开发文档(英文不好,可以是有百度翻译),一步步进行测试
①. 前期准备
- 首先,需要得到
Merchant Code
、Merchant Key
,其次需要在后台配置 IP 白名单 - 其次,根据文档提示,整理开发步骤【User Scan】如下:
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.
- 通过梳理测试,我发现仅靠网站和开发文档并不能实现 业务场景所需要的网关支付
无果,需要向 ipay88官方进行邮件转达,等了一周才得到技术提供的请求示例
最终得到了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 );
}
附录
①. 参考文章
-
- php 对接国外支付 ipay88支付
-
- 分布式.RPC-WebService三要素,三个规范, Soap协议
-
- 如何修复guzzlehttp截断的500内部服务器错误代码点火器
②. 问题整理
-
- 支付金额
测试发现,金额小于 1.00 无法生成支付二维码
- 支付金额
-
- 回调处理
注意文档中提到的一点,需要在后台配置地址
- 回调处理
-
- 请求测试
个人经验:代码测试一般找问题较慢,推荐先使用 Post请求工具,提前测试
如下是我用APIPost
工具测试截图
- 请求测试
-
Payment ID
取值参考