关于php后台开发头条小程序支付遇到的坑以及解决方案

释放双眼,带上耳机,听听看~!

整体上看 和微信小程序的签名过程一致
无非就是构建参数 按规则排序 拼接秘钥 加密 发起请求
返回结果
2019年6月19日更新
实现头条支付的全过程如下:

  1. 调用头条支付的统一下单接口(第一次签名) 获取支付单号trade_no
  2. 使用支付宝sdk获取 可以调起APP (第二次为支付宝签名)SDK支付的url
  3. 为前端调起支付宝支付的参数签名(第三次)
  4. 为前端返回所有所需的参数

最后要使用的时候 实例化下面这个类 调用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&notify_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&timestamp=2018-08-21+17%3A56%3A24&version=1.0'
// 如果是老版支付宝,url 示例:
url: '_input_charset=\"utf-8\"&body=\"novel\"&it_b_pay=\"30m\"&notify_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 //接口名称
&notify_url=           //回调地址
&sign=                  //获取到的签名
&sign_type=RSA2     //商户生成签名字符串所使用的签名算法类型
&timestamp=         //发送请求的时间
&version=1.0        //调用的接口版本

我日这他娘的不就是支付宝APP支付吗

然后最搞笑的地方不是这里
签两次名而已 支付宝支付的sdk官网还是有的

但是支付总要支付宝的公钥,秘钥,app账号

然后需要自己去申请支付宝应用(一定是APP支付)

签完名之后 获得 可以调起支付宝支付的url

然后整合参数为前端调起支付时的参数 再签一次名

总的来说需要签三次名

人已赞赏
php笔记

[转]PHP优秀资源,都给你整理好了

2019-6-13 17:44:56

php笔记

PHP 设计模式系列 —— 概述及常用设计模式大全

2019-8-9 13:58:34

3 条回复 A文章作者 M管理员
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索