本人针对字节调动小程序的官方开发文档真的无力吐槽,文档真的简陋。而且文档有错别字。槽点有点多。

头条因为没有自己的支付渠道,所以使用的是支付宝,利用tt.requestPayment()调起支付宝APP支付:

支付具体流程为:

一、后端通过openid和自己这边的订单号生成一个头条的订单号,具体操作可以看文档流程
二、生成调用支付宝的一个字符串,这里需要使用支付宝文档,我使用的支付宝SDK,这边支付宝文档很好的,网上资料很多。
三、最后就是组装后返回给前端调用支付宝。特别注意的是这里使用的所有appid等参数都是头条分配给商户的appid等。

java 支付宝回调 demo 支付回调接口_支付


首先我们通过代码来实现字节跳动小程序支付单号(trade_no)的生成。我这里用的是PHP代码,通过文档,对参数进行处理。

//这里是真的回调地址进行的简单处理
$http_type = ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')) ? 'https://' : 'http://';
$website                = $http_type . $_SERVER['HTTP_HOST'];

$risk_info              = $this->getRealIp();    //获取用户真实IP(我在这里写了一个获取IP的方法)
$payload['app_id']      = $tt_pay_app_id;  		 //头条支付分配给业务方的ID(不是头条小程序的appid)
$app_secret             = $tt_pay_secret_key;  	 //头条支付分配给业务方的支付秘钥
$payload['charset']     = "utf-8";               // 请求使用的编码格式
$payload['method']      = "tp.trade.create";  	 // 接口名称
$payload['timestamp']   = time();           	 // 发送请求的时间
$biz_content            = [
    "out_order_no" => $data['out_trade_no'],    	//商户订单号
    "uid"          => $data['openid'],    			//唯一标识用户open_id
    "merchant_id"  => $tt_merchant_id,    			//头条支付分配给业务方的商户号
    "total_amount" => $data['fee'] * 100,    		//金额,分为单位,应传整型
    "currency"     => "CNY", 						//币种
    "subject"      => $data['subject'],    			//商户订单名称
    "body"         => $data['body'],    			//商户订单详情
    "trade_time"   => time(),    					//下单时间戳
    "valid_time"   => "60",    						//订单有效时间     单位:秒
    "notify_url"   => $website . '/payment/toutiao/notify.php',    //服务器异步通知http地址
    "risk_info"    => json_encode(['ip' => $risk_info]),    	   //用户的真实ip
]; // 请求参数的集合   json
$payload['biz_content'] = json_encode($biz_content);
$payload['sign_type']   = "MD5";		//头条采用的是MD5加密
$payload['format']      = "json";
$payload['version']     = "1.0";
$stringToBeSigned = $this->getSignContent($payload , $payload['charset'] , $app_secret);	//这里写了一个签名的方法
$payload["sign"]  = md5($stringToBeSigned);

$url        = "https://tp-pay.snssdk.com/gateway"; // 请求地址正式环境
$result     = ihttp_post($url, $payload);
$result     = json_decode($result['content'], true);
$resultCode = $result['response']['code'];
if (!empty($resultCode) && $resultCode == 10000) {
	//后面会针对这一块进行处理
	//这里需要拼装参数请求支付宝APP支付

    return ['error'=>0 , 'msg'=>'success' , 'res'=>$result];
} else {
    return ['error'=>-1 , 'msg'=>'failed' , 'res'=>$result];
}

当我们点击点单支付按钮的时候,将所需要的参数传至以上接口方法中进行处理,能够正常返回头条的trade_no。

返回结果如下:

java 支付宝回调 demo 支付回调接口_回调_02


接下来就是上述方法中的两个方法的调用,一个使用户IP地址的获取,一个是头条签名的处理方法。重点是后面一个。

先看用户IP地址的获取方法,如下:

/**
 * 获取用户真实IP
 * @return bool
 */
public function getRealIp() {
    $ip = false;
    if (!empty($_SERVER["HTTP_CLIENT_IP"])) {
        $ip = $_SERVER["HTTP_CLIENT_IP"];
    }
    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $ips = explode(", ", $_SERVER['HTTP_X_FORWARDED_FOR']);
        if ($ip) {
            array_unshift($ips, $ip);
            $ip = FALSE;}
        for ($i = 0; $i < count($ips); $i++) {
            if (!eregi("^(10│172.16│192.168).", $ips[$i])) {
                $ip = $ips[$i];
                break;
            }
        }
    }
    return ($ip ? $ip : $_SERVER['REMOTE_ADDR']);
}

该方法不做过多描述,接下来是重点。

java 支付宝回调 demo 支付回调接口_回调_03


通过文档可以看出,我们要先对请求参数去除空值和不参与签名的字段,然后进行排序。代码如下:

/**
     * 签名处理
     * @param $params
     * @param $charset
     * @return string
     */
    public function getSignContent($params , $charset,$app_secret) {
        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.$app_secret;
        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;
    }

