大家好,我是小悟

听我说哈,先看效果:

抖音小程序开发,唤起收银台支付(可以选择支付宝APP支付或微信H5支付)_抖音支付

字节跳动也开放了小程序给商家接入,可以在旗下APP如抖音、今日头条、今日头条极速版等应用中即点即用,基于庞大的数亿用户量为其引流,帮助商家获取用户流量,销售商品,其模式和微信小程序差不多。

1、后台管理

抖音小程序开发,唤起收银台支付(可以选择支付宝APP支付或微信H5支付)_抖音小程序_02

2、开发文档

抖音小程序开发,唤起收银台支付(可以选择支付宝APP支付或微信H5支付)_抖音小程序_03

不像微信和支付宝,字节跳动没有自己的支付,但是在小程序里集成了微信和支付宝支付可供商家满足支付的需求,接下来来看代码是怎么实现的。

首先要在小程序平台上开通支付功能,这个直接去看开发文档,里面的教程说得很清楚,这里就不再敖述。特别要注意的是,申请完微信支付,要登陆微信商户号对应的商户平台 - "产品中心" - "开发配置"自行配置H5 支付域名:snssdk.com,不然微信支付会报错。

再则字节跳动小程序集成微信和支付宝支付,前提要先调通单纯的微信H5支付和支付宝支付。

代码实现

实体类:TTPayParamIn

@Data
public class TTPayParamIn {
/**
* 头条小程序分配给商户的商户id
*/
private String toutiaoMchId;
/**
* 头条小程序分配给商户的appid
*/
private String toutiaoMchAppId;
/**
* 头条小程序分配给商户的appSecrect
*/
private String toutiaoMchSecrect;
/**
* 唯一标识用户的id,小程序开发者请传open_id
*/
private String uid;
/**
* 金额,单位:元
*/
private Long totalAmount;
/**
* 商户订单名称
*/
private String subject;
/**
* 商户订单详情
*/
private String body;
/**
* 订单有效时间,单位:秒
*/
private String validTime;
/**
* 服务器异步通知http地址,请填支付宝下单接口对应的异步通知url
*/
private String notifyUrl;
/**
* 订单号
*/
private String outOrderNo;
}

实体类:CheckoutCounterIn

@Data
public class CheckoutCounterIn extends TTPayParamIn {
//支付宝支付信息
/**
* 支付宝应用id
*/
private String alipayAppId;
/**
* 支付宝应用私钥
*/
private String alipayAppPrivateKey;
/**
* 支付宝公钥
*/
private String alipayPublicKey;
/**
* 支付宝公共回传参数,如果请求时传递了该参数,则返回给商户时会在异步通知时将该参数原样返回。
*/
private String passbackParams;
/**
* 支付宝回调地址
*/
private String alipayNotifyUrl;
//微信支付信息
/**
* 微信小程序appid
*/
private String wxAppId;
/**
* 微信小程序商户id
*/
private String wxMchId;
/**
* 微信小程序商户支付apikey
*/
private String wxApiKey;
/**
* 微信小程序支付回调地址
*/
private String wxNotifyUrl;
/**
* 服务器地址 如:https://xxx.xxx.com/xxx
*/
private String serverPath;
/**
* 金额
*/
private Double wxMoney;
/**
* 微信自定义参数
*/
private String wxAttach;
}

支付通用工具类:PayCommonUtil

