SpringBoot实现微信小程序支付功能

10 篇文章 1 订阅
1 篇文章 0 订阅

情景:最近有个微信小程序支付的项目,在回调返回时踩坑,特此在这记录一下。

需求流程:小程序用户钱包充值→调用小程序官方小程序支付接口生成于支付界面→支付完成后回调提示支付成功回写相关参数到用户表中

1.小程序:点击支付按钮,请求后台接口,返回5个必要参数(用于之后调wx.requestPayment(OBJECT))。
2.JAVA: 调用统一下单接口,生成微信预支付订单,并返回结果。
3.小程序:接收到5个参数后,调用wx.requestPayment(OBJECT),此时小程序唤起了输入密码的支付弹窗,我们可以选择关闭弹窗和支付,然后根据情况跳转到不同页面。
4.JAVA: 当在小程序内支付完后,此时微信服务器会有规律性给你的支付回调地址发起请求,通知你支付结果。

官方文档地址:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1 想了解的可以去看看

准备工作:提前申请好小程序支付密钥和证书相关信息(PS:这里就不啰嗦啦!)

1.在application.yml配置

wx:
  mini:
    appid: xxxx
    secret:xxxx 
    ## 商品名称
    body: 小程序支付(测试商品)
    ## 微信商户号
    mch_id: xxxx
    ## 商户秘钥
    mch_key: xxxx
    #本地 外部支付成功跳转路径(需要外部服务可访问)
    base_notify_url: https://tishang.cn.utools.club
    notify_url1: ${wx.mini.base_notify_url}/api/wx/pay/notifyWalletUrl
    #证书地址 服务器配置存放路径  本地测试就放到本地文件
    apiclient: D:/usr/server/pkcs12/apiclient_cert.p12

2.支付用到的实体如下:

微信用户实体:WxUser 

package com.seeker.wxpayment.wx.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.seeker.wxpayment.common.base.DataEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.math.BigDecimal;

/**
 * 微信用户信息 
 *
 * @author seeker
 * @since 2021-05-11
 */
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class WxUser extends DataEntity<WxUser> {

    private static final long serialVersionUID = 1L;

    /**
     * id
     */
    private String id;

    /**
     * OpenId
     */
    private String openid;

    /**
     * 用户名称
     */
    private String userName;

    /**
     *  性别 0 男 1女
     */
    private String sex;

    /**
     * 上传头像
     */
    private String imageUrl;

    /**
     * 登录名
     */
    private String loginName;

    /**
     * 手机
     */
    private String mobile;

    /**
     * 登录密码
     */
    private String password;

    /**
     * 钱包支付密码
     */
    private String payPassword;

    /**
     * 会员类型 0非会员1会员
     */
    private String memberType;

    /**
     * 会员余额
     */
    private BigDecimal balanceAmount;

    /**
     * 联系人
     */
    private String contact;

    @Override
    public String getPkVal() {
        return this.id;
    }

    @Override
    public void setPkVal(String pkVal) {
        this.id = pkVal;
    }

}

 两个共用的实体DataEntity和Entity(注:可以不用)

import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
@Data
public abstract class DataEntity<T extends Model> extends BaseEntity<T> {
    /**
     * 创建者
     */
    @JSONField(serialize = false)
    @TableField("create_by")
    protected String createBy;

    /**
     * 创建日期
     */
    @JSONField(serialize = true, format = "yyyy-MM-dd HH:mm:ss")
    @TableField("create_date")
    protected Date createDate;

    /**
     * 更新者
     */
    @JSONField(serialize = false)
    @TableField("update_by")
    protected String updateBy;

    /**
     * 更新日期
     */
    @JSONField(serialize = true, format = "yyyy-MM-dd HH:mm:ss")
    @TableField("update_date")
    protected Date updateDate;

    /**
     * 状态(0正常 1删除 2停用)
     */
    @JSONField(serialize = false)
    @TableField("del_flag")
    protected String delFlag;

    /**
     * 备注
     */
    protected String remarks;

    @TableField(exist = false)
    private String orderBy;

