Java开发实现微信小程序支付(微信支付)

2年前 (2022) 程序员胖胖胖虎阿
224 0 0

目录

  • 一、前期准备工作
    • 1.申请小程序开发者账号并认证
    • 2.小程序开通微信支付
    • 3.开发指引
    • 4.到官方下载Java的支付SDK
  • 二、代码实现
    • 1.开发流程:
    • 2.实现步骤
      • 2.1 下单,生成待支付订单
      • 2.2 调用微信统一下单
      • 2.3 小程序发起微信支付
    • 3.后台业务逻辑涉及到的工具类及参数封装类
      • 3.1 WeChatPayDto
      • 3.2 WeChatPayUtil:微信支付工具类
      • 3.3 WXPayUtil、WxPayNotifyResponse
      • 3.4 HttpUtils工具类
      • 3.5 ErrorCode:返回码
      • 3.6 request.js:小程序request请求封装
    • 4.效果图

前段时间,开发了一个微信小程序项目,里面涉及到微信支付,在此整理记录一下:

小程序支付官方文档

前提条件:申请微信小程序,进行企业认证,认证通过后,需要接入微信支付。

一、前期准备工作

1.申请小程序开发者账号并认证

申请小程序开发者账号,进行微信认证,获取appid登录mp.weixin.qq.com,注册一个小程序的开发者账号。
申请指引:https://mp.weixin.qq.com/debug/wxadoc/introduction/index.html
Java开发实现微信小程序支付(微信支付)
获取到小程序开发者账号后,进行微信认证。
微信认证指引:https://mp.weixin.qq.com/debug/wxadoc/introduction/#

小程序申请微信认证:

  • 微信认证入口:登录小程序-设置-微信认证详情Java开发实现微信小程序支付(微信支付)

2.小程序开通微信支付

小程序开通微信支付,即申请或复用微信支付商户号 申请完小程序后,登录小程序后台(mp.weixin.qq.com)。点击左侧导航栏的微信支付,在页面中进行开通。(开通申请要求小程序已发布上线)

Java开发实现微信小程序支付(微信支付)
点击开通按钮后,有2种方式可以获取微信支付能力,新申请微信支付商户号或绑定一个已有的微信支付商户号,请根据你的业务需要和具体情况选择,只能二选一。
Java开发实现微信小程序支付(微信支付)

3.开发指引

除被扫支付场景以外,商户系统先调用统一下单接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易会话标识后再按扫码、JSAPI、APP、小程序等不同场景生成交易串调起支付,具体API接口请查看"API列表"

注意:

  • appid必须为最后拉起收银台的小程序appid;
  • mch_id为和appid成对绑定的支付商户号,收款资金会进入该商户号;
  • trade_type请填写JSAPI;
  • openid为appid对应的用户标识,即使用wx.login接口获得的openid

A.小程序支付的交互图
Java开发实现微信小程序支付(微信支付)
B.商户系统和微信支付系统主要交互:

①小程序内调用登录接口,获取到用户的openid,api参见公共api【小程序登录API】

②商户server调用支付统一下单,api参见公共api【统一下单API】

③商户server调用再次签名,api参见公共api【再次签名】

④商户server接收支付通知,api参见公共api【支付结果通知API】

⑤商户server查询支付结果,如未收到支付通知的情况,商户后台系统可调用【查询订单API】 (查单实现可参考:支付回调和查单实现指引)

4.到官方下载Java的支付SDK

下载地址:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=11_1#
Java开发实现微信小程序支付(微信支付)
下载后进行解压,可以看到sdk下的文件,下图:
Java开发实现微信小程序支付(微信支付)
用于参考。

二、代码实现

1.开发流程:

1.进入小程序,进行下单,生成待支付商品订单信息,生成成功返回商品订单编号;

2.在小程序端(上一步的回调函数中)调用后台微信统一下单接口,组装必要的参数(商品订单编号、openid),调用小程序API:https://api.mch.weixin.qq.com/pay/unifiedorder,调用成功返回预付单信息prepay_id、appId、timeStamp、nonceStr、paySign等

3.上一步的回调函数中,使用wx.requestPayment方法发起支付,支付成功后,后台会进入配置的回调接口,进行相关逻辑处理。

