字节跳动集成支付宝支付

文档地址

收银台(字节跳动小程序)接入文档:microapp.bytedance.com/docs/paymen…

字节跳动小程序官方开发文档 :www.w3cschool.cn/microapp/mi…

抖音小程序入口介绍:forum.microapp.bytedance.com/topic/1265

申请开通支付

申请开通支付功能时,需要在小程序开发者的后台提交申请, 如下图所示,并且提供以下资料:

商户名称(公司名称)

法人姓名

渠道支付的业务场景(暂时只支持支付宝App支付,未来会支持微信支付等更多支付方式)

支付类型(开发者勾选):虚拟支付 实物支付

渠道密钥类型(开发者勾选):RSA2 RSA

支付场景描述(描述会使用支付的场景,注意iOS上虚拟物品不支持使用支付宝/微信支付,有虚拟物品支付的开发者,只能在安卓端上使用支付功能)


image.png

审核通过以后就能够在小程序开发者后台查看分配的支付app_id、支付秘钥secret和商户号(merchant_id)


image.png

1、先接入支付宝的app支付,接入文档:docs.open.alipay.com/204/105297/ 快速签名教程:docs.open.alipay.com/291/105972 登录支付宝开放平台保证创建的应用app支付已签约

image.png

回调通知参数说明 :docs.open.alipay.com/204/105301/ 代码如下

代码内容

package com.austin.microapp.common.alipay;

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.response.AlipayTradeAppPayResponse;
import com.austin.microapp.common.utils.CommonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * @description
 */
public class Alipaytrade {

    private static Logger logger = LoggerFactory.getLogger(Alipaytrade.class);

    //签名方式
    private static final String SIGN_TYPE = "RSA2";
    //编码格式
    private static final String CHARSET = "utf-8";

    public static String appPay(String totalAmount) {
        String APP_ID="支付宝应用的appid";
        String APP_PRIVATE_KEY="支付宝应用私钥,就是用支付宝工具生成的私钥";
        String ALIPAY_PUBLIC_KEY="支付宝公钥,就是在支付宝后台上传完公钥后生成的支付宝公钥";
        //签名方式
        String sign_type="RSA2";
        //编码格式
        String CHARSET="utf-8";
        //正式环境支付宝网关,如果是沙箱环境需更改成https://openapi.alipaydev.com/gateway.do
        String url="https://openapi.alipay.com/gateway.do";
        //实例化客户端
        AlipayClient alipayClient = new DefaultAlipayClient(url, APP_ID, APP_PRIVATE_KEY, "json", CHARSET, ALIPAY_PUBLIC_KEY,sign_type);
        //实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
        AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
        //SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
        AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
        model.setBody("我是测试数据");
        //请保证OutTradeNo值每次保证唯一
        model.setOutTradeNo(CommonUtil.getOrderNo());
        model.setSubject("字节跳动-订单编号"+model.getOutTradeNo());
        model.setTimeoutExpress("30m");
        model.setTotalAmount("0.01");
        model.setProductCode("QUICK_MSECURITY_PAY");
        request.setBizModel(model);
        request.setNotifyUrl("商户外网可以访问的异步地址");
        try {
            //这里和普通的接口调用不同,使用的是sdkExecute
            AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
            //就是orderString 可以直接给客户端请求,无需再做处理。
            String result = response.getBody();
            //就是orderString 可以直接给客户端请求,无需再做处理。
            System.out.println(result);
            return result;
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 支付宝支付回调
     * @param alipayPublicKey
     * @param request
     * @return
     */
    public static String AlipayCallBack(String alipayPublicKey,HttpServletRequest request) {
        Map<String, String> result = new HashMap<String, String>();
        Map requestParams = request.getParameterMap();
        for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
            String name = (String) iter.next();
            String[] values = (String[]) requestParams.get(name);
            String valueStr = "";
            for (int i = 0; i < values.length; i++) {
                valueStr = (i == values.length - 1) ? valueStr + values[i]
                        : valueStr + values[i] + ",";
            }
            //乱码解决,这段代码在出现乱码时使用。
            //valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
            result.put(name, valueStr);
        }

        String outTradeNo = result.get("out_trade_no");
        String appId = result.get("app_id");
        String sellerId = result.get("out_trade_no");
        String totalAmount = result.get("total_amount");
        logger.info("outTradeNo=={},appId=={},sellerId=={},totalAmount=={}",outTradeNo,appId,sellerId,totalAmount);
        //切记alipaypublickey是支付宝的公钥,请去open.alipay.com对应应用下查看。
        //boolean AlipaySignature.rsaCheckV1(Map<String, String> params, String publicKey, String charset, String sign_type)
        try {
            boolean flag = AlipaySignature.rsaCheckV1(result, alipayPublicKey, CHARSET, SIGN_TYPE);
            if (flag) {
                return "success";
            } else {
                return "failure";
            }
        } catch (AlipayApiException e) {
            e.printStackTrace();
            return "failure";
        }
    }

    public static void main(String[] args) {
//        appPay("0.01");
    }
}

2、字节跳动接入支付宝支付 注意:


image.png

total_amount参数是Long,分为单位, 发送请求时Content-Type为application/x-www-form-urlencoded

HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
            conn.setSSLSocketFactory(ssf);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            // 设置请求方式(GET/POST)
            conn.setRequestMethod(requestMethod);
            conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
package com.austin.microapp.common.toutiao;

import com.austin.microapp.common.alipay.Alipaytrade;
import com.austin.microapp.common.utils.AESDecodeUtils;
import com.austin.microapp.common.utils.CommonUtil;
import com.austin.microapp.common.utils.PayCommonUtil;
import net.sf.json.JSONObject;
import org.apache.tomcat.util.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * @description
 */
public class TouTiaoMicroApp {