    /**
     * 插入之前执行方法,需要手动调用
     */
    @Override
    public void preInsert() {
        String uuid = IdWorker.getIdStr();
        setPkVal(uuid);
        this.updateDate = new Date();
        this.createDate = this.updateDate;
    }
    /**
     * 更新之前执行方法,需要手动调用
     */
    @Override
    public void preUpdate() {
        this.updateDate = new Date();
        this.createDate = this.updateDate;
    }
}
import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.Data;
import java.util.Map;

/**
 * Entity支持类
 */
@Data
public abstract class BaseEntity<T extends Model> extends Model {

    private static final long serialVersionUID = 1L;

    /**
     * 获取主键
     *
     * @return
     */
    public abstract String getPkVal();

    /**
     * 设置主键
     *
     * @return
     */
    public abstract void setPkVal(String pkVal);

    /**
     * 插入之前执行方法,子类实现
     */
    public abstract void preInsert();

    /**
     * 更新之前执行方法,子类实现
     */
    public abstract void preUpdate();

    /**
     * 自定义SQL(SQL标识,SQL内容)
     */
    @JSONField(serialize = false)
    @TableField(exist = false)
    protected Map<String, String> sqlMap;

    /**
     * mybatis-plus分页参数
     */
    @JSONField(serialize = false)
    @TableField(exist = false)
    private IPage<T> page;

    public Page<T> getPage() {
        return new Page<>(pageNo, pageSize);
    }

    /**
     * 当前页码
     */
    @TableField(exist = false)
    private long pageNo = 1L;

    /**
     * 每页条数
     */
    @TableField(exist = false)
    private long pageSize = 10L;

    /**
     * 删除标记(0:正常;1:删除;)
     */
    public static final String DEL_FLAG_NORMAL = "0";
    public static final String DEL_FLAG_DELETE = "1";

}

 

充值消费记录实体:WxUserConsumeLog

package com.seeker.wxpayment.wx.entity;
import com.seeker.wxpayment.common.base.DataEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * 微信用户消费记录  
 *
 * @author seeker
 * @since 2021-0-11
 */
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class WxUserConsumeLog extends DataEntity<WxUserConsumeLog> {

    private static final long serialVersionUID = 1L;

    /**
     * id
     */
    private String id;
    /**
     * 微信用户ID wx_user中id
     */
    private String wxUserId;

    /**
     * 消费类型 0 开通VIP  1 钱包充值  2钱包支付
     */
    private String consumeType;

    /**
     * 消费内容
     */
    private String consumeContent;

    /**
     * 支付金额
     */
    private String payAmount;

    @Override
    public String getPkVal() {
        return this.id;
    }

    @Override
    public void setPkVal(String pkVal) {
        this.id = pkVal;
    }

}

传参实体:Pay


import com.seeker.common.base.DataEntity;
import java.io.Serializable;
public class Pay extends DataEntity<Pay> {

    private String id;
    private String orderId; //订单ID
    private String openid; //
    private String code;
    private double payPrice; //支付金额
    private String type; //支付方式 1 VIP  2钱包充值 3 订单支付
    

    public String getOrderId() {
        return orderId;
    }
    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }
    public String getCode() {
        return code;
    }
    public void setCode(String code) {
        this.code = code;
    }

    public String getIndustry() {
        return industry;
    }

    public void setIndustry(String industry) {
        this.industry = industry;
    }

    public double getPayPrice() {
        return payPrice;
    }

    public void setPayPrice(double payPrice) {
        this.payPrice = payPrice;
    }

    public String getOpenid() {
        return openid;
    }

    public void setOpenid(String openid) {
        this.openid = openid;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    @Override
    public String getPkVal() {
        return this.id;
    }

    @Override
    public void setPkVal(String uuid) {
        this.id = uuid;
    }
}

3、创建一个支付调用Controller层:WXPayController

