Java微信支付

目录

前言

代码实现

1、yaml配置

2、前端唤醒微信支付controller

3、前端唤醒微信支付controller传参

4、前端唤醒微信支付controller返回参数

5、前端唤醒微信支付service具体实现

6、常量配置

7、微信支付工具类

8、微信支付回调

9、解析回调参数


前言

微信支付开发者文档

代码实现

1、yaml配置

#微信配置
wechat:
  appId: xxxxxxxx
  secret: xxxxxxxxxxxxxxxxxxxxxxxxxxx
  #商户号
  mchId: xxxxxxxx
  #商户秘钥
  key: xxxxxxxxxxxxxxxxxxxxxxx
  #终端IP
  clientIp: xxx.xx.xxx.xxx
  #订单微信支付回调地址
  orderPayNotifyUrl: https://xx.xxxxxxx.com/wechat/callback
  #订单微信退款回调地址
  orderRefundNotifyUrl: https://xx.xxxxxxx.com/wechat/refund/callback
  #退款证书
  refundCertPath: cert/apiclient_cert.p12

2、前端唤醒微信支付controller


    @PostMapping(value = "/wechat")
    @ApiOperation("微信支付")
    @ApiImplicitParam(paramType = Constants.HEADER, dataType = Constants.STRING, name = Constants.AUTHORIZATION, value = "授权token", required = true)
    public Result<OrderWechatPayVO> wechat(@Valid @RequestBody OrderPayDTO params) {
        OrderWechatPayVO orderWechatPayVO = orderPayService.wechatPay(params);
        return Result.ok(orderWechatPayVO);
    }

3、前端唤醒微信支付controller传参

@Data
@Accessors(chain = true)
@ApiModel("订单支付")
public class OrderPayDTO {

    @ApiModelProperty(value = "订单id", required = true)
    @NotNull(message = "订单id不能为空")
    private Long orderId;

    @ApiModelProperty(value = "下单用户终端 1.app 2.微信小程序", required = true)
    @NotNull(message = "下单用户终端不能为空")
    private Integer terminal;

    @ApiModelProperty(value = "是否部分支付", required = true)
    @NotNull(message = "是否部分支付不能为空")
    private Boolean partPay;

    @ApiModelProperty(value = "部分支付金额,部分支付时必传", required = false)
    private BigDecimal payPrice;

}

4、前端唤醒微信支付controller返回参数

@Data
@ApiModel(value = "订单微信支付返回")
public class OrderWechatPayVO {

    @ApiModelProperty(value = "支付流水id")
    private Long id;

    @ApiModelProperty(value = "支付流水号")
    private String payNo;

    @ApiModelProperty(value = "支付金额")
    private BigDecimal payPrice;

    @ApiModelProperty(value = "支付唤醒参数")
    private Map<String, Object> payData;

}

5、前端唤醒微信支付service具体实现


    /**
     * 微信支付
     *
     * @param params
     */
    @Transactional(rollbackFor = Exception.class)
    public OrderWechatPayVO wechatPay(OrderPayDTO params) {
        //创建支付流水
        OrderPay orderPay = createOrderPay(params, PayMethodCode.WECHAT_PAY.getCode());

        Map<String, Object> payResult;
        //app支付
        if (orderPay.getTerminal() == OrderTerminalCode.APP.getCode()) {
            payResult = wechatPayUtil.appOrder(orderPay.getPayNo(), orderPay.getPayPrice(), "购买", wechatProperties.getCourseOrderPayNotifyUrl());
        } else {
            //TODO 小程序支付获取会员openid
            payResult = wechatPayUtil.mpOrder(orderPay.getPayNo(), orderPay.getPayPrice(), "购买", "", wechatProperties.getCourseOrderPayNotifyUrl());
        }

        OrderWechatPayVO wechatPayVO = new OrderWechatPayVO();
        wechatPayVO.setId(orderPay.getId());
        wechatPayVO.setPayNo(orderPay.getPayNo());
        wechatPayVO.setPayPrice(orderPay.getPayPrice());
        wechatPayVO.setPayData(payResult);
        return wechatPayVO;
    }

    /**
     * 创建支付流水
     *
     * @param params
     * @param patMethod
     * @return
     */
    private OrderPay createOrderPay(OrderPayDTO params, int patMethod) {
        Order order = orderRepository.findByIdAndMemberId(params.getOrderId(), SecurityContextHolder.getUserId());
        Assert.notNull(order, "订单不存在");
        Assert.isFalse(order.getStatus() != OrderStatusCode.WAIT_PAY.getCode() && order.getStatus() != OrderStatusCode.PART_PAY.getCode(), "该订单无法支付");

        OrderPay orderPay = new OrderPay();
        orderPay.setMemberId(courseOrder.getMemberId());
        orderPay.setOrderId(courseOrder.getId());
        orderPay.setOrderNo(courseOrder.getOrderNo());
        orderPay.setTerminal(params.getTerminal());
        orderPay.setPayMethod(patMethod);
        orderPay.setOrderType(PayOrderTypeCode.COURSE_ORDER.getCode());
        orderPay.setStatus(PayStatusCode.WAIT_PAY.getCode());
        String payNo = DateUtil.format(new Date(), "yyyyMMddHHmmssSSS") + RandomUtil.randomNumbers(7);
        orderPay.setPayNo(payNo);
        orderPay.setPayPrice(courseOrder.getWaitPayPrice());
        orderPayRepository.insert(orderPay);
        return orderPay;
    }

