1.为什么要写这篇文章
参考了字节跳动官方的文档之后发现写的太简单,完全一头雾水摸不清头脑,后来在百度了别人的实现方案,才得以总结出来。
2.背景
我司要开发一个头条小程序,需要支持支付宝和微信支付
3.签约以及配置流程(不过多描述)
4.业务代码
一开始看了字节跳动的官方文档tt.pay确实是没怎么看懂(可能是能力问题),而且demo中也没有php的案例,让phper实在是为难,后来百度了一下,发现广大的网友还是很强大的,总结了一下实现的方式:
第一种
- 第一步:获取预支付单号
- 第二步:获取支付宝APP支付返回的orderInfo
- 第三步:生成字节跳动小程序支付请求参数
但是这种我在官方文档中并没有发现这中实现方式,可能是文档更新了(疑问疑问??)
<?php
/**
* Created by PhpStorm.
* User: langziqiang
* Date: 2020-05-22
* Time: 10:43
*/
namespace Common\Service;
use http\Exception;
class TouTiaoService{
private $tt_config; //头条小程序配置
private $order; //订单信息
private $user; //用户信息
private $ip; //用户ip,调用预支付单号需要传
private $tt_trade_no; //支付预支付单号
private $ali_orderinfo; //支付宝app支付orderInfo
const PRE_PAY_NUMBER_URL = 'https://tp-pay.snssdk.com/gateway';
public function __construct($order, $uid, $ip){
$this->tt_config = C('ZIJIETIAODONG');
$this->order = $order;
$this->user = M('Users')->find($uid);
$this->ip = $ip;
}
/**
* Notes:入口
* User: langziqiang
* Date: 2020-05-22
* Time: 14:28
* @return array
*/
public function getTouTiaoAliPayResult(){
//第一步:获取预支付单号
$this->createPrePayNumber();
//第二步:获取支付宝APP支付返回的orderInfo
$this->createAliAppPay();
//第三步:生成字节跳动小程序支付请求参数
$res = $this->createProgramData();
return $res;
}
/**
* Notes:获取预支付单号
* User: langziqiang
* Date: 2020-05-22
* Time: 11:04
*/
public function createPrePayNumber(){
//业务数据
$biz_content = array(
'out_order_no' => date("YmdHi") . intval(microtime() * 1000) . $this->user['uid'], //商户唯一订单号
'uid' => 'openid', //头条用户openid
'merchant_id' => $this->tt_config['merchant_id'], //字节跳动小程序支付中的商户id
'total_amount' => $this->order['all_money'] * 100, //订单金额
'currency' => 'CNY', //固定CNY
'subject' => $this->user['company'] . "的账单", //商户订单名称
'body' => $this->user['company'] . "的账单", //商户订单详情
'trade_time' => time(), //下单时间戳,精确到秒
'valid_time' => '120', //订单有效时间(单位 秒)
'notify_url' => get_ali_app_notify(), //填任意非空 URL 即可
'risk_info' => json_encode(['ip' => $this->ip]) // 输出示例: "{\"ip\":\"110.90.28.70\"}",
);
$biz_content = json_encode($biz_content);
//公共请求参数
$params['app_id'] = $this->tt_config['app_id']; //头条appid
$params['biz_content'] = $biz_content; //业务数据json
$params['method'] = 'tp.trade.create'; //注意:此处固定值 tp.trade.create
$params['charset'] = 'utf-8'; //定值
$params['sign_type'] = 'MD5'; //定值
$params['timestamp'] = time(); //当前时间戳
$params['version'] = 2.0; //定值
$this->getSignContent($params);
//注意post的参数必须是a=1&b=2这种格式,官方文档并没有说明
$res = Http::curl_post2(self::PRE_PAY_NUMBER_URL, http_build_query($params));
$res = json_decode($res, true);
$res = $res['response'];
if($res['code'] == '10000'){
$this->tt_trade_no = $res['trade_no']; //头条预支付单号
}else{
E("支付失败!");
}
}
/**
* Notes:创建支付宝app支付
* User: langziqiang
* Date: 2020-05-22
* Time: 14:23
*/
public function createAliAppPay(){
$alipay = new AliPayService();
$alipay->order_id = $this->order['order_id'];
$alipay->order = $this->order;
$orderInfo = $alipay->aliAppPay($this->order);
$this->ali_orderinfo = $orderInfo;
}
/**
* Notes:组装小程序唤起参数
* User: langziqiang
* Date: 2020-05-22
* Time: 14:23
* @return array
*/
public function createProgramData(){
$res = [];
$res['app_id'] = $this->tt_config['app_id']; //字节跳动小程序app_id
$res['sign_type'] = 'MD5'; //定值
$res['uid'] = 'open_id'; //字节跳动小程序open_id
$res['total_amount'] = $this->order['all_money'] * 100; //金额
$res['timestamp'] = (string)time(); //当前时间戳
$res['trade_no'] = $this->tt_trade_no; //注意:这里为字节跳动小程序返回的预支付单号
$res['merchant_id'] = $this->tt_config['merchant_id']; //字节跳动商户号
$res['params'] = json_encode(['url' => $this->ali_orderinfo]); //特殊parme,json
$this->getSignContent($res); //sign
$res['method'] = 'tp.trade.confirm'; //注意:这里于获取预支付单号不同,固定为 tp.trade.confirm
$res['pay_channel'] = 'ALIPAY_NO_SIGN'; //定值
$res['pay_type'] = "ALIPAY_APP"; //定值
$res['risk_info'] = $this->ip; //ip
$res['url'] = $this->ali_orderinfo; //支付宝链接
return $res;
}
/**
* 签名处理
* @param $params
* @param $charset
* @return string
*/
public function getSignContent(&$params, $charset = 'utf-8'){
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 . $this->tt_config['secret'];
unset ($k, $v);
$params['sign'] = md5($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;
}
}
第二种
- 直接返回orderInfo给前端
public function getTouTiaoAliPayResult2(){
$data = [
'merchant_id' => $this->tt_config['merchant_id'], //头条商户id
'app_id' => $this->tt_config['app_id'], //头条appId
'sign_type' => 'MD5', //定值
'out_order_no' => date("YmdHi") . intval(microtime() * 1000) . $this->user['uid'], //商家订单号
'timestamp' => time(), //时间戳
'version' => '2.0', //定值
'trade_type' => 'H5', //定值
'product_code' => 'pay', //定值
'payment_type' => 'direct', //定值
'uid' => 'open_id', //头条openid
'total_amount' => intval($this->order['all_money'] * 100), //订单金额
'currency' => 'CNY', //定值
'subject' => $this->user['company'] . "的账单", //商品名称
'body' => $this->user['company'] . "的账单", //商品详情
'trade_time' => time(), //下单时间戳
'valid_time' => 120, //订单有效时间(单位 秒)
'notify_url' => get_ali_app_notify(), //回调地址
'risk_info' => json_encode(['ip' => $this->ip]), //用户ip
];
$data['alipay_url'] = $this->createAliAppPay($data); //支付宝url
$this->getSignContent($data, 'utf-8'); //签名
dump($data);
}
小程序前段代码(针对第二种实现方案)
$.ajax({
url: "后台接口",
method: "POST",
data: {
cid: that.data.info.id
},
success(r) {
tt.pay({
orderInfo: r.data,
service: 1,
getOrderStatus(res) {
let { out_order_no } = res;
return new Promise(function (resolve, reject) {
// 商户前端根据 out_order_no 请求商户后端查询微信支付订单状态
tt.request({
url: "https://www.xxxxx.com/api/v1/wechat/notify",
data: { out_order_no: out_order_no },
method: 'POST',
success(res) {
// 商户后端查询的微信支付状态,通知收银台支付结果
resolve({ code: res.data.code })
},
fail(err) {
reject(err);
}
});
});
},
success(res) {
if (res.code == 0) {
// 支付成功处理逻辑,只有res.code=0时,才表示支付成功
// 但是最终状态要以商户后端结果为准
that.onLoad(that.data.info)
}
},
fail(res) {
// 调起收银台失败处理逻辑
tt.showModal({
content: JSON.stringify(res) + '1'
});
}
});
console.log(r.data)
}
})
总结:字节跳动的api文档写的真的不可描述
大家有问题可以相互交流