开通支付功能(对着文档看就行,无雷点)
OpenSSL下载地址:Win32/Win64 OpenSSL Installer for Windows - Shining Light Productions
生成的pem文件有3个,复制公钥时只需要复制字符串!
申请审核通过后就能看到支付配置信息,红框内的参数在发起支付时会用到:
前端代码:
//请使用自己的请求函数
request({
url: `pay.php`,
data: {
"order_id": id.value,
},
method: 'POST',
needLoading: true
}).then(v => {
swan.requestPolymerPayment({
orderInfo:v.orderInfo,
success: function() {
console.log('支付成功')
},
fail: function(res) {
console.log(res)
console.log('支付失败')
}
})
}).catch(e => {
console.log(e)
onsole.log('支付失败')
})
后端代码:(非拷贝即用!只提供正确的代码传参及调用格式供避坑)
applet_info.php
<?php
//小程序的配置,在小程序管理中心,基本设置里有
$app_id = "1xxxxxxx";
$app_key = "fxxxxxxxxxxxxxxx";
$app_secret = "Mxxxxxxxxxxxxxxxxxxxxxx";
//支付配置,在小程序管理中心,功能管理-支付管理里有
$dealId = "3xxxxx";
$app_id_pay = "6xxxxxxxx";
$app_key_pay = "Mxxxxxxxxx";
$platform_public_key = "xxxxxxxxxxxxxxxxx";
//自己的公钥,在上面生成的文件里有,复制下来,只要字符串!
$public_key = "Mxxxxxxxxxxxxxxxxxxxxx";
//自己的私钥,在上面生成的文件里有,复制下来,只要字符串!
$private_key = "Mxxxxxxxxxxxxxxxxx";
?>
sign.php(就是拷贝的官方的)
<?php
// 通用签名工具,基于openssl扩展,提供使用私钥生成签名和使用公钥验证签名的接口
class RSASign
{
/**
* @desc 使用私钥生成签名字符串
* @param array $assocArr 入参数组
* @param string $rsaPriKeyStr 私钥原始字符串,不含PEM格式前后缀
* @return string 签名结果字符串
* @throws Exception
*/
public static function sign(array $assocArr, $rsaPriKeyStr)
{
$sign = '';
// var_dump($assocArr);
// echo $rsaPriKeyStr;
// exit;
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);
}
/**
* @desc 使用公钥校验签名
* @param array $assocArr 入参数据,签名属性名固定为rsaSign
* @param string $rsaPubKeyStr 公钥原始字符串,不含PEM格式前后缀
* @return bool true 验签通过|false 验签不通过
* @throws Exception
*/
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;
}
/**
* @desc 将密钥由字符串(不换行)转为PEM格式
* @param string $rsaKeyStr 原始密钥字符串
* @param int $keyType 0 公钥|1 私钥,默认0
* @return string PEM格式密钥
* @throws Exception
*/
public static function convertRSAKeyStr2Pem($rsaKeyStr, $keyType = 0)
{
$pemWidth = 64;
$rsaKeyPem = '';
$begin = '-----BEGIN ';
$end = '-----END ';
$key = ' KEY-----';
$type = $keyType ? '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;
}
}
pay.php
<?php
date_default_timezone_set('prc');
$price = 0;
//自行获取前端上传的数据
$pay_type = $_POST['pay_type '];
$rootPath = $_SERVER['DOCUMENT_ROOT'];
//自行获取price价格
$price = (float)$price;
include 'applet_info.php';
include 'sign.php';
if($price === 0){
$json = json_encode(array(
"resultCode"=>500,
"message"=>'价格错误',
),256);
exit($json);
}
//以分为单位
$totalAmount = $price * 100;
//自行上传参数
$rsaSign = RSASign::sign(
array(
"dealId"=>$dealId,
"appKey"=>$app_key_pay,
"totalAmount"=>$totalAmount,
"tpOrderId"=>$trade_no,
), $private_key
);
if(!$rsaSign){
echo json_encode(array("resultCode"=>800,"message"=>"签名失败"));
exit;
}
//自行上传参数
$bizInfo = array(
"tpData"=>array(
"returnData"=>array(
"order_id"=>$order_id,
"user_id"=>$user_id,
)
)
);
$bizInfo = json_encode($bizInfo,256);
//自行上传参数
$orderInfo = array(
"dealId"=>$dealId,
"appKey"=>$app_key_pay,
"totalAmount"=>$totalAmount,
"tpOrderId"=>$trade_no,
"dealTitle"=>$title,
"signFieldsRange"=>"1",
"rsaSign"=>$rsaSign,
"payResultUrl"=>"/pages/index/index",
"bizInfo"=>"$bizInfo"
);
$array = array(
"resultCode"=>200,
"message"=>'ok',
"orderInfo"=> $orderInfo,
"trade_no"=>$trade_no
);
echo json_encode($array,256);
?>
生成orderInfo成功,前端就会打开百度收银台:
支付通知回调:
notify.php
<?php
date_default_timezone_set('prc');
// 日志记录函数
function logMessage($message) {
$logFile = __DIR__ . '/callback.log';
file_put_contents($logFile, date('Y-m-d H:i:s') . " - $message\n", FILE_APPEND);
}
$rootPath = $_SERVER['DOCUMENT_ROOT'];
include 'sign.php';
include 'applet_info.php';
$out_trade_no = null;
// 更新订单状态函数
function updateOrderStatus($responseArr) {
$time = date('Y-m-d H:i:s');
$out_trade_no = $responseArr['tpOrderId'];
$root = $_SERVER['DOCUMENT_ROOT'];
$msg_json = json_encode($responseArr);
$returnData = json_decode($responseArr['returnData'],true);
$order_id = $returnData['order_id'];
$userId = $responseArr['userId'];
$orderId = $responseArr['orderId'];
$timestamp = $responseArr['payTime'];
if($responseArr['status'] == 2){ //已支付
echo json_encode(["errno" => 0, "msg" => "success","data"=>["isConsumed"=>2]]);
//订单金额
$price = (float)$responseArr['totalMoney'] / 100;
//实际支付金额
$pay_price = (float)$responseArr['payMoney'] / 100;
//你的支付成功逻辑
//你的支付成功逻辑
//请注意把orderId,userId等参数保存起来,(怎么实现都行)退款时会用到
$sql_bd = "insert into xxxxxxxxxxxxxxxx";
mysqli_query($sql_bd);
// }
}else{
logMessage('订单支付失败:'. $out_trade_no . " - order_id: $order_id" . " - orderId: $orderId");
echo json_encode(["errno" => 0, "msg" => "fail:not paid","data"=>["isConsumed"=>1]]);
}
}
// 主逻辑
if (!$_POST || empty($_POST)) {
echo "Invalid request";
logMessage('Invalid request:'. json_encode($_POST));
exit;
}
$checkSignResult = RSASign::checkSign($_POST,$platform_public_key);
if(!$checkSignResult){
echo "Invalid sign";
logMessage('订单支付回调签名验证失败:'. json_encode($_POST));
exit;
}
$root = $_SERVER['DOCUMENT_ROOT'];
//连接数据库省略
//连接数据库省略
header('Content-Type: application/json');
updateOrderStatus($_POST);
?>
发起退款
发起请求要以表单形式,非json,加上这一句:curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
refund.php
<?php
date_default_timezone_set("Asia/Shanghai");
$rootPath = $_SERVER['DOCUMENT_ROOT'];
include 'applet_info.php';
function logMessage($message) {
$logFile = __DIR__ . '/callback_refund.log';
file_put_contents($logFile, date('Y-m-d H:i:s') . " - $message\n", FILE_APPEND);
}
$ch = curl_init();
$url = "https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id=$app_key&client_secret=$app_secret&scope=smartapp_snsapi_base";
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$response = json_decode($response,true);
$access_token = $response['access_token'];
// 设置请求的 URL
$url = 'https://openapi.baidu.com/rest/2.0/smartapp/pay/paymentservice/applyOrderRefund';
if(!$myRefundPrice){
$json = json_encode(array(
"resultCode"=>925,
"message"=>'退款金额不正确',
),256);
exit($json);
}
if($id){
$id = $id;
}else if($order_id){
$id = $order_id;
}
//取出保存的userId,orderId(每个人实现方式不同,都行,反正这里要用到userId,orderId)
$sql_bd = "select userId,orderId,price from xxxxxx where xxxxxxxx";
$rs_bd = mysql_query($sql_bd);
if(!$rs_bd){
$array = array(
"resultCode"=>926,
"message"=>'错误:检索订单支付信息超时,请联系客服',
);
$json = json_encode($array,256);
echo $json;
logMessage("退款失败:$id 检索订单支付信息超时");
exit;
}
if(mysql_num_rows($rs_bd) == 0){
$array = array(
"resultCode"=>930,
"message"=>'错误:订单支付信息不存在,请联系客服',
);
$json = json_encode($array,256);
echo $json;
logMessage("退款失败:$id 订单支付信息不存在");
exit;
}
$row_bd = mysql_fetch_assoc($rs_bd);
$bd_orderId = $row_bd['orderId']; //百度收银台订单id
$bd_userId = $row_bd['userId'];
$bd_price = $row_bd['price'];
$refund_amount = $myRefundPrice; //以分为单位
//有部分退款需求,所以这里做了区分
if($refund_amount < $bd_price){
$refundType = 2;
}else{
$refundType = 1;
}
$data = [
"access_token" => $access_token,
"applyRefundMoney" => $refund_amount,
"bizRefundBatchId"=>$refund_no,
"isSkipAudit" =>0,
"orderId" => $bd_orderId, //百度收银台订单id
"refundReason"=>"取消订单退款",
"refundType"=>$refundType, //用户退款
"refundNotifyUrl" => "https://xxxx/notify_refund.php",
"tpOrderId"=> $myTradeNo, //开发者订单id
"userId"=> $bd_userId, //收银台用户ID
"pmAppKey"=> $app_key_pay,
];
logMessage("data: ".json_encode($data,256));
// 将数组转换为 JSON 格式
// $jsonData = json_encode($data);
// 设置 cURL 选项
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
//请注意一定要用表单方式提交,百度不支持json格式
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// 执行 cURL 会话
$response = curl_exec($ch);
// 检查是否有错误发生
if (curl_errno($ch)) {
$error_msg = curl_error($ch);
}
// 关闭 cURL 会话
curl_close($ch);
//你的逻辑
//插入退款记录等
//你的逻辑
logMessage("response: $response");
// 如果有错误,输出错误信息
if (isset($error_msg)) {
echo 'Curl error: ' . $error_msg;
}
?>
退款审核(注意百度与其他支付不同点有一个退款审核机制)
notify_refund_check.php
<?php
date_default_timezone_set('prc');
// 日志记录函数
function logMessage($message) {
$logFile = __DIR__ . '/callback_refund_check.log';
file_put_contents($logFile, date('Y-m-d H:i:s') . " - $message\n", FILE_APPEND);
}
$rootPath = $_SERVER['DOCUMENT_ROOT'];
include 'applet_info.php';
include 'sign.php';
// 主逻辑
if (!$_POST || empty($_POST)) {
echo "Invalid request";
exit;
}
$checkSignResult = RSASign::checkSign($_POST,$platform_public_key);
if(!$checkSignResult){
echo "Invalid sign";
exit;
}
$applyRefundMoney = $_POST['applyRefundMoney'];
echo json_encode(["errno" => 0, "msg" => "success","data"=>[
"auditStatus"=>1,
"calculateRes"=>array(
"refundPayMoney"=>$applyRefundMoney,
)
]]);
?>
退款通知回调(和支付通知回调基本一致)
notify_refund.php
<?php
date_default_timezone_set('prc');
// 日志记录函数
function logMessage($message) {
$logFile = __DIR__ . '/callback_refund.log';
file_put_contents($logFile, date('Y-m-d H:i:s') . " - $message\n", FILE_APPEND);
}
$rootPath = $_SERVER['DOCUMENT_ROOT'];
include 'sign.php';
include 'applet_info.php';
$out_trade_no = null;
// 更新订单状态函数
function refund_notify($responseArr) {
$time = date('Y-m-d H:i:s');
$out_trade_no = $responseArr['tpOrderId'];
$refund_no = $responseArr['refundBatchId'];
$orderId = $responseArr['orderId'];
$root = $_SERVER['DOCUMENT_ROOT'];
$msg_json = json_encode($responseArr);
if($responseArr['refundStatus'] != 1){
//退款失败
exit;
}
//退款成功更新状态
}
// 主逻辑
if (!$_POST || empty($_POST)) {
echo "Invalid request";
exit;
}
$checkSignResult = RSASign::checkSign($_POST,$platform_public_key);
if(!$checkSignResult){
echo "Invalid sign";
exit;
}
$root = $_SERVER['DOCUMENT_ROOT'];
//连接数据库
//连接数据库
header('Content-Type: application/json');
refund_notify($_POST);
echo json_encode(["errno" => 0, "msg" => "success","data"=>[]]);
?>