    private static Logger logger = LoggerFactory.getLogger(TouTiaoMicroApp.class);

    /**
     * 通过login接口获取到登录凭证后,开发者可以通过服务器发送请求的方式获取session_key和openId
     * https://developer.toutiao.com/docs/server/auth/jscode2session.html
     * @param appid
     * @param appSecrect
     * @param code
     * @return
     */
    public static JSONObject jscode2session(String appid, String appSecrect, String code) {
        String strUrl = TouTiaoMicroAppUrls.JSCODE2_SESSION + "?appid=" + appid + "&secret=" + appSecrect + "&code=" + code;
        JSONObject result = JSONObject.fromObject(CommonUtil.httpsRequest(strUrl, "GET", null));
        logger.info("jscode2session result==={}", result);
        return result;
    }

    /**
     * 获取手机号
     * https://developer.toutiao.com/docs/comp/getPhoneNumber.html
     * @param appid
     * @param appSecrect
     * @param code
     * @param encryptedData
     * @param iv
     * @return
     */
    public static JSONObject getPhoneNumber(String appid, String appSecrect, String code, String encryptedData, String iv) {
        JSONObject jsonObject = jscode2session(appid, appSecrect, code);
        byte[] encrypData = Base64.decodeBase64(encryptedData);
        byte[] ivData = Base64.decodeBase64(iv);
        byte[] sessionKey = Base64.decodeBase64(jsonObject.getString("session_key"));
        try {
            String str = AESDecodeUtils.decrypt(sessionKey, ivData, encrypData);
            JSONObject result = JSONObject.fromObject(str);
            logger.info("getPhoneNumber result==={}", result);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("error==={}", e.getMessage());
            return null;
        }
    }

