php对接工商银行聚富通支付

开发前准备


联系工行业务员开户,开户后可以得到以下参数:

appid:工商银行appid,问工行业务员提供
mer_id:工商银行商户账号,商户线下档案编号,特约商户12位,特约部门15位,问工行业务员提供
icbc_pulic_key:网关公钥,问工行业务员提供
private_key:应用私钥,用工行的工具生成,生成后自己保存
public_key:应用公钥,用工行的工具生成,生成后提供给工行业务员
encryptKey:AES加密密钥,问工行业务员提供
url:正式环境地址,问工行业务员提供,一般是https://gw.open.icbc.com.cn

以下开发将在thinkphp5框架进行,其他php框架同理

文档和工具


  • 工行聚富通api文档:工行|开放平台 (icbc.com.cn)

  • SDK和密钥生成工具:工行|开放平台 (icbc.com.cn),php版本的生成密钥好像不全,建议用java版

  • 工行对客工具包:问工行提供内部调试的软件,直接解压打开BINHELPER.exe就能用,作用不大,不过里面有一些代码demo可以参考

    链接:https://pan.baidu.com/s/1tkzws5gyi1n0mN6-_ljw_A?pwd=w4ug
    提取码:w4ug

下载SDK


下载sdk有两种方式:

1.官网下载

本文用这种方式,但是官方提供的sdk没有命名空间,可以借助框架加载或自己加上去,不过可能有些bug要修改才能用;

工行|开放平台 (icbc.com.cn)页面点击sdk下载,下载php版本。

将下载好的sdk解压到你的框架扩展目录下,以thinkphp5框架为例,将解压得到的icbc-api-sdk-cop-php文件夹放到框架目录下的vendor即可

注意注意:工行的sdk有坑!!!

注意注意:工行的sdk有坑!!!

注意注意:工行的sdk有坑!!!

icbc-api-sdk-cop-php目录下找到IcbcConstants.php这个文件打开编辑,大概在48行,默认的时区是要修改才能用的,修改成下面的时区即可

	/** Date默认时区 **/
	//public static $DATE_TIMEZONE = "Etc/GMT+8";//java版GMT+8,错误的时区
    public static $DATE_TIMEZONE = "Asia/Shanghai";//东八区,正确的时区

如果你的请求参数正确,工行确一直返回下图这个“参数无效或非法,请检查请求参数”很有可能是这个原因导致

2.composer安装

由于官方没有提供composer安装扩展,我自己上传了一个,修复了部分bug并添加了命名空间,里面也有工行所有的sdk文件,我在php7(thinkphp框架)和php8(webman框架)下使用暂时没有发现问题

安装命令:

composer require jian1098/icbc-sdk

