接入微信支付API v3的两种方式

简介: 接入微信支付API v3的两种方式

微信支付文档:https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml
image.png

本篇介绍两种对接方式。

第一种:根据官方文档自定义对接

定义微信支付服务

@Slf4j
@Component
@RefreshScope
public class WxPayService {
   
   
    //商户id
    @Value("${wechat.merchantId}")
    private String merchantId;
    //应用ID
    @Value("${wechat.appId}")
    private String appId;
    //商户API私钥
    @Value("${wechat.privateKey}")
    private String privateKey;
    //微信支付平台证书
    @Value("${wechat.cert}")
    private String payCertificate;
    //商户证书序列号
    @Value("${wechat.mchSerialNo}")
    private String certificateSn;
    //微信支付平台证书
    @Value("${wechat.notifyUrl}")
    private String notifyUrl;
    //key
    @Value("${wechat.apiV3Key}")
    private String apiV3Key;

    private CloseableHttpClient httpClient;

    /**
     * 初始化参数
     */
    @PostConstruct
    public void initData() {
   
   
        try {
   
   
            PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(privateKey));
            X509Certificate certificate = PemUtil.loadCertificate(new FileInputStream(payCertificate));

            List<X509Certificate> list = new ArrayList<>();
            list.add(certificate);

            httpClient = WechatPayHttpClientBuilder.create()
                    .withMerchant(merchantId, certificateSn, merchantPrivateKey)
                    .withWechatPay(list).build();
        } catch (FileNotFoundException e) {
   
   
            log.error(e.getMessage());
        }
    }

    /**
     * 获取预支付交易会话标识
     *
     * @param order
     * @return
     * @throws Exception
     */
    @Override
    public String prepay(OrderInfo order) throws Exception {
   
   
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/app");
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");

        JSONObject amount = new JSONObject();
        //订单总金额,单位为分
        amount.put("total", order.getPayment());
        //CNY:人民币,境内商户号仅支持人民币
        amount.put("currency", "CNY");

        JSONObject param = new JSONObject();
        param.put("mchid", merchantId);
        param.put("appid", appId);
        //商品描述
        param.put("description", order.getDescription());
        //回调地址
        param.put("notify_url", notifyUrl);
        //商户订单号
        param.put("out_trade_no", order.getOrderId());
        param.put("amount", amount);

        log.info("prepay request=>{}", param);
        CloseableHttpResponse response = null;
        try {
   
   
            httpPost.setEntity(new StringEntity(param.toString(), "UTF-8"));
            response = httpClient.execute(httpPost);
            String bodyAsString = EntityUtils.toString(response.getEntity());

            JSONObject resBody = JSON.parseObject(bodyAsString);
            return resBody.getString("prepay_id");
        } finally {
   
   
            if (response != null) {
   
   
                response.close();
            }
        }
    }

    /**
     * 签名
     *
     * @param time
     * @param prepayId
     * @param nonce
     * @return
     * @throws Exception
     */
    @Override
    public String signature(Long time, String prepayId, String nonce) throws Exception {
   
   
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(privateKey));
        String text = String.format("%s\n%s\n%s\n%s\n", appId, time / 1000, nonce, prepayId);
        log.info("signature text=>{}", text);
        return sign(text, merchantPrivateKey);
    }

    /**
     * 解码
     *
     * @param associated
     * @param nonce
     * @param ciphertext
     * @return
     * @throws Exception
     */
    @Override
    public String decrypt(String associated, String nonce, String ciphertext) throws Exception {
   
   
        return new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8)).decryptToString(associated.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
    }

    /**
     * 关闭订单
     *
     * @param orderId
     * @return
     * @throws Exception
     */
    @Override
    public Boolean close(Long orderId) throws Exception {
   
   
        String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/%s/close";
        HttpPost httpPost = new HttpPost(String.format(url, orderId));
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");

        JSONObject reqBody = new JSONObject();
        reqBody.put("mchid", merchantId);

        httpPost.setEntity(new StringEntity(reqBody.toString(), "UTF-8"));
        CloseableHttpResponse response = httpClient.execute(httpPost);
        log.info(response.toString());
        // 返回204则表示成功
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode == HttpStatus.NO_CONTENT.value()) {
   
   
            return Boolean.TRUE;
        } else {
   
   
            return Boolean.FALSE;
        }
    }

    private String sign(String data, PrivateKey privateKey) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
   
   
        byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
        //用私钥对信息生成数字签名
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(dataBytes);

        return Base64.getEncoder().encodeToString(signature.sign());
    }

    /**
     * 退款申请
     *
     * @param order
     * @param reason
     * @return
     * @throws Exception
     */
    @Override
    public Boolean refund(OrderInfo order, String reason) throws Exception {
   
   

        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds");
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");

        JSONObject amount = new JSONObject();
        //原订单金额
        amount.put("total", order.getPayment());
        //退款金额
        amount.put("refund", order.getPayment());
        //退款币种
        amount.put("currency", "CNY");

        JSONObject reqBody = new JSONObject();
        //退款结果回调url
        reqBody.put("notify_url", notifyUrl);
        //商户订单号
        reqBody.put("out_trade_no", order.getOrderId());
        //商户退款单号
        reqBody.put("out_refund_no", order.getOrderId());
        //退款原因,非必填
        reqBody.put("reason", reason);
        reqBody.put("amount", amount);

        httpPost.setEntity(new StringEntity(reqBody.toString(), "UTF-8"));
        CloseableHttpResponse response = httpClient.execute(httpPost);
        log.info(response.toString());

        String bodyAsString = EntityUtils.toString(response.getEntity());

        JSONObject resBody = JSON.parseObject(bodyAsString);
        log.info(resBody.toString());

        return Boolean.TRUE;
    }
}