    /**
     * https://developer.toutiao.com/docs/payment/#给开发者使用的服务端下单接口
     * https://developer.toutiao.com/docs/open/requestPayment.html#输入
     * @param
     * @return
     */
    public static JSONObject payment() {
        Map<String, Object> parameters = new HashMap<>();
        //支付分配给业务方的ID,用于获取签名/验签的密钥信息,不是小程序appid
        parameters.put("app_id", "支付分配给业务方的appid");
        //接口名称
        parameters.put("method", "tp.trade.create");
        //仅支持JSON
        parameters.put("format", "JSON");
        //请求使用的编码格式,如utf-8、gbk、gb2312等,目前只支持utf-8
        parameters.put("charset", "utf-8");
        //商户生成签名字符串所使用的签名算法类型,目前支持MD5 RSA2
        parameters.put("sign_type", "MD5");
        //发送请求的时间,发送请求的时间,长整型的时间戳,单位是秒
        parameters.put("timestamp", Long.toString(System.currentTimeMillis() / 1000));
        //调用的接口版本,固定为:1.0
        parameters.put("version", "1.0");
        //请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,json格式
        Map<String, Object> bizContent = new HashMap<>();
        //订单号

        bizContent.put("out_order_no", Long.toString(System.currentTimeMillis()));
        //唯一标识用户的id,小程序开发者请传open_id。open_id获取方法
        bizContent.put("uid", "小程序用户open_id");
        //小程序开发者可不传,请忽略
//        bizContent.put("uid_type","");
        //支付分配给业务方的商户号
        bizContent.put("merchant_id", "支付分配给业务方的商户号");
        //金额,分为单位,应传整型
        bizContent.put("total_amount", 1);
        //币种
        bizContent.put("currency", "CNY");
        //商户订单名称
        bizContent.put("subject", "商户订单名称");
        //商户订单详情
        bizContent.put("body", "支付宝支付");
        //折扣 格式(3段):订单号^金额^方式|订单号^金额^方式。 方式目前仅支持红包: coupon如:423423^1^coupon。 可选,目前暂不支持
//        bizContent.put("pay_discount","combine");
        //下单时间戳,unix时间戳
        bizContent.put("trade_time", Long.toString(System.currentTimeMillis() / 1000));
        //订单有效时间(单位 秒)
        bizContent.put("valid_time", "180");
        //服务器异步通知http地址,请填支付宝下单接口对应的异步通知url
        bizContent.put("notify_url", "服务器异步通知http地址,请填支付宝下单接口对应的异步通知url");
        //平台手续费
//        bizContent.put("service_fee","");
        //风控信息,标准的json字符串格式,目前需要传入用户的真实ip: "{"ip":"123.123.123.1"}" "{\"ip\":" + 获取Ip地址+ "}"
        Map<String, Object> riskInfo = new HashMap<>();
        riskInfo.put("ip","123.123.123.1");
        com.alibaba.fastjson.JSONObject ipJson = new com.alibaba.fastjson.JSONObject(riskInfo);
        bizContent.put("risk_info", ipJson);

        //biz_content是请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,json格式
        com.alibaba.fastjson.JSONObject bizContentJson = new com.alibaba.fastjson.JSONObject(bizContent);
        parameters.put("biz_content", bizContentJson.toString());

        String sign = PayCommonUtil.toutiaoAlipaySign(parameters, "支付分配给业务方秘钥secret");
        //商户请求参数的签名串,详见签名方法
//        parameters.put("sign", sign);
        String requestParam = PayCommonUtil.toutiaoAlipaySign(parameters,null)+"&sign="+sign;
        String result = CommonUtil.httpsRequest(TouTiaoMicroAppUrls.PAYMENT, "POST", requestParam);

        Map<String, Object> payParams = new HashMap<String, Object>();
        payParams.put("app_id","支付分配给业务方的appid");
        payParams.put("sign_type","MD5");
        payParams.put("timestamp",Long.toString(System.currentTimeMillis()/1000));

        JSONObject jsonObject = JSONObject.fromObject(result);

        String sign2 = jsonObject.getString("sign");
        logger.info("payment sign2==={}",sign2);

        JSONObject data = JSONObject.fromObject(jsonObject.getString("response"));

        String code = data.getString("code");
        String msg = data.getString("msg");
        String tradeNo = data.getString("trade_no");

        logger.info("payment code==={},msg==={},trade_no==={}",code,msg,tradeNo);

        payParams.put("trade_no",tradeNo);
        payParams.put("merchant_id","支付分配给业务方的商户号");
        payParams.put("uid","小程序用户open_id");
        payParams.put("total_amount",1);

        //获取调起支付宝支付的url,如:alipay_sdk=alipay-sdk-java-3.7.4.ALL&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
        String paramsUrl = Alipaytrade.appPay(null);

        //拼接在字节跳动小程序调起支付宝支付所需参数返回给前端
        Map<String, Object> params = new HashMap<>();
        params.put("url",paramsUrl);
        com.alibaba.fastjson.JSONObject url = new com.alibaba.fastjson.JSONObject(params);
        payParams.put("params",url);
        String paySign = PayCommonUtil.toutiaoAlipaySign(payParams,"支付分配给业务方秘钥secret");

        payParams.put("method","tp.trade.confirm");
        payParams.put("sign",paySign);

        payParams.put("riskInfoIp","123.123.123.1");
        payParams.put("paramsUrl",paramsUrl);

        payParams.put("pay_channel","ALIPAY_NO_SIGN");
        payParams.put("pay_type","ALIPAY_APP");
        logger.info("前端调起支付宝支付所需参数payParams==={}",payParams);
        System.out.println("JSONObject="+JSONObject.fromObject(payParams));
        return JSONObject.fromObject(payParams);
    }

    public static void main(String[] args) {
        payment();
    }

测试 前端调用

defaultTapPay() {
    tt.requestPayment({
    data: {
        app_id: '支付分配给业务方的appid',
        method: 'tp.trade.confirm',
        sign: '后台返回的签名',
        sign_type: 'MD5',
        timestamp: '1564924929',
        trade_no: '字节跳动',
        merchant_id: '支付分配给业务方的商户号',
        uid: '小程序用户open_id',
        total_amount: 1,
        risk_info:JSON.stringify({
            ip: '123.123.123.1'
        }),
        pay_channel: 'ALIPAY_NO_SIGN',
        pay_type: 'ALIPAY_APP',
        params: JSON.stringify({
            // 如果是新版支付宝,url 示例:
            url: 'alipay_sdk=alipay-sdk-java-3.7.4.ALL&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\"'
        }),
    },
    success (res) {
        console.log('success===',res);
    },
    fail (res) {
        console.log('fail===',res);
    }
})
}
image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容