通过以上方法我们已经能够获取到正常的响应数据了。就像这样

{
    "response":{
        "code":"10000",
        "msg":"Success",
        "trade_no":"1004130000127421"
    },
    "sign":"ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"
}

但是,接下来问题来了。我们需要再次的拼装参数,去请求支付宝APP支付。然后将数据返回给前端,通过前端拉起支付宝收银台。
但是这一块,官方文档写的很… … 以至于,本小白看的是一脸懵。

我在正确获取trade_no订单号的时候,做了以下处理:
关于这一块的参数,可以参考文档:http://developer.toutiao.com/docs/open/requestPayment.html

//在这里是要将数据返回给前端,通过前端拉起支付宝收银台

$data2                 = [];
$data2['trade_no']     = $result['response']['trade_no'];	//刚刚获取到的trade_no订单号
$data2['app_id']       = $payload['app_id'];				//头条支付分配给业务方的ID
$data2['sign_type']    = $payload['sign_type'];				//头条采用的MD5
$data2['timestamp']    = $payload['timestamp'];				//发送请求的时间
$data2['merchant_id']  = $biz_content['merchant_id'];		//头条支付分配给业务方的商户号
$data2['uid']          = $biz_content['uid'];				//唯一标识用户open_id(这个是头条的)
$data2['total_amount'] = $biz_content['total_amount'];		//金额,分为单位,应传整型
$data2['params']       = json_encode(['url' => $this->aliapppaytest($data , $biz_content , $data2 , $website , $config)]);		//这里是支付宝APP支付的请求参数,我在这里使用了支付宝的SDK

$stringToBeSigned      = $this->getSignContent($data2, $payload['charset'], $app_secret);	//将以上参数再次进行签名处理
$data2["sign"]         = md5($stringToBeSigned);

$data2['pay_channel']  = "ALIPAY_NO_SIGN";
$data2["pay_type"]     = "ALIPAY_APP";
$data2['method']       = 'tp.trade.confirm';
$data2['risk_info']    = $biz_content['risk_info'];
return ['error'=>0 , 'msg'=>'success' , 'res'=>$data2];			// 将这些参数,返回给前端处理就可以了

主要是参数params中的url处理

java 支付宝回调 demo 支付回调接口_java 支付宝回调 demo_04


这里使用了支付宝的SDK,如下:

public function aliapppaytest($data , $biz_content , $data2 , $website , $config){
        require  dirname(__FILE__) .'/../library/alipay/AopSdk.php';
        $aop                     = new \AopClient();
        $aop->gatewayUrl         = "https://openapi.alipay.com/gateway.do";
        $aop->appId              = $config['ali_app_app_id'];                //支付宝APPID
        $aop->rsaPrivateKey      = $config['ali_app_rsa_pri_key'];        //支付宝秘钥
        $aop->format             = "json";
        $aop->charset            = "UTF-8";
        $aop->signType           = "RSA2";
        $aop->alipayrsaPublicKey = $config['ali_app_pay_rsa_pub_key'];   //支付宝公钥

        //实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
        $request = new AlipayTradeAppPayRequest();  //SDK已经封装掉了公共参数,这里只需要传入业务参数
        $data2 = [
            'body'            => $data['body'],
            'subject'         => $data['subject'],
            'out_trade_no'    => $data2['trade_no'],
            'timeout_express' => '30m',
            'total_amount'    => $data['fee'],
            'product_code'    => 'QUICK_MSECURITY_PAY',
        ];
        $postdata = json_encode($data2);
        $request->setNotifyUrl($website . '/payment/toutiao/notify.php');    //服务器异步通知http地址);
        $request->setBizContent($postdata);
        $response = $aop->sdkExecute($request);     //这里和普通的接口调用不同,使用的是sdkExecute
        //htmlspecialchars是为了输出到页面时防止被浏览器将关键参数html转义,实际打印到日志以及http传输不会有这个问题
//        return htmlspecialchars($str);//就是orderString 可以直接给客户端请求,无需再做处理。
        return $response;//就是orderString 可以直接给客户端请求,无需再做处理。
    }

到这里,整个支付阶段就已经结束了。此时,前端拉起支付宝收银台进行付款,支付成功之后,关于回调的处理,其实就是支付宝本身的回调处理了。直接$_POST接收数据,通过支付宝验签处理就可以了。详情,参考PHP 支付宝小程序 支付以及回调处理

关于唤起支付页面报错的问题,请参考支付宝APP支付:
ALI40247-自查方案:https://openclub.alipay.com/read.php?tid=250&fid=60
ALI38173-排查方案:https://openclub.alipay.com/read.php?tid=3546&fid=60
ALIN10146-自查方案:https://openclub.alipay.com/read.php?tid=6918&fid=60

以上欢迎各位指点。