4.支付回调:回调中会返回业务结果、进行签名校验;校验成功,进行自己的逻辑处理,如订单状态修改、减库存等。

2.实现步骤

创建Springboot项目,添加微信支付sdk相关依赖:

<!--web -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--微信支付 -->
<dependency>
     <groupId>com.github.wxpay</groupId>
     <artifactId>wxpay-sdk</artifactId>
     <version>0.0.3</version>
 </dependency>

 <dependency>
     <groupId>com.github.binarywang</groupId>
     <artifactId>weixin-java-miniapp</artifactId>
     <version>3.3.0</version>
 </dependency>

 <!-- https://mvnrepository.com/artifact/com.github.binarywang/weixin-java-pay -->
 <dependency>
     <groupId>com.github.binarywang</groupId>
     <artifactId>weixin-java-pay</artifactId>
     <version>3.7.8.B</version>
 </dependency>

2.1 下单,生成待支付订单

controller:

@RestController
@RequestMapping("/anon/course")
public class WxCourseController {
	/**
	 * 生成确认订单信息
	 * @param request
	 * @param cartIds 购物车ids
	 * @param third_session 登录用户唯一凭证
	 * @return
	 * @throws Exception
	 */
	@RequestMapping(value="addConfirmOrderInfo",method = RequestMethod.POST)
	public Map<String, Object> addConfirmOrderInfo(HttpServletRequest request) throws Exception{
		synchronized (WxCourseService.class) {
			return wxCourseService.addConfirmOrderInfo(request);
		}
	}
}

serviceImpl:

	/**
	 * 生成确认订单信息
	 */
	@Override
	public Map<String, Object> addConfirmOrderInfo(HttpServletRequest request) throws Exception {
		Map<String, Object> result = new HashMap<String, Object>();
		//把请求参数转为map
		Map<String, Object> paramMap=GetParamToMap.getParameterMapWXCode(request,redisUtil);
		
		String cartIds = String.valueOf(paramMap.get("cartIds"));
		String s[]=cartIds.split(",");
		//根据cartIds获取购物车列表信息
		List<Map<String,Object>> cartList=courseMapper.getConfirmOrderByCartIds(cartIds.split(","));
		List<Map<String,Object>> detaillist = new ArrayList<Map<String,Object>>();
		String order_number=OrderNoUtil.getOrderNumber();//订单编号
		for(Map<String,Object> data:cartList){
			//根据课程id获取课程信息
			Map<String,Object> productInfo=courseMapper.getCourseInfoById(data);
			if(productInfo == null){
				result.put(ErrorCode.MSG, "订单中有课程已不存在"); 
				result.put(ErrorCode.STATE, ErrorCode.FAIl); 
				return result;
			}else{
				String status= String.valueOf(productInfo.get("status"));//1:已发布  2:已下架
				String del_flag= String.valueOf(productInfo.get("del_flag"));//删除标志(0代表存在 1代表删除)
				if(status.equals("2")){
					result.put(ErrorCode.MSG, "订单中有课程已下架"); 
					result.put(ErrorCode.STATE, ErrorCode.FAIl); 
					return result;
				}
				if(del_flag.equals("1")){
					result.put(ErrorCode.MSG, "订单中有课程已删除"); 
					result.put(ErrorCode.STATE, ErrorCode.FAIl); 
					return result;
				}
				//组装订单详情信息
				data.put("order_number", order_number);
				detaillist.add(data);
			}
		}
		
		//根据cartIds获取购物总金额
		double totalMoney = courseMapper.getTotalMoneyByCartIds(cartIds.split(","));
		//过期时间15分钟
		Date nowDate = new Date();
		Date afterDate = new Date(nowDate.getTime()+300000*3);
	    //生成订单信息
		Map<String, Object> orderDataMap = new HashedMap<String, Object>();
		orderDataMap.put("order_number", order_number);//订单编号
		orderDataMap.put("order_money", totalMoney);//订单金额
		orderDataMap.put("order_status", 1);//订单状态 1待付款 2已付款 3已取消 4免费课程
		orderDataMap.put("openid", paramMap.get("openid"));//下单用户openid
		orderDataMap.put("wx_user_id", paramMap.get("MY_USERID"));//下单用户id
		orderDataMap.put("paymoney", totalMoney);//实际支付金额
		orderDataMap.put("remark", paramMap.get("remark"));//订单备注
		orderDataMap.put("end_time", afterDate);//支付结束时间
		Integer n = courseMapper.addConfirmOrderInfo(orderDataMap);
		if(n>0){
			//添加课程订单详情信息
			courseMapper.addOrderDetail(detaillist);
			//删除购物车
			for(Map<String,Object> data:cartList){
				courseMapper.delCourseShoppingCart(data);
			}
			result.put(ErrorCode.DATA, order_number);
			result.put(ErrorCode.STATE, ErrorCode.SUCCESS);
		}else{
			result.put(ErrorCode.MSG, "操作失败"); 
			result.put(ErrorCode.STATE, ErrorCode.FAIl);
		}
        
		return result;
	}