调用示例

/**
 * 创建微信订单
 *
 * @param userId  用户id
 * @param request 请求参数
 * @return
 */
@Override
@Transactional(rollbackFor = Exception.class)
public CreateOrderDTO createWxOrder(Long userId, CreateOrderRequest request) {
   
   
    //商品id
    Long goodsId = request.getGoodsId();
    //根据用户id,商品id和订单状态查询历史订单
    List<OrderInfo> list = orderMapper.query(new QueryOrderRequest()
            .setUserId(userId)
            .setGoodsId(goodsId)
            .setStatus(OrderStatus.CODE_UNPAID));

    try {
   
   
        // 当不存在历史订单时新创建订单,否则获取历史订单继续支付
        OrderInfo order;
        if (CollectionUtils.isEmpty(list)) {
   
   
            order = generateOrder(userId, goodsId);
        } else {
   
   
            order = list.get(0);
        }
        //预支付标识
        String prepayId = order.getPrepayId();
        //随机字符串
        String nonce = RandomUtil.randomString(32);
        long now = order.getGmtCreate().getTime();
        //签名
        String signature = wxPayService.signature(now, prepayId, nonce);
        CreateOrderDTO dto = new CreateOrderDTO();
        dto.setPrepayId(prepayId);
        dto.setTimestamp(now / 1000);
        dto.setNoncestr(nonce);
        dto.setSign(signature);
        dto.setOrderId(order.getOrderId());
        return dto;
    } catch (Exception e) {
   
   
        throw new ServerException("创建订单失败");
    }
}

/**
 * 生成订单信息
 *
 * @param userId   用户id
 * @param goodsId  商品id
 * @return
 */
private OrderInfo generateOrder(Long userId, Long goodsId) throws Exception {
   
   
    GoodsInfoDO goods = goodsMapper.getGoodsById(goodsId);
    if (null == goods) {
   
   
        throw new NotFoundException("商品不存在");
    }

    OrderInfo order = new OrderInfo();
    order.setUserId(userId);
    order.setGoodsId(goodsId);
    order.setOrderId(new IdWorker().nextId());
    order.setGmtCreate(new Date());
    order.setDescription(goods.getDescription());
    order.setPayment(goods.getPrice());

    // 微信预支付接口
    String prepayId = wxPayService.prepay(order);
    order.setPrepayId(prepayId);
    orderMapper.addOrder(order);
    return order;
}

/**
 * 关闭订单
 *
 * @param orderId 订单id
 * @return
 */
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean closeOrder(Long orderId) throws Exception {
   
   
    OrderInfo info = orderMapper.getOrderById(orderId);
    if (info == null) {
   
   
        throw new NotFoundException("订单不存在");
    }

    if(info.getStatus() != OrderStatus.CODE_UNPAID) {
   
           
        throw new ServerException("状态不正确,不允许关闭");
    }
    info.setStatus(OrderStatus.CODE_CLOSED);
    boolean flag = orderMapper.updateByOrderId(info) > 0;
    log.info("更新订单[{}]状态结果:{}", info.getOrderId(), flag);
    if (flag) {
   
   
        // 调用微信接口关闭订单
        try {
   
   
            wxPayService.close(orderId);
        } catch (Exception e) {
   
   
            e.printStackTrace();
        }
    }
    return flag;
}