package com.seeker.advertising.api;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.seeker.advertising.auth.WxLoginUser;
import com.seeker.advertising.auth.WxMiniTokenService;
import com.seeker.advertising.order.entity.OrderInfo;
import com.seeker.advertising.order.entity.OrderPayRefund;
import com.seeker.advertising.order.entity.OutSuccess;
import com.seeker.advertising.order.entity.Pay;
import com.seeker.advertising.order.service.OrderInfoService;
import com.seeker.advertising.order.service.OrderPayInfoService;
import com.seeker.advertising.order.service.OrderPayRefundService;
import com.seeker.advertising.wx.entity.WxUser;
import com.seeker.advertising.wx.entity.WxUserInfo;
import com.seeker.advertising.wx.service.WxUserConsumeLogService;
import com.seeker.advertising.wx.service.WxUserCouponService;
import com.seeker.advertising.wx.service.WxUserService;
import com.seeker.common.utils.*;
import lombok.extern.java.Log;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;


/**
 * @Title:
 * @Description: 微信小程序支付
 * @Author:seeker
 * @Since:2021年5月20日
 * @Version:1.1.0
 */
@Log
@RestController
@RequestMapping("/api/wx/pay")
public class WXPayController {

    @Value("${wx.mini.appid}")
    private String wxspAppid;
    @Value("${wx.mini.secret}")
    private String wxspSecret;
    @Value("${wx.mini.mch_id}")
    private String mch_id;
    @Value("${wx.mini.mch_key}")
    private String mch_key;
    @Value("${wx.mini.body}")
    private String body;
    @Value("${wx.mini.notify_url}")
    private String notify_url;
    @Value("${wx.mini.apiclient}")
    private String apiclient;



    @Autowired
    private WxMiniTokenService wxMiniTokenService;

    @Autowired
    private WxUserService wxUserService;

    @Autowired
    private OrderPayInfoService OrderPayInfoService;

    @Autowired
    private OrderInfoService orderInfoService;

    protected Logger logger = LoggerFactory.getLogger(getClass());

    /**
     ** 大体流程分析:
     1.小程序:点击支付按钮,请求后台接口,返回5个必要参数(用于之后调wx.requestPayment(OBJECT))。
     2.JAVA: 调用统一下单接口,生成微信预支付订单,并返回结果。
     3.小程序:接收到5个参数后,调用wx.requestPayment(OBJECT),此时小程序唤起了输入密码的支付弹窗,我们可以选择关闭弹窗和支付,然后根据情况跳转到不同页面。
     4.JAVA: 当在小程序内支付完后,此时微信服务器会有规律性给你的支付回调地址发起请求,通知你支付结果。
     */