如果报错Internal Server Error {"error":"Class \"jian1098\\icbc\\DefaultIcbcClient\" not found",那么在composer.json手动加载一次,具体步骤如下:

1.在autoload节点增加代码

"classmap": [
  "vendor/jian1098/icbc-sdk/src"
]

2.在命令行执行composer dump-autoload -o即可


这里另外提供其他大佬上传的扩展包,但不保证能用(好像有部分文件会报有默认值的参数必须放在最后的错误):

1.https://github.com/johnorcc/icbcPayLib

(这个库使用rsa密钥对的时候好像必须要有类似-----BEGIN PUBLIC KEY-----开头结尾并且64个字符换一行的格式,否则openssl_verify会报错)

2.https://github.com/fatryst/icbc

开通权限


工行业务员开好平台账户之后接着要做的就是让他给你开通需要的接口权限,每个接口基本上都要单独开通,否则就会出现下面这个没有权限的报错,需要的接口都可以在开放平台的接口列表中找到

创建客户端


工行提供的sdk里面包含两种客户端:DefaultIcbcClient为api接口客户端,这种是通过json传输的,需要自己写界面;UiIcbcClient是带h5界面的客户端,不需要自己写界面,打开直接会跳转到工行的h5界面,这种适用于h5的项目。

<?php

namespace app\api\controller;

use app\common\controller\Api;
use IcbcConstants;
use think\Request;
use DefaultIcbcClient;
use UiIcbcClient;

/**
 * 工行聚富通api
 */
class Icbc extends Api
{
    protected $noNeedLogin = ['*'];
    protected $config; // 工行配置
    protected $apiClient; // api客户端
    protected $uiClient; // h5客户端

    /**
     * 构造方法
     */
    public function __construct(Request $request = null)
    {
        parent::__construct($request);

        // 初始化工行配置
        $this->config = [
            // 工行申请的app_id
            'appId' => 'your appid',
            // 商户号(档案编号,特约商户12位,特约部门15位),暂时用不到
            'merId' => 'your merid',
            // 工行api地址
            'url' => 'https://gw.open.icbc.com.cn', //正式环境url
            //AES密钥
            'AesKey' => 'your aeskey',
            // APP应用私钥,需要工行工作人员先在工行后台【用户中心-我的应用-密钥信息】配置对应的应用公钥(不需要以'------'的密钥开头和结尾也不用换行)
            'privateKey' => 'your privateKey',
            // 生产环境网关公钥,问工行工作人员获取(不需要以'------'的密钥开头和结尾也不用换行)
            'icbcPulicKey' => 'your icbcPulicKey',
        ];

        //由于sdk没有使用命名空间,所以需要手动引入
        Loader::import('icbc-api-sdk-cop-php.DefaultIcbcClient', VENDOR_PATH, '.php');
        Loader::import('icbc-api-sdk-cop-php.UiIcbcClient', VENDOR_PATH, '.php');

        //创建api客户端
        $this->apiClient = new \DefaultIcbcClient(
            $this->config['appId'],          //app_id
            $this->config['privateKey'],     //应用私钥
            IcbcConstants::$SIGN_TYPE_RSA2,  //签名类型
            '',
            '',
            $this->config['icbcPulicKey'],  //工行网关公钥
            '',
            '',
            '',
            '');

        //创建h5客户端
        $this->uiClient = new \UiIcbcClient(
            $this->config['appId'],          //app_id
            $this->config['privateKey'],     //应用私钥
            IcbcConstants::$SIGN_TYPE_RSA2,  //签名类型
            '',
            '',
            '',
            '',
            '',
            '',
            '');
    }

}

注册子商户


在调用支付功能前必须要有一个商家收款账户,也就是商家必须用证件注册子商户,然后用商户号收款。对于同一个工行appid,可以在该appid下注册很多个子商户,商户注册子商户之后可以获得一个商户号,这个商户号是用来收款用的。不同appid下注册的子商户不通用,如果商户在不同平台开店,平台的appid不同,那就需要在每个平台都要注册子商户。

下面分别用h5和api方式调用工行子商户注册功能:

<?php

namespace app\api\controller;

use app\common\controller\Api;
use IcbcConstants;
use think\Loader;
use think\Request;


/**
 * 工行聚富通api
 */
class Icbc extends Api
{
    protected $noNeedLogin = ['*'];
    protected $config; // 工行配置
    protected $apiClient; // api客户端
    protected $uiClient; // h5客户端

    /**
     * 构造方法
     */
    public function __construct(Request $request = null)
    {
        parent::__construct($request);

        // 初始化工行配置
        $this->config = [
            // 工行申请的app_id
            'appId' => 'your appid',
            // 商户号(档案编号,特约商户12位,特约部门15位),暂时用不到
            'merId' => 'your merid',
            // 工行api地址
            'url' => 'https://gw.open.icbc.com.cn', //正式环境url
            //AES密钥
            'AesKey' => 'your aeskey',
            // APP应用私钥,需要工行工作人员先在工行后台【用户中心-我的应用-密钥信息】配置对应的应用公钥
            'privateKey' => 'your privateKey',
            // 生产环境网关公钥,问工行工作人员获取
            'icbcPulicKey' => 'your icbcPulicKey',
        ];

        //由于sdk没有使用命名空间,所以需要手动引入
        Loader::import('icbc-api-sdk-cop-php.DefaultIcbcClient', VENDOR_PATH, '.php');
        Loader::import('icbc-api-sdk-cop-php.UiIcbcClient', VENDOR_PATH, '.php');

        //创建api客户端
        $this->apiClient = new \DefaultIcbcClient(
            $this->config['appId'],          //app_id
            $this->config['privateKey'],     //应用私钥
            IcbcConstants::$SIGN_TYPE_RSA2,  //签名类型
            '',
            '',
            $this->config['icbcPulicKey'],  //工行网关公钥
            '',
            '',
            '',
            '');

        //创建h5客户端
        $this->uiClient = new \UiIcbcClient(
            $this->config['appId'],          //app_id
            $this->config['privateKey'],     //应用私钥
            IcbcConstants::$SIGN_TYPE_RSA2,  //签名类型
            '',
            '',
            '',
            '',
            '',
            '',
            '');
    }
    
    //随机生成消息id
    private function getMsgId()
    {
        return substr(md5(time() . mt_rand(1, 1000000)), 8, 13);
    }

    /**
     * h5注册子商户
     */
    public function uiVendorRegister(){
        $request = [
            'serviceUrl' => 'https://gw.open.icbc.com.cn/ui/jft/ui/vendor/register/V3',
            'method' => 'POST',
            'isNeedEncrypt' => false,
            'biz_content' => array(
                'appId' => $this->config['appId'],
                'outUpperVendorId' => $this->config['appId'],
                'outVendorId' => 'M10000123',
                'outUserId' =>  '123',
            ),
            'extraParams' => []
        ];

        $res = $this->uiClient->buildPostForm($request, $this->getMsgId(), '');
        echo $res; //工行返回的是前端代码,直接输出即可
    }

    /**
     * api注册子商户
     */
    public function apiVendorRegister()
    {
        //请求参数,通过前端表单提交,具体参数参考官方文档
        $data = $this->request->post();
        $request = [
            'serviceUrl' => $this->config['url'] . '/api/jft/api/vendor/info/register/V2',
            'method' => 'POST',
            'isNeedEncrypt' => false,
            'biz_content' => array(
                'appId' => $this->config['appId'],
                'outUpperVendorId' => $this->config['appId'],
                'outVendorId' => $data['vendorId'],  //子商户号
                'outUserId' => $data['userId'],    //系统用户id
                'vendorName' => $data['vendorName'],
                'vendorShortName' => $data['vendorShortName'],
                'vendorPhone' => $data['vendorPhone'],
                'vendorEmail' => $data['email'],
                'province' => $data['province'],
                'city' => $data['city'],
                'county' => $data['county'],
                'address' => $data['address'],
                'postcode' => $data['postcode'], //营业执照邮政编码
                'operatorName' => $data['name'],
                'operatorMobile' => $data['email'],
                'operatorEmail' => $this->email,
                'operatorIdNo' => $data['idCard'],
                'vendorType' => $data['vendorType'],
                'corprateIdType' => '0', //法人/负责人证件类型固定为身份证
                'corprateName' => $data['name'],
                'corprateMobile' => $data['mobile'],
                'corprateIdNo' => $data['idCard'],
                'corprateIdPic1' => $data['corprateIdPic1'],
                'corprateIdPic2' => $data['corprateIdPic2'],
                'certType' => '101',//商户注册证件类型 101-营业执照
                'certPic' => $data['certPic'],
                'certNo' => $data['certNo'], //营业执照编号
                'accountName' => $data['bankAccountName'], //商户绑定银行账号户名
                'accountBankProvince' => $data['bankProvince'],
                'accountBankCity' => $data['bankCity'],
                'accountBankNm' => $data['bankName'],
                'accountBankName' => $data['bankBranch'],
                'accountBankCode' => $data['bankCode'],
                'accountNo' => $data['bankAccount'],
                'accountMobile' => $data['bankMobile'],
            ),
            'extraParams' => []
        ];

        //调用接口
        $res = $this->apiClient->execute($request, $this->getMsgId(), '');
        var_dump($res);
    }
}

h5注册子商户页面打开如下图

注意:商户提交注册之后需要工行业务员审核通过商户号(outVendorId)才可以使用,一般需要主动跟业务员沟通,否则一直在审核状态;工行子商户审核状态一般有下面两步,只有到最后法客审批通过才能真正使用商户号,每一步的状态如下:

//工行网金部审核状态
public $icbcAuditStatus = [ 
    '01' => '待网金部建档',
    '02' => '待送网金部审核',
    '03' => '网金部审核中',
    '04' => '网金部审核通过',
    '05' => '网金部审核拒绝',
];

//法客审核状态
public $cbmsAuditStatus = [
    '0'=>'待法客修改',
    '1'=>'待法客审核',
    '2'=>'法客审核拒绝',
    '3'=>'法客审核退回',
    '4'=>'法客待审批',
    '5'=>'法客审批退回',
    '8'=>'法客建档完成',
    '12'=>'法客审批拒绝',
    '11'=>'法客审批通过',
];

发起支付


在子商户注册的过程中有个参数是子商户号(outVendorId),这个子商户号可以用来发起支付功能;工行支付同样有api接口和h5支付,h5比较简单,直接打开工行的支付页面即可,api接口方式需要自己写前端页面,参数也更复杂,下面提供两种支付方式的参考代码:

需要注意的是api支付功能在工行叫埋名支付,如果像购物车那样需要多商户同时支付还要对接并笔支付功能

	//H5支付
    public function h5Pay($data)
    {
        //请求参数
        $request = [
            'serviceUrl' => $this->config['url'] . '/ui/jft/ui/pay/h5/V3',
            'method' => 'POST',
            'isNeedEncrypt' => false,
            'biz_content' => [
                'appId' => $this->config['appId'],
                'outOrderId' => $data['order_num'],
                'outVendorId' => $data['vendor_id'],
                'outUserId' => $data['user_id'],
                'payAmount' => $data['amount'],
                'payType' => '01',
                'notifyUrl' => $data['notify_url'],
                'jumpUrl' => $data['jump_url'],
                'goodsName' => $data['goods_name'],
                'trxIp' => $data['ip'],
                'trxChannel' => '03',
                'subMerRateWx' => $data['fee_wx'],
                'subMerRateZfb' => $data['fee_zfb'],
                'subMerRateUnionPay' => '0',
                'subMerRateOwn' => '0',
                'subMerRateOther' => '0',
            ],
            'extraParams' => []
        ];

        if($data['type'] == 2){ //支付宝
            $request['biz_content']['unionId'] = $data['openid'];
        }else{ //微信
            $request['biz_content']['tpOpenId'] = $data['openid'];
            $request['biz_content']['tpAppId'] = $data['appid'];
        }

        //执行请求
        $res = $this->uiClient->buildPostForm($request, $this->getMsgId(), '');
        echo $res;
    }

    //api支付(埋名支付)
    public function tracelessPay($data)
    {
        //获取微信配置
        $wechatapp = Config::get('site.wechatapp');

        //请求参数
        $request = [
            'serviceUrl' => $this->config['url'] . '/api/jft/api/pay/add/h5/traceless/order/V2',
            'method' => 'POST',
            'isNeedEncrypt' => false,
            'biz_content' => [
                'appId' => $this->config['appId'],
                'outOrderId' => $data['order_num'], //订单号
                'outVendorId' => $data['vendor_id'] ?? $this->platVendorId,//收方商户编号, 默认为平台商户号
                'outUserId' => '' . $data['user_id'] . '',    //用户id
                'payAmount' => (string)$data['amount'],     //金额
                'payType' => '01',                  //01.单订单支付
                'payMode' => $data['pay_mode'],     //支付模式 微信小程序: 01,支付宝生活号: 02,微信公众号: 03
                'notifyUrl' => $data['notify_url'], //支付回调
                'goodsName' => $data['goods_name'], //商品名称
                'trxIp' => $data['ip'],             //用户ip
                'trxChannel' => '03',               //交易渠道:03
                'tpAppId' => $wechatapp['app_id'],  //
                'tpOpenId' => $data['open_id'],     //
                'subMerRateWx' => !empty($data['fee']) ? bcmul($data['fee'], 100000) : '0', //子商户服务费率(微信),单位是十万分之一,平台分润比例 = 商家费率(商家列表)-工行的手续费费率(签约)
                'autoConfirm' => '0',               //是否即时解冻 1是 0否 商户为担保模式有效, 平台商户发起担保支付后,子商户该笔订单资金属于冻结状态,不可结算。确认收货成功及对应订单清算完成后资金解冻可结算
                'expireTime' => date('YmdHis', time() + 300), //订单过期时间
            ],
            'extraParams' => []
        ];

        //执行请求
        $res = $this->apiClient->execute($request, $this->getMsgId(), '');
        $resData = json_decode($res,true);
        if (empty($resData['return_code']) || $resData['return_code'] != 10100000){
            return false;
        }

        return json_decode($resData['paySign'],true); //返回的是微信和支付宝需要的支付参数,提供给前端即可调起支付
    }

	/**
     * 确认收货
     * @desc 平台商户发起担保支付后,子商户该笔订单资金属于冻结状态,不可结算。确认收货成功及对应订单清算完成后资金解冻可结算
     */
    public function unfreeze($data){
        //请求参数
        $request = [
            'serviceUrl' => $this->config['url'] . '/api/jft/api/pay/unfreeze/accept/V1',
            'method' => 'POST',
            'isNeedEncrypt' => false,
            'biz_content' => [
                'appId' => $this->config['appId'],
                'userId' => (string)$data['user_id'], //用户id
                'vendorId' => $data['vendor_id'],   //收方商户编号
                'payType' => '01',                  //01-单订单支付 02-合并订单
                'orderId' => $data['order_num'],    //订单号
                'notifyUrl' => $data['notify_url'], //回调地址
            ],
            'extraParams' => []
        ];

        //执行请求
        $res = $this->apiClient->execute($request, $this->getMsgId(), '');
        return json_decode($res, true);
    }

    /**
     * 测试
     */
    public function test(){
        $data = [
            'order_num' => '254170649142832531', //订单号
            'vendor_id' => 'M10000466', //子商户号
            'user_id' => '1000', //用户id
            'amount' => '0.01', //支付金额
            'notify_url' => $this->request->domain() . '/api/Callback/merchCodePayNotify', //回调方法
            'jump_url' => 'https://www.baidu.com', //支付完成跳转地址
            'goods_name' => 'iphone15', //商品名称
            'ip' => $this->request->ip(), //ip
            'fee_wx' => '0', //子商户服务费率十万分比(微信),平台收子商户佣金
            'fee_zfb' => '0',//子商户服务费率十万分比(支付宝),平台收子商户佣金
            'openid' => 'o6Xx25M5ZHQUWgAQEakrOTb_1YGg', //微信公众号或小程序openid
            'unionid' => 'oB4qR6s4_l1a9vdy-NUPcU9q3cWM', //微信/支付宝unionid
            'appid' => 'wx4cfeaa2702dc5782', //微信公众号或小程序appid
            'type' => 1, //微信or支付宝
        ];

        $this->h5Pay($data);
    }

注意事项:

  • 支付成功后,资金不会立即到子商户那里,需要另外对接确认收货接口工行|开放平台 (icbc.com.cn)),h5和api方式都要;
  • 到账一般是T+1;
  • 并笔支付虽然同样是用埋名支付接口,但是还是要工行业务员另外开通权限;
  • h5支付页面每一种支付方式都要工行开通权限;

