目录
前提条件
正文
代码流程:
详细代码
一、获取字节跳动订单
二、获取支付宝alipay_url参数
三、开始生成orderInfo 给前端吊起支付宝咯
总结哈 keke
前提条件
字节跳动:
- API 支付支持版本(不拉起收银台,直接拉起微信/支付宝):今日头条(iOS & Android)7.4.3+版本;抖音 iOS 9.1.0+版本 / Android 8.7.0+版本
- 在字节跳动开放平台 -> 基础设置 -> 企业认证完成 -> 将所有信息填写好
- 开发管理->开发设置 -> 小程序Key&服务器域名配置好
- 在字节跳动开放平台 -> 功能管理 -> 支付 -> 收银台支付 完成信息填写(图片真的找不到原图抱歉) 填写之后 这个样儿
- 提交一个测试版(用于支付宝APP签约)
支付宝:
- 注册支付宝开放平台账号并完成企业认证
- 在开发者中心控制台->我的应用中创建网页&移动应用->支付接入; 填入名称与抖音小程序相同->选择网页应用->url无所谓
- 在能力列表中添加能力选择APP支付; 选择确认后需要签约, 此处签约的APP名称一定要填写测试版字节跳动小程序的名称(必须), 其他的看着填写(最好有截图放到word中上传)
- 签约完成后需要在应用信息中设置接口加签方式(选RSA2接口加密方式自己百度很简单) 保存下载好任何可以下载的文件; 授权回调地址用来接收支付宝验签用暂时不管他
正文
工作中用到抖音小程序与支付宝开发 记录一次摸(cai)索(keng)过程 !
首先要梳理下思路: 要区分为两个部分{ 支付宝 , 字节跳动 } 本文需要通过 字节跳动的 tt.pay 前端方法 吊起支付宝支付
一定要看好前提条件, 不然很容易乱
代码流程:
大概过程: 前端请求服务端接口, 服务端返回orderInfo
服务端生成的orderInfo过程:
1、获取字节跳动签名(sign)
2、获取支付宝的alipay_url
3、组合参数 orderInfo 给前端
详细代码
一、获取字节跳动订单
- 抖音用户授权登录(需要Openid)
- 获取字节跳动订单 , 此处需要创建自定义订单信息 (随便写除了openid) 我的代码 -----------获取字节跳动订单-----------
//------------------自定义订单信息--------------------------
$data = [
'out_order_no' => date('Ymd') . $time, //随便搞个订单号
'openid' => $userinfo['openid'], //抖音用户openid
'fee' => '1', //金额 单位:分!分!分!
'cid' => $cid,
'time' => $time,
'body' => '123', //支付的内容(支付宝)
'subject' => '456', //支付的标题(支付宝)
//body 和 subject 刚开始先用数字(中文会有其他问题)
];
//------------------自定义订单信息--------------------------
//TP5框架 fastadmin
//------------------组合请求sign信息--------------------------
//↓↓↓获取用户真实IP
$risk_info = request()->ip();
//↓↓↓头条支付分配给业务方的ID(不是头条小程序的appid)
$payload['app_id'] = $this->config['tt_pay_app_id'];
//↓↓↓头条支付分配给业务方的支付秘钥
$app_secret = $this->config['tt_pay_app_secret'];
//↓↓↓请求使用的编码格式
$payload['charset'] = "utf-8";
//↓↓↓接口名称
$payload['method'] = "tp.trade.create";
//↓↓↓发送请求的时间
$payload['timestamp'] = $time;
// 请求参数的集合 json
$biz_content = [
//商户订单号
"out_order_no" => $data['out_order_no'],
//唯一标识用户open_id
"uid" => $data['openid'],
//金额,分为单位,应传整型
"total_amount" => $data['fee'],
//商户订单名称
"subject" => $data['subject'],
//商户订单详情
"body" => $data['body'],
//头条支付分配给业务方的商户号
"merchant_id" => $this->config['merchant_id'],
//货币种类
"currency" => "CNY",
//下单时间戳
"trade_time" => $time,
//订单有效时间(此处测试 时间留的长) 单位:秒
"valid_time" => "3000",
//服务器异步通知地址尽量https 没试过http
"notify_url" => 'https://**********.com/api/pay/verificationSign',
//用户的真实ip 一定要json序列化
"risk_info" => json_encode(['ip' => $risk_info]),
];
$payload['biz_content'] = json_encode($biz_content);
//字节跳动采用的是MD5加密
$payload['sign_type'] = "MD5";
$payload['format'] = "json";
$payload['version'] = "1.0";
//这里写了一个签名的方法, 千万别乱, 此处签名用来请求的, 与其他签名没有任何关联;(共3个签名)
$stringToBeSigned = $this->getSignContent($payload, $payload['charset']);
$payload["sign"] = md5($stringToBeSigned . $app_secret);
$url = "https://tp-pay.snssdk.com/gateway"; // 请求地址正式环境
$result = $this->curl($url, $payload);
/*返回示例:
{"response":{"code":"10000","msg":"Success","trade_no":"SP2020081509594510822988870791"},"sign":"E7RRJSJCVAhA4DMyMPr/Q1IOc1RKpyQNikl9l8b3ObW6dAYzep7rK6wY5YVjSubhmINsI0iWb/cu+YCqp1D+amifkXh4nX2JG3D0xgi2eWJrTv3Ou27zuEPbEb5y10SBG1f4QCYoa7r2upmOL5xbjY6kG5iDPjiS4JIthsojR5Q="}*/
$result = json_decode($result, true);
//------------------组合请求sign信息--------------------------
------------------------------------------------------------------获取字节跳动签名------------------------------------------------------------------
- 返回值{"response":{"code":"10000","msg":"Success","trade_no":"SP2020081509594510822988870791"},"sign":"E7RRJSJCVAhA4DMyMPr/Q1IOc1RKpyQNikl9l8b3ObW6dAYzep7rK6wY5YVjSubhmINsI0iWb/cu+YCqp1D+amifkXh4nX2JG3D0xgi2eWJrTv3Ou27zuEPbEb5y10SBG1f4QCYoa7r2upmOL5xbjY6kG5iDPjiS4JIthsojR5Q="} trade_no(订单号)留着用哈
二、获取支付宝alipay_url参数
//阿里url证书
public function aliUrlZhengshu($data)
{
//需要在AopCertClient.php文件中加入
//namespace app\api\controller;
//use think\Exception;
//引入文件 用来实例化
require_once VENDOR_PATH . 'aop/AopCertClient.php';
$c = new AopCertClient;
$appCertPath = VENDOR_PATH . 'aop/crt/appCertPublicKey.crt';//应用证书路径(要确保证书文件可读)
$alipayCertPath = VENDOR_PATH . 'aop/crt/alipayCertPublicKey_RSA2.crt';//支付宝公钥证书路径(要确保证书文件可读)
$rootCertPath = VENDOR_PATH . 'aop/crt/alipayRootCert.crt';//支付宝根证书路径(要确保证书文件可读)
$c->gatewayUrl = "https://openapi.alipay.com/gateway.do";
$c->appId = $this->config['ali_app_app_id'];
$c->rsaPrivateKey = $this->config['ali_app_rsa_pri_key'];
$c->format = "json";
$c->charset = "UTF-8";
$c->signType = "RSA2";
//调用getPublicKey从支付宝公钥证书中提取公钥
$c->alipayrsaPublicKey = $c->getPublicKey($alipayCertPath);
//是否校验自动下载的支付宝公钥证书,如果开启校验要保证支付宝根证书在有效期内
$c->isCheckAlipayPublicCert = true;
//调用getCertSN获取证书序列号
$c->appCertSN = $c->getCertSN($appCertPath);
//调用getRootCertSN获取支付宝根证书序列号
$c->alipayRootCertSN = $c->getRootCertSN($rootCertPath);
//实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.open.public.template.message.industry.modify
//文件中加入 namespace app\api\controller; 即可
require_once VENDOR_PATH . 'aop/request/AlipayTradeAppPayRequest.php';
$request = new AlipayTradeAppPayRequest();
//此次只是参数展示,未进行字符串转义,实际情况下请转义
$request->setBizContent($this->getcontent($data));
$response = $c->sdkExecute($request);
return $response;
}
- 一定有人发现 getcontent 方法不存在 嘿嘿 上代码:
//业务数据
public function getcontent($data)
{
$biz_content = array(
'out_trade_no' => $data['out_order_no'], //之前咱们自定义的订单号 out_trade_no
'product_code' => 'QUICK_MSECURITY_PAY', //定死了 别动
'total_amount' => $data['fee'] / 100, //单位换算
'subject' => $data['subject'], //之前定好的 标题
'method' => 'alipay.trade.app.pay', //定死了 别动
'notify_url' => 'https://******.com/api/pay/verificationSign',//回调接口需要配置到支付宝
'body' => $data['body'], //之前定好的 内容
'timeout_express' => '1m', //支付超时时间 文档去支付宝搜索咯 1m-15d
);
return json_encode($biz_content);
}
三、开始生成orderInfo 给前端吊起支付宝咯
$res = 获取字节跳动的订单号(一、获取字节跳动订单)
if ($res['result']['response']['code'] != 10000) {
$this->error('错误', $res['result']['response']['code']);
} elseif ($res) {
$out_order_no = $res['out_order_no'];//自定义的订单号
$aliurl = $this->aliUrlZhengshu($res);//获取 alipay_url
$arr = [
'merchant_id' => $this->config['merchant_id'],//字节跳动商户号 前提条件->字节跳动->4 完成填写后
'app_id' => $this->config['tt_pay_app_id'],//字节跳动APPID 前提条件->字节跳动->4 完成填写后
'sign_type' => 'MD5',//定死的别动!!!
'timestamp' => strval($res['time']),//需要为字符串类型的时间戳
'version' => '2.0',//定死的别动!!!
'trade_type' => 'H5',//定死的别动!!!
'product_code' => 'pay',//定死的别动!!!
'payment_type' => 'direct',//定死的别动!!!
'out_order_no' => strval($out_order_no),//自定义的订单号
'uid' => $this->openid,// 用户的openid 登录后可以获取到
'total_amount' => $res['fee'],//金额 这里单位:分
'currency' => 'CNY',//定死的别动!
'trade_no' => $res['trade_no'],//刚刚获取的字节跳动订单 忘了往上找找
'subject' => $res['subject'],//之前定好的标题
'body' => $res['body'],//之前定好的内容
'trade_time' => strval($res['time']),//一定要和 上面的 timestamp 字段相同
'valid_time' => '3000',//测试留的时间长
'notify_url' => 'https://tp-pay.snssdk.com/paycallback',//定死的别动!!!
'alipay_url' => $aliurl,//刚刚生成的 记得不
];
$stringToBeSigned = $this->getSignContent($arr);//这里待签名处理.方法下面
$sign = md5($stringToBeSigned . $this->config['tt_pay_app_secret']);//这生成签名咯, 不要乱, 签名好多的
//这两个字段的写入原因: 在待签名字符串 getSignContent 方法中不能有 sign和risk_info 所以在生成签名($sign)之后写入到里面
$arr['sign'] = $sign;
$arr['risk_info'] = json_encode(['ip' => request()->ip()]);
//这两个字段的写入原因: 在待签名字符串 getSignContent 方法中不能有 sign和risk_info 所以在生成签名($sign)之后写入到里面
$res = htmlspecialchars_decode(json_encode($arr));//这里html的编译解析, 防止html编译
$this->success('返回orderinfo', $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++;
}
}
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;
}
总结哈 keke
$res 就是我们要的orderInfo 处理好之后是个json串 所以前端取到数据后需要json反序列化一下.
前端uniapp写的.
可以分享下我们前端 重要的是 错误代码(错误代码:CD0015 CD0025 这个是字节跳动的错误)
_this.getOrderPayInfo().then(e => { //调用后端接口得到orderInfo
let currenttime = Math.round(new Date() / 1000);
let order = JSON.parse(e.data.data);
if(e.data){
uni.requestPayment({
provider: 'toutiao',
service: 4, // 不拉起字节跳动小程序收银台
_debug: 1,
payChannel: {
default_pay_channel: 'alipay' // wx || alipay
},
orderInfo: order, // 订单信息
getOrderStatus(res) {
let { out_order_no } = res;
return new Promise(function (resolve, reject) {
})
},
success: (res) => {
console.log("成功");
console.log(res);
},
fail: (res) => {
console.log("失败");
console.log(res); // 错误代码:CD0015 CD0025
}
})
// _this.loadModal = false;
}
})