    /**
     * 【1】调用微信小程序支付
     * 1.调用微信小程序API
     * 2.1支付成功后设置钱包余额,返回成功状态
     * 2.2支付失败,返回错误状态
     */
    @RequestMapping(value = "variedPayInfo")
    public R variedPayInfo(@RequestBody(required = false) Pay pay, HttpServletRequest request, HttpServletResponse response) {
        R r = R.ok();
        try {
            WxLoginUser loginUser = wxMiniTokenService.getLoginUser(ServletUtils.getRequest());
            //获取openid
            String openid = "";
            //判断是否登陆
            if (null == loginUser || StringUtils.isBlank(loginUser.getUser().getOpenid())) {
                // 授权(必填)
                String grant_type = "authorization_code";
                // 1、向微信服务器 使用登录凭证 code 获取 session_key 和 openid
                // 请求参数
                String params = "appid=" + wxspAppid + "&secret=" + wxspSecret + "&js_code=" + pay.getCode()
                        + "&grant_type=" + grant_type;
                // 发送请求
                String sr = HttpRequestUtils.sendGet("https://api.weixin.qq.com/sns/jscode2session", params);
                WxUserInfo wxUserInfo = JSON.parseObject(sr, new TypeReference<WxUserInfo>() {
                });
                log.info("wxUserInfo === " + wxUserInfo);
                openid = wxUserInfo.getOpenid();
            } else {
                openid = loginUser.getUser().getOpenid();
            }
            //组装参数,用户生成统一下单接口的签名
            Map<String, String> paraMap = new HashMap<String, String>();
            paraMap.put("appid", wxspAppid);
            paraMap.put("body", body);
            paraMap.put("mch_id", mch_id);
            paraMap.put("nonce_str", WXPayUtil.create_nonce_str()); //随机字符串
            paraMap.put("openid", openid);
            if (StringUtils.isNotEmpty(pay.getOrderId())) {
                paraMap.put("out_trade_no", pay.getOrderId());        //商户订单号
            } else {
                paraMap.put("out_trade_no", "DT" + IdWorker.getIdStr());        //商户订单号
            }
            paraMap.put("spbill_create_ip", WXPayUtil.getAddrIp(request));   //ip地址
            double totalFee = 0;
            //支付方式 1 VIP  2钱包充值 3 订单支付
            if (StringUtils.isNotEmpty(pay.getType())) {
                if (pay.getType().equals("2")) {
                    //totalFee = pay.getPayPrice();//钱包充值  
                    totalFee = 0.01 * 100; //测试 使用1分钱
                    paraMap.put("notify_url", notify_url);
                } 
            }
            int money = (int) totalFee;
            // int money = (int) AmountUtils.Amount.PAY_AMOUNT * 100;
            paraMap.put("total_fee", String.valueOf(money));    //标价金额
            paraMap.put("trade_type", ConstantUtils.JSAPI);     //交易类型(小程序取值如下:JSAPI)
            //保存支付信息(支付成功才会回调,关闭支付弹窗不会回调)
            // paraMap.put("notify_url", notify_url);//支付成功后的回调地址      异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
            //MD5运算生成签名,这里是第一次签名,用于调用统一下单接口
            String sign = WXPayUtil.getSign(paraMap, mch_key);
            paraMap.put("sign", sign);
            //map转化为xml格式
            String requestXml = WXPayUtil.mapToXml(paraMap);
            //调用统一下单接口,并接受返回的结果
            String resultXml = HttpRequestUtils.httpsRequestWeiXin(ConstantUtils.UNIFIED_ORDER_URL, ConstantUtils.REQUESTMETHODPOST, requestXml);
            // 将解析结果存储在HashMap中
            Map<String, String> param = isResponseSignatureValid(resultXml, mch_key);
            String return_code = param.get("return_code");//返回状态码
            String result_code = param.get("result_code");//返回状态码
            //返回给小程序端需要的参数(字段必须 驼峰模式 严格按照小程序支付文档)
            Map<String, String> payMap = new HashMap<String, String>();
            if (return_code.equals("SUCCESS") && return_code.equals(result_code)) {
                //设置订单基础参数
                if (StringUtils.isNotEmpty(pay.getOrderId())) {
                    OrderInfo orderInfo = new OrderInfo();
                    orderInfo.setId(pay.getOrderId());
                    orderInfo.setIndustry(pay.getIndustry());
                    orderInfo.setBrandName(pay.getBrandName());
                    orderInfo.setRemarks(pay.getRemarks());
                    orderInfoService.update(orderInfo);
                }
                payMap.put("appId", wxspAppid);
                payMap.put("timeStamp", WXPayUtil.create_timestamp());   //这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误 //拼接签名需要的参数
                payMap.put("nonceStr", WXPayUtil.create_nonce_str());    //生成签名的随机串
                //prepay_id ==>微信生成的预支付会话标识,用于后续接口调用中使用,该值有效期为2小时
                payMap.put("package", "prepay_id=" + param.get("prepay_id"));   //订单详情扩展字符串
                payMap.put("signType", ConstantUtils.SIGNTYPE);                //签名方式 md5
                String paySign = WXPayUtil.getSign(payMap, mch_key);            //签名
                payMap.put("paySign", paySign);
                r.put("map", payMap);
                r.put("code", 0);
                r.put("msg", "预支付成功!");
            } else {
                r.put("code", 1);
                r.put("msg", param.get("err_code_des"));
            }
        } catch (Exception e) {
            e.printStackTrace();
            r = R.error();
        }
        return r;
    }