小程序端:

  • 下单方法:
	//添加课程订单(微信统一下单接口)
	addConfirmOrderInfo(){
		var that = this;
		//购物车ids
		var cartIds = this.data.cartIds
		//订单备注
		var remark = this.data.remark
		var obj={
		 	cartIds: cartIds,
			remark:remark
		}
		api.addConfirmOrderInfo(obj).then(res =>{
		 	console.log('res',res)
		 	if(res.returnCode == 200){//成功
				obj.order_number=res.data
		 		//调用微信统一下单
				that.wxUnifiedorder(obj)
		 	}
		})
	},
  • api接口:
	//业务api
	import fly from './request.js'
	const baseURL = 'http://192.168.43.142:8100'
    //生成确认订单
    addConfirmOrderInfo:(data) => fly.post('/anon/course/addConfirmOrderInfo',data,{
        baseURL: baseURL
    }),

2.2 调用微信统一下单

controller:

	/**
	 * 微信统一下单接口
	 * @return
	 * @throws Exception 
	 */
    @RequestMapping(value="wxUnifiedorder",method = RequestMethod.POST)
    public Map<String, Object> getPrePayInfo(HttpServletRequest request) throws Exception {
    	synchronized (WxCourseService.class) {
			return wxCourseService.getPrePayInfo(request);
		}
    }

serviceImpl:

	/**
	 * 微信统一下单
	 */
	@Override
	public Map<String, Object> getPrePayInfo(HttpServletRequest request) throws Exception {
		Map<String, Object> result = new HashMap<String, Object>();
		//把请求参数转为map
		Map<String, Object> paramMap=GetParamToMap.getParameterMapWXCode(request,redisUtil);
		String order_number = String.valueOf(paramMap.get("order_number"));
		//根据order_number获取订单总金额
		Map<String,Object> orderInfo=courseMapper.getOrderInfo(paramMap);
		Double totalMoney=0.0;
		if(orderInfo!=null){
			totalMoney = Double.valueOf(String.valueOf(orderInfo.get("order_money")));
		}
		Double nDouble = totalMoney*100;
		//接口调用总金额单位为分换算一下(测试金额改成1,单位为分则是0.01,根据自己业务场景判断是转换成float类型还是int类型)
		Integer totalFee=Integer.valueOf(nDouble.intValue());
		//Integer totalFee = 1;
				
		String openId = String.valueOf(paramMap.get("openid"));
		WeChatPayDto miniDTO = new  WeChatPayDto();
		miniDTO.setBody("xxx校园");
		miniDTO.setOutTradeNo(order_number);
		miniDTO.setTotalFee(String.valueOf(totalFee));//单位为分
        
		WeChatPayUtil weChatPayUtil = new WeChatPayUtil();
		//微信统一下单接口
		Map<String, String> resultMap = weChatPayUtil.getPrePayInfo(miniDTO, openId);
		if(resultMap != null){
			result.put(ErrorCode.DATA, resultMap);
			result.put(ErrorCode.STATE, ErrorCode.SUCCESS);
		}else{
			result.put(ErrorCode.MSG, "微信统一下单失败"); 
			result.put(ErrorCode.STATE, ErrorCode.FAIl);
		}
        return result;
	}

其中,