/**
 * 发起退款申请
 *
 * @param userId
 * @param orderId 订单号
 * @param reason 退款原因
 * @return
 */
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Boolean> refund(Long userId, Long orderId, String reason) {
   
   
    OrderInfo info = new OrderInfo();
    info.setOrderId(orderId);
    info.setUserId(userId);
    OrderInfo order = orderMapper.getOrderByIdAndUserId(info);
    if (order == null || order.getId() == 0) {
   
   
        throw new ServerException("订单未找到");
    }
    //申请退款
    info.setStatus(OrderStatus.CODE_REFUNDING);
    info.setMark(reason);
    boolean flag = orderMapper.updateByOrderId(info) > 0;
    if (flag) {
   
   
        log.info("发起退款,开始调用wxpay的接口...");
        flag = wxPayService.refund(info, reason);
        log.info("调用wxpay退款接口结果:{}", flag);
    }
    return flag;
}


private static final String PAY_SUCCESS = "TRANSACTION.SUCCESS";
private static final String REFUND_SUCCESS = "REFUND.SUCCESS";

/**
* 回调接口
* @param request
**/
@Override
public Result<Boolean> callback(NotifyRequest request) {
   
   
    NotifyResource resource = request.getResource();
    String summary = request.getSummary();
    String eventType = request.getEvent_type();
    JSONObject notify;
    try {
   
   
        String msg = wxPayService.decrypt(resource.getAssociated_data(), resource.getNonce(), resource.getCiphertext());
        notify = JSONObject.parseObject(msg);
    } catch (Exception e) {
   
   
        return Result.fail(e.getMessage());
    }
    TransactionSuccessNotify successNotify = notify.toJavaObject(TransactionSuccessNotify.class);
    log.warn("notify = {}", successNotify);
    // 标记订单状态
    Long orderId = successNotify.getOut_trade_no();
    OrderInfo order = orderMapper.getOrderById(orderId);
    if (order == null) {
   
   
        throw new NotFoundException("订单未找到");
    }
    switch (eventType) {
   
           
        case PAY_SUCCESS: // 支付回调
            order.setStatus(OrderStatus.CODE_PAID);
            orderMapper.updateByOrderId(order);
            return Result.success(Boolean.TRUE);
        case REFUND_SUCCESS: // 退款回调
            order.setStatus(OrderStatus.CODE_REFUNDED);
            orderMapper.updateByOrderId(order);
            return Result.success(Boolean.TRUE);
        default:
            log.error("不支持的通知: {}", eventType);
            throw new NotFoundException("不支持的通知");
    }
}
第二种:使用第三方库

引入必要的jar包
pom.xml

<!-- 微信支付-->
<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-apache-httpclient</artifactId>
    <version>0.4.8</version>
</dependency>
<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-java</artifactId>
    <version>0.1.0</version>
</dependency>

<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-pay</artifactId>
    <version>4.4.0</version>
</dependency>
<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-mp</artifactId>
    <version>4.4.0</version>
</dependency>
<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-miniapp</artifactId>
    <version>4.4.0</version>
</dependency>

调用示例

/**
* 关闭订单
* @param orderId
**/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean closeOrder(Long orderId) throws WxPayException {
   
   
    OrderInfo order = wxOrderInfoMapper.selectByOrderId(orderId);
    if (order.getStatus() != OrderStatus.CODE_UNPAID) {
   
   
        throw new ServerException("当前状态不允许关闭");
    }
    order.setStatus(OrderStatus.CODE_CLOSED);
    Boolean res = wxOrderInfoMapper.updateStatusByOrderId(order);
    if (res) {
   
   
        // 调用微信的关闭订单接口
        wxPayService.closeOrderV3(orderId.toString());
    }
    return res;
}