    /**
     * 【2】微信支付通知(微信回调)钱包充值费用
     *
     * @param request
     * @param response
     * @return outResult 支付是否成功
     */
    @RequestMapping(value = "notifyWalletUrl", method = RequestMethod.POST)
    public void notifyWalletUrl(HttpServletRequest request, HttpServletResponse response) {
        log.info("微信支付——异步回调开始");
        OutSuccess outResult = new OutSuccess();
        String resXml = "";
        try {
            ServletInputStream inputStream = request.getInputStream();
            Map<String, String> params = WXPayUtil.parseXml(inputStream);
            log.info(params.toString());
            String returnCode = params.get("return_code");
            String resultCode = params.get("result_code");
            String returnMsg = params.get("return_msg");
            log.info("returnMsg:" + returnMsg);
            if ("SUCCESS".equals(returnCode) && "SUCCESS".equals(resultCode)) {
                String sign = params.get("sign");
                // 签名sign不需要参加移除
                params.remove("sign");
                String signResult = WXPayUtil.getSign(params, mch_key);
                //通过签名验证的签名结果,可认为是微信官方的通知
                outResult = OrderPayInfoService.saveWechatPayWallet(params, sign, signResult);
                if ("SUCCESS".equals(outResult.getReturn_code())) {
                    //支付成功
                    resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
                            + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml>";
                    log.info("打印出成功回调结果---->resXml" + resXml);
                } else {
                    resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                            + "<return_msg><![CDATA[签名错误]></return_msg>" + "</xml>";
                    logger.error("签名错误,xml中签名:" + sign + ",校验后的签名:" + signResult);
                }
            } else {
                resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                        + "<return_msg><![CDATA[报文为空]></return_msg>" + "</xml>";
                logger.info("支付失败:" + resXml);
                log.info("获取数据失败,错误代码:" + params.get("err_code") + ",错误描述:" + params.get("err_code_des"));
            }
            logger.info("【小程序支付回调响应】 响应内容:\n" + resXml);
            response.getWriter().print(resXml);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }



}

数据公共类:ConstantUtils 

package com.seeker.common.utils;

/**
 * Created by seeker on 2021/5/20
 */
public class ConstantUtils {

    /**
     * 微信
     */
    public final static String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    public final static String REFUND_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund";

    //微信支付类型
    public final static String JSAPI = "JSAPI";

    public static final String FAIL = "FAIL";

    public static final String SUCCESS = "SUCCESS";

    public static final String HMACSHA256 = "HMAC-SHA256";

    public final static String SIGNTYPE = "MD5";

    public final static String REQUESTMETHODPOST = "POST";

    public final static String REQUESTMETHODGET = "GET";
  
    //随机字符串
    public static final String RANDOM_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

}

微信请求封装类:HttpRequestUtils 

package com.seeker.common.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import java.io.*;
import java.net.ConnectException;
import java.net.URL;
import java.net.URLConnection;

public class HttpRequestUtils {
	protected static Logger logger = LoggerFactory.getLogger("HttpRequestUtils");

	/**
	 * 向指定URL发送GET方法的请求
	 *
	 * @param url
	 *            发送请求的URL
	 * @param param
	 *            请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
	 * @return URL 所代表远程资源的响应结果
	 */
	public static String sendGet(String url, String param) {
		String result = "";
		BufferedReader in = null;
		try {
			String urlNameString = url + "?" + param;
			URL realUrl = new URL(urlNameString);
			// 打开和URL之间的连接
			URLConnection connection = realUrl.openConnection();
			// 设置通用的请求属性
			connection.setRequestProperty("accept", "*/*");
			connection.setRequestProperty("connection", "Keep-Alive");
			connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
			// 建立实际的连接
			connection.connect();
			// 获取所有响应头字段
			/*Map<String, List<String>> map = connection.getHeaderFields();
			// 遍历所有的响应头字段
			 * for (String key : map.keySet()) { logger.info(key + " === " +
			 * map.get(key)); }
			 */
			// 定义 BufferedReader输入流来读取URL的响应
			in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
			String line;
			while ((line = in.readLine()) != null) {
				result += line;
			}
		} catch (Exception e) {
			logger.info("发送GET请求出现异常!" + e);
			e.printStackTrace();
		}
		// 使用finally块来关闭输入流
		finally {
			try {
				if (in != null) {
					in.close();
				}
			} catch (Exception e2) {
				e2.printStackTrace();
			}
		}
		return result;
	}