6、常量配置


/**
 * 微信支付常量
 *
 * @author: CYL
 * @date: 2022-01-17 16:09
 */
public final class WechatPayConstants {

    /**
     * 小程序支付url
     */
    public static final String JSAPI_PAY_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    /**
     * app支付url
     */
    public static final String APP_PAY_URL = "https://api.mch.weixin.qq.com/v3/pay/transactions/app";

    /**
     * 查询订单url
     */
    public static final String QUERY_URL = "https://api.mch.weixin.qq.com/pay/orderquery";

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

    /**
     * 响应成功
     */
    public static final String SUCCESS = "SUCCESS";

    /**
     * 响应失败
     */
    public static final String FAIL = "FAIL";

    /**
     * JSAPI--JSAPI支付(或小程序支付)
     */
    public static final String JSAPI = "JSAPI";

    /**
     * APP--app支付
     */
    public static final String APP = "APP";

    /**
     * 支付加密方式md5
     */
    public static final String MD5 = "MD5";


}
/**
 * 订单状态
 *
 * @author: cyl
 * @date: 2022-01-14 11:53
 */
@Getter
public enum OrderStatusCode {

    WAIT_PAY(1, "待支付"),
    PART_PAY(2, "部分支付"),
    PAY_SUCCESS(3, "支付成功"),
    CANCEL(7, "已取消"),
    CLOSE(8, "已关闭"),
    ;

    private final int code;
    private final String msg;

    OrderStatusCode(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

}
/**
 * 支付方式
 *
 * @author: cyl
 * @date: 2022-01-17 15:52
 */
@Getter
public enum PayMethodCode {

    WECHAT_PAY(1, "微信支付"),
    ALIPAY_PAY(2, "支付宝支付"),
    BALANCE_PAY(3, "余额支付"),
    POS_PAY(4, "POS机支付");

    private final int code;
    private final String msg;

    PayMethodCode(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }


}
package com.lw.order.properties;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * 微信支付配置
 *
 * @author: cyl
 * @date: 2022-01-17 16:14
 */
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "wechat")
public class WechatProperties {

    /**
     * 微信appId
     */
    private String appId;

    /**
     * 微信secret
     */
    private String secret;

    /**
     * 商户号
     */
    private String mchId;

    /**
     * 商户key
     */
    private String key;

    /**
     * 服务端ip地址
     */
    private String clientIp;

    /**
     * 订单支付回调地址
     */
    private String courseOrderPayNotifyUrl;

    /**
     * 订单微信退款回调地址
     */
    private String courseOrderRefundNotifyUrl;

    /**
     * 退款证书存储路径
     */
    private String refundCertPath;


}

7、微信支付工具类

package com.lw.order.util;

import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.XmlUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.HttpRequest;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.lw.order.constant.WechatPayConstants;
import com.lw.order.properties.WechatProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;

import javax.net.ssl.SSLContext;
import java.io.InputStream;
import java.math.BigDecimal;
import java.security.KeyStore;
import java.util.List;
import java.util.Map;

