目录

前提条件

正文

代码流程: 

详细代码

 一、获取字节跳动订单

二、获取支付宝alipay_url参数

 三、开始生成orderInfo 给前端吊起支付宝咯

总结哈 keke



前提条件

字节跳动:

  1. API 支付支持版本(不拉起收银台,直接拉起微信/支付宝):今日头条(iOS & Android)7.4.3+版本;抖音 iOS 9.1.0+版本 / Android 8.7.0+版本
  2. 字节跳动开放平台 -> 基础设置 -> 企业认证完成 -> 将所有信息填写好
  3. 开发管理->开发设置 -> 小程序Key&服务器域名配置好
  4. 字节跳动开放平台 -> 功能管理 -> 支付 -> 收银台支付 完成信息填写(图片真的找不到原图抱歉) 填写之后 这个样儿
  5. 提交一个测试版(用于支付宝APP签约)

支付宝:

  1. 注册支付宝开放平台账号并完成企业认证
  2. 开发者中心控制台->我的应用中创建网页&移动应用->支付接入; 填入名称与抖音小程序相同->选择网页应用->url无所谓
  3. 在能力列表中添加能力选择APP支付; 选择确认后需要签约, 此处签约的APP名称一定要填写测试版字节跳动小程序的名称(必须), 其他的看着填写(最好有截图放到word中上传)
  4. 签约完成后需要在应用信息中设置接口加签方式(选RSA2接口加密方式自己百度很简单) 保存下载好任何可以下载的文件; 授权回调地址用来接收支付宝验签用暂时不管他

正文

工作中用到抖音小程序与支付宝开发 记录一次摸(cai)索(keng)过程 ! 

首先要梳理下思路:  要区分为两个部分{ 支付宝 , 字节跳动 } 本文需要通过 字节跳动的 tt.pay 前端方法 吊起支付宝支付

一定要看好前提条件, 不然很容易乱

 

代码流程: 

大概过程: 前端请求服务端接口, 服务端返回orderInfo

服务端生成的orderInfo过程:

1、获取字节跳动签名(sign)

2、获取支付宝的alipay_url 

3、组合参数 orderInfo 给前端

 

 

 

详细代码

 一、获取字节跳动订单

  1. 抖音用户授权登录(需要Openid)
  2. 获取字节跳动订单 , 此处需要创建自定义订单信息 (随便写除了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信息--------------------------

------------------------------------------------------------------获取字节跳动签名------------------------------------------------------------------

  1. 返回值{"response":{"code":"10000","msg":"Success","trade_no":"SP2020081509594510822988870791"},"sign":"E7RRJSJCVAhA4DMyMPr/Q1IOc1RKpyQNikl9l8b3ObW6dAYzep7rK6wY5YVjSubhmINsI0iWb/cu+YCqp1D+amifkXh4nX2JG3D0xgi2eWJrTv3Ou27zuEPbEb5y10SBG1f4QCYoa7r2upmOL5xbjY6kG5iDPjiS4JIthsojR5Q="}    trade_no(订单号)留着用哈

二、获取支付宝alipay_url参数

  1. 我用的是支付宝sdk中证书方法生成 alipay_url 
  2. 下载支付宝sdk点我下载  tp5将aop文件夹放到vendor ;   生成支付宝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;
}
  1. 一定有人发现 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;
	}
})