本人针对字节调动小程序的官方开发文档真的无力吐槽,文档真的简陋。而且文档有错别字。槽点有点多。
头条因为没有自己的支付渠道,所以使用的是支付宝,利用tt.requestPayment()调起支付宝APP支付:
支付具体流程为:
一、后端通过openid和自己这边的订单号生成一个头条的订单号,具体操作可以看文档流程
二、生成调用支付宝的一个字符串,这里需要使用支付宝文档,我使用的支付宝SDK,这边支付宝文档很好的,网上资料很多。
三、最后就是组装后返回给前端调用支付宝。特别注意的是这里使用的所有appid等参数都是头条分配给商户的appid等。
首先我们通过代码来实现字节跳动小程序支付单号(trade_no)的生成。我这里用的是PHP代码,通过文档,对参数进行处理。
//这里是真的回调地址进行的简单处理
$http_type = ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')) ? 'https://' : 'http://';
$website = $http_type . $_SERVER['HTTP_HOST'];
$risk_info = $this->getRealIp(); //获取用户真实IP(我在这里写了一个获取IP的方法)
$payload['app_id'] = $tt_pay_app_id; //头条支付分配给业务方的ID(不是头条小程序的appid)
$app_secret = $tt_pay_secret_key; //头条支付分配给业务方的支付秘钥
$payload['charset'] = "utf-8"; // 请求使用的编码格式
$payload['method'] = "tp.trade.create"; // 接口名称
$payload['timestamp'] = time(); // 发送请求的时间
$biz_content = [
"out_order_no" => $data['out_trade_no'], //商户订单号
"uid" => $data['openid'], //唯一标识用户open_id
"merchant_id" => $tt_merchant_id, //头条支付分配给业务方的商户号
"total_amount" => $data['fee'] * 100, //金额,分为单位,应传整型
"currency" => "CNY", //币种
"subject" => $data['subject'], //商户订单名称
"body" => $data['body'], //商户订单详情
"trade_time" => time(), //下单时间戳
"valid_time" => "60", //订单有效时间 单位:秒
"notify_url" => $website . '/payment/toutiao/notify.php', //服务器异步通知http地址
"risk_info" => json_encode(['ip' => $risk_info]), //用户的真实ip
]; // 请求参数的集合 json
$payload['biz_content'] = json_encode($biz_content);
$payload['sign_type'] = "MD5"; //头条采用的是MD5加密
$payload['format'] = "json";
$payload['version'] = "1.0";
$stringToBeSigned = $this->getSignContent($payload , $payload['charset'] , $app_secret); //这里写了一个签名的方法
$payload["sign"] = md5($stringToBeSigned);
$url = "https://tp-pay.snssdk.com/gateway"; // 请求地址正式环境
$result = ihttp_post($url, $payload);
$result = json_decode($result['content'], true);
$resultCode = $result['response']['code'];
if (!empty($resultCode) && $resultCode == 10000) {
//后面会针对这一块进行处理
//这里需要拼装参数请求支付宝APP支付
return ['error'=>0 , 'msg'=>'success' , 'res'=>$result];
} else {
return ['error'=>-1 , 'msg'=>'failed' , 'res'=>$result];
}
当我们点击点单支付按钮的时候,将所需要的参数传至以上接口方法中进行处理,能够正常返回头条的trade_no。
返回结果如下:
接下来就是上述方法中的两个方法的调用,一个使用户IP地址的获取,一个是头条签名的处理方法。重点是后面一个。
先看用户IP地址的获取方法,如下:
/**
* 获取用户真实IP
* @return bool
*/
public function getRealIp() {
$ip = false;
if (!empty($_SERVER["HTTP_CLIENT_IP"])) {
$ip = $_SERVER["HTTP_CLIENT_IP"];
}
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ips = explode(", ", $_SERVER['HTTP_X_FORWARDED_FOR']);
if ($ip) {
array_unshift($ips, $ip);
$ip = FALSE;}
for ($i = 0; $i < count($ips); $i++) {
if (!eregi("^(10│172.16│192.168).", $ips[$i])) {
$ip = $ips[$i];
break;
}
}
}
return ($ip ? $ip : $_SERVER['REMOTE_ADDR']);
}
该方法不做过多描述,接下来是重点。
通过文档可以看出,我们要先对请求参数去除空值和不参与签名的字段,然后进行排序。代码如下:
/**
* 签名处理
* @param $params
* @param $charset
* @return string
*/
public function getSignContent($params , $charset,$app_secret) {
ksort($params);
$stringToBeSigned = "";
$i = 0;
foreach ($params as $k => $v) {
if (false === $this->checkEmpty($v) && "@" != substr($v, 0, 1)) {
// 转换成目标字符集
$v = $this->characet($v, $charset);
if ($i == 0) {
$stringToBeSigned .= "$k" . "=" . "$v";
} else {
$stringToBeSigned .= "&" . "$k" . "=" . "$v";
}
$i++;
}
}
$stringToBeSigned = $stringToBeSigned.$app_secret;
unset ($k, $v);
return $stringToBeSigned;
}
/**
* 校验$value是否非空
* @param $value
* @return boolean;
* if not set ,return true;
* if is null , return true;
**/
public function checkEmpty($value) {
if (!isset($value))
return true;
if ($value === null)
return true;
if (trim($value) === "")
return true;
return false;
}
/**
* 转换字符集编码
* @param $data
* @param $targetCharset
* @return string
*/
public function characet($data, $targetCharset) {
if (!empty($data)) {
$fileType = "UTF-8";
if (strcasecmp($fileType, $targetCharset) != 0) {
$data = mb_convert_encoding($data, $targetCharset, $fileType);
}
}
return $data;
}
通过以上方法我们已经能够获取到正常的响应数据了。就像这样
{
"response":{
"code":"10000",
"msg":"Success",
"trade_no":"1004130000127421"
},
"sign":"ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"
}
但是,接下来问题来了。我们需要再次的拼装参数,去请求支付宝APP支付。然后将数据返回给前端,通过前端拉起支付宝收银台。
但是这一块,官方文档写的很… … 以至于,本小白看的是一脸懵。
我在正确获取trade_no订单号的时候,做了以下处理:
关于这一块的参数,可以参考文档:http://developer.toutiao.com/docs/open/requestPayment.html
//在这里是要将数据返回给前端,通过前端拉起支付宝收银台
$data2 = [];
$data2['trade_no'] = $result['response']['trade_no']; //刚刚获取到的trade_no订单号
$data2['app_id'] = $payload['app_id']; //头条支付分配给业务方的ID
$data2['sign_type'] = $payload['sign_type']; //头条采用的MD5
$data2['timestamp'] = $payload['timestamp']; //发送请求的时间
$data2['merchant_id'] = $biz_content['merchant_id']; //头条支付分配给业务方的商户号
$data2['uid'] = $biz_content['uid']; //唯一标识用户open_id(这个是头条的)
$data2['total_amount'] = $biz_content['total_amount']; //金额,分为单位,应传整型
$data2['params'] = json_encode(['url' => $this->aliapppaytest($data , $biz_content , $data2 , $website , $config)]); //这里是支付宝APP支付的请求参数,我在这里使用了支付宝的SDK
$stringToBeSigned = $this->getSignContent($data2, $payload['charset'], $app_secret); //将以上参数再次进行签名处理
$data2["sign"] = md5($stringToBeSigned);
$data2['pay_channel'] = "ALIPAY_NO_SIGN";
$data2["pay_type"] = "ALIPAY_APP";
$data2['method'] = 'tp.trade.confirm';
$data2['risk_info'] = $biz_content['risk_info'];
return ['error'=>0 , 'msg'=>'success' , 'res'=>$data2]; // 将这些参数,返回给前端处理就可以了
主要是参数params中的url处理
这里使用了支付宝的SDK,如下:
public function aliapppaytest($data , $biz_content , $data2 , $website , $config){
require dirname(__FILE__) .'/../library/alipay/AopSdk.php';
$aop = new \AopClient();
$aop->gatewayUrl = "https://openapi.alipay.com/gateway.do";
$aop->appId = $config['ali_app_app_id']; //支付宝APPID
$aop->rsaPrivateKey = $config['ali_app_rsa_pri_key']; //支付宝秘钥
$aop->format = "json";
$aop->charset = "UTF-8";
$aop->signType = "RSA2";
$aop->alipayrsaPublicKey = $config['ali_app_pay_rsa_pub_key']; //支付宝公钥
//实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
$request = new AlipayTradeAppPayRequest(); //SDK已经封装掉了公共参数,这里只需要传入业务参数
$data2 = [
'body' => $data['body'],
'subject' => $data['subject'],
'out_trade_no' => $data2['trade_no'],
'timeout_express' => '30m',
'total_amount' => $data['fee'],
'product_code' => 'QUICK_MSECURITY_PAY',
];
$postdata = json_encode($data2);
$request->setNotifyUrl($website . '/payment/toutiao/notify.php'); //服务器异步通知http地址);
$request->setBizContent($postdata);
$response = $aop->sdkExecute($request); //这里和普通的接口调用不同,使用的是sdkExecute
//htmlspecialchars是为了输出到页面时防止被浏览器将关键参数html转义,实际打印到日志以及http传输不会有这个问题
// return htmlspecialchars($str);//就是orderString 可以直接给客户端请求,无需再做处理。
return $response;//就是orderString 可以直接给客户端请求,无需再做处理。
}
到这里,整个支付阶段就已经结束了。此时,前端拉起支付宝收银台进行付款,支付成功之后,关于回调的处理,其实就是支付宝本身的回调处理了。直接$_POST接收数据,通过支付宝验签处理就可以了。详情,参考PHP 支付宝小程序 支付以及回调处理
关于唤起支付页面报错的问题,请参考支付宝APP支付:
ALI40247-自查方案:https://openclub.alipay.com/read.php?tid=250&fid=60
ALI38173-排查方案:https://openclub.alipay.com/read.php?tid=3546&fid=60
ALIN10146-自查方案:https://openclub.alipay.com/read.php?tid=6918&fid=60
以上欢迎各位指点。