h5支付页面如下:

验签解密和应答


工行支付和退款返回的回调数据是经过加密的,其中biz_content是我们需要解密的数据,注意:支付宝返回字段用的是transData,微信用的是biz_content,回调数据结构如下:

{
    "api": "/jft/api/pay/gen/pre/order/V1",
    "app_id": "11000000000000001111",
    "biz_content": "加密数据",
    "charset": "UTF-8",
    "format": "json",
    "from": "icbc-api",
    "sign": "OtXRATH9LSV1iFU8StOXa9bMlOjQuOg0YIVBdAFYF3MSSUyt2w9T1fQjh3Hobtx1rzmWI9BTgKhP5WtXFx5UbfaA90r+XQ/ZA+uRdL0AYlRtyuBWgpraPE+riiLNO7s7bHLQ2GWWkQGG8QUA9Xo4V3o8/L8sqOwAo2Ci1X4JKSQ=",
    "sign_type": "RSA",
    "timestamp": "2023-11-21 16:14:29"
}

数据里面有签名可以对数据进行验签,解密后的数据里面有我们需要的支付状态和订单号等信息,收到回调后记得给工行返回应答报文,不然每10分钟工行就会继续回调,最多回调6次;

下面是验签demo、解密demo和返回报文的demo:

    /**
     * 验签
     * @param $params array 参数
     * @param $sign string 签名
     * @return int
     * @throws Exception
     */
    public function verify($params, $sign)
    {
        //网关公钥
        $APIGW_PUBLIC_KEY = $this->config['icbcPulicKey'];//回调地址
        $notifyUrl = "/api/notify/testPayNotify"; //回调地址(不含域名),此url需与发起支付提交的保持一致

        //参数排序
        $strToSign = WebUtils::buildOrderedSignStr($notifyUrl, $params);

        //验签,1为验签成功
        $passed = IcbcSignature::verify($strToSign, $params["sign_type"], $APIGW_PUBLIC_KEY, $params['charset'], $sign, '');
        if (!$passed) {
            throw new Exception("icbc sign verify not passed!");
        }

        return $passed;
    }

    /**
     * 回调数据解码
     * @param $params array json_decode后的工行回调json数据
     * @return array
     */
    public function callBackDecode($params)
    {
        if (empty($params)) {
            return false;
        }
        if (!empty($params['transData'])) { //支付宝返回字段用的是transData,微信用的是biz_content
            $params['biz_content'] = $params['transData'];
        }
        $data['biz_content'] = htmlspecialchars_decode($params['biz_content']);//调用业务参数集合
        $bizContent = json_decode(htmlspecialchars_decode($data['biz_content']), true);
        $notifyData = base64_decode(str_replace(' ', '+', urldecode($bizContent['notifyData']))); //先urldecode然后再base64_decode,不然中文会乱码

        return $notifyData; //返回解密数据
    }

    /**
     * 生成返回报文给通知方
     * @param $charset
     * @param $copReturnCode
     * @param $copReturnMsg
     * @return string
     */
    public function callBackResponse($success = 1)
    {
        /**
         * @desc 回调应答
        注意:1.必须要有大括号。
        2.sign字段必须放在最后。
        3.报文中不能出现空格等特殊字符。
        4.sign_type要与appid在api内管上注册时保持一致。
        5.sign之前都为签名内容。
        6.报文格式必须为上述示例。
        7.报文格式需转为json格式。示例:{"response_biz_content":{"return_code":0,"return_msg":"SUCCESS","msg_id":"20190531150004510"},"sign_type":"RSA2","sign":"gOTyXj9KyzS+JnQ906pHTKcOtzSLgwtTl9SXfgYxW6rIqfedZ9A/67vVQS9EGbHhju7ZdVpO6u7OSwAEEGsaG7HGiU+rB3we4bRmEfEGwiza0vYN8LC/yPqqQmGuWDXjUvIQLB574tIRQOZ8osEfA8sYn2kbK0bVmd15OwCL/5B1ZxWPH65Wx18ePl9X5EL6JzLM2rUNUBLIHmLrHkP/7SPa8oCUveFKdb9eTzuKMwj80FQSFnlVaPJ/75fzSOY0hG/Bp2zuEDLQWRfnvngKjGY2fe9g/rhj4tAe3zguw8oqPRh3EFV2qHvBaSPPjH1pNlWYd0goE1UChiYsw3RMyg=="}
         */

        if ($success){  //成功场景
            $copReturnCode = "0";// 通知合作方接收成功的返回码,固定
            $copReturnMsg = "success";// 合作方的返回信息,固定
        }else{  //失败场景
            $copReturnCode = "-12345";
            $copReturnMsg = "icbc jft sign not pass.";
        }

        $privateKey = $this->config['privateKey'];
        $msg_id = date('YmdHis', time());   //yyyyMMddHHmmss按照这个格式生成的时间戳 形成msg_id
        $responseBizContent = "{\"return_code\":\"" . $copReturnCode . "\",\"return_msg\":\"" . $copReturnMsg . "\""
            . ",\"msg_id\":\"" . $msg_id . "\"}";
        $charset = 'utf-8';// 调用过程使用的编码格式

        /**********
         * 商户对消息返回响应进行签名,签名方式需与在API平台登记APP的sign_type保持一致(目前基本都是RSA2)
         **********/
        $signType = IcbcConstants::$SIGN_TYPE_RSA2;
        $signStr = "\"response_biz_content\":" . $responseBizContent . "," . "\"sign_type\":" . "\"" . $signType
            . "\"";
        $signParam = IcbcSignature::sign($signStr, $signType, $privateKey, $charset, "");
        $returnJson = "{\"response_biz_content\":" . $responseBizContent . ",\"sign_type\":\"" . $signType . "\""
            . ",\"sign\":\"" . $signParam . "\"}";
        return $returnJson;//给工行返回结果
    }

    public function test()
    {
        //验签测试
        $data = json_decode(file_get_contents('php://input'), true); //接收回调的json数据
        $biz_content = htmlspecialchars_decode($data['biz_content']); //处理json转义字符

        //封装参数,这些参数都是从回调的Request里读取的
        $params = [];
        $params['from'] = $data['from'];
        $params['api'] = $data['api'];
        $params['app_id'] = $data['app_id'];
        $params['charset'] = $data['charset'];
        $params['format'] = $data['format'];
        $params['timestamp'] = $data['timestamp'];
        $params['biz_content'] = $biz_content;
        $params['sign_type'] = $data['sign_type'];

        $opensslVerify = $this->verify($params, $data['sign']);
        var_dump("验签结果:" . $opensslVerify);


        //解密测试
        //$data = $this->request->param();
        //$res = $this->callBackDecode($data);
        //var_dump("解密数据:" . $res);

        //返回报文测试
        $res = $this->callBackResponse(1);
        var_dump("返回报文:" . $res);
    }