	/**
	 * 向指定 URL 发送POST方法的请求
	 *
	 * @param url
	 *            发送请求的 URL
	 * @param param
	 *            请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
	 * @return 所代表远程资源的响应结果
	 */
	public static String sendPost(String url, String param) {
		PrintWriter out = null;
		BufferedReader in = null;
		String result = "";
		try {
			URL realUrl = new URL(url);
			// 打开和URL之间的连接
			URLConnection conn = realUrl.openConnection();
			// 设置通用的请求属性
			conn.setRequestProperty("accept", "*/*");
			conn.setRequestProperty("connection", "Keep-Alive");
			conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
			// 发送POST请求必须设置如下两行
			conn.setDoOutput(true);
			conn.setDoInput(true);
			// 获取URLConnection对象对应的输出流
			out = new PrintWriter(conn.getOutputStream());
			// 发送请求参数
			out.print(param);
			// flush输出流的缓冲
			out.flush();
			// 定义BufferedReader输入流来读取URL的响应
			in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
			String line;
			while ((line = in.readLine()) != null) {
				result += line;
			}
		} catch (Exception e) {
			logger.info("发送 POST 请求出现异常!" + e);
			e.printStackTrace();
		}
		// 使用finally块来关闭输出流、输入流
		finally {
			try {
				if (out != null) {
					out.close();
				}
				if (in != null) {
					in.close();
				}
			} catch (IOException ex) {
				ex.printStackTrace();
			}
		}
		return result;
	}

	/**
	 * 发送https请求
	 *
	 * @param requestUrl    请求地址
	 * @param requestMethod 请求方式(GET、POST)
	 * @param outputStr     提交的数据
	 * @return 返回微信服务器响应的信息
	 */
	public static String httpsRequestWeiXin(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");
			// 当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();
			return buffer.toString();
		} catch (ConnectException ce) {
			logger.error("连接超时:{}", ce);
		} catch (Exception e) {
			logger.error("https请求异常:{}", e);
		}
		return null;
	}
}

传递和接收数据封装:WXPayUtil

package com.seeker.common.utils;


import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.naming.NoNameCoder;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.util.*;


public class WXPayUtil {

