整体上看 和微信小程序的签名过程一致
无非就是构建参数 按规则排序 拼接秘钥 加密 发起请求
返回结果
2019年6月19日更新
实现头条支付的全过程如下:
- 调用头条支付的统一下单接口(第一次签名) 获取支付单号trade_no
- 使用支付宝sdk获取 可以调起APP (第二次为支付宝签名)SDK支付的url
- 为前端调起支付宝支付的参数签名(第三次)
- 为前端返回所有所需的参数
最后要使用的时候 实例化下面这个类 调用ttpay方法 就好了
<?php
//自己写个方便的一点的名空间
/**
* 头条支付
*/
class PayService
{
protected $appid;
protected $mch_id;
protected $key;
protected $openid;
protected $out_trade_no;
protected $body;
protected $total_fee;
protected $notify_url;
/*
* 头条支付函数
* 参数说明
* @param $biz 需要签名的字符串
* @param $key 头条支付的秘钥
* @param $appid 头条小程序的appid
*/
public function ttpay($biz,$key,$appid)
{
$url = 'https://tp-pay.snssdk.com/gateway';
//构建签名信息
$parameters = array(
'app_id' => $appid,
'method' => 'tp.trade.create',
'charset' => 'utf-8',
'sign_type' => 'MD5',
'timestamp' =>time(),
'version' =>'1.0',
'biz_content'=>$biz
);
//统一下单签名
$parameters['sign'] = $this->getSign($parameters,$key);
// 发起请求
$req = $this->ttpost($url,$parameters);
// 获取并返回信息
return $req['ret'] ? $req['msg'] : '';
}
// 自己写的post请求 也可以使用自己封装的
function ttpost($url, $params = [], $method = 'POST', $options = []){
$method = strtoupper($method);
$protocol = substr($url, 0, 5);
$query_string = is_array($params) ? http_build_query($params) : $params;
// halt($query_string);
$ch = curl_init();
$defaults = [];
if ('GET' == $method)
{
$geturl = $query_string ? $url . (stripos($url, "?") !== FALSE ? "&" : "?") . $query_string : $url;
$defaults[CURLOPT_URL] = $geturl;
}
else
{
$defaults[CURLOPT_URL] = $url;
if ($method == 'POST')
{
$defaults[CURLOPT_POST] = 1;
}
else
{
$defaults[CURLOPT_CUSTOMREQUEST] = $method;
}
$defaults[CURLOPT_POSTFIELDS] = $query_string;
}
$defaults[CURLOPT_HEADER] = FALSE;
$defaults[CURLOPT_USERAGENT] = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.98 Safari/537.36";
$defaults[CURLOPT_FOLLOWLOCATION] = TRUE;
$defaults[CURLOPT_RETURNTRANSFER] = TRUE;
$defaults[CURLOPT_CONNECTTIMEOUT] = 3;
$defaults[CURLOPT_TIMEOUT] = 3;
// disable 100-continue
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
if ('https' == $protocol)
{
$defaults[CURLOPT_SSL_VERIFYPEER] = FALSE;
$defaults[CURLOPT_SSL_VERIFYHOST] = FALSE;
}
curl_setopt_array($ch, (array) $options + $defaults);
$ret = curl_exec($ch);
$err = curl_error($ch);
if (FALSE === $ret || !empty($err))
{
$errno = curl_errno($ch);
$info = curl_getinfo($ch);
curl_close($ch);
return [
'ret' => FALSE,
'errno' => $errno,
'msg' => $err,
'info' => $info,
];
}
curl_close($ch);
return [
'ret' => TRUE,
'msg' => $ret,
];
}
/**
* 生成签名
* @param $Obj
* @return string
*/
function getSign($Obj,$key)
{
foreach ($Obj as $k => $v) {
$Parameters[$k] = $v;
}
//签名步骤一:按字典序排序参数
ksort($Parameters);
$String = $this->formatBizQueryParaMap($Parameters, false);
//签名步骤二:在string后加入KEY
$String = $String . $key;
// halt($String);
//签名步骤三:MD5加密
$String = md5($String);
return $String;
}
/**
* 格式化参数,签名过程需要使用
* @param $paraMap
* @param $urlencode
* @return string
*/
private function formatBizQueryParaMap($paraMap, $urlencode)
{
$buff = "";
ksort($paraMap);
foreach ($paraMap as $k => $v) {
if ($urlencode) {
$v = urlencode($v);
}
$buff .= $k . "=" . $v . "&";
}
$reqPar = '';
if (strlen($buff) > 0) {
$reqPar = substr($buff, 0, strlen($buff) - 1);
}
return $reqPar;
}
}
业务代码如下
//构建支付宝配置
$configs = [
'app_id' => '*******',
'notify_url' => 'https://*******',
'return_url' => 'https://*******/',
'ali_public_key' => '********',
'private_key' => '****',
'log' => [ // optional
'file' => LOG_PATH . '/epaylogs/alipay' . date("Y-m-d") . '.log',
'level' => 'info', // 建议生产环境等级调整为 info,开发环境为 debug
'type' => 'single', // optional, 可选 daily.
'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天
],
'http' => [ // optional
'timeout' => 5.0,
'connect_timeout' => 5.0,
// 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html)
],
// 'mode' => 'dev', // optional,设置此参数,将进入沙箱模式
];
//实例化支付宝支付 此处使用的是yangsongda的支付类
$alipay = Pay::alipay($configs);
//获取客户端ip地址
$risk = [
'ip' => $_SERVER['REMOTE_ADDR'],
];
$risk_data = json_encode($risk);
$config = [
'out_order_no' => $order['order_num'],//订单号
'uid' => $openid, //openid
'merchant_id' => $data['mchid'], //头条分配的商户号
'total_amount' => $order['snap_product']['price'] * 100, //订单金额
'currency' => 'CNY', //币种
'subject' => $order['snap_product']['title'], //订单介绍
'body' => $order['snap_product']['title'],//订单详情
'trade_time' => time(), //当前时间戳
'valid_time' => 30, //超时时间
'notify_url' => '***', //回调地址
'risk_info' => $risk_data,
];
$post_data = json_encode($config);
$PayService = new PayService();
//实例化上面的类 实现统一下单
$result = $PayService->ttpay($post_data,$data['partner_key'],$data['appid']);
$result = json_decode($result);
//构建支付宝订单信息
$orders = [
'out_trade_no' => $order['order_num'],
'total_amount' => $order['snap_product']['price'],
'subject' => $order['snap_product']['title'],
];
//获取可调起支付宝支付的url
$url = $alipay->app($orders);
$urls = [
'url' => $url
];
$urls = json_encode($urls);
//为前端调起支付的参数签名
$params = [
'app_id'=>$data['appid'],
'sign_type'=>'MD5',
'timestamp'=>time(),
'trade_no'=> $result->response->trade_no,//这个是统一下单返回的支付订单号
'merchant_id'=>$data['mchid'],//头条提供的商户号
'uid'=>$openid,
'total_amount'=> $order['snap_product']['price']* 100,
'params'=> $urls //可以调起支付宝的url
];
//获取签名
$sign = $PayService->getSign($params,$data['partner_key']);
//halt($url);
//构建头条支付订单信息
$this->success('统一下单成功!',['ttback'=>$result,'order'=>$params,'url'=>$url,'app_id'=>$data['appid'],'risk_info'=>$risk_data,'sign'=>$sign]);
这样就可以获取到头条支付的支付参数了
前端的代码示例
let obj = {
data:{
app_id: res.data.app_id,
method: 'tp.trade.confirm',
sign: res.data.sign,
sign_type: 'MD5',
timestamp: res.data.order.timestamp.toString(),
trade_no: res.data.order.trade_no,
merchant_id: res.data.order.merchant_id,
uid: res.data.order.uid,
risk_info: res.data.risk_info,
total_amount: res.data.order.total_amount,
pay_channel: 'ALIPAY_NO_SIGN',
pay_type: 'ALIPAY_APP',
params: JSON.stringify({url: res.data.url})
},
success: function (res) {
tt.showToast({
title: '支付成功',
icon: 'success'
})
setTimeout(() => {
tt.redirectTo({
url: './success',
})
}, 1500)
},
fail: function (res) {
console.log(res)
tt.showToast({
title: '支付失败',
icon: 'none'
})
}
}
console.log(obj);
tt.requestPayment(obj)
关键的问题是 头条支付的过程中需要可以调起支付宝支付的url
具体内容如下
// 如果是新版支付宝,url 示例:
url: 'app_id=2018041302549907&biz_content=%7B%22body%22%3A%22novel%22%2C%22subject%22%3A%22%E6%B5%8B%E8%AF%95%E7%9A%84%E5%95%86%E5%93%81%22%2C%22out_trade_no%22%3A%22201808211756233909095950%22%2C%22timeout_express%22%3A%2230m%22%2C%22total_amount%22%3A%220.01%22%2C%22seller_id%22%3A%22jrtoutiaoyxgs%40bytedance.com%22%2C%22product_code%22%3A%22QUICK_MSECURITY_PAY%22%7D&charset=utf-8&format=JSON&method=alipay.trade.app.pay¬ify_url=https%3A%2F%2Ftp-pay-test.snssdk.com%2Fcallback%2Fali_pay&sign=ZfVkvu%2FSzBqFuqQMgr6MvsXomlr6BCuz7GYDnpsxd3SLVfCssV0q2cnxZyfjh%2FY%2Bk7PO1IeEl4rppQg%2FXgRuIqMXyKdhmigj4oPdQVJEkbSQEcCW4m8mwpXLNjlLH%2FHae3u3hjrMDVPuVXeIxjoq1NLPXy09GY5u1MX8E2lkn8xtmOxA2cXXRIrAa8gTplUoXWkSSkZMgvSTzQ9RjRmlKtK4nERdDWh5RBXLNDU%2FD2FfqIeZuLNZh%2BW8j4dYGtPDm9nWYRz0tLizJDm6E76aTM3qvLi0havCCrHgxZ5d8tVN7GNztA6olbGOiXubEGUq4yBqCojiALEEVpKqfQdZGQ%3D%3D&sign_type=RSA2×tamp=2018-08-21+17%3A56%3A24&version=1.0'
// 如果是老版支付宝,url 示例:
url: '_input_charset=\"utf-8\"&body=\"novel\"&it_b_pay=\"30m\"¬ify_url=\"https://tp-pay-test.snssdk.com/callback/ali_pay\"&out_trade_no=\"201808211755020406852103\"&partner=\"2088801374045154\"&payment_type=\"1\"&seller_id=\"adsense@bytedance.com\"&service=\"mobile.securitypay.pay\"&subject=\"测试的商品\"&total_fee=\"0.01\"&sign=\"RGdwAoCy5DsjdFBdtrN9WzdYtyZGlUHn8dbAQVQsIPidLTR9s%2BCVtAj%2BtYzL8oAHP0IXJZw8U6EGlyA2MG2ZxhJRI1N1RhDMZOz56eAXO%2FITZYiGSB01hkhx9yhqmWAUJQfUMRHJZswS1DEpwam1JfaoahZ%2Bf%2FEE%2FkvG6ma67t4%3D\"&sign_type=\"RSA\"'
官方提供的示例如图所示
接下来我们
把url拆分来看
app_id=2018041302549907 //支付宝分配给开发者的应用ID
&biz_content= //订单信息
&charset=utf-8 //编码
&format=JSON //仅支持JSON
&method=alipay.trade.app.pay //接口名称
¬ify_url= //回调地址
&sign= //获取到的签名
&sign_type=RSA2 //商户生成签名字符串所使用的签名算法类型
×tamp= //发送请求的时间
&version=1.0 //调用的接口版本
我日这他娘的不就是支付宝APP支付吗
然后最搞笑的地方不是这里
签两次名而已 支付宝支付的sdk官网还是有的
但是支付总要支付宝的公钥,秘钥,app账号
然后需要自己去申请支付宝应用(一定是APP支付)
签完名之后 获得 可以调起支付宝支付的url
然后整合参数为前端调起支付时的参数 再签一次名
总的来说需要签三次名