iPay88 支付网关Gateway-User Scan(用户扫码模式 - PHP实现)

背景

  • 近期因为合作客户有马来西亚的业务,需要对接 【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 CodeMerchant 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-TypeSOAPAction
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

③. 核心代码提供

根据业务场景,整理核心代码如下

    1. 网关支付、下单方法封装如下:
/**
     * @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??[]];
    }
    1. 提供 加密、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);
    }

④. 接口请求测试

根据以上代码的部署,已经配置信息的整合,进行正式请求测试

    1. 构造的 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>
    1. 下单接口,请求成功后,返回信息如下(可根据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>
    1. 请求测试代码如下:
$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 支付开发中...');
    1. 返回代码显示:
string(0) ""
array(1) {
["_qr"]=>
string(108)
"https://payment.ipay88.com.my/ePayment/WebService/QR/AliPayOfflineQR/QrAli1704425934.76971-T042904908324.Png"
}
string(25) "iPay88 支付开发中..."
    1. 支付二维码显示、支付宝扫码结果如下:

⑤. 支付回调处理

回调处理接口,即下单请求时配置的参数 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 );
    }

附录

①. 参考文章

②. 问题整理

    1. 支付金额
      测试发现,金额小于 1.00 无法生成支付二维码
    1. 回调处理
      注意文档中提到的一点,需要在后台配置地址
    1. 请求测试
      个人经验:代码测试一般找问题较慢,推荐先使用 Post请求工具,提前测试
      如下是我用 APIPost 工具测试截图
    1. Payment ID 取值参考
  • 25
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值