最近因为项目重构的原因,对百度支付重新进行了编写封装,本次重写,添加了对签名的处理、添加用户退款,方便之后开发的使用。
因为百度电商开放平台的升级,支付功能已移至智能小程序内部,具体申请流程:百度收银台支付开通指引,(https://smartprogram.baidu.com/docs/operations/transform/pay/)
(注:在支付服务中,服务电话应填写银行预留手机号,如填写错误报【银行预留手机号码格式校验不通过】)
百度支付文档:百度收银台接口2.0(https://smartprogram.baidu.com/docs/develop/function/tune_up_2.0/)
一、申请通过后,填写百度支付相关配置:
$config = array( 'deal_id' => '', // 百度收银台的财务结算凭证 'app_key' => '', // 表示应用身份的唯一ID 'private_key' => '', // 私钥原始字符串 'public_key' => '', // 平台公钥 'notify_url' => '', // 支付回调地址 );
二、调用封装的支付方法,将返回信息,传递到百度小程序
<?php include './BaiduPay.php'; $baidupay = new fengBaiduPay($config); $order_sn = time().rand(1000,9999); $order = array( 'body' => '测试商品', // 产品描述 'total_amount' => '1', // 订单金额(分) 'order_sn' => $order_sn, // 订单编号 ); $re = $baidupay->xcxPay($order); die(json_encode($re)); // JSON化直接返回小程序客户端 PHP
小程序支付类 xcxPay:
/** * [xcxPay 百度小程序支付] * @param [type] $order [订单信息数组] * @return [type] [description] * $order = array( * 'body' => '', // 产品描述 * 'total_amount' => '', // 订单金额(分) * 'order_sn' => '', // 订单编号 * ); */ public static function xcxPay($order) { if(!is_array($order) || count($order) < 3) die("数组数据信息缺失!"); $config = self::$config; $requestParamsArr = array( 'appKey' => $config['app_key'], 'dealId' => $config['deal_id'], 'tpOrderId' => $order['order_sn'], 'totalAmount' => $order['total_amount'], ); $rsaSign = self::makeSign($requestParamsArr, $config['private_key']); // 声称百度支付签名 $bizInfo = array( 'tpData' => array( "appKey" => $config['app_key'], "dealId" => $config['deal_id'], "tpOrderId" => $order['order_sn'], "rsaSign" => $rsaSign, "totalAmount" => $order['total_amount'], "returnData" => '', "displayData" => array( "cashierTopBlock" => array( array( [ "leftCol" => "订单名称", "rightCol" => $order['body'] ], [ "leftCol" => "数量", "rightCol" => "1" ], [ "leftCol" => "订单金额", "rightCol" => $order['total_amount'] ] ), array( [ "leftCol" => "服务地址", "rightCol" => "北京市海淀区上地十街10号百度大厦" ], [ "leftCol" => "服务时间", "rightCol" => "2018/10/29 14:51" ], [ "leftCol" => "服务人员", "rightCol" => "百度App" ] ) ) ), "dealTitle" => $order['body'], "dealSubTitle" => $order['body'], "dealThumbView" => "https://b.bdstatic.com/searchbox/icms/searchbox/img/swan-logo.png", ), "orderDetailData" => '' ); $bdOrder = array( 'dealId' => $config['deal_id'], 'appKey' => $config['app_key'], 'totalAmount' => $order['total_amount'], 'tpOrderId' => $order['order_sn'], 'dealTitle' => $order['body'], 'signFieldsRange' => 1, 'rsaSign' => $rsaSign, 'bizInfo' => json_encode($bizInfo), ); return $bdOrder; }
三、百度智能小程序端的使用
SWAN
<view class="wrap"> <view class="card-area"> <button bind:tap="requestPolymerPayment" type="primary" hover-stop-propagation="true">支付0.01元</button> </view> </view> HTML
JS
Page({ requestPolymerPayment(e) { swan.request({ url: 'https://mbd.baidu.com/xxx', // 仅为示例,并非真实的接口地址,开发者从真实接口获取orderInfo的值 success: res => { res.data.data.dealTitle = '百度小程序Demo支付测试'; let data = res.data; if (data.errno !== 0) { console.log('create order err', data); return; } swan.requestPolymerPayment({ orderInfo: data.data, success: res => { swan.showToast({ title: '支付成功', icon: 'success' }); console.log('pay success', res); }, fail: err => { swan.showToast({ title: err.errMsg, icon: 'none' }); console.log('pay fail', err); } }); }, fail: err => { swan.showToast({ title: '订单创建失败', icon: 'none' }); console.log('create order fail', err); } }); } });
四、支付回调
<?php include './BaiduPay.php'; $baidupay = new fengBaiduPay($config); $re = $baidupay->notify(); if ($re) { // 这里回调处理订单操作 // 以验证返回支付成功后的信息,可直接对订单进行操作,已通知微信支付成功 $baidupay->success(); // 支付返还成功,通知结果 } else { // 支付失败 $baidupay->error(); // 支付失败,返回状态(无论支付成功与否都需要通知百度) }
百度完整支付类(BaiduPay.php),包含小程序支付、验签、回调、退款:
<?php /** * @Author: [FENG] <1161634940@qq.com> * @Date: 2020-09-27T16:28:31+08:00 * @Last Modified by: [FENG] <1161634940@qq.com> * @Last Modified time: 2020-10-15T10:23:07+08:00 */ namespace feng; class BaiduPay { private static $config = array( 'deal_id' => '', // 百度收银台的财务结算凭证 'app_key' => '', // 表示应用身份的唯一ID 'private_key' => '', // 私钥原始字符串 'public_key' => '', // 平台公钥 'notify_url' => '', // 支付回调地址 ); /** * [__construct 构造函数] * @param [type] $config [传递支付相关配置] */ public function __construct($config=NULL){ $config && self::$config = $config; } /** * [xcxPay 百度小程序支付] * @param [type] $order [订单信息数组] * @return [type] [description] * $order = array( * 'body' => '', // 产品描述 * 'total_amount' => '', // 订单金额(分) * 'order_sn' => '', // 订单编号 * ); */ public static function xcxPay($order) { if(!is_array($order) || count($order) < 3) die("数组数据信息缺失!"); $config = self::$config; $requestParamsArr = array( 'appKey' => $config['app_key'], 'dealId' => $config['deal_id'], 'tpOrderId' => $order['order_sn'], 'totalAmount' => $order['total_amount'], ); $rsaSign = self::makeSign($requestParamsArr, $config['private_key']); // 声称百度支付签名 $bizInfo = array( 'tpData' => array( "appKey" => $config['app_key'], "dealId" => $config['deal_id'], "tpOrderId" => $order['order_sn'], "rsaSign" => $rsaSign, "totalAmount" => $order['total_amount'], "returnData" => '', "displayData" => array( "cashierTopBlock" => array( array( [ "leftCol" => "订单名称", "rightCol" => $order['body'] ], [ "leftCol" => "数量", "rightCol" => "1" ], [ "leftCol" => "订单金额", "rightCol" => $order['total_amount'] ] ), array( [ "leftCol" => "服务地址", "rightCol" => "北京市海淀区上地十街10号百度大厦" ], [ "leftCol" => "服务时间", "rightCol" => "2018/10/29 14:51" ], [ "leftCol" => "服务人员", "rightCol" => "百度App" ] ) ) ), "dealTitle" => $order['body'], "dealSubTitle" => $order['body'], "dealThumbView" => "https://b.bdstatic.com/searchbox/icms/searchbox/img/swan-logo.png", ), "orderDetailData" => '' ); $bdOrder = array( 'dealId' => $config['deal_id'], 'appKey' => $config['app_key'], 'totalAmount' => $order['total_amount'], 'tpOrderId' => $order['order_sn'], 'dealTitle' => $order['body'], 'signFieldsRange' => 1, 'rsaSign' => $rsaSign, 'bizInfo' => json_encode($bizInfo), ); return $bdOrder; } /** * [refund baidu支付退款] * @param [type] $order [订单信息] * @param [type] $type [退款类型] * $order = array( * 'body' => '', // 退款原因 * 'total_amount' => '', // 退款金额(分) * 'order_sn' => '', // 订单编号 * 'access_token' => '', // 获取开发者服务权限说明 * 'order_id' => '', // 百度收银台订单 ID * 'user_id' => '', // 百度收银台用户 id * ); */ public static function refund($order=[], $type=1) { $config = self::$config; $data = array( 'access_token' => $order['access_token'], // 获取开发者服务权限说明 'applyRefundMoney' => $order['total_amount'], // 退款金额,单位:分。 'bizRefundBatchId' => $order['order_sn'], // 开发者退款批次 'isSkipAudit' => 1, // 是否跳过审核,不需要百度请求开发者退款审核请传 1,默认为0; 0:不跳过开发者业务方审核;1:跳过开发者业务方审核。 'orderId' => $order['order_id'], // 百度收银台订单 ID 'refundReason' => $order['body'], // 退款原因 'refundType' => $type, // 退款类型 1:用户发起退款;2:开发者业务方客服退款;3:开发者服务异常退款。 'tpOrderId' => $order['order_sn'], // 开发者订单 ID 'userId' => $order['user_id'], // 百度收银台用户 id ); $array = ['errno'=>0, 'msg'=>'success', 'data'=> ['isConsumed'=>2] ]; $url = 'https://openapi.baidu.com/rest/2.0/smartapp/pay/paymentservice/applyOrderRefund'; $response = self::post_curl($url, $data); $result = json_decode($response, true); // // 显示错误信息 // if ($result['msg']!='success') { // return false; // // die($result['msg']); // } return $result; } /** * [notify 回调验证] * @return [array] [返回数组格式的notify数据] */ public static function notify() { $data = $_POST; // 获取xml $config = self::$config; if (!$data || empty($data['rsaSign'])) die('暂无回调信息'); $result = self::checkSign($data, $config['public_key']); // 进行签名验证 // 判断签名是否正确 判断支付状态 if ($result && $data['status']==2) { return $data; } else { return false; } } /** * [success 通知支付状态] */ public static function success() { $array = ['errno'=>0, 'msg'=>'success', 'data'=> ['isConsumed'=>2] ]; die(json_encode($array)); } /** * [error 通知支付状态] */ public static function error() { $array = ['errno'=>0, 'msg'=>'success', 'data'=> ['isErrorOrder'=>1, 'isConsumed'=>2] ]; die(json_encode($array)); } /** * [makeSign 使用私钥生成签名字符串] * @param array $assocArr [入参数组] * @param [type] $rsaPriKeyStr [私钥原始字符串,不含PEM格式前后缀] * @return [type] [签名结果字符串] */ public static function makeSign(array $assocArr, $rsaPriKeyStr) { $sign = ''; if (empty($rsaPriKeyStr) || empty($assocArr)) { return $sign; } if (!function_exists('openssl_pkey_get_private') || !function_exists('openssl_sign')) { throw new Exception("openssl扩展不存在"); } $rsaPriKeyPem = self::convertRSAKeyStr2Pem($rsaPriKeyStr, 1); $priKey = openssl_pkey_get_private($rsaPriKeyPem); if (isset($assocArr['sign'])) { unset($assocArr['sign']); } ksort($assocArr); // 参数按字典顺序排序 $parts = array(); foreach ($assocArr as $k => $v) { $parts[] = $k . '=' . $v; } $str = implode('&', $parts); openssl_sign($str, $sign, $priKey); openssl_free_key($priKey); return base64_encode($sign); } /** * [checkSign 使用公钥校验签名] * @param array $assocArr [入参数据,签名属性名固定为rsaSign] * @param [type] $rsaPubKeyStr [公钥原始字符串,不含PEM格式前后缀] * @return [type] [验签通过|false 验签不通过] */ public static function checkSign(array $assocArr, $rsaPubKeyStr) { if (!isset($assocArr['rsaSign']) || empty($assocArr) || empty($rsaPubKeyStr)) { return false; } if (!function_exists('openssl_pkey_get_public') || !function_exists('openssl_verify')) { throw new Exception("openssl扩展不存在"); } $sign = $assocArr['rsaSign']; unset($assocArr['rsaSign']); if (empty($assocArr)) { return false; } ksort($assocArr); // 参数按字典顺序排序 $parts = array(); foreach ($assocArr as $k => $v) { $parts[] = $k . '=' . $v; } $str = implode('&', $parts); $sign = base64_decode($sign); $rsaPubKeyPem = self::convertRSAKeyStr2Pem($rsaPubKeyStr); $pubKey = openssl_pkey_get_public($rsaPubKeyPem); $result = (bool)openssl_verify($str, $sign, $pubKey); openssl_free_key($pubKey); return $result; } /** * [convertRSAKeyStr2Pem 将密钥由字符串(不换行)转为PEM格式] * @param [type] $rsaKeyStr [原始密钥字符串] * @param integer $keyType [0 公钥|1 私钥,默认0] * @return [type] [PEM格式密钥] */ public static function convertRSAKeyStr2Pem($rsaKeyStr, $keyType = 0) { $pemWidth = 64; $rsaKeyPem = ''; $begin = '-----BEGIN '; $end = '-----END '; $key = ' KEY-----'; $type = $keyType ? 'RSA PRIVATE' : 'PUBLIC'; $keyPrefix = $begin . $type . $key; $keySuffix = $end . $type . $key; $rsaKeyPem .= $keyPrefix . "n"; $rsaKeyPem .= wordwrap($rsaKeyStr, $pemWidth, "n", true) . "n"; $rsaKeyPem .= $keySuffix; if (!function_exists('openssl_pkey_get_public') || !function_exists('openssl_pkey_get_private')) { return false; } if ($keyType == 0 && false == openssl_pkey_get_public($rsaKeyPem)) { return false; } if ($keyType == 1 && false == openssl_pkey_get_private($rsaKeyPem)) { return false; } return $rsaKeyPem; } /** * curl post请求 * @param string $url 地址 * @param string $postData 数据 * @param array $header 头部 * @return bool|string * @Date 2020/9/17 17:12 * @Author wzb */ public static function post_curl($url='',$postData='',$header=[]){ $ch = curl_init($url); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_HEADER, false); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5000); curl_setopt($ch, CURLOPT_TIMEOUT, 5000); if($header){ curl_setopt($ch, CURLOPT_HTTPHEADER,$header); } curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); $result = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlErrNo = curl_errno($ch); $curlErr = curl_error($ch); curl_close($ch); return $result; } }