/**
 * 微信支付
 *
 * @author: cyl
 * @date: 2022-01-17 16:06
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class WechatPayUtil {

    private final WechatProperties wechatProperties;


    /**
     * 小程序微信支付
     *
     * @param outTradeNo 订单号
     * @param amount     支付金额
     * @param body       支付明细
     * @param openid     用户openid
     * @param notifyUrl  支付回调地址
     * @return
     */
    public Map<String, Object> mpOrder(String outTradeNo, BigDecimal amount, String body, String openid, String notifyUrl) {
        //订单金额单位分
        int totalAmount = NumberUtil.mul(amount, 100).intValue();
        Map<String, Object> params = Maps.newHashMap();
        params.put("appid", wechatProperties.getAppId());
        params.put("mch_id", wechatProperties.getMchId());
        params.put("nonce_str", RandomUtil.randomString(30));
        params.put("sign_type", WechatPayConstants.MD5);
        params.put("body", body);
        params.put("openid", openid);
        params.put("trade_type", WechatPayConstants.JSAPI);
        params.put("out_trade_no", outTradeNo);
        params.put("total_fee", String.valueOf(totalAmount));
        params.put("spbill_create_ip", wechatProperties.getClientIp());
        params.put("notify_url", notifyUrl);
        params.put("sign", buildSign(params));
        String xml = XmlUtil.mapToXmlStr(params);
        log.info("app小程序支付xml:{}", xml);

        String result = HttpRequest.post(WechatPayConstants.JSAPI_PAY_URL)
                .body(xml)
                .timeout(20000)
                .execute()
                .body();

        log.info("微信支付响应:{}", result);

        Map<String, Object> map = XmlUtil.xmlToMap(result);
        if (!WechatPayConstants.SUCCESS.equals(map.get("return_code"))) {
            log.error("微信支付异常:{}", map.get("return_msg").toString());
            throw new IllegalArgumentException("微信支付异常");
        }

        if (!WechatPayConstants.SUCCESS.equals(map.get("result_code"))) {
            log.error("微信支付异常:{}", map.get("err_code_des").toString());
            throw new IllegalArgumentException("微信支付异常");
        }

        long timestamp = System.currentTimeMillis() / 1000;

        Map<String, Object> data = Maps.newHashMap();
        data.put("appid", map.get("appid"));
        data.put("noncestr", map.get("nonce_str"));
        data.put("package", "Sign=WXPay");
        data.put("partnerid", map.get("mch_id"));
        data.put("prepayid", map.get("prepay_id"));
        data.put("timestamp", timestamp);
        String sign = buildSign(data);
        data.put("paySign", sign);
        return data;
    }


    /**
     * app微信支付
     *
     * @param outTradeNo 订单号
     * @param amount     支付金额
     * @param body       商品描述
     * @param notifyUrl  支付回调地址
     * @return
     */
    public Map<String, Object> appOrder(String outTradeNo, BigDecimal amount, String body, String notifyUrl) {
        //订单金额单位分
        int totalAmount = NumberUtil.mul(amount, 100).intValue();
        Map<String, Object> params = Maps.newHashMap();
        params.put("appid", wechatProperties.getAppId());
        params.put("mch_id", wechatProperties.getMchId());
        params.put("nonce_str", RandomUtil.randomString(30));
        params.put("sign_type", WechatPayConstants.MD5);
        params.put("body", body);
        params.put("trade_type", WechatPayConstants.APP);
        params.put("out_trade_no", outTradeNo);
        params.put("total_fee", String.valueOf(totalAmount));
        params.put("spbill_create_ip", wechatProperties.getClientIp());
        params.put("notify_url", notifyUrl);
        params.put("sign", buildSign(params));
        String xml = XmlUtil.mapToXmlStr(params);
        log.info("app微信支付xml:{}", xml);

        String result = HttpRequest.post(WechatPayConstants.APP_PAY_URL)
                .body(xml)
                .timeout(20000)
                .execute()
                .body();

        log.info("微信支付响应:{}", result);

        Map<String, Object> map = XmlUtil.xmlToMap(result);
        if (!WechatPayConstants.SUCCESS.equals(map.get("return_code"))) {
            log.error("微信支付异常:{}", map.get("return_msg").toString());
            throw new IllegalArgumentException("微信支付异常");
        }

        if (!WechatPayConstants.SUCCESS.equals(map.get("result_code"))) {
            log.error("微信支付异常:{}", map.get("err_code_des").toString());
            throw new IllegalArgumentException("微信支付异常");
        }

        long timestamp = System.currentTimeMillis() / 1000;

        Map<String, Object> data = Maps.newHashMap();
        data.put("appid", map.get("appid"));
        data.put("partnerid", map.get("mch_id"));
        data.put("prepayid", map.get("prepay_id"));
        data.put("noncestr", map.get("nonce_str"));
        data.put("timestamp", String.valueOf(timestamp));
        data.put("package", "Sign=WXPay");
        data.put("sign", buildSign(map));
        return data;
    }


    /**
     * 微信退款
     *
     * @param outTradeNo   订单号
     * @param outRefundNo  订单退款单号
     * @param totalAmount  订单总金额
     * @param refundAmount 退款金额
     * @param notifyUrl    退款回调地址
     * @return
     */
    public String refund(String outTradeNo, String outRefundNo, BigDecimal totalAmount, BigDecimal refundAmount, String notifyUrl) {
        try {
            //订单金额单位分
            int totalAmountFee = NumberUtil.mul(totalAmount, 100).intValue();
            //退款金额单位分
            int refundAmountFee = NumberUtil.mul(refundAmount, 100).intValue();

            Map<String, Object> params = Maps.newHashMap();
            params.put("appid", wechatProperties.getAppId());
            params.put("mch_id", wechatProperties.getMchId());
            params.put("nonce_str", RandomUtil.randomString(30));
            params.put("out_trade_no", outTradeNo);
            params.put("out_refund_no", outRefundNo);
            params.put("total_fee", String.valueOf(totalAmountFee));
            params.put("refund_fee", String.valueOf(refundAmountFee));
            params.put("notify_url", notifyUrl);
            params.put("sign", buildSign(params));

            String xml = XmlUtil.mapToXmlStr(params);
            log.info("退款xml:{}", xml);

            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(wechatProperties.getRefundCertPath())) {
                keyStore.load(inputStream, wechatProperties.getMchId().toCharArray());
            }

            SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, wechatProperties.getMchId().toCharArray()).build();
            SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null, new DefaultHostnameVerifier());
            CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build();

            HttpPost httpPost = new HttpPost(WechatPayConstants.REFUND_URL);
            httpPost.setEntity(new StringEntity(xml, "UTF-8"));
            HttpResponse response = httpClient.execute(httpPost);
            String result = EntityUtils.toString(response.getEntity(), "UTF-8");

            Map<String, Object> map = XmlUtil.xmlToMap(result);

            if (WechatPayConstants.FAIL.equals(map.get("return_code"))) {
                log.error(map.get("return_msg").toString());
                return map.get("return_msg").toString();
            }

            if (WechatPayConstants.FAIL.equals(map.get("result_code"))) {
                log.error("退款申请提交业务失败," + map.get("err_code_des") + ";错误代码:" + map.get("err_code"));
                return "退款申请提交业务失败," + map.get("err_code_des") + ";错误代码:" + map.get("err_code");
            }

            if (WechatPayConstants.SUCCESS.equals(map.get("result_code"))) {
                return WechatPayConstants.SUCCESS;
            }

        } catch (Exception e) {
            log.error("微信退款异常", e);
        }
        return WechatPayConstants.FAIL;
    }


    /**
     * 查询订单状态
     *
     * @param outTradeNo
     * @return
     */
    public boolean queryOrder(String outTradeNo) {
        Map<String, Object> params = Maps.newHashMap();
        params.put("appid", wechatProperties.getAppId());
        params.put("mch_id", wechatProperties.getMchId());
        params.put("nonce_str", RandomUtil.randomString(30));
        params.put("sign_type", WechatPayConstants.MD5);
        params.put("out_trade_no", outTradeNo);
        params.put("sign", buildSign(params));
        String xml = XmlUtil.mapToXmlStr(params);

        String result = HttpRequest.post(WechatPayConstants.QUERY_URL)
                .body(xml)
                .timeout(20000)
                .execute()
                .body();

        Map<String, Object> map = XmlUtil.xmlToMap(result);

        if (!WechatPayConstants.SUCCESS.equals(map.get("return_code"))) {
            log.error("微信查询接口失败:{}", result);
            return false;
        }

        if (!WechatPayConstants.SUCCESS.equals(map.get("result_code"))) {
            log.error("微信查询接口失败:{}", result);
            return false;
        }

        return WechatPayConstants.SUCCESS.equals(map.get("trade_state"));
    }


    /**
     * 参数签名
     *
     * @param params 参数
     * @return 签名字符串
     */
    public String buildSign(Map<String, Object> params) {
        List<String> list = Lists.newArrayList();
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            if ("sign".equals(entry.getKey())) {
                continue;
            }

            //参数值为空不参与签名
            if (StrUtil.isNotEmpty(entry.getValue().toString())) {
                list.add(entry.getKey() + "=" + entry.getValue());
            }
        }
        list.sort(String::compareTo);
        list.add("key=" + wechatProperties.getKey());
        return SecureUtil.md5(Joiner.on("&").join(list)).toUpperCase();
    }


    /**
     * 支付回调响应
     *
     * @param returnCode 返回状态
     * @param returnMsg  返回提示
     * @return
     */
    public String buildResponse(String returnCode, String returnMsg) {
        Map<String, String> map = Maps.newHashMap();
        map.put("return_code", returnCode);
        map.put("return_msg", returnMsg);
        return XmlUtil.mapToXmlStr(map);
    }

}