public class PayCommonUtil {
private static Logger log = LoggerFactory.getLogger(PayCommonUtil.class);
public static String CreateNoncestr() {
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
String res = "";
for (int i = 0; i < 16; i++) {
Random rd = new Random();
res += chars.charAt(rd.nextInt(chars.length() - 1));
}
return res;
}
public static String createSign(String characterEncoding,SortedMap<Object,Object> parameters,String apiKey){
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
Object v = entry.getValue();
if(null != v && !"".equals(v)
&& !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + apiKey);
String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
log.info("createSign方法sign签名=====:"+sign);
return sign;
}
public static String getRequestXml(SortedMap<Object,Object> parameters){
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set es = parameters.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
String v = (String)entry.getValue();
if ("attach".equalsIgnoreCase(k)||"body".equalsIgnoreCase(k)||"sign".equalsIgnoreCase(k)) {
sb.append("<"+k+">"+"<![CDATA["+v+"]]></"+k+">");
}else {
sb.append("<"+k+">"+v+"</"+k+">");
}
}
sb.append("</xml>");
return sb.toString();
}
public static String setXML(String return_code, String return_msg) {
return "<xml><return_code><![CDATA[" + return_code
+ "]]></return_code><return_msg><![CDATA[" + return_msg
+ "]]></return_msg></xml>";
}
public static String toutiaoAlipaySign(Map<String, Object> map,String salt) {
String result = getSignCheckContent(map);
if (StringUtils.isBlank(salt)) {
return result;
}
result=result + salt;
String sign = DigestUtils.md5Hex(result.toString());
log.info("toutiaoAlipaySign-cnotallow={};sign={}",result,sign);
return sign;
}
//组装参数
public static String getSignCheckContent(Map<String, Object> map) {
ArrayList<String> list = new ArrayList<String>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getKey().equals("sign")) {
continue;
}
if (entry.getValue() != null && entry.getValue() != "") {
list.add(entry.getKey() + "=" + entry.getValue() + "&");
}
}
int size = list.size();
String[] arrayToSort = list.toArray(new String[size]);
Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < size; i++) {
sb.append(arrayToSort[i]);
}
String result = sb.toString();
result = result.substring(0, result.length()-1);
return result;
}
}

请求工具类:CommonUtil

