最近因為項目重構的原因,對百度支付重新進行了編寫封裝,本次重寫,添加了對簽名的處理、添加用戶退款,方便之后開發的使用。
因為百度電商開放平臺的升級,支付功能已移至智能小程序內部。
具體申請流程:百度收銀臺支付開通指引,(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 \feng\BaiduPay($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化直接返回小程序客戶端
小程序支付類 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>
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 \feng\BaiduPay($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; } }