(1)WeChatPayDto:统一下单必要的参数对象
(2)WeChatPayUtil:微信支付工具类
(3)WXPayUtil为官方SDK中的工具类

  • generateNonceStr() 方法用与获取随机数

Java开发实现微信小程序支付(微信支付)

  • 生成签名:
    Java开发实现微信小程序支付(微信支付)
  • mapToXml:map转xml
    Java开发实现微信小程序支付(微信支付)
  • 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据
    Java开发实现微信小程序支付(微信支付)

小程序端调用:

  • 微信统一下单方法:
	//微信统一下单
	wxUnifiedorder(obj){
		//微信统一下单
		api.wxUnifiedorder(obj).then(res=>{
			if(res.returnCode == 200){//成功
				console.log('微信支付接口之前先生成签名成功',res)
				if(res.data.prepayId !=null){
					var appId = res.data.appId;//appId
					var nonceStr = res.data.nonceStr;//随机串
					var outTradeNo = res.data.out_trade_no;//保存订单号信息
					var packageStr = res.data.package;
					var paySign = res.data.paySign;//签名
					var prepayId = res.data.prepayId;//
					var signType = res.data.signType;//
					var timeStamp = res.data.timeStamp;//时间戳

					console.log("outTradeNo",outTradeNo)

					//支付
					wx.requestPayment({
						timeStamp: timeStamp,
						nonceStr: nonceStr,
						package: packageStr,
						signType: signType,
						paySign: paySign,
						success (res1) { 
							console.log('requestPayment',res1)
							if(res1.errMsg=="requestPayment:ok"){
								//支付成功,跳转订单列表
								// 进入我的订单 0:全部 1:待支付 2:已支付 3:已取消 4:免费课程
								var type = 2;
      							wx.navigateTo({ url: '/pages/packageMy/my-order/my-order?type='+type });

							}else{
								var type = 1;
      							wx.navigateTo({ url: '/pages/packageMy/my-order/my-order?type='+type });
							}
						},
						fail (res1){
							var type = 1;
      						wx.navigateTo({ url: '/pages/packageMy/my-order/my-order?type='+type });
						}
				  })
				}
				
			}
		})
	},
  • api接口:
	//业务api
	import fly from './request.js'
	const baseURL = 'http://192.168.43.142:8100'
    // 微信统一下单
    wxUnifiedorder:(data) => fly.post('/anon/course/wxUnifiedorder',data,{
        baseURL: baseURL
    }),

2.3 小程序发起微信支付

小程序通过调用微信统一下单接口,拿到返回的预支付单信息,作为wx.requestPayment的参数,发起支付:

//支付
wx.requestPayment({
	timeStamp: timeStamp,
	nonceStr: nonceStr,
	package: packageStr,
	signType: signType,
	paySign: paySign,
	success (res1) { 
		console.log('requestPayment',res1)
		if(res1.errMsg=="requestPayment:ok"){
			//支付成功,跳转订单列表
			// 进入我的订单 0:全部 1:待支付 2:已支付 3:已取消 4:免费课程
			var type = 2;
 			wx.navigateTo({ url: '/pages/packageMy/my-order/my-order?type='+type });

		}else{
			var type = 1;
 			wx.navigateTo({ url: '/pages/packageMy/my-order/my-order?type='+type });
		}
	},
	fail (res1){
		var type = 1;
 		wx.navigateTo({ url: '/pages/packageMy/my-order/my-order?type='+type });
	}
 })

支付成功,小程序端会跳转订单列表;服务端会自动调用后台配置的回调地址,执行回调接口中的相关逻辑。
回调接口:

package com.milu.boss.wx.pay;

import com.milu.boss.project.course.mapper.CourseMapper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.wxpay.sdk.WXPayUtil;
import com.milu.boss.wx.weChat.WeChatConfig;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

/**
 * 微信:小程序支付回调
 */
@RestController
@RequestMapping("/anon/weChatPay")
@AllArgsConstructor
@Slf4j
public class WeChatPayNoticeController {
    @Autowired
    private CourseMapper courseMapper;

