之前写的一个开发注意事项总结:
微信百度支付开发
百度流程代码梳理:
百度小程序代码
今天梳理一下微信小程序支付这块儿的代码。
controller层的代码主要是为了和前端进行交互,传一些参数,跟自己的业务需求相关,就不梳理了。主要说一下service层以及一些utils。
/**
* 微信端操作,调用微信统一支付订单API,并将信息封装
*
* @param openId
* @param fee
* @param IP
*/
@Override
public Map<String, String> WXPaymentService((一些params,根据自己的业务来)) {
//相关参数校验逻辑
*******
//构建订单相关信息
WXOrderInfoVO wxOrderInfoVO = buildOrderInfo(openId, fee, IP);
//转换成要求格式
String paramString = WXPayUtil.mapToXml(ObjectToMapUtil.objectToMap(wxOrderInfoVO));
//调用统一支付接口
String result = WXPayUtil.httpRequest(payURL, RewardConstants.POST, paramString);
//返回参数解析
SortedMap<String, String> returnParams = WXPayUtil.parseXml(result);
//校验,二次签名
Map<String, String> returnInfo = WXPayUtil.reSign(returnParams, wxOrderInfoVO);
//生成预付订单成功才存入数据库
调用DAO层,操作数据库,为了之后对账
return returnInfo;
}
/**
* 生成微信统一支付订单相关信息,对照微信文档,一个个属性赋值
*中间用到了一些我自己定义的常量约束
* @param openId
* @param fee
* @param IP
* @return
*/
private WXOrderInfoVO buildOrderInfo(String openId, Integer fee, String IP) {
WXOrderInfoVO wxOrderInfoVO = new WXOrderInfoVO();
wxOrderInfoVO.setAppid(appid);
wxOrderInfoVO.setMch_id(mch_id);
wxOrderInfoVO.setOpenid(openId);
wxOrderInfoVO.setTotal_fee(fee);
//生成唯一的随机商户订单号,微信文档有算法介绍
wxOrderInfoVO.setOut_trade_no(GenerateTradeNumberUtil.getTradeNumber(RewardConstants.WXTRADELENGTH));
//生成随机字符串,微信文档有生成算法推荐
wxOrderInfoVO.setNonce_str(RandomStringUtil.getRandomStringByLength(RewardConstants.WXNONCELENGTH));
wxOrderInfoVO.setBody(RewardConstants.DESCRIPTION);
wxOrderInfoVO.setSpbill_create_ip(IP);
wxOrderInfoVO.setNotify_url(RewardConstants.NOTIFYURL);
wxOrderInfoVO.setTrade_type(RewardConstants.TRADETYPE);
//根据要传递的参数生成签名字符串
String sign = WXPayUtil.sign(wxOrderInfoVO);
wxOrderInfoVO.setSign(sign);
return wxOrderInfoVO;
}
用到的微信订单包装类:
/**
* 订单信息,调用微信支付统一下单API用,直接可以转成String类型的xml,所以变量命名不符合小驼峰
* 和微信要求的参数名称保持了一致
*/
@Data
public class WXOrderInfoVO {
String appid;
String mch_id;
String device_info;
String nonce_str;
String sign;
String sign_type;
String body;
String detail;
String attach;
String out_trade_no;
String fee_type;
Integer total_fee;
String spbill_create_ip;
String time_start;
String time_expire;
String goods_tag;
String notify_url;
String trade_type;
String product_id;
String limit_pay;
String openid;
String receipt;
JSONObject scene_info;
}
用到的工具类:因为生成签名时要根据参数的名称排字典序,所以我没有用hashmap而是直接用了treemap,这样put的时候就自动按字典顺序进行排序了。
/**
* 支付操作工具类
*
* @author zishuzhao
*/
@Slf4j
public class WXPayUtil {
private static final String SIGN = "sign";
private static final String SIGNTYPE = "sign_type";
private static InputStream String2Inputstream(String str) {
return new ByteArrayInputStream(str.getBytes());
}
/**
* 将返回的XML字符串转换为Map
*/
public static SortedMap<String, String> parseXml(String strxml) {
if (null == strxml || "".equals(strxml)) {
log.error("wx return null");
return Maps.newTreeMap();
}
try {
SortedMap<String, String> m = new TreeMap<>();
InputStream in = String2Inputstream(strxml);
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 = getChildrenText(children);
}
m.put(k, v);
}
//关闭流
in.close();
return m;
} catch (Exception e) {
log.error("parse returnInfo error.");
throw new ServiceException(SystemCode.PARSE_FAIL);
}
}
/**
* 获取子节点的xml
*/
private 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(getChildrenText(list));
}
sb.append(value);
sb.append("</" + name + ">");
}
}
return sb.toString();
}
/**
* map转换成xml格式
*/
public static String mapToXml(SortedMap<String, String> map) {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
//防止XXE攻击
documentBuilderFactory.setXIncludeAware(false);
documentBuilderFactory.setExpandEntityReferences(false);
DocumentBuilder documentBuilder = null;
try {
documentBuilder = documentBuilderFactory.newDocumentBuilder();
org.w3c.dom.Document document = documentBuilder.newDocument();
org.w3c.dom.Element root = document.createElement("xml");
document.appendChild(root);
for (String key : map.keySet()) {
String value = map.get(key);
if (value == null || value.equals("")) {
continue;
}
value = value.trim();
org.w3c.dom.Element filed = document.createElement(key);
filed.appendChild(document.createTextNode(value));
root.appendChild(filed);
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMSource source = new DOMSource(document);
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
String output = writer.getBuffer().toString();
//去掉第一行的约束字段
int index = output.indexOf("<xml>");
writer.close();
return output.substring(index);
} catch (Exception e) {
log.error("map to xml error.");
throw new ServiceException(SystemCode.GENERATEPARAM_FAIL);
}
}
/**
* 拼接参数字符串,在参数最后拼接上商户的密钥
*/
public static String createLinkString(SortedMap<String, String> params) {
List<String> keys = new ArrayList<>(params.keySet());
StringBuilder prestr = new StringBuilder();
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key);
if (value == null || value.equals("")) {
continue;
}
if (i == keys.size() - 1) {
prestr.append(key).append("=").append(value);
} else {
prestr.append(key).append("=").append(value).append("&");
}
}
prestr.append("&").append("key=").append(RewardConstants.WXSECRETKEY);
return prestr.toString();
}
/**
* 通过订单信息生成sign签名
*/
public static String sign(WXOrderInfoVO wxOrderInfoVO) {
SortedMap<String, String> params = buildMap(wxOrderInfoVO);
String src = createLinkString(params);
String sign = DigestUtils.md5Hex(src).toUpperCase();
return sign;
}
/**
* 参数字符串已拼接好,生成签名
*/
public static String sign(String src) {
return DigestUtils.md5Hex(src).toUpperCase();
}
/**
* 将订单信息转换为参数map
*/
private static SortedMap<String, String> buildMap(WXOrderInfoVO wxOrderInfoVO) {
try {
return ObjectToMapUtil.objectToMap(wxOrderInfoVO);
} catch (Exception e) {
log.error("class to map error.");
e.printStackTrace();
}
return null;
}
/**
* 调用微信统一支付接口
*
* @param requestUrl 请求地址
* @param requestMethod 请求方法类型
* @param outputStr 请求参数信息
*/
public static String httpRequest(String requestUrl, String requestMethod, String outputStr) {
log.info("begin call wx download api");
// 创建SSLContext
StringBuffer buffer = null;
try {
URL url = new URL(requestUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(requestMethod);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.connect();
//往服务器端写内容
if (null != outputStr) {
OutputStream os = conn.getOutputStream();
os.write(outputStr.getBytes("utf-8"));
os.close();
}
// 读取服务器端返回的内容
InputStream is = conn.getInputStream();
InputStreamReader isr = new InputStreamReader(is, "utf-8");
BufferedReader br = new BufferedReader(isr);
buffer = new StringBuffer();
String line = null;
while ((line = br.readLine()) != null) {
buffer.append(line);
buffer.append("\n");
}
br.close();
log.info("receive data done");
} catch (Exception e) {
log.error("call api error.");
throw new ServiceException(SystemCode.CALLAPI_FAIL);
}
return buffer.toString();
}
/**
* 微信返回的参数解析,sign,然后返回给前端
*/
public static Map<String, String> reSign(SortedMap<String, String> params, WXOrderInfoVO wxOrderInfoVO) {
log.info("enter resign");
Map<String, String> payMap = new HashMap<>();
String returnCode = params.get("return_code");
String resultCode = params.get("result_code");
String returnMessage = params.get("return_msg");
String errCode = params.get("err_code");
String errCodeDes = params.get("err_code_des");
log.info("begin resign");
//都为SUCCESS才会有下列信息
if (returnCode.equals(RewardConstants.SUCCESS) && resultCode.equals(RewardConstants.SUCCESS)) {
if (!checkParam(params, wxOrderInfoVO)) {
log.error("check return params error.");
throw new ServiceException(SystemCode.CHECK_FAIL);
}
payMap.put("nonceStr", wxOrderInfoVO.getNonce_str());
payMap.put("package", "prepay_id=" + params.get("prepay_id"));
Long timeStamp = System.currentTimeMillis() / 1000;
payMap.put("timeStamp", String.valueOf(timeStamp));
//拼接签名需要的参数
String stringSignTemp = "appId=" + wxOrderInfoVO.getAppid() + "&nonceStr=" + wxOrderInfoVO.getNonce_str()
+ "&package=prepay_id=" + params.get("prepay_id") + "&signType=MD5&timeStamp=" + timeStamp
+ "&key=" + RewardConstants.WXSECRETKEY;
//再次签名,这个签名用于小程序端调用wx.requesetPayment方法
String paySign = WXPayUtil.sign(stringSignTemp);
payMap.put("paySign", paySign);
payMap.put("appid", wxOrderInfoVO.getAppid());
return payMap;
}
if (returnCode.equals(RewardConstants.SUCCESS) && resultCode.equals(RewardConstants.FAIL)) {
log.error("WeChat report error,pay fail.");
throw new ServiceException(SystemCode.RETURN_FAIL, errCodeDes);
}
if (returnCode.equals(RewardConstants.FAIL)) {
log.error("WeChat report error,pay fail.");
throw new ServiceException(SystemCode.RETURN_FAIL, returnMessage);
}
log.error("resign error");
return null;
}
/**
* 二次签名前对微信接口返回的信息进行校验
*/
private static boolean checkParam(SortedMap<String, String> params, WXOrderInfoVO wxOrderInfoVO) {
if (params.get("appid").isEmpty() || !params.get("appid").equals(wxOrderInfoVO.getAppid())) {
return false;
}
if (params.get("mch_id").isEmpty() || !params.get("mch_id").equals(wxOrderInfoVO.getMch_id())) {
return false;
}
if (params.get("nonce_str").isEmpty()) {
return false;
}
//应该根据返回的信息重新生成sign,再和这个sign进行比较
if (params.get("sign").isEmpty() || !checkSign(params)) {
return false;
}
if (params.get("prepay_id").isEmpty()) {
return false;
}
if (!params.get("trade_type").equals(RewardConstants.TRADETYPE)) {
return false;
}
return true;
}
/**
* 对微信返回的信息重新生成签名,并和微信返回信息中的签名进行比对
*/
private static boolean checkSign(SortedMap<String, String> params) {
String remoteSign = params.get("sign");
params.remove("sign");
String localSign = sign(createLinkString(params));
if (!remoteSign.equals(localSign)) {
return false;
}
params.put("sign", remoteSign);
return true;
}
/**
* 去除返回信息参数中的空值和签名
*/
public static SortedMap<String, String> paraFilter(Map<String, String> sArray) {
SortedMap<String, String> result = new TreeMap<>();
if (sArray == null || sArray.size() <= 0) {
return result;
}
for (String key : sArray.keySet()) {
String value = sArray.get(key);
if (value == null || value.equals("") || key.equalsIgnoreCase(SIGN)
|| key.equalsIgnoreCase(SIGNTYPE)) {
continue;
}
result.put(key, value);
}
return result;
}
}
这里边还用到了一个工具类,就是把自己的包装类转换成map,这里我也直接转为了treemap,具体代码如下:
/**
* 类转为SortedMap
*
* @author zishuzhao
*/
@Slf4j
public class ObjectToMapUtil {
/**
* 将指定对象属性名称和属性值转化为Map键值对
*
* @param obj
* @return
*/
public static SortedMap<String, String> objectToMap(Object obj) {
if (obj == null) {
log.error("obj empty.");
throw new ServiceException(SystemCode.PARAM_MISS);
}
Class clazz = obj.getClass();
SortedMap<String, String> map = new TreeMap();
getClass(clazz, map, obj);
SortedMap<String, String> newMap = convertSortedMap(map);
return newMap;
}
/**
*
*/
private static void getClass(Class clazz, SortedMap map, Object obj) {
if (clazz.getSimpleName().equals("Object")) {
return;
}
Field[] fields = clazz.getDeclaredFields();
if (fields == null || fields.length <= 0) {
throw new ServiceException(SystemCode.PARAM_MISS);
}
for (int i = 0; i < fields.length; i++) {
fields[i].setAccessible(true);
String name = fields[i].getName();
Object value = null;
try {
value = fields[i].get(obj);
} catch (IllegalAccessException e) {
log.error("field get value error");
e.printStackTrace();
}
map.put(name, value);
}
Class superClzz = clazz.getSuperclass();
getClass(superClzz, map, obj);
}
/**
* @param map
* @return
* @throws Exception
*/
private static SortedMap convertSortedMap(SortedMap map) {
SortedMap newMap = new TreeMap();
Set keys = map.keySet();
Iterator it = keys.iterator();
while (it.hasNext()) {
Object key = it.next();
convertToString(map.get(key), newMap, key);
}
return newMap;
}
/**
* @param value
* @param newMap
* @param key
*/
private static void convertToString(Object value, SortedMap newMap, Object key) {
if (value != null) {
Class clazz = value.getClass();
if (isBaseType(clazz)) {
newMap.put(key, value.toString());
}
}
}
/**
* @param clazz
* @return
*/
private static boolean isBaseType(Class clazz) {
if (clazz == String.class) {
return true;
}
if (clazz == Integer.class) {
return true;
}
return false;
}
}
中间用的到常量约束,随机字符串生成工具类(雪花算法等)我就不贴出来了,都是很简单的一些代码,根据个人和业务要求进行选择。
至此,整个支付功能就打通了,剩下的就是前段调起支付界面,然后后端接受微信的回调信息。
**TIPS:**因为可能会有高并发的情况,但是不能同时有多个相同的订单,所以自己根据业务需求解决高并发。
这是我controller层回调函数接口:
/**
* 回调接口,处理微信返回的支付消息
*/
@PostMapping("/wxpayback")
public void WXNotify(HttpServletRequest request, HttpServletResponse response) {
log.info("wx payback enter");
String resultXML = wxPayCallbackService.payCallback(request, response);
try {
BufferedOutputStream out = new BufferedOutputStream(
response.getOutputStream());
out.write(resultXML.getBytes());
out.flush();
out.close();
log.info("response wx callback api");
} catch (IOException e) {
log.error("stream close error.");
e.printStackTrace();
}
}
处理回调的service层代码:
public String payCallback(HttpServletRequest request, HttpServletResponse response) {
String resultXML = "";
//微信返回的数据流,xml形式的String
String notityXml = parseData(request);
if (notityXml == null) {
log.error("callback parse error.");
resultXML = WXCallbackUtil.callbackError();
} else {
SortedMap<String, String> map = WXPayUtil.parseXml(notityXml);
//只有返回成功时,才有参数,才会进行参数校验
String returnCode = map.get("return_code");
if (returnCode == null) {
log.error("wx return format error.");
resultXML = WXCallbackUtil.callbackError();
} else {
if (returnCode.equals(RewardConstants.SUCCESS)) {
String sign = WXPayUtil.sign(WXPayUtil.createLinkString(WXPayUtil.paraFilter(map)));
Integer originalFee = weChatOrderInfoMapper.searchFee(map.get("out_trade_no"));
//对通知消息的签名校验,并对金额参数和原始参数进行校验
int checkResult = WXCallbackUtil.checkCallbackParam(map, sign, originalFee);
//校验通过,对消息的处理
if (checkResult == RewardConstants.CHECKSUCCESS) {
updateOrder(map, buildCallbackInfo(map.get("out_trade_no"), notityXml));
resultXML = WXCallbackUtil.callbackSuccess();
}
//签名错误,返回对应信息
if (checkResult == RewardConstants.SIGNERROR) {
resultXML = WXCallbackUtil.signError();
}
//参数错误,金额对不上或者订单查不到
if (checkResult == RewardConstants.PARAMERROR) {
resultXML = WXCallbackUtil.paramError();
}
}
log.info("receive wx callback request");
}
}
return resultXML;
}
/**
* 解析返回的数据流信息
*/
private String parseData(HttpServletRequest request) {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
String line = null;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) {
sb.append(line);
}
br.close();
return sb.toString();
} catch (IOException e) {
log.error("stream parse error.");
e.printStackTrace();
}
return null;
}
/**
* 构造返回信息表包装类
*/
private PayCallbackInfo buildCallbackInfo(String localOrderNO, String text) {
PayCallbackInfo payCallbackInfo = new PayCallbackInfo();
payCallbackInfo.setLocalOrderNo(localOrderNO);
payCallbackInfo.setCallback(text);
return payCallbackInfo;
}
/**
* 业务处理,DAO层代码
*/
private void updateOrder(SortedMap<String, String> param, PayCallbackInfo payCallbackInfo) {
自己的业务逻辑代码
}
/**
* 创建支付成功消息
*/
private UserMessageFO buildSuccessInfo(String userId) {
UserMessageFO userMessageFO = new UserMessageFO();
userMessageFO.setUserId(userId);
userMessageFO.setAppId(AppIdEnum.APPLET_WX.getCode());
userMessageFO.setTitle("您的打赏支付已成功");
userMessageFO.setContent("您提交的打赏支付已经成功");
return userMessageFO;
}
/**
* 创建支付失败消息
*/
private UserMessageFO buildFailInfo(String userId) {
UserMessageFO userMessageFO = new UserMessageFO();
userMessageFO.setUserId(userId);
userMessageFO.setAppId(AppIdEnum.APPLET_WX.getCode());
userMessageFO.setTitle("您的打赏支付失败");
userMessageFO.setContent("您提交的打赏支付未成功");
return userMessageFO;
}
其中WXCallbackUtil工具类是校验参数,和生成对应状态返回字符串信息的相关函数,没什么难度,就不在这里贴出来了。
**TIPS:**需要注意的是,处理消息要注意幂等性,解决幂等性的问题有很多种方法,自己根据业务需要选择一种方法。
接下来是对账的函数,因为微信时每天上午九点出前一天的账单,所以为了保险起见,我设置的是每天上午十一点去下载前一天的账单。
只有一个service层就够了。
/**
* 每天的上午十一点下载前一天的账单,进行对账
*/
@Scheduled(cron = "0 0 11 * * ?")
public void downloadBill() {
log.info("build request parameters");
String requestParam = buildRequestParam();
//调用微信下载API,发送下载请求,接受返回数据
log.info("begin request wx api");
String resultParam = WXPayUtil.httpRequest(downloadURL, "POST", requestParam);
//对账时间
String date = getBillDate();
//对返回数据进行解析
log.info("begin parse return data");
if (!resultParam.contains(RewardConstants.FAIL)) {
//自己查账的业务逻辑
}
}
}
/**
* 生成下载对账单的请求参数
*/
private String buildRequestParam() {
SortedMap<String, String> param = new TreeMap<>();
Date lastDate = new Date(System.currentTimeMillis() - 1000 * 60 * 60 * 24);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
String date = dateFormat.format(lastDate);
param.put("appid", appId);
param.put("mch_id", mchId);
param.put("nonce_str", RandomStringUtil.getRandomStringByLength(RewardConstants.WXNONCELENGTH));
param.put("bill_date", date);
param.put("bill_type", RewardConstants.SUCCESSBILL);
String temp = WXPayUtil.createLinkString(param);
String sign = WXPayUtil.sign(temp);
param.put("sign", sign);
//请求参数Map转换成为xml类型的字符串
String requestParam = WXPayUtil.mapToXml(param);
return requestParam;
}
/**
* 取微信返回账单中上一天的订单总金额和订单数量
*/
private List<Integer> WXCount(String resultBill) {
List<Integer> result = new ArrayList<>();
String[] str = resultBill.split("\n");
int count = str.length;
String[] info = str[count - 1].replace("`", "").split(",");
if (info.length == str[count - 2].split(",").length) {
//订单总数
result.add(Integer.valueOf(info[0]));
//订单总金额,金额单位为元,要换算为分
log.info(info[1]);
double totalFee = Double.valueOf(info[1]);
result.add((int) (totalFee * 100));
} else {
log.error("bill message error.");
}
return result;
}
/**
* 对返回的账单中的每个订单数据进行解析,都是字符串类型,不涉及类型转换,可以不进行非空判断
*/
private List<WXBillInfoVO> parseBill(String resultBill) {
List<WXBillInfoVO> wxBillInfoVOList = new ArrayList<>();
String[] str = resultBill.split("\n");
int count = str.length;
//第一行是表头信息,所以不读取,后两行是总金额信息,所以也不读取
for (int i = 1; i < count - 2; i++) {
String[] info = str[i].replace("`", " ").split(",");
WXBillInfoVO wxBillInfoVO = new WXBillInfoVO();
if (info.length != str[0].split(",").length) {
log.error("bill message error.");
continue;
}
wxBillInfoVO.setTransDate(info[0]);
wxBillInfoVO.setAppId(info[1]);
wxBillInfoVO.setMchId(info[2]);
wxBillInfoVO.setChildBusinessNo(info[3]);
wxBillInfoVO.setEquipmentNo(info[4]);
wxBillInfoVO.setTransactionId(info[5]);
wxBillInfoVO.setOutTradeNo(info[6]);
wxBillInfoVO.setOpenId(info[7]);
wxBillInfoVO.setTransType(info[8]);
wxBillInfoVO.setTransStatus(info[9]);
wxBillInfoVO.setBankType(info[10]);
wxBillInfoVO.setFeeType(info[11]);
wxBillInfoVO.setTotalFee(info[12]);
wxBillInfoVO.setCouponFee(info[13]);
wxBillInfoVO.setGoodsName(info[14]);
wxBillInfoVO.setAttach(info[15]);
wxBillInfoVO.setServiceCharge(info[16]);
wxBillInfoVO.setRate(info[17]);
wxBillInfoVO.setOrderFee(info[18]);
wxBillInfoVO.setRateRemark(info[19]);
wxBillInfoVOList.add(wxBillInfoVO);
}
return wxBillInfoVOList;
}
/**
* 获取账单时间
*/
private String getBillDate(){
Date lastDate = new Date(System.currentTimeMillis() - 1000 * 60 * 60 * 24);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
return dateFormat.format(lastDate);
}
我只写出了主要的流程代码,业务逻辑以及DAO层的操作自己每个人的业务不同,因此写的内容也不同,所以不具有参考性,这些逻辑代码已经经过业务验证没有问题,但是在展示中我删除了一些跟业务有关的代码,所以直接粘贴是没法运行的,各位自己补充。另外此次代码实现功能不包括退款,查询订单等功能,后续业务有需求,我会再继续开发,也会继续贴出来和大家分享。
TIPS:本次业务代码中HTTP请求处理的不够好,有能力的同学可以修改为其他包装工具,欢迎交流。
转载需要经过同意!!!!!!!!不得擅自转载!!!!!!