8、微信支付回调


    @PostMapping(value = "/wechat/callback")
    @ApiOperation(value = "微信支付回调", hidden = true)
    public String wechatCallback(HttpServletRequest request) {
        RLock lock = null;
        try {
            Document xml = XmlUtil.readXML(request.getInputStream());
            String xmlStr = XmlUtil.toStr(xml);
            log.info("[微信支付异步回调通知报文]:" + xmlStr);
            Map<String, Object> resultMap = XmlUtil.xmlToMap(xmlStr);

            if (!WechatPayConstants.SUCCESS.equalsIgnoreCase(resultMap.get("return_code").toString())) {
                //响应不成功
                return wechatPayUtil.buildResponse(WechatPayConstants.FAIL, "return_code不为SUCCESS");
            }

            String tradeNo = resultMap.get("out_trade_no").toString();
            String sign = wechatPayUtil.buildSign(resultMap);
            if (!sign.equals(resultMap.get("sign"))) {
                log.error("[微信支付回调通知签名校验不通过]");
                return wechatPayUtil.buildResponse(WechatPayConstants.FAIL, "签名校验不通过");
            }

            if (!WechatPayConstants.SUCCESS.equalsIgnoreCase(resultMap.get("result_code").toString())) {
                return wechatPayUtil.buildResponse(WechatPayConstants.FAIL, "支付未成功");
            }

            //使用redisson获取分布式锁
            lock = redissonClient.getLock(DLockConstants.WECHAT_OUT_TRADE_NO + tradeNo);
            //尝试加锁,设置等待时间10秒,锁超时时间10秒
            boolean locked = lock.tryLock(10, 10, TimeUnit.SECONDS);

            if (locked) {
                OrderPay orderPay = orderPayService.findByPayNo(tradeNo);
                if (null == orderPay || orderPay.getStatus() != PayStatusCode.WAIT_PAY.getCode()) {
                    return wechatPayUtil.buildResponse(WechatPayConstants.FAIL, "订单不存在或已支付");
                }

                //重新调用微信接口查询订单状态
                if (wechatPayUtil.queryOrder(tradeNo)) {
                    orderPayService.paySuccess(orderPay, PayMethodCode.WECHAT_PAY.getCode(), resultMap.get("transaction_id").toString());
                    return wechatPayUtil.buildResponse(WechatPayConstants.SUCCESS, "支付成功");
                }

                log.error("微信支付回调通知二次校验不通过:{}", tradeNo);
                return wechatPayUtil.buildResponse(WechatPayConstants.FAIL, "二次校验订单不通过");
            }

            log.error("微信支付回调通知排队中:{}", tradeNo);
            return wechatPayUtil.buildResponse(WechatPayConstants.FAIL, "处理异常");
        } catch (Exception e) {
            log.error("[微信支付回调处理异常]", e);
            return wechatPayUtil.buildResponse(WechatPayConstants.FAIL, "处理异常");
        } finally {
            if (null != lock && lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

9、解析回调参数

    /**
     * 解析回调参数
     *
     * @param request
     * @return
     */
    private static Map<String, String> requestParam(HttpServletRequest request) {
        Map<String, String> params = Maps.newHashMap();
        Map<String, String[]> requestParams = request.getParameterMap();
        for (Map.Entry<String, String[]> entry : requestParams.entrySet()) {
            String valueStr = String.join(",", entry.getValue());
            params.put(entry.getKey(), valueStr);
        }
        return params;
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值