    //这里是支付回调接口,微信支付成功后会自动调用
    @PostMapping("/notify")
    public String wxNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
    	log.info("进入支付回调---------------------------------");
    	System.out.println("进入支付回调---------------------------------");
        Map orderMap = new HashMap();
        BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream) request.getInputStream()));
        String line = null;
        StringBuilder sb = new StringBuilder();
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }
        String notityXml = sb.toString();
        String resXml = "";
        Map resPrint = new HashMap();
        Map<String, String> resultMap = WXPayUtil.xmlToMap(notityXml);
        //业务结果
        String returnCode = (String) resultMap.get("return_code");
        //订单号
        String orderNo = resultMap.get("out_trade_no");
        //获取微信签名
        String sign = resultMap.get("sign");
        //去除签名字段
        resultMap.remove("sign");
        //重新签名
        String signNew = WXPayUtil.generateSignature(resultMap, WeChatConfig.WECHAT_key);
        if (signNew.equals(sign)) {
            if ("SUCCESS".equals(returnCode)) {
                System.out.println(signNew + "ppppp");
                resPrint.put("return_code", "SUCCESS");
                resPrint.put("return_msg", "ok");
                resXml = WXPayUtil.mapToXml(resPrint);

                orderMap.put("order_status", 2);
                orderMap.put("order_number", orderNo);
                orderMap.put("pay_time", "2");
                // 自己的业务逻辑
                //修改订单状态为已支付
                courseMapper.updateOrderStatusInfo(orderMap);
                return WxPayNotifyResponse.success("成功");
            } else {
                System.out.println("业务结果失败");
                //WxPayNotifyResponse.success("code:" + 9999 + "微信回调结果异常,异常原因:");
                return null;
            }

        } else {
            resPrint.put("return_code", "FAIL");
            resPrint.put("return_msg", "签名失败");
            resXml = WXPayUtil.mapToXml(resPrint);
        }
        log.info(resXml);
        BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
        out.write(resXml.getBytes());
        out.flush();
        out.close();
        br.close();
        return null;
    }
}

3.后台业务逻辑涉及到的工具类及参数封装类

3.1 WeChatPayDto

WeChatPayDto : 统一下单必要的参数

package com.milu.boss.wx.weChat;

import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;

/**
 * 微信:小程序支付 统一下单必要的参数
 */
@Data
public class WeChatPayDto implements Serializable {

    /**
     * 商品描述
     */
    private String body;

    /**
     * 订单号
     */
    @NotNull(message = "缺少请求参数")
    private String outTradeNo;

    /**
     * 金额
     */
    private String totalFee;

    /**
     * 终端IP
     */
    private String spbillCreateIp;

    /**
     * 支付类型
     */
    @NotBlank(message = "支付类型不能为空")
    private String payType;
}

3.2 WeChatPayUtil:微信支付工具类

package com.milu.boss.wx.utils.pay;

import com.github.wxpay.sdk.WXPayUtil;
import com.google.common.collect.Maps;
import com.milu.boss.wx.weChat.WeChatConfig;
import com.milu.boss.wx.weChat.WeChatPayDto;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;
import static com.github.wxpay.sdk.WXPayUtil.*;


/**
 * 微信支付工具类
 */
@Slf4j
@Component
public class WeChatPayUtil {