/**
* 预支付
*
* @param orderId
* @param openId
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Result<OrderPrepayInfoDTO> toPay(String orderId, String openId) {
   
   
   OrderInfo order = wxOrderInfoMapper.selectByOrderId(orderId);      
   JSONObject object = prePay(orderId, openId, order);

   OrderPrepayInfoDTO prepay = new OrderPrepayInfoDTO();
   prepay.setOrderId(orderId);
   prepay.setAppId(object.getString("appId"));
   prepay.setTimestamp(object.getString("timeStamp"));
   prepay.setPackageValue(object.getString("packageValue"));
   prepay.setSignType(object.getString("signType"));
   prepay.setPaySign(object.getString("paySign"));
   prepay.setNonceStr(object.getString("nonceStr"));
   return Result.success(prepay);
}

/**
* 预支付订单
*
* @param orderId
* @param openId
* @param order
* @return
* @throws WxPayException
*/
private JSONObject prePay(String orderId, String openId, OrderInfo order) throws WxPayException {
   
   
   WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
   WxPayUnifiedOrderV3Request.Payer payer = new WxPayUnifiedOrderV3Request.Payer();
   payer.setOpenid(openId);
   request.setPayer(payer);
   WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
   amount.setTotal(order.getPayment());
   amount.setCurrency("CNY");
   request.setAmount(amount);
   request.setDescription(order.getDescription());
   request.setOutTradeNo(orderId);
   log.info("预支付订单请求-->{}", request.toString());
   WxPayUnifiedOrderV3Result.JsapiResult orderV3 = wxPayService.createOrderV3(TradeTypeEnum.JSAPI, request);
   return JSON.parseObject(JSON.toJSONString(orderV3));
}

/**
* 需要与退款回调分开写
* @param notify
**/
@Override
public Result<Boolean> payCallback(OriginNotifyResponse notify, HttpServletRequest request) {
   
   
   log.info("Wechat pay callback");
   try {
   
   
       WxPayOrderNotifyV3Result v3Result = wxPayService.parseOrderNotifyV3Result(jsonStrSort(notify), getSignatureHeader(request));
       WxPayOrderNotifyV3Result.DecryptNotifyResult result = v3Result.getResult();
       log.info("DecryptNotifyResult=>{}", result.toString());
       Long orderId = Long.valueOf(result.getOutTradeNo());
       OrderInfo order = wxOrderInfoMapper.selectByOrderId(orderId);
       if (order == null) {
   
   
           throw new ClientErrorException(ErrorCode.INVALID_ORDER);
       }

       String tradeState = result.getTradeState();
       log.info("tradeState=>{}", tradeState);
       if ("SUCCESS".equals(tradeState)) {
   
   
           // 修改订单状态为已支付
           order.setStatus(OrderStatus.CODE_PAID);
           // 付款时间
           order.setPayTime(new Date());
           wxOrderInfoMapper.updateStatusByOrderId(order);
           // 将分享置为【1-已购买】状态
           userService.updateUserShareStatus(order.getShareId(), 1);
           return Result.success(Boolean.TRUE);
       }
   } catch (WxPayException e) {
   
   
       log.error("WxPayException:", e);
       return Result.fail(ResponseCode.ERROR_SERVER_EXCEPTION, e.getCustomErrorMsg());
   }
   return Result.fail();
}

/**
* 退款申请
* @param request 请求参数
* @param userId 用户id
**/
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Boolean> refund(RefundOrderRequest request, Long userId) {
   
   
   OrderInfo order = wxOrderInfoMapper.getOrderByUserIdAndOrderId(userId, Long.valueOf(request.getOrderId()));
   if (order == null) {
   
   
       throw new ResourceNotFoundException(ErrorCode.ORDER_NOT_FOUND);
   }
   order.setReason(request.getReason());
   // 退款申请中
   order.setStatus(WXOrderStatus.CODE_REFUNDING);          
    Boolean res = wxOrderInfoMapper.updateStatusByOrderId(order);
    if (!res) {
   
   
        throw new ServerErrorException(ErrorCode.INTERNAL_SERVER_ERROR);
    }
    WxPayRefundV3Result result = toRefund(order);
    switch (result.getStatus()) {
   
   
        case "SUCCESS":
            log.info("退款成功");
            order.setStatus(WXOrderStatus.CODE_REFUNDED);
            wxOrderInfoMapper.updateStatusByOrderId(order);
            break;
        case "CLOSED":
            log.info("退款关闭");
            break;
        case "PROCESSING":
            log.info("退款处理中");
            break;
        case "ABNORMAL":
            log.info("退款异常");
            break;
        default:
            log.info("受理失败");
            break;
    }
    return new Result<>(Boolean.TRUE, messageUtil.getMessage(ErrorCode.REFUND_REQUEST_RECEIVED));       
   }   
}