解密后的数据格式如下:

{
    "txCode": "02",
    "respCode": "00000",
    "respMsg": "交易失败",
    "trxAmount": 96.00,
    "payMethod": "09",
    "payMode": "02",
    "orderStatus": "03",
    "refundStatus": null,
    "orderId": "15417005540345335",
    "refundId": null,
    "orderCreateDate": "20231121",
    "orderCreateTime": "16:07:16",
    "completeDate": "20231121",
    "completeTime": "16:12:26",
    "appId": "11000000000000001111",
    "vendorId": "M10000466",
    "userId": "1373",
    "merName": "xxxx有限公司",
    "subMerName": "xxxx有限公司",
    "orderNo": null,
    "realUnfreezeAmt": null,
    "vendorDiscount": 0.00,
    "bankDiscount": 0.00,
    "isJftDiscount": "0",
    "jftDiscountAmt": 0.00,
    "shopCode": null,
    "icbcOrderId": "201048161155000562111111",
    "comment": null,
    "extension": null,
    "errorCode": null,
    "jOrderId": "02023112100282711111",
    "jParentRefundId": null,
    "jRefundId": null,
    "confirmStatus": null,
    "merConfirmId": null,
    "subOrderInfoList": null,
    "subRefunds": null,
    "paySplitDetallBeanList": null,
    "splitAccountStatus": null,
    "merSplitId": null,
    "couponIdList": null,
    "smartWithdrawStatus": null,
    "thirdTradeNo": null,
    "withdrawDateGroup": null,
    "custId": null,
    "payAmount": 96.00,
    "amountSerialNo": null,
    "amount": null,
    "walletId": null,
    "businessCode": null
}

