微信H5支付抖音小程序支付宝APP支付

微信

H5支付

官网文档链接:https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_4

流程图:

1、用户在商户侧完成下单,使用微信支付进行支付
2、由商户后台向微信支付发起下单请求(调用统一下单接口)注:交易类型trade_type=MWEB
3、统一下单接口返回支付相关参数给商户后台,如支付跳转url(参数名“mweb_url”),商户通过mweb_url调起微信支付中间页
4、中间页进行H5权限的校验,安全性检查(此处常见错误请见文档)
5、如支付成功,商户后台会接收到微信侧的异步通知
6、用户在微信支付收银台完成支付或取消支付,返回商户页面(默认为返回支付发起页面)
7、商户在展示页面,引导用户主动发起支付结果的查询
8,9、商户后台判断是否接到收微信侧的支付结果通知,如没有,后台调用我们的订单查询接口确认订单状态
10、展示最终的订单支付结果给用户

统一下单接口

URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder

必要参数

参数含义可以去官方文档查看,本文就不做解释了

<xml>
<appid>wx2421b1c4370ec43b</appid>
<attach>支付测试</attach>
<body>H5支付测试</body>
<mch_id>10000100</mch_id>
<nonce_str>1add1a30ac87aa2db72f57a2375d8fec</nonce_str>
<notify_url>http://wxpay.wxutil.com/pub_v2/pay/notify.v2.php</notify_url>
<openid>oUpF8uMuAJO_M2pxb1Q9zNjWeS6o</openid>
<out_trade_no>1415659990</out_trade_no>
<spbill_create_ip>14.23.150.211</spbill_create_ip>
<total_fee>1</total_fee>
<trade_type>MWEB</trade_type>
<scene_info>{"h5_info": {"type":"IOS","app_name": "王者荣耀","package_name": "com.tencent.tmgp.sgame"}}</scene_info>
<sign>0CB01533B8C1EF103065174F50BCA001</sign>
</xml>

签名

这些参数里面个人感觉也就签名稍微麻烦那么一点,需要把你将发送或者接收到的数据全部按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA,最后再拼上key得到即将加密的字符串stringSignTemp