    /**
     * XML格式字符串转换为Map
     *
     * @param strXML XML字符串
     * @return XML数据转换后的Map
     * @throws Exception
     */
    public static Map<String, String> xmlToMap(String strXML) throws Exception {
        //System.out.println("==========================xml2:" + strXML);
        try {
            Map<String, String> data = new HashMap<String, String>();
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            org.w3c.dom.Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }
            try {
                stream.close();
            } catch (Exception ex) {
                // do nothing
            }
            return data;
        } catch (Exception ex) {
            WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
            throw ex;
        }

    }

    /**
     * 将Map转换为XML格式的字符串
     *
     * @param data Map类型数据
     * @return XML格式的字符串
     * @throws Exception
     */
    public static String mapToXml(Map<String, String> data) throws Exception {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
        org.w3c.dom.Document document = documentBuilder.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) {
        }
        //System.out.println("==============================xml:" + output);
        return output;
    }


    /**
     * 生成带有 sign 的 XML 格式字符串
     *
     * @param data Map类型数据
     * @param key API密钥
     * @return 含有sign字段的XML
     */
    public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
        return generateSignedXml(data, key, WXPayConstants.SignType.MD5);
    }

    /**
     * 生成带有 sign 的 XML 格式字符串
     *
     * @param data Map类型数据
     * @param key API密钥
     * @param signType 签名类型
     * @return 含有sign字段的XML
     */
    public static String generateSignedXml(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
        String sign = generateSignature(data, key, signType);
        data.put(WXPayConstants.FIELD_SIGN, sign);
        return mapToXml(data);
    }


    /**
     * 判断签名是否正确
     *
     * @param xmlStr XML格式数据
     * @param key API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
        Map<String, String> data = xmlToMap(xmlStr);
        if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
            return false;
        }
        String sign = data.get(WXPayConstants.FIELD_SIGN);
        return generateSignature(data, key).equals(sign);
    }

    /**
     * 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
     *
     * @param data Map类型数据
     * @param key API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
        return isSignatureValid(data, key, WXPayConstants.SignType.MD5);
    }

    /**
     * 判断签名是否正确,必须包含sign字段,否则返回false。
     *
     * @param data Map类型数据
     * @param key API密钥
     * @param signType 签名方式
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
        if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
            return false;
        }
        String sign = data.get(WXPayConstants.FIELD_SIGN);
        return generateSignature(data, key, signType).equals(sign);
    }

    /**
     * 生成签名
     *
     * @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);
    }

    /**
     * 生成签名. 注意,若含有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)) {
                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)) {
            return HMACSHA256(sb.toString(), key);
        }
        else {
            throw new Exception(String.format("Invalid sign_type: %s", signType));
        }
    }


    /**
     * 获取随机字符串 Nonce Str
     *
     * @return String 随机字符串
     */
    public static String generateNonceStr() {
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
    }


    /**
     * 生成 MD5
     *
     * @param data 待处理数据
     * @return MD5结果
     */
    public static String MD5(String data) throws Exception {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] array = md.digest(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();
    }

    /**
     * 生成 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();
    }

    /**
     * 日志
     * @return
     */
    public static Logger getLogger() {
        Logger logger = LoggerFactory.getLogger("wxpay java sdk");
        return logger;
    }

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

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

    /**
     * 生成 uuid, 即用来标识一笔单,也用做 nonce_str
     * @return
     */
    public static String generateUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
    }

    public static String create_nonce_str() {
        String chars = ConstantUtils.RANDOM_CHARS;
        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 getAddrIp(HttpServletRequest request) {
        return request.getRemoteAddr();
    }

    public static String create_timestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }

    public static String getSign(Map<String, String> params, String paternerKey) throws UnsupportedEncodingException {
        String string1 = createSign(params, false);
        String stringSignTemp = string1 + "&key=" + paternerKey;
        String signValue = DigestUtils.md5Hex(stringSignTemp).toUpperCase();
        return signValue;
    }

    /**
     * 将流的xml数据转成map
     * @param inputStream
     * @return
     */
    public static Map<String, String> parseXml(InputStream inputStream ) {
        Map<String, String> map = new HashMap<String, String>();
        try {
            SAXReader reader = new SAXReader();
            // 读取输入流
            Document document = reader.read(inputStream);
            // 得到xml根元素
            Element rootElement = document.getRootElement();
            // 得到根元素所有的子元素
            @SuppressWarnings("unchecked")
            List<Element> elements = rootElement.elements();
            // 遍历所有子节点
            for (Element e : elements) {
                if (StringUtils.isNotEmpty(e.getText())) {// 去除非空的数据
                    map.put(e.getName(), e.getText());
                }
            }
            inputStream.close();
            inputStream = null;
        } catch (DocumentException e) {
        } catch (IOException e) {

        }
        return map;
    }

    /**
     * 自动包装<![CDATA]>代码块
     * @param o
     * @return
     */
    public static String toXmlWithCDATA(Object o){
        escapeXstream.alias("xml", o.getClass());
        return escapeXstream.toXML(o);
    }

    /**
     * 支持CDATA块
     *
     */
    private static XStream escapeXstream = new XStream(new XppDriver() {
        public HierarchicalStreamWriter createWriter(Writer out) {
            return new PrettyPrintWriter(out,new NoNameCoder()) {
                public void startNode(String name, @SuppressWarnings("rawtypes") Class clazz) {
                    super.startNode(name, clazz);
                }
                protected void writeText(QuickWriter writer, String text) {
                    writer.write("<![CDATA[");
                    writer.write(text);
                    writer.write("]]>");
                }
            };
        }
    });


    /**
     * 构造签名
     * @param params
     * @param encode
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String createSign(Map<String, String> params, boolean encode) throws UnsupportedEncodingException {
        Set<String> keysSet = params.keySet();
        Object[] keys = keysSet.toArray();
        Arrays.sort(keys);
        StringBuffer temp = new StringBuffer();
        boolean first = true;
        for (Object key : keys) {
            if (first) {
                first = false;
            } else {
                temp.append("&");
            }
            temp.append(key).append("=");
            Object value = params.get(key);
            String valueString = "";
            if (null != value) {
                valueString = value.toString();
            }
            if (encode) {
                temp.append(URLEncoder.encode(valueString, "UTF-8"));
            } else {
                temp.append(valueString);
            }
        }
        return temp.toString();
    }
}

4、支付成功后业务处理及返回消息:OrderPayInfoService处理

    /**
     * 保存微信支付钱包信息
     *
     * @param params
     * @param sign
     * @param signResult
     * @return
     */
    @Transactional(readOnly = false)
    public OutSuccess saveWechatPayWallet(Map<String, String> params, String sign, String signResult) {
        OutSuccess outResult;
        BigDecimal oldAmount;
        BigDecimal allAmount;
        if (sign.equals(signResult)) {
            logger.info("微信——状态验证成功,同步本地订单状态");
            String transaction_id = params.get("transaction_id");// 微信交易号
            String totalFee = params.get("total_fee");
            logger.info("微信交易号=====" + transaction_id);
            String openid = params.get("openid");//

            WxUser wxUser = wxUserMapper.getByOpenid(openid);
            //设置用户余额
            if (null == wxUser.getBalanceAmount() || "".equals(wxUser.getBalanceAmount()) || "0".equals(wxUser.getBalanceAmount()) || "0.00".equals(wxUser.getBalanceAmount())) {
                oldAmount = new BigDecimal(0);
            } else {
                oldAmount = wxUser.getBalanceAmount();
            }

            String amount = CalculateUtils.CalculateUtil1(new BigDecimal(totalFee), new BigDecimal(100));
            BigDecimal newAmount = new BigDecimal(amount);
            allAmount = newAmount.add(oldAmount);

            wxUser.setBalanceAmount(allAmount);//设置余额
            wxUser.preUpdate();
            wxUserMapper.updateById(wxUser);

            logger.info("微信wxuser=====" + wxUser);
            //添加一条消费记录
            WxUserConsumeLog wxUserConsumeLog = new WxUserConsumeLog();
            //微信用户ID
            wxUserConsumeLog.setWxUserId(wxUser.getId());
            //微信支付金额
            wxUserConsumeLog.setPayAmount(String.valueOf(amount));
            //消费类型 0 开通VIP  1 钱包充值
            wxUserConsumeLog.setConsumeType("1");
            wxUserConsumeLog.setConsumeContent("用户在【" +
                    DateUtils.formatDate(new Date(), "yyyy-MM-dd HH:mm:ss")
                    + "】钱包充值,本次充值金额为:" + amount + "元,充值之前余额为:" + oldAmount + "元,充值之后余额为:" + allAmount + "元");
            wxUserConsumeLog.preInsert();
            wxUserConsumeLogMapper.insert(wxUserConsumeLog);
            logger.info("wxUserConsumeLog=====" + wxUserConsumeLog);
            outResult = new OutSuccess("SUCCESS", "OK");
            //outResult = WXPayUtil.toXmlWithCDATA(success);
            logger.info("微信--同步本地订单支付成功");
        } else {
            outResult = new OutSuccess("FAIL", "ERROR_SIGN");
            logger.error("签名错误,xml中签名:" + sign + ",校验后的签名:" + signResult);
            // outResult = WXPayUtil.toXmlWithCDATA(success);
        }
        return outResult;
    }

总结:微信小程序支付,需要先把微信小程序相关的密钥和证书申请好,然后调用预支付页面,最后支付完成调用业务层接口回写相关数据就欧了。注意的是 支付成功回调路径和返回参数要严格按照微信小程序官方文档,不然就会找不到回调路径以及会出现多次回调问题。

OK 到这里就结束了 

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值