public class CommonUtil {
private static Logger log = LoggerFactory.getLogger(CommonUtil.class);
public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {
try {
// 创建SSLContext对象,并使用我们指定的信任管理器初始化
TrustManager[] tm = { new MyX509TrustManager() };
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
sslContext.init(null, tm, new java.security.SecureRandom());
// 从上述SSLContext对象中得到SSLSocketFactory对象
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url = new URL(requestUrl);
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");
//conn.setRequestProperty("Content-Type", "text/html;charset=utf-8");
// 当outputStr不为null时向输出流写数据
if (null != outputStr) {
OutputStream outputStream = conn.getOutputStream();
// 注意编码格式
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 从输入流读取返回内容
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
// 释放资源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
inputStream = null;
conn.disconnect();
System.out.println(buffer.toString());
return buffer.toString();
} catch (ConnectException ce) {
log.error("连接超时:{}", ce);
} catch (Exception e) {
log.error("https请求异常:{}", e);
}
return null;
}
}

xml 工具类:XMLUtil

public class XMLUtil {
/**
* 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
* @param strxml
* @return
* @throws JDOMException
* @throws IOException
*/
public static Map<String,String> doXMLParse(String strxml) throws JDOMException, IOException {
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");

if(null == strxml || "".equals(strxml)) {
return null;
}

Map<String,String> m = new HashMap<>();

InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(in);
Element root = doc.getRootElement();
List list = root.getChildren();
Iterator it = list.iterator();
while(it.hasNext()) {
Element e = (Element) it.next();
String k = e.getName();
String v = "";
List children = e.getChildren();
if(children.isEmpty()) {
v = e.getTextNormalize();
} else {
v = XMLUtil.getChildrenText(children);
}

m.put(k, v);
}
//关闭流
in.close();

return m;
}

/**
* 获取子结点的xml
* @param children
* @return String
*/
public static String getChildrenText(List children) {
StringBuffer sb = new StringBuffer();
if(!children.isEmpty()) {
Iterator it = children.iterator();
while(it.hasNext()) {
Element e = (Element) it.next();
String name = e.getName();
String value = e.getTextNormalize();
List list = e.getChildren();
sb.append("<" + name + ">");
if(!list.isEmpty()) {
sb.append(XMLUtil.getChildrenText(list));
}
sb.append(value);
sb.append("</" + name + ">");
}
}
return sb.toString();
}
}

支付宝app支付已经在另一篇文章写过了,请自行参考 [抖音小程序集成支付宝支付] 里面的内容

字节跳动小程序收银台:ToutiaoMicroApp

public class ToutiaoMicroApp {

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

/**
* 字节跳动小程序收银台,支付宝APP支付、微信H5支付、银行卡支付
* @return
*/
public static JSONObject microCheckoutCounter(CheckoutCounterIn in,HttpServletRequest request) {
Map<String, Object> payParams = new HashMap<>();
payParams.put("merchant_id", in.getToutiaoMchId());
payParams.put("app_id", in.getToutiaoMchAppId());
payParams.put("sign_type", "MD5");
payParams.put("timestamp", Long.toString(System.currentTimeMillis() / 1000));
payParams.put("product_code", "pay");
payParams.put("trade_type", "H5");
payParams.put("payment_type", "direct");
payParams.put("version", "2.0");
payParams.put("out_order_no", in.getOutOrderNo());
payParams.put("uid", in.getUid());
payParams.put("total_amount", in.getTotalAmount());
payParams.put("currency", "CNY");
payParams.put("subject", in.getSubject());
payParams.put("body", in.getBody());
payParams.put("trade_time", Long.toString(System.currentTimeMillis() / 1000));
payParams.put("valid_time", in.getValidTime());
payParams.put("notify_url", in.getNotifyUrl());
//调用支付宝 App 支付所需的支付请求参数(形如 'app_id=xxx&biz_cnotallow=xxx...')
payParams.put("alipay_url", "");
WxPayParamVo vo = new WxPayParamVo();
vo.setApiKey(in.getWxApiKey());
vo.setAppId(in.getWxAppId());
vo.setAttach(in.getWxAttach());
vo.setBody(in.getBody());
vo.setMchId(in.getWxMchId());
vo.setMoney(in.getWxMoney());
vo.setNotifyUrl(in.getWxNotifyUrl());
vo.setServerPath(in.getServerPath());
vo.setTradeType("MWEB");
vo.setOutTradeNo(in.getOutOrderNo());
SortedMap<Object, Object> params = WeixinPay.weixinPayMobile(vo,request);
//调用微信 H5 支付统一下单接口 返回的 mweb_url 字段值(请注意不要进行 urlencode)
payParams.put("wx_url", params.get("mwebUrl").toString());
payParams.put("wx_type", "MWEB");
String sign = PayCommonUtil.toutiaoAlipaySign(payParams, in.getToutiaoMchSecrect());
payParams.put("sign", sign);
payParams.put("ip", request.getRemoteAddr());
payParams.put("deviceId", Long.toString(System.currentTimeMillis()));
payParams.put("prepayId", params.get("prepayId").toString());
logger.info("parameters==={}",payParams);
return JSONObject.fromObject(payParams);
}
}

将payParams实体类字段内容返回给前端,前端调用tt.pay唤起收银台支付

前端代码:

orderInfo: {
app_id: "800000040005",
sign_type: "MD5",
out_order_no: "MicroApp7075638135",
merchant_id: "1300000004",
timestamp: "1566720681",
product_code: "pay",
payment_type: "direct",
total_amount: 1,
trade_type: "H5",
uid: "2019012211",
version: "2.0",
currency: "CNY",
subject: "microapp test",
body: "microapp test",
trade_time: "1566720681",
valid_time: "300",
notify_url: "https://tp-pay.snssdk.com/cashdesk/test/paycallback",
wx_url:
"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx25161122572189727ea14cfd1832451500&package=2746219290",
wx_type: "MWEB",
alipay_url: "",
sign: "15aa99cd80878661a4d442b7540bdf96",
risk_info: '{"ip":"127.0.0.1","device_id":"485737374363263"}'
},
service: 1,
getOrderStatus(res) {
let { out_order_no } = res;
return new Promise(function(resolve, reject) {
// 商户前端根据 out_order_no 请求商户后端查询微信支付订单状态
tt.request({
url: "<your-backend-url>",
success(res) {
// 商户后端查询的微信支付状态,通知收银台支付结果
resolve({ code: 0 | 1 | 2 | 3 | 9 });
},
fail(err) {
reject(err);
}
});
});
},
success(res) {
if (res.code == 0) {
// 支付成功处理逻辑,只有res.code=0时,才表示支付成功
// 但是最终状态要以商户后端结果为准
}
},
fail(res) {
// 调起收银台失败处理逻辑
}
});


您的一键三连,是我更新的最大动力,谢谢

山水有相逢,来日皆可期,谢谢阅读,我们再会

我手中的金箍棒,上能通天,下能探海

上一篇:​​抖音小程序集成支付宝支付​