    /**
     * 微信统一下单接口
     * @param miniDTO
     * @param openId
     * @return
     * @throws Exception
     */
    public Map<String, String> getPrePayInfo(WeChatPayDto miniDTO, String openId) throws Exception {
         Map<String, String> map = Maps.newHashMap();
        //微信分配的小程序ID
        map.put("appid", "wxd678efh567hg6787");
        //微信支付分配的商户号
        map.put("mch_id", "1230000109");
        //随机字符串,长度要求在32位以内
        map.put("nonce_str", WXPayUtil.generateNonceStr());
        //商品简单描述
        map.put("body", miniDTO.getBody());
        //商户系统内部订单号
        map.put("out_trade_no", miniDTO.getOutTradeNo());
        //订单总金额,单位为分
        map.put("total_fee", miniDTO.getTotalFee());
        //支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IP
        map.put("spbill_create_ip", getLocalIp());
        //交易类型  小程序用JSAPI
        map.put("trade_type", "JSAPI");
        //回调地址  异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数
        map.put("notify_url", "https://wx.zschool.com/anon/weChatPay/notify");
        //trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识
        map.put("openid", openId);
        String unifiedorderUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder"; // 微信统一下单URL
        String sign = generateSignature(map, "ABCDEFGHIJKabcdefghijk1211167890");// 生成签名 PAY_API_SECRET=微信支付相关API调用时使用的秘钥
        map.put("sign", sign);  // 参数配置 我直接写成"sign"
        String xml = mapToXml(map);
        //请求微信统一下单接口
        String xmlStr = HttpUtils.httpRequest(unifiedorderUrl, "POST", xml);

        //解析xml
        Map map1 = HttpUtils.doXMLParse(xmlStr);
        String return_code = (String) map1.get("return_code");//返回状态码
        String result_code = (String) map1.get("result_code");//返回状态码
        String err_code = (String) map1.get("err_code");//返回状态码
        String err_code_des = (String) map1.get("err_code_des");//返回状态码
        log.info(xmlStr);
        if (return_code.equals("SUCCESS") || return_code.equals(result_code)) {
            // 业务结果
            String prepay_id = (String) map1.get("prepay_id");//返回的预付单信息
            Map<String, String> payMap = new HashMap<>();
            payMap.put("appId", WeChatConfig.WECHAT_APPID);  // 参数配置
            payMap.put("timeStamp", HttpUtils.getCurrentTimestamp() + "");  //时间
            payMap.put("nonceStr", generateNonceStr());  // 获取随机字符串
            payMap.put("signType", "MD5");
            payMap.put("package", "prepay_id=" + prepay_id);
            String paySign = generateSignature(payMap, WeChatConfig.WECHAT_key); //第二次生成签名
            payMap.put("paySign", paySign);
            payMap.put("prepayId", prepay_id);
            payMap.put("out_trade_no", miniDTO.getOutTradeNo());
            return payMap;   //返回给前端,让前端去调支付 ,完成后你去调支付查询接口,看支付结果,处理业务。
        } else {
            //打印失败日志
        }
        return null;

    }

    /**
     * 获取当前机器的ip
     *
     * @return String
     */
    public static String getLocalIp() {
        InetAddress ia = null;
        String localip = null;
        try {
            ia = InetAddress.getLocalHost();
            localip = ia.getHostAddress();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return localip;

    }


}

3.3 WXPayUtil、WxPayNotifyResponse

WXPayUtil、WxPayNotifyResponse 为 微信SDK自带的工具类。

3.4 HttpUtils工具类

HttpUtils工具类:主要用于http服务器请求、解析xml

package com.milu.boss.wx.utils.pay;


import com.github.wxpay.sdk.WXPayConstants;
import lombok.extern.slf4j.Slf4j;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.SecureRandom;
import java.util.*;
import static com.github.wxpay.sdk.WXPayUtil.MD5;

/**
 * @Created by Wu
 * @Date 2020/4/27
 * @Description
 */
@Slf4j
public class HttpUtils {
    private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

    private static final Random RANDOM = new SecureRandom();

    /**
     * 获取随机字符串 Nonce Str
     *
     * @return String 随机字符串
     */
    public static String generateNonceStr() {
        char[] nonceChars = new char[32];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
        }
        return new String(nonceChars);
    }


    /**
     * 生成签名
     *
     * @param data 待签名数据
     * @param key  API密钥
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, String key) throws Exception {
        return generateSignature(data, key, WXPayConstants.SignType.MD5);  //MD5是常量 不想写常量可以直接写成"MD5"
    }

    /**
     * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
     *
     * @param data     待签名数据
     * @param key      API密钥
     * @param signType 签名方式
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (k.equals(WXPayConstants.FIELD_SIGN)) {   // FIELD_SIGN = sign
                continue;
            }
            if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
                sb.append(k).append("=").append(data.get(k).trim()).append("&");
        }
        sb.append("key=").append(key);
        if (WXPayConstants.SignType.MD5.equals(signType)) {
            return MD5(sb.toString()).toUpperCase();
        } else if (WXPayConstants.SignType.HMACSHA256.equals(signType)) {  //HMACSHA256常量 可以直接写成 "HMACSHA256"
            return HMACSHA256(sb.toString(), key);
        } else {
            throw new Exception(String.format("Invalid sign_type: %s", signType));
        }
    }

    /**
     * 生成 HMACSHA256
     *
     * @param data 待处理数据
     * @param key  密钥
     * @return 加密结果
     * @throws Exception
     */
    public static String HMACSHA256(String data, String key) throws Exception {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }


    /**
     * 将Map转换为XML格式的字符串
     *
     * @param data Map类型数据
     * @return XML格式的字符串
     * @throws Exception
     */
    public static String mapToXml(Map<String, String> data) throws Exception {
     /*   org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
        org.w3c.dom.Element root = document.createElement("xml");
        document.appendChild(root);
        for (String key: data.keySet()) {
            String value = data.get(key);
            if (value == null) {
                value = "";
            }
            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(); //.replaceAll("\n|\r", "");
        try {
            writer.close();
        } catch (Exception ex) {
        }
        return output;
    }

    /**
     * 发送https post请求。
     *
     * @param requestUrl 请求的url(https://xxx.com)
     * @param postData   post传输的数据。
     * @return 请求成功返回请求内容,请求失败直接返回null。
     */
    public static String httpsPost(String requestUrl, String postData) {
        return httpsRequest2(requestUrl, "POST", postData);
    }


    /**
     * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
     *
     * @param strxml
     * @return
     * @throws
     * @throws IOException
     */
    public static Map doXMLParse(String strxml) throws Exception {
        if (null == strxml || "".equals(strxml)) {
            return null;
        }

        Map m = new HashMap();
        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;
    }

    public static InputStream String2Inputstream(String str) {
        return new ByteArrayInputStream(str.getBytes());
    }

    /**
     * 获取子结点的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(getChildrenText(list));
                }
                sb.append(value);
                sb.append("</" + name + ">");
            }
        }

        return sb.toString();
    }


    public static String httpRequest(String requestUrl, String requestMethod, String outputStr) {
        // 创建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);
            }
            br.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return buffer.toString();
    }


    /**
     * 发送https请求,请求成功返回请求内容,请求失败直接返回空串。
     *
     * @param requestUrl 请求的url(https://xxx.com)
     * @param method     请求方法,一般有GET和POST方法。
     * @param outputStr  请求时附带的数据。
     * @return 请求成功返回请求内容,请求失败直接返回null。
     */
    public static String httpsRequest2(String requestUrl, String method, String outputStr) {
        HttpsURLConnection conn = null;
        OutputStream outputStream = null;
        InputStream inputStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;
        try {
            // 创建SSLContext对象,并使用我们指定的信任管理器初始化
            TrustManager[] tm = null;
            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
            sslContext.init(null, tm, new SecureRandom());
            // 从上述SSLContext对象中得到SSLSocketFactory对象
            SSLSocketFactory ssf = sslContext.getSocketFactory();

            URL url = new URL(requestUrl);
            conn = (HttpsURLConnection) url.openConnection();
            conn.setSSLSocketFactory(ssf);

            conn.setDoInput(true);
            conn.setUseCaches(false);
            // 设置请求方式(GET/POST)
            conn.setRequestMethod(method);

            // 当outputStr不为null时向输出流写数据
            if (null != outputStr) {
                conn.setDoOutput(true);
                outputStream = conn.getOutputStream();
                // 注意编码格式
                outputStream.write(outputStr.getBytes("UTF-8"));
                outputStream.close();
            }

            // 从输入流读取返回内容
            inputStream = conn.getInputStream();
            inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
            bufferedReader = new BufferedReader(inputStreamReader);
            String str = null;
            StringBuffer buffer = new StringBuffer();
            while ((str = bufferedReader.readLine()) != null) {
                buffer.append(str);
            }

            return buffer.toString();
        } catch (ConnectException ce) {
            log.error("连接超时:{}", ce);
        } catch (Exception e) {
            log.error("https请求异常:{}", e);
        } finally {
            try {
                if (bufferedReader != null) {
                    bufferedReader.close();
                }
                if (inputStreamReader != null) {
                    inputStreamReader.close();
                }
                if (inputStream != null) {
                    inputStream.close();
                }
                if (conn != null) {
                    conn.disconnect();
                }
            } catch (IOException e) {
                log.warn(e.getLocalizedMessage(), e);
            }
        }

        return null;
    }

    /**
     * 获取当前时间戳,单位秒
     *
     * @return
     */
    public static long getCurrentTimestamp() {
        return System.currentTimeMillis() / 1000;
    }


}

3.5 ErrorCode:返回码

package com.milu.boss.wx.utils;

/*小程序端返回码信息*/
public class ErrorCode {
	/**
	 * 状态码
	 */
	public static final String STATE ="returnCode";
	/**
	 * 返回信息
	 */
	public static final String MSG ="msg";
	/**
	 * 返回数据的key
	 */
	public static final String DATA ="data";
	/**
	 * 成功 200
	 */
	public static final int SUCCESS =200;
	/**
	 * 失败 400
	 */
	public static final int FAIl =400;
	/**
	 * 登录状态失效 403
	 */
	public static final int INVALID =403;
	/**
	 * 服务器错误 500
	 */
	public static final int ERROR =500;
	/**
	 * code,3rd_session失效 401
	 */
	public static final int CODE_INVALID =401;
}

3.6 request.js:小程序request请求封装

/*flyio: 
   一个支持所有JavaScript运行环境的基于Promise的、支持请求转发、强大的http请求库。
   可以让您在多个端上尽可能大限度的实现代码复用。
*/
/**配置fly 请求体 */
//引入下载的源码文件
const Fly = require("./fly/dist/npm/wx")
const fly =new Fly()//创建fly实例  
import {doLogin} from '../utils/wechat'
//设置超时时间
fly.config.timeout = 60000

//获取白名单
import whiteList from './whiteList'

//添加请求拦截器
fly.interceptors.request.use((request) => {
    wx.showLoading({
        title: '加载中',
        'mask': true
    })
    //不显示加载中页面的接口
    if(whiteList.loading.indexOf(request.url) != -1){//indexOf()如果要检索的字符串值没有出现,则该方法返回 -1
        //隐藏loading遮罩
        wx.hideLoading();
    }
    // if(request.body == undefined || request.body =='undefined'){
    //     request.body=request.params
    // }
    //默认加入3rd_session
    let third_session = wx.getStorageSync("third_session");
    if(!third_session){//不存在third_session,重新登录
        request.body['third_session'] = '';
    }else{
        request.body['third_session'] = third_session;
    }
    request.params = request.body;
    request.headers['Content-Type'] = 'application/json;charset=utf-8';

    console.log("request.body:"+JSON.stringify(request.body));
    return request;
});

//添加响应拦截器,响应拦截器会在then/catch处理之前执行
fly.interceptors.response.use((response, promise) => {
    //console.log("response",response)
    wx.hideLoading()
    switch(response.data.returnCode){
        case 200: //正常
            break;
        case 400: //后台自定义错误
            wx.showToast({
                title:response.data.msg,
                icon: 'none',
                duration: 2000
            })
            break;
        case 401: //pp_session过期  
            console.log("------------------------401------------------------") 
            //重新静默登录获取code
            wx.removeStorageSync('loginFlag')
            doLogin()
            break;
        case 403: //登录状态失效  
            wx.removeStorageSync('loginFlag')
            wx.showToast({
                title:'登录状态丢失,请重新登录',
                icon: 'none'
            })
            setTimeout(() => {
                //跳转登录页面
                wx.navigateTo({
                    url: '/pages/login/login',
                })  
            }, 2000);
            break;
        case 500: //服务器错误
            wx.showToast({
                title:'服务器错误,请稍后重试',
                icon: 'none'
            })
            break;
        default:    
    }
    return promise.resolve(response.data)
},(err, promise) => {
    wx.hideLoading()
    wx.showToast({
        title:'网络错误,请检查网络后重试',
        icon: 'none'
    })
    return promise.resolve()
})

export default fly

4.效果图

Java开发实现微信小程序支付(微信支付)

版权声明:程序员胖胖胖虎阿 发表于 2022年10月25日 下午2:24。
转载请注明:Java开发实现微信小程序支付(微信支付) | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...