常见问题


  • sub_mch_id与sub_appid不匹配:需要提供公众号或小程序appid参数给工行业务员配置,或者检查是否配置错误
  • 当前页面的URL未注册:需要工行业务员将你的域名配置到他们后台的JS API支付授权目录
  • 工行支付回调无法正常回调,我们就遇到这种情况,让工行排查,结果是域名证书的问题,我们用的是宝塔面板Let’s encrypt提供的免费证书,但是工行不认,没有办法,最后花钱买OV证书解决问题

写在最后


这里只分享注册子商户和支付功能,其他的功能基本是都是依葫芦画瓢了,参照官方文档基本问题不大,而且有问题可以咨询工行的业务员;

工行支付本质是对微信支付和支付宝等支付方式的二次封装,实际上只是提供参数以调起微信和支付宝;

工行支付有风控机制,尽量不要频繁进行小额支付,例如0.01元等,否则账号会被禁用;

平台支付后工行会从支付金额收取手续费,不过一般第一年会免费;

众所周知,银行政府等机构想要开通功能都是很慢的,从申请平台账号,到开通接口权限,再到请教问题调试接口、绑定微信公众号和小程序等等,每一步几乎至少要几天到几周时间,如果要评估开发时间,可以大胆预留多一些时间;

由于大部分是用java开发的,网上对于php对接的文章太少了,sdk还有bug,导致有些地方卡了挺久的,不过最后还是顺利搞定了,花了两天时间重写走了一遍程序写这篇文章让大家少走弯路,祝大家早实现日财富自由,远离资本家。

  • 25
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值