<?php
include_once "icbc-api-sdk-cop-php/DefaultIcbcClient.php";
include_once "icbc-api-sdk-cop-php/UiIcbcClient.php";
include_once "icbc-api-sdk-cop-php/WebUtils.php";
include_once "icbc-api-sdk-cop-php/IcbcSignature.php";
include_once "icbc-api-sdk-cop-php/IcbcConstants.php";
include_once "icbc-api-sdk-cop-php/IcbcJftPayI.php";
class IcbcPay
{
public static $app_id = "";
public static $private_key = '私钥';
public static $public_key = '公钥';
public static $getaway_public_key = '工行公钥';
public static $encrypt_key = '加密串';
public static $wechatAppletAppId = "微信小程序appid";
public static $payNotifyUrl = '/apk49/Pay/icbcPayNotify';
public static $refundNotifyUrl = '/apk49/PayRefund/icbcRefundNotify';
/**
* 构建数据
* @param string $service_url
* @param string $request_type
* @param array $biz_content
* @param bool $isNeedEncrypt
* @return array
* @author 张志强
*/
private static function buildData(string $service_url, string $request_type, array $biz_content, bool $isNeedEncrypt = false): array
{
return [
'serviceUrl' => $service_url,
'biz_content' => $biz_content,
'extraParams' => [],
'method' => $request_type,
'isNeedEncrypt' => $isNeedEncrypt
];
}
/**
* 验签
* @param string $respStr
* @return false|string
* @throws Exception
*/
public static function verifySign(string $respStr)
{
$response = explode("&", $respStr);
$responseArr = [];
$sign = "";
foreach ($response as $v) {
if (strstr($v, "=", true) == 'sign') {
$sign = substr(strstr($v, "="), 1);
} else {
$responseArr[strstr($v, "=", true)] = substr(strstr($v, "="), 1);
}
}
$path = parse_url($_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'] . $_SERVER["REQUEST_URI"], PHP_URL_PATH);
$respBizContentStr = WebUtils::buildOrderedSignStr($path, $responseArr);
//解析响应
$passed = IcbcSignature::verify($respBizContentStr, IcbcConstants::$SIGN_TYPE_RSA, self::$getaway_public_key, IcbcConstants::$CHARSET_UTF8, $sign, "");
if (!$passed) {
throw new Exception("icbc sign verify not passed!");
}
//返回解析结果
return base64_decode(json_decode(str_replace("%2B", '+', $responseArr['biz_content']), true)['notifyData']);
}
/**
* 加签
* @param string $copReturnCode
* @param string $copReturnMsg
* @return string
* @throws Exception
*/
public static function sign(string $copReturnCode, string $copReturnMsg): string
{
$responseBizContentArr = [
'return_code' => $copReturnCode,
'return_msg' => $copReturnMsg,
'msg_id' => date("YmdHis", time())
];
$signStr = '"response_biz_content":'.json_encode($responseBizContentArr).',"sign_type":"'.IcbcConstants::$SIGN_TYPE_RSA2.'"';
$signArr = [
'response_biz_content' => $responseBizContentArr,
'sign_type' => IcbcConstants::$SIGN_TYPE_RSA2,
];
$signArr['sign'] = IcbcSignature::sign($signStr, IcbcConstants::$SIGN_TYPE_RSA2, self::$private_key, IcbcConstants::$CHARSET_UTF8, '');
return json_encode($signArr);
}
/**
* 发起ui界面的支付 e支付
* @param string $service_url
* @param string $request_type
* @param array $biz_content
* @return string
* @author 张志强
*/
public static function ui(string $service_url, string $request_type, array $biz_content): string
{
$data = self::buildData($service_url, $request_type, $biz_content);
$uiClient = new UiIcbcClient(self::$app_id, self::$private_key, IcbcConstants::$SIGN_TYPE_RSA2, IcbcConstants::$CHARSET_UTF8, IcbcConstants::$FORMAT_JSON, self::$getaway_public_key, self::$encrypt_key, IcbcConstants::$ENCRYPT_TYPE_AES, "", "");
return $uiClient->buildPostForm($data, time() . '', '');
}
/**
* 发起无界面的支付
* @param string $service_url
* @param string $request_type
* @param array $biz_content
* @return array
* @throws Exception
* @author 张志强
*/
public static function api(string $service_url, string $request_type, array $biz_content): array
{
$data = self::buildData($service_url, $request_type, $biz_content, true);
$apiClient = new DefaultIcbcClient(self::$app_id, self::$private_key, IcbcConstants::$SIGN_TYPE_RSA2, IcbcConstants::$CHARSET_UTF8, IcbcConstants::$FORMAT_JSON, self::$getaway_public_key, self::$encrypt_key, IcbcConstants::$ENCRYPT_TYPE_AES, "", "");
$result = $apiClient->execute($data, time() . '', '');
return json_decode($result, true);
}
/**
* 单笔支付
* @param $orderId string 订单号
* @param $icbc_jft_submerchant_num string 子商户号
* @param $money string 支付金额(元)
* @param $rate string 费率
* @return array
* @throws Exception
*/
public static function singleOrderPay(string $orderId, string $icbc_jft_submerchant_num, string $money, string $rate): array
{
$service_url = 'https://gw.open.icbc.com.cn/api/jft/api/pay/gen/pre/order/V1';
$server = explode(":", $_SERVER['HTTP_HOST']);
$biz_content = [
'appId' => self::$app_id,
'outVendorId' => $icbc_jft_submerchant_num, // 收方商户编号
'outUserId' => $icbc_jft_submerchant_num, // 用户外部编号
'notifyUrl' => 'http://'.$server[0].self::$payNotifyUrl, // 回调地址
'outOrderId' => $orderId, // 合作方订单id(唯一)
'goodsName' => '物业账单', // 商品名称
'trxChannel' => '01', // 交易渠道 固定送01:手机APP,否则校验不过
'payAmount' => $money, // 支付金额(元)整数位不超8位,小数位不超2位
'subMerRateWx' => $rate, // 微信支付费率
'subMerRateZfb' => $rate, // 支付宝支付费率
'subMerRateOwn' => $rate, // e支付本行卡费率
'subMerRateOther' => $rate, // e支付他行卡费率
'subMerRateVipCard' => $rate, // 会员卡费率
'subMerRateUnionPay' => $rate, // 云闪付费率
'version' => '1.0.1'
];
$result = self::api($service_url, 'POST', $biz_content);
if ($result['return_code'] != '10100000') {
throw new Exception($result['return_msg']);
}
return $result;
}
/**
* 并笔支付
* @param string $parentOrderId 父订单id
* @param string $goodsName 商品名称
* @param string $money 金额
* @param array $subOrder 子订单信息
* @return array
* @throws Exception
*/
public static function multipleOrderPay(string $parentOrderId, string $goodsName, string $money, array $subOrder): array
{
$service_url = 'https://gw.open.icbc.com.cn/api/jft/api/pay/mergepay/gen/order/V1';
$biz_content = [
'appId' => self::$app_id,
'outUserId' => '1', // 用户外部编号(并笔支付可以随便输入不影响子商户)
'notifyUrl' => self::$payNotifyUrl, // 回调地址
// 'jumpUrl' => '', // 商户跳转URL
'outOrderId' => $parentOrderId, // 合作方订单id(唯一)
'goodsName' => $goodsName, // 商品名称
'trxChannel' => '01', // 交易渠道 固定送01:手机APP,否则校验不过
'payAmount' => $money, // 支付金额(元)整数位不超8位,小数位不超2位
'version' => '1.0.1', // 工行版本号
'suborders' => $subOrder // 子订单
];
return self::api($service_url, 'POST', $biz_content);
}
/**
* h5单笔埋名支付 跳转微信小程序
* @param string $orderId 订单id
* @param string $icbc_jft_submerchant_num
* @param string $money
* @param string $goodsName
* @param string $ip
* @param string $rate
* @param string $openid
* @return array
* @throws Exception
* @author 张志强
* @creatime 2022-08-24
*/
public static function h5SinglePay(string $orderId, string $icbc_jft_submerchant_num, string $money, string $goodsName, string $ip, string $rate, string $openid): array
{
$service_url = 'https://gw.open.icbc.com.cn/api/jft/api/pay/add/h5/traceless/order/V2';
$server = explode(":", $_SERVER['HTTP_HOST']);
$biz_content = [
'appId' => self::$app_id,
'outOrderId' => $orderId, // 合作方订单id(唯一)
'outVendorId' => $icbc_jft_submerchant_num, // 收方商户编号
'outUserId' => $icbc_jft_submerchant_num, // 用户外部编号(并笔支付可以随便输入不影响子商户)
'payAmount' => $money, // 支付金额(元)整数位不超8位,小数位不超2位
'payType' => '01', // 合并支付表示 01 代表单笔支付
'payMode' => '01', // 支付模式 01 微信小程序 02 支付宝生活号 03 微信公众号
'notifyUrl' => 'http://'.$server[0].self::$payNotifyUrl, // 回调地址
'goodsName' => $goodsName, // 商品名称
'trxIp' => $ip, // 交易ip,支付提交的用户的ip地址
'trxChannel' => '03', // 交易渠道 01:手机APP
'subMerRateWx' => $rate, // 微信支付费率
'subMerRateZfb' => $rate, // 支付宝支付费率
'tpAppId' => self::$wechatAppletAppId,
'tpOpenId' => $openid,
'autoConfirm' => '1', // 是否及时解冻 1 是 0 否 商户为担保模式有效 默认是0
];
$result = self::api($service_url, 'POST', $biz_content);
if ($result['return_code'] != '10100000') {
throw new Exception($result['return_msg']);
}
return $result;
}
/**
* h5并笔埋名支付 跳转微信小程序
* @throws Exception
* @author 张志强
* @creatime 2022-08-24
*/
public static function h5MultiplePay(string $orderId, string $icbc_jft_submerchant_num, string $money, string $goodsName, string $ip, array $subOrder, string $openid): array
{
$service_url = 'https://gw.open.icbc.com.cn/api/jft/api/pay/add/h5/traceless/order/V2';
$biz_content = [
'appId' => self::$app_id,
'outOrderId' => $orderId, // 合作方订单id(唯一)
'outUserId' => $icbc_jft_submerchant_num, // 用户外部编号(并笔支付可以随便输入不影响子商户)
'payAmount' => $money, // 支付金额(元)整数位不超8位,小数位不超2位
'payType' => '02', // 合并支付表示 01 代表单笔支付 02 并笔支付
'payMode' => '01', // 支付模式 01 微信小程序 02 支付宝生活号 03 微信公众号
'notifyUrl' => self::$payNotifyUrl, // 回调地址
'goodsName' => $goodsName, // 商品名称
'trxIp' => $ip, // 交易ip,支付提交的用户的ip地址
'trxChannel' => '03', // 交易渠道 01:手机APP
'tpAppId' => IcbcJftPay::$wechatAppletAppId,// 小程序appid
'tpOpenId' => $openid, // 小程序对应的openid
'autoConfirm' => '1', // 是否及时解冻 1 是 0 否 商户为担保模式有效 默认是0
'subOrders' => $subOrder // 子订单信息
];
return self::api($service_url, 'POST', $biz_content);
}
/**
* 订单查询
* @param $orderId string 订单号
* @return array
* @throws Exception
*/
public static function getOrder(string $orderId): array
{
$service_url = 'https://gw.open.icbc.com.cn/api/jft/api/pay/query/order/V1';
$biz_content = [
'appId' => self::$app_id,
'outOrderId' => $orderId, // 订单号
];
return self::api($service_url, 'POST', $biz_content);
}
/**
* 单笔退款
* @param string $subMerchantId
* @param string $orderId
* @param string $refundNum
* @param string $refundAmount
* @return void
* @throws Exception
*/
public static function refund(string $subMerchantId, string $orderId, string $refundNum, string $refundAmount, string $paltformAmount): void
{
$service_url = 'https://gw.open.icbc.com.cn/api/jft/api/pay/refund/accept/V1';
$server = explode(":", $_SERVER['HTTP_HOST']);
$biz_content = [
'appId' => self::$app_id,
'vendorId' => $subMerchantId,
'userId' => $subMerchantId,
'payType' => '01',
'orderId' => $orderId,
'refundId' => $refundNum,
'refundAmount' => $refundAmount,
'notifyUrl' => 'http://'.$server[0].self::$refundNotifyUrl,
'fixedCommissionOwn' => $paltformAmount,
'fixedCommissionOther' => $paltformAmount,
'fixedCommissionWx' => $paltformAmount,
'fixedCommissionZfb' => $paltformAmount,
'fixedCommissionDigital' => $paltformAmount,
'fixedCommission' => $paltformAmount
];
// dump($biz_content);
$result = self::api($service_url, 'POST', $biz_content);
// dump($result);die();
if ($result['return_code'] != '0') {
throw new Exception($result['return_msg']);
}
}
/**
* 单笔退款查询
* @param string $subMerchantId
* @param string $orderId
* @param string $refundNum
* @return array
* @throws Exception
*/
public static function queryRefund(string $subMerchantId, string $orderId, string $refundNum): array
{
$service_url = 'https://gw.open.icbc.com.cn/api/jft/api/pay/refund/query/V1';
$biz_content = [
'appId' => self::$app_id,
'vendorId' => $subMerchantId,
'payType' => '01',
'orderId' => $orderId,
'refundId' => $refundNum
];
$result = self::api($service_url, 'POST', $biz_content);
if ($result['return_code'] !== '0') {
throw new Exception($result['return_msg']);
}
return $result;
}
}
调用示例
<?php
$wechatApplet = A("WechatApplet");
$openid = $wechatApplet->get_wx_openid($wechatApplet->_wx_appid, $wechatApplet->_wx_secret, $code);
$prePay = IcbcPay::h5SinglePay($orderReturn['serialNumber'], $mchid, bcdiv($orderReturn['totalAmount'], 100, 2), $orderReturn['orderDesc'], $ip, $orderReturn['rate'], $openid['openid']);
$return['payData'] = json_decode($prePay['paySign'], true);
回调示例
public function icbcPayNotify(): void
{
vendor("IcbcPay/IcbcPay");
try {
// 接收数据
// 获取参数
$responseStr = urldecode(file_get_contents("php://input"));
if (!$responseStr) {
throw new Exception("接收不到参数");
}
addTxtLog("icbcPayNotifl", "工行聚富通支付回调原始数据头--->" . $responseStr);
// 验证签名
$resourceData = json_decode(IcbcPay::verifySign($responseStr), true);
// 验证状态
if ($resourceData['respCode'] != '00000' || $resourceData['orderStatus'] != '02') {
throw new Exception("支付状态不正确");
}
addTxtLog("icbcPayNotifl", "工行聚富通支付回调验签解析数据--->" . json_encode($resourceData));
// 防止回调运行时间长微信多次调用回调
$lock = F("appletCallbackLock_" . $resourceData['orderId']);
if (!$lock) {
F("appletCallbackLock_" . $resourceData['orderId'], 1);
// 判断订单类型
switch (substr($resourceData['orderId'], -2)) {
case 'yx':
$orderType = 3;
break;
case 'wo':
$orderType = 4;
break;
case 'xw':
$orderType = 5;
break;
default;
$orderType = 0;
}
if ($orderType == 0) {
throw new Exception("订单类型不对");
}
if ($resourceData['payMethod'] == '01') {
$payType = 'epay';
} else {
$payType = 'icbcPay';
}
// 发送消息
$this->sendMessage($resourceData['orderId'], $payType, 2, 1, $orderType, $this->judgeIsFormalData());
F("appletCallbackLock_" . $resourceData['orderId'], NULL);
addTxtLog("icbcPayNotifl", "工行聚富通支付回调数据成功");
echo IcbcPay::sign(0, 'success');
}
} catch (Exception $exception) {
$error = $exception->getMessage();
addTxtLog("icbcPayNotifl", "工行聚富通支付回调数据错误--->$error");
echo IcbcPay::sign(1, 'error');
}
}