/**
* 调用微信退款服务
*
* @param order
* @throws WxPayException
*/
private WxPayRefundV3Result toRefund(OrderInfo order) throws WxPayException {
   
   
   WxPayRefundV3Request refundRequest = new WxPayRefundV3Request();
   refundRequest.setOutTradeNo(order.getOrderId().toString());
   refundRequest.setOutRefundNo(order.getOrderId().toString());
   WxPayRefundV3Request.Amount amount = new WxPayRefundV3Request.Amount();
   amount.setTotal(order.getPayment());
   amount.setCurrency("CNY");
   amount.setRefund(order.getPayment());
   refundRequest.setAmount(amount);
   // 退款回调
   refundRequest.setNotifyUrl(paymentConfig.getMa_refund_notifyUrl());

   WxPayRefundV3Result result = wxPayService.refundV3(refundRequest);
   log.info("toRefund result=>{}", result);
   return result;
}

/**
* 需要与支付回调分开写,否则接收不到
**/
@Override
public Result<Boolean> refundCallback(OriginNotifyResponse notify, HttpServletRequest request) {
   
   
   try {
   
   
       WxPayRefundNotifyV3Result v3Result = wxPayService.parseRefundNotifyV3Result(jsonStrSort(notify), getSignatureHeader(request));
       WxPayRefundNotifyV3Result.DecryptNotifyResult result = v3Result.getResult();
       log.info("DecryptNotifyResult=>{}", result.toString());
       OrderInfo order = wxOrderInfoMapper.selectByOrderId(result.getOutTradeNo());
       if (order == null) {
   
   
           throw new ClientErrorException(ErrorCode.INVALID_ORDER);
       }

       String refundStatus = result.getRefundStatus();
       log.info("refundStatus=>{}", refundStatus);
       if ("SUCCESS".equals(refundStatus)) {
   
   
           // 修改订单状态为退款成功
           order.setStatus(WXOrderStatus.CODE_REFUNDED);
           wxOrderInfoMapper.updateStatusByOrderId(order);
           return Result.success();
       }
   } catch (WxPayException e) {
   
   
       log.error("WxPayException:", e);
   }
   return Result.fail();
}
相关文章
|
19天前
|
前端开发 小程序 API
【微信小程序】-- 使用 npm 包 - API Promise化(四十二)
【微信小程序】-- 使用 npm 包 - API Promise化(四十二)
|
8月前
|
小程序 前端开发 JavaScript
微信小程序框架---视图层&逻辑层&API&事件
微信小程序框架---视图层&逻辑层&API&事件
138 0
|
19天前
|
人工智能 机器人 API
【Python+微信】【企业微信开发入坑指北】3. 如何利用企业微信API给微信群推送消息
【Python+微信】【企业微信开发入坑指北】3. 如何利用企业微信API给微信群推送消息
38 0
|
19天前
|
缓存 人工智能 API
【Python+微信】【企业微信开发入坑指北】2. 如何利用企业微信API主动给用户发应用消息
【Python+微信】【企业微信开发入坑指北】2. 如何利用企业微信API主动给用户发应用消息
27 0
|
19天前
|
小程序 API 开发者
微信小程序授权登录流程以及应用到的API
微信小程序授权登录流程以及应用到的API
219 0
|
19天前
|
JSON Java API
微信支付JSAPI3微信支付开发API V3
微信支付JSAPI3微信支付开发API V3
31 0
|
19天前
|
缓存 开发框架 小程序
微信小程序(uniapp)api讲解
微信小程序(uniapp)api讲解
90 0
|
19天前
|
小程序 API 开发者
微信小程序有关跳转的API
微信小程序有关跳转的API
91 0
|
19天前
|
小程序 API
微信小程序登录授权流程及所用API
微信小程序登录授权流程及所用API
218 0
|
19天前
|
小程序 JavaScript API
微信小程序获取手机号流程以及用到的API
微信小程序获取手机号流程以及用到的API
100 0