key设置路径:微信商户平台(https://pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置

使用工具类生成微信方需要的签名

package com.abroad.utils;import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;/*** @author 萧一旬* @date Create in 10:35 2019/10/25*/
public class MD5 {private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7","8", "9", "a", "b", "c", "d", "e", "f"};/*** 转换字节数组为16进制字串** @param b 字节数组* @return 16进制字串*/public static String byteArrayToHexString(byte[] b) {StringBuilder resultSb = new StringBuilder();for (byte aB : b) {resultSb.append(byteToHexString(aB));}return resultSb.toString();}/*** 转换byte到16进制** @param b 要转换的byte* @return 16进制格式*/private static String byteToHexString(byte b) {int n = b;if (n < 0) {n = 256 + n;}int d1 = n / 16;int d2 = n % 16;return hexDigits[d1] + hexDigits[d2];}/*** MD5编码** @param origin 原始字符串* @return 经过MD5加密之后的结果*/public static String MD5Encode(String origin) {String resultString = null;try {resultString = origin;MessageDigest md = MessageDigest.getInstance("MD5");resultString = byteArrayToHexString(md.digest(resultString.getBytes("UTF-8")));} catch (Exception e) {e.printStackTrace();}return resultString;}/*** @param payKey 商户平台设置的密钥key* @param map    所有发送或者接收到的数据集合* @return signValue 签名*/public static String getSign(String payKey, Map<String, Object> map) {ArrayList<String> list = new ArrayList<String>();for (Map.Entry<String, Object> entry : map.entrySet()) {if (entry.getValue() != "") {list.add(entry.getKey() + "=" + entry.getValue() + "&");}}int size = list.size();String[] arrayToSort = list.toArray(new String[size]);//根据字符ASCII码进行递增排序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 += ("key=" + (payKey.trim()));return MD5.MD5Encode(result.toString()).toUpperCase();}/*** @param payKey 商户平台设置的密钥key* @param map    所有发送或者接收到的数据集合* @return signValue 签名*/public static String getSignToDouYin(String payKey, Map<String, Object> map) {ArrayList<String> list = new ArrayList<String>();for (Map.Entry<String, Object> entry : map.entrySet()) {if (entry.getValue() != "") {list.add(entry.getKey() + "=" + entry.getValue() + "&");}}int size = list.size();String[] arrayToSort = list.toArray(new String[size]);//根据字符ASCII码进行递增排序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 += payKey.trim();return MD5.MD5Encode(result.toString()).toUpperCase();}
}

将所有参数封装到map集合,调用getSign()方法生成所需要的签名

例子

//生成签名Map<String, Object> params = new HashMap<String, Object>();params.put("appid", order.getAppid());params.put("body", body);params.put("mch_id", order.getMch_id().trim());params.put("nonce_str", order.getNonce_str());params.put("notify_url", order.getNotify_url());params.put("out_trade_no", order.getOut_trade_no());params.put("spbill_create_ip", order.getSpbill_create_ip());params.put("total_fee", order.getTotal_fee());params.put("trade_type", order.getTrade_type());params.put("sign_type", "MD5");String sign = MD5.getSign(Configure.getKey(), params);

生成的签名,也可以使用官方的签名验证工具来验证签名的准确性
链接:https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=20_1

只要你生成的和用官方测试工具生成的签名一致,那签名这块就没问题了

当确认签名无误之后就可以封装数据了,需要准备的数据,大致就和上述的差不多,具体还需要什么具体对待

StringBuffer data = new StringBuffer();
data.append("<xml>");
data.append(" <appid>" + "<![CDATA[" + order.getAppid() + "]]>" + "</appid>");
data.append(" <body>" + "<![CDATA[" + body + "]]>" + "</body>");
data.append(" <mch_id>" + order.getMch_id().trim() + "</mch_id>");
data.append(" <nonce_str>" + "<![CDATA[" + order.getNonce_str() + "]]>" + "</nonce_str>");
data.append(" <notify_url>" + "<![CDATA[" + order.getNotify_url() + "]]>" + "</notify_url>");
data.append("  <out_trade_no>" + "<![CDATA[" + order.getOut_trade_no() + "]]>" + "</out_trade_no>");
data.append("  <spbill_create_ip>" + "<![CDATA[" + order.getSpbill_create_ip() + "]]>" + "</spbill_create_ip>");
data.append("  <total_fee>" + "<![CDATA[" + order.getTotal_fee() + "]]>" + "</total_fee>");
data.append("  <trade_type>" + "<![CDATA[" + order.getTrade_type() + "]]>" + "</trade_type>");
data.append("  <sign_type>" + "<![CDATA[MD5]]>" + "</sign_type>");
data.append("  <sign>" + "<![CDATA[" + order.getSign() + "]]>" + "</sign>");
data.append("</xml>");String result = HttpRequest.doPostJson("https://api.mch.weixin.qq.com/pay/unifiedorder", data.toString());

参数封装好后,就需要发请求给微信服务器了,发请求也是一个需要注意的点,最好还是使用那种广为流传的Http工具类,比如RestTemplate,HttpClient等等,因为这个原因,也算是走了不少的冤枉路

我使用的就是下面这个工具类

import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;/*** HttpClientUtil请求工具类** @author 萧一旬* @createTime 2018/6/27 15:24*/
public class HttpRequest {private HttpRequest(){}public static String doGet(String url, Map<String, String> param) {// 创建Httpclient对象CloseableHttpClient httpclient = HttpClients.createDefault();String resultString = "";CloseableHttpResponse response = null;try {// 创建uriURIBuilder builder = new URIBuilder(url);if (param != null) {for (String key : param.keySet()) {builder.addParameter(key, param.get(key));}}URI uri = builder.build();// 创建http GET请求HttpGet httpGet = new HttpGet(uri);// 执行请求response = httpclient.execute(httpGet);// 判断返回状态是否为200if (response.getStatusLine().getStatusCode() == 200) {resultString = EntityUtils.toString(response.getEntity(), "UTF-8");}} catch (Exception e) {e.printStackTrace();} finally {try {if (response != null) {response.close();}httpclient.close();} catch (IOException e) {e.printStackTrace();}}return resultString;}public static String doGet(String url) {return doGet(url, null);}public static String doPost(String url, Map<String, String> param) {// 创建Httpclient对象CloseableHttpClient httpClient = HttpClients.createDefault();CloseableHttpResponse response = null;String resultString = "";try {// 创建Http Post请求HttpPost httpPost = new HttpPost(url);// 创建参数列表if (param != null) {List<NameValuePair> paramList = new ArrayList<>();for (String key : param.keySet()) {paramList.add(new BasicNameValuePair(key, param.get(key)));}// 模拟表单UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);httpPost.setEntity(entity);}// 执行http请求response = httpClient.execute(httpPost);resultString = EntityUtils.toString(response.getEntity(), "utf-8");} catch (Exception e) {e.printStackTrace();} finally {try {response.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}return resultString;}public static String doPost(String url) {return doPost(url, null);}public static String doPostJson(String url, String json) {// 创建Httpclient对象CloseableHttpClient httpClient = HttpClients.createDefault();CloseableHttpResponse response = null;String resultString = "";try {// 创建Http Post请求HttpPost httpPost = new HttpPost(url);// 创建请求内容StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);httpPost.setEntity(entity);// 执行http请求response = httpClient.execute(httpPost);resultString = EntityUtils.toString(response.getEntity(), "utf-8");} catch (Exception e) {e.printStackTrace();} finally {try {response.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}return resultString;}}

当你给微信服务器发送请求之后,正常来说,他会给你返回一个xml格式的数据:

<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg><appid><![CDATA[wx2421b1c4370ec43b]]></appid><mch_id><![CDATA[10000100]]></mch_id><nonce_str><![CDATA[IITRi8Iabbblz1Jc]]></nonce_str><sign><![CDATA[7921E432F65EB8ED0CE9755F0E86D72F]]></sign><result_code><![CDATA[SUCCESS]]></result_code><prepay_id><![CDATA[wx201411101639507cbf6ffd8b0779950874]]></prepay_id><trade_type><![CDATA[MWEB]]></trade_type><mweb_url><![CDATA[https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx2016121516420242444321ca0631331346&package=1405458241]]></mweb_url>
</xml>

关于XML格式数据使用问题

xml格式数据当然不怎么方便我们使用,所以还得转换成所需的参数格式来方便使用

方式一

XStream是一个简单的基于Java库,Java对象序列化到XML,反之亦然(即:可以轻易的将Java对象和xml文档相互转换)

XStream xStream = new XStream();
xStream.alias("xml", OrderReturnInfo.class); OrderReturnInfo returnInfo = (OrderReturnInfo)xStream.fromXML(result);

转换成功后就可以做一些其他操作,比如生成支付订单信息什么的,这里就不上代码了,直接略过

方式二

其实不推荐直接把xml格式数据直接转成实体类,为什么这么说呢。
微信最近就对于退款返回的参数做了修改,然后转成实体类的时候,因为他新添了字段,但是实体类中却没有,所以转换的时候就会报错,这个时候还得重新修改代码再发布。
当时我的项目在线上就是使用的上述方法,然后微信突然多加了几个返回字段,导致退款是成功了,但是请求退款后,微信的返回值的处理失败,导致线上退款成功逻辑走不通。

所以还是推荐把xml格式数据转成JSONObject对象来使用

<!-- xml 转 json、-->
<dependency><groupId>com.fasterxml</groupId><artifactId>jackson-xml-databind</artifactId><version>0.6.2</version>
</dependency><!-- https://mvnrepository.com/artifact/net.sf.json-lib/json-lib -->
<!-- JSONObject -->
<dependency><groupId>net.sf.json-lib</groupId><artifactId>json-lib</artifactId><version>2.4</version>
</dependency>

PS:引入json-lib的时候可能报一下错误

  • Could not find artifact net.sf.json-lib: json-lib: jar:2.4 in central (https://repo.maven.apache.org/maven2)

解决办法:

<!-- https://mvnrepository.com/artifact/net.sf.json-lib/json-lib -->
<!-- JSONObject -->
<dependency><groupId>net.sf.json-lib</groupId><artifactId>json-lib</artifactId><version>2.4</version><classifier>jdk15</classifier>
</dependency>

使用测试:

@Test
void contextLoads() {//展示的测试数据就是最新的微信退款所返回的xml格式数据,可以和之前的对比,会发现多了很多数据,所以转成JavaBean的话就需要对JavaBean也做对应的修改String s = "<xml><return_code><![CDATA[SUCCESS]]></return_code>" +"<return_msg><![CDATA[OK]]></return_msg>" +"<appid><![CDATA[wxf518db02e2]]></appid>" +"<mch_id><![CDATA[14932]]></mch_id>" +"<nonce_str><![CDATA[RLP3V9ZOl]]></nonce_str>" +"<sign><![CDATA[2EA5B6559FC1CB79C1EE9D]]></sign>" +"<result_code><![CDATA[SUCCESS]]></result_code>" +"<transaction_id><![CDATA[4200005768960]]></transaction_id>" +"<out_trade_no><![CDATA[WeCh52]]></out_trade_no>" +"<out_refund_no><![CDATA[78hkdq8mf9autkyebfsfptxi]]></out_refund_no>" +"<refund_id><![CDATA[5000000642200624]]></refund_id>" +"<refund_channel><![CDATA[]]></refund_channel>" +"<refund_fee>10</refund_fee>" +"<coupon_refund_fee>0</coupon_refund_fee>" +"<total_fee>10</total_fee>" +"<cash_fee>10</cash_fee>" +"<coupon_refund_count>0</coupon_refund_count>" +"<cash_refund_fee>10</cash_refund_fee>" +"</xml>";XmlMapper xmlMapper = new XmlMapper();JSONObject jsonObject = null;try {jsonObject = xmlMapper.readValue(s, JSONObject.class);System.out.println("jsonObject = " + jsonObject);if ("SUCCESS".equals(jsonObject.get("return_code")) && "SUCCESS".equals(jsonObject.get("result_code"))) {System.out.println("退款成功");}} catch (Exception e) {e.printStackTrace();}}

封装返回

当你做完所有操作后,就得开始封装返回给字节跳动的数据,所以我们对于字节跳动的文档多多少少也还是要熟悉一点。
当我们返回出去后,前端就会调用字节跳动的tt.pay接口,所以我们返回的参数还需要根据这个接口来规定

官方链接:https://microapp.bytedance.com/dev/cn/mini-app/develop/open-capacity/payment/pay

/*** @author 萧一旬* @date Create in 9:19 2019/10/31*/@Data
public class HFivePayResultToDouYin {/*** 头条支付分配给商户 app_id*/private String appid;/*** 签名*/private String sign;/*** 交易类型*/private String trade_type;/*** 微信支付跳转链接*/private String mweb_url;/*** 用户id*/private String userId;/*** 下单时间戳*/private long trade_time;/*** 请求时间戳*/private long timestamp;/*** 订单号*/private String out_order_no;/*** 商户号*/private String merchant_id;/*** 商户订单详情*/private String body;/*** 商户订单名称*/private String subject;/*** 金额*/private int total_amount;/*** 调用支付宝 App 支付所需的支付请求参数*/private String alipay_url;
}

实体类有了,接下来肯定就是给实体类封装参数了,其他参数没什么好说的,字节跳动需要的订单号out_order_no,是生成订单时的订单唯一标识

然后还值得一提的就是字节跳动需要的签名了,这个签名我用上述的那个签名方法说我签名错误,最后还是看官方给的Demo才成功签名

这个签名我们需要使用下面这个签名类,我不知道上面微信的签名能不能使用这个方法,我也没去尝试

调用BuildMd5WithSalt()即可,map集合依旧是我们的参数集合,salt就是字节跳动给我们的支付secret

package com.abroad.utils;import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.codec.binary.Base64;import java.io.IOException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;public class SignUtil {public static String BuildMd5WithSalt(Map<String, Object> dataMap, String salt) {String signStr = GenSignStr(dataMap);return DigestUtils.md5Hex(signStr + salt);}private static String GenSignStr(Map<String, Object> data) {StringBuilder sb = new StringBuilder();Set<String> entrySet = data.keySet();List<String> list = new ArrayList<String>(entrySet);Collections.sort(list);for (String key: list) {if ( key.equals("") || data.get(key).equals("") || data.get(key)==null) {continue;}sb.append(key);sb.append("=");sb.append(data.get(key));sb.append("&");}if (sb.length() > 0) {sb.deleteCharAt(sb.length() - 1);}return sb.toString();}public static boolean VerifyMd5WithRsa(Map<String, Object> params, String sign, String publicKey) throws SignatureException, InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException {String signStr = GenSignStr(params);return RsaVerify(signStr, sign, publicKey);}private static boolean RsaVerify(String target, String verifySign, String publicKey) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException {//获取公钥byte[] keyBytes = Base64.decodeBase64(publicKey);//解密由base64编码的公钥X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); // 构造X509EncodedKeySpec对象//公钥验签KeyFactory keyFactory = KeyFactory.getInstance("RSA");PublicKey publicK = keyFactory.generatePublic(keySpec);Signature signature = Signature.getInstance("MD5withRSA");signature.initVerify(publicK);signature.update(target.getBytes());byte[] signBytes = Base64.decodeBase64(verifySign);return signature.verify(signBytes);}}

例子:

Map<String, Object> objectMap = new HashMap<>();objectMap.put("app_id", hFivePayResultToDouYin.getAppid());objectMap.put("merchant_id", hFivePayResultToDouYin.getMerchant_id());objectMap.put("timestamp", hFivePayResultToDouYin.getTimestamp());objectMap.put("sign_type", "MD5");objectMap.put("out_order_no", hFivePayResultToDouYin.getOut_order_no());objectMap.put("total_amount", hFivePayResultToDouYin.getTotal_amount());objectMap.put("product_code", "pay");objectMap.put("payment_type", "direct");objectMap.put("trade_type", "H5");objectMap.put("version", "2.0");objectMap.put("currency", "CNY");objectMap.put("subject", hFivePayResultToDouYin.getSubject());objectMap.put("body", hFivePayResultToDouYin.getBody());objectMap.put("uid", hFivePayResultToDouYin.getUserId());objectMap.put("trade_time", hFivePayResultToDouYin.getTrade_time());objectMap.put("valid_time", "300");objectMap.put("notify_url", "https://tp-pay-test.snssdk.com/cashdesk/test/paycallback");objectMap.put("wx_type", hFivePayResultToDouYin.getTrade_type());objectMap.put("wx_url", hFivePayResultToDouYin.getMweb_url());String douyinSign = SignUtil.BuildMd5WithSalt(objectMap, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
//                Map<String,Object> objectMap = new HashMap<>();
//                objectMap.put("app_id", "800000000001");
//                objectMap.put("merchant_id", "1900000001");
//                objectMap.put("timestamp", "1570694312");
//                objectMap.put("sign_type", "MD5");
//                objectMap.put("out_order_no", "201900000000000001");
//                objectMap.put("total_amount", "1");
//                objectMap.put("product_code", "pay");
//                objectMap.put("payment_type", "direct");
//                objectMap.put("trade_type", "H5");
//                objectMap.put("version", "2.0");
//                objectMap.put("currency", "CNY");
//                objectMap.put("subject", "测试订单");
//                objectMap.put("body", "测试订单");
//                objectMap.put("uid", "0000000000000001");
//                objectMap.put("trade_time", "1570585744");
//                objectMap.put("valid_time", "300");
//                objectMap.put("notify_url", "");
//                objectMap.put("wx_type", "MWEB");
//                objectMap.put("wx_url", "https://wx.tenpay.com/xxx");
//                objectMap.put("alipay_url", "app_id=2019000000000006&biz_content=xxxx");
//                String douyinSign = SignUtil.BuildMd5WithSalt(objectMap,"a");hFivePayResultToDouYin.setSign(douyinSign);

如果你想验证这个签名方式的正确性的话,可以使用官方给你的测试数据(被我注释的部分)进行签名,如果你生成的签名和官方给的结果一样,那就说明签名是正确的

这里需要注意的是:

  • 微信签名最后拼接key的时候,是&key=xxxxxx,而字节跳动的签名是直接拼接xxxxxx

关于网友的疑惑

有网友评论说没有在官方文档中看到需要拼接字符密钥key,所以在这里附上官网文档的地址:https://microapp.bytedance.com/dev/cn/mini-app/develop/open-capacity/payment/mini-app-pay-plugin-reference/server-sign

当你调用微信统一下单接口成功返回之后,也就字节跳动的签名需要稍微注意一点,其他被需要的参数都很容易获取,封装好后返回给前端即可

这个时候如果你没有配置支付域名的话好像是还不能成功吊起支付的

首先我们需要在微信支付平台->产品中心->我的产品配置H5支付域名
其次,还需要在微信公众平台-网页授权域名,这个没什么好说的,跟着配置就行,我是把这个文件直接丢到了项目里

做完这些,微信统一下单以及客户端跳转支付就完成了。

查询订单

逻辑代码:微信小程序技术摘要-查询订单

退款

逻辑代码:微信小程序技术摘要-退款

下单退款简化

使用第三方简化请求过程,内容在微信小程序文章中:微信小程序技术摘要-使用第三方API实现支付退款

支付宝

根据官方文档可知,字节跳动小程序调用支付宝支付是接入APP支付,像那些创建应用,签约支付以及密钥设置获取的操作,跟着官方文档来就行

app支付接口2.0

HTTPS请求地址:https://openapi.alipay.com/gateway.do

请求支付宝接口并按要求返回

此代码被整合成了一篇新的博客:支付宝接入技术摘要-APP支付

alipayResponse.getBody()封装到上述说的返回JavaBean中返回即可

hFivePayResultToDouYin.setAlipay_url(alipayResponse.getBody());

同样的,在待签名的数据中也要把它加进去

objectMap.put("alipay_url", hFivePayResultToDouYin.getAlipay_url());

这个时候,前端字节跳动收银台也可以进入到支付宝进行支付了,反正我是可以了[捂脸.jpg]

退款

代码访问链接:支付宝接入技术摘要-退款

支付回调

微信和支付宝的回调从某种角度上其实可以说是一致的,所以其实回调API基本上没什么区别

代码详见:支付宝接入技术摘要-支付回调-解析流获取参数

支付宝官方文档:https://opendocs.alipay.com/open/203/105286

微信官方文档:https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_7&index=8

PS:官方文档都均提到了验签,但是实践表明也可以不验签。以及微信官方文档说,当一切无误需按指定的格式返回给微信,但是我测试的时候也没有按指定格式返回也并未出什么错(百度小程序是需要严格按照规定格式返回)

解析获取用户手机号

这个方法将用户的信息转成了JSONObject对象

//这三个参数前端都会传
public static JSONObject getDyUserPhoneNumber(String code, String iv, String encryptedData) {String appid = "";String appSecret = "";String apiUrl = "https://developer.toutiao.com/api/apps/jscode2session?appid=" + appid + "&secret=" + appSecret + "&code=" + code;System.out.println(apiUrl);String responseBody = HttpClientUtil.doGet2(apiUrl);System.out.println(responseBody);//这里是获取session_key,解密用的JSONObject jsonObject = JSON.parseObject(responseBody);if (!StringUtil.isEmpty(jsonObject.getString("openid")) && !StringUtil.isEmpty(jsonObject.getString("session_key"))) {//解密获取用户信息JSONObject userInfoJSON = getUserInfo(encryptedData, jsonObject.getString("session_key"), iv);if (userInfoJSON != null) {userInfoJSON.put("openId", jsonObject.getString("openid"));return userInfoJSON;}}return null;
}public static JSONObject getUserInfo(String encryptedData, String sessionKey, String iv) {// 被加密的数据byte[] dataByte = Base64Utils.decode(encryptedData.getBytes());// 加密秘钥byte[] keyByte = Base64Utils.decode(sessionKey.getBytes());// 偏移量byte[] ivByte = Base64Utils.decode(iv.getBytes());try {// 如果密钥不足16位,那么就补足.  这个if 中的内容很重要int base = 16;if (keyByte.length % base != 0) {int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);byte[] temp = new byte[groups * base];Arrays.fill(temp, (byte) 0);System.arraycopy(keyByte, 0, temp, 0, keyByte.length);keyByte = temp;}// 初始化Security.addProvider(new BouncyCastleProvider());Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");parameters.init(new IvParameterSpec(ivByte));cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化byte[] resultByte = cipher.doFinal(dataByte);if (null != resultByte && resultByte.length > 0) {String result = new String(resultByte, "UTF-8");return JSON.parseObject(result);}} catch (NoSuchAlgorithmException e) {log.error(e.getMessage(), e);} catch (NoSuchPaddingException e) {log.error(e.getMessage(), e);} catch (InvalidParameterSpecException e) {log.error(e.getMessage(), e);} catch (IllegalBlockSizeException e) {log.error(e.getMessage(), e);} catch (BadPaddingException e) {log.error(e.getMessage(), e);} catch (UnsupportedEncodingException e) {log.error(e.getMessage(), e);} catch (InvalidKeyException e) {log.error(e.getMessage(), e);} catch (InvalidAlgorithmParameterException e) {log.error(e.getMessage(), e);} catch (NoSuchProviderException e) {log.error(e.getMessage(), e);}return null;
}

结语

代码写到这的话,基本上也就差不多了,支付和退款。
另外支付宝上也没有类似的微信的订单查询接口[可能有但是我没找到],然后再者字节跳动文档上tt.paygetOrderStatus(res)的注释中也只是说查询微信而没有查支付宝,所以我也就没有写这个接口了

还有值得注意的就是退款方面了,支付宝支付的订单需要在支付宝退款,微信支付的订单需要在微信退款,所以这就需要在支付回调的时候区分开来(比如加个字段值来记录是微信或者支付宝,然后这个字段的值是由支付回调接口里判断给值)

字节跳动小程序技术摘要相关推荐

  1. 字节跳动小程序担保支付请求签名算法-Java实现

    最近在做字节跳动小程序支付相关的功能,官方提供的支付接口,调用需要使用请求签名,但是官方给的加签示例没有Java的实现.这里记录一下Java实现的加签算法. 官方要求的请求签名算法: 1. sign, ...

  2. 百度小程序,字节跳动小程序支付总结

    百度小程序支付: 百度小程序支付生成的签名是rsa签名,这个签名的生成java版本官方给的有demo直接拿过来用就可以,参数传输正确,该去空格的去空格,该加标点符号的加标点符号,rsa公钥私钥保存好, ...

  3. 字节跳动pest分析_字节跳动小程序分析:前景及优势都是什么?

    近几年小程序渐渐成为微信.百度.支付宝等巨头的标配,各大互联网巨头纷纷加码小程序,字节跳动自然也不甘落后.字节跳动小程序前景如何呢?我们来做一个详细的字节跳动小程序分析: 1.平台条件 小程序要想做起 ...

  4. 字节跳动小程序接入支付功能

    我个人博客文章地址:字节跳动小程序接入支付功能 这个文章只是我做完支付后的总结,尽量写的简单易懂,也是方便自己以后忘了看一眼就能懂

  5. 关于今日头条小程序(字节跳动小程序)相关问题

    上周突然遇到让开发今日头条小程序(字节跳动小程序)的需求,然后看了下文档发现真的简陋.而且文档有错别字.槽点有点多.但是和老版本的小程序相差不多,然后我就抱着试一试的心态,真的是试一试把微信小程序换了 ...

  6. 关于字节跳动小程序授权问题解决方案

    授权弹窗无法调起 在字节跳动小程序中调用有关于用户授权的接口的时候, 如果在首次调起授权弹窗的时候用户选择了拒绝,此时再次调用该接口的时候,将不会再次调起授权弹窗. 在字节跳动官方文档中有关于这种情况 ...

  7. 字节跳动小程序对接环信IM遇到的问题

    前言 这里是一个工作了1年多的前端小白,这是从业以来的第一篇文章,主要跟大家分享一下最近在开发字节跳动小程序时遇到一些问题与自己的解决方法,如果有什么更好的解决方案欢迎大佬们指点一下,写的不好的地方还 ...

  8. 字节跳动小程序tt.pay支付流程和遇到的问题

    搞了好几天的服务暂不可用,或者sign错误,缺少app_id什么的,终于可以了.  我主要说下流程和我遇到的问题 我是用的tt.pay(). 首先流程:一.后端发送post请求到http://tp-p ...

  9. 字节跳动小程序服务器域名怎么填,字节跳动小程序配置

    ## **字节跳动小程序配置流程** **1.字节跳动官方后台申请小程序并认证** 字节跳动注册:https://microapp.bytedance.com/ **2.下载字节跳动开发者工具**: ...

最新文章

  1. Java 中 PO 与 VO 的区别
  2. 23、Power Query-XML与JSON数据获取
  3. laravel5.8笔记八:数据库(单库和多库)
  4. MySQL触发器介绍
  5. 孕妇可以使用计算机,【电脑对孕妇有影响吗】电脑对孕妇的危害,孕妇能玩电脑吗 - 妈妈网百科...
  6. jquery插件导出WORD
  7. 洛谷3396 哈希冲突 【分块】
  8. OSPF的基本配置介绍
  9. 摄像头网络安全如何保障
  10. chrome网页另存pdf_如何在Google Chrome中将网页另存为PDF
  11. OPNET14.5+WIN10+VS2010安装教程(附安装包)
  12. Python 绘制属于你的世界地图
  13. MIPI 和 DSI 协议
  14. 机器学习与算法(4)--本地散点平滑估计(LOESS)
  15. 如何将高效设计应用于 DAO?
  16. Ubuntu 安装 XDM 2018 ( Xtreme Download Manager 2018 )
  17. Leap Motion 之Unity 开发实战(一. 制作手的HandController)
  18. Outlook Connector用途
  19. 堆叠降噪自动编码器 Stacked Denoising Auto Encoder(SDAE)
  20. matlab第六章课后答案,MATLAB教程2012a第6章习题解答-张志涌

热门文章

  1. Hashmap,Set,Map,List,ArrayList的区别
  2. 【css】垂直居中的几种写法
  3. 地图之CLLocationManager的使用 定位功能使用
  4. Java的List排序
  5. 如何提高分布式系统的可用性
  6. java regex
  7. 【转载】如何:为 Windows Phone 创建警报和提醒
  8. android 中文 API (41) —— RatingBar.OnRatingBarChangeListener
  9. 如何在全局程序集缓存 (GAC) 中安装 DLL 文件
  10. Python--day28--set去重