基于wechatpay-java实现微信支付JSAPI


相较于 v2 版本,v3 版本的接口文档在阅读上可能显得相对凌乱。它的组织结构可能不太清晰,难以快速理解整个流程。但是,一旦我们对基本流程有了大致了解,我们可以利用 wechatpay-java 来简化开发过程(一把梭哈)。

相关官方文档:

springboot-wechat_pay 示例项目模拟简单电商支付业务,根据官方文档完成前置工作替换项目配置即可使用。希望对您有所帮助!

用户付款流程

如图1,用户通过分享或扫描二维码进入商户小程序,用户选择购买,完成选购流程。

步骤2:如图3,调起微信支付控件,用户开始输入支付密码。

imgimgimg
图1 打开商户小程序图2 请求微信支付图3 调起微信支付控件

如图4,密码验证通过,支付成功。商户后台得到支付成功的通知。

步骤4:如图5,返回商户小程序,显示购买成功。

步骤5:如图6,微信支付公众号下发支付凭证。

img 图4 请求支付成功img 图5 返回商户小程序img
图4 请求支付成功图5 返回商户小程序图6 下发支付凭证

业务流程讲解

业务流程图如下:
在这里插入图片描述
重点步骤说明:

步骤4:用户下单发起支付,商户可通过JSAPI下单创建支付订单。

步骤9:商户小程序内使用小程序调起支付API(wx.requestPayment)发起微信支付,详见小程序API文档

步骤16:用户支付成功后,商户可接收到微信支付支付结果通知支付通知API

步骤21:商户在没有接收到微信支付结果通知的情况下需要主动调用查询订单API查询支付结果。

接入前准备

详细操作流程参考官方文档:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtml#part-1

  • Java 1.8+。
  • 成为微信支付商户
  • 商户 API 证书:指由商户申请的,包含证书序列号、商户的商户号、公司名称、公钥信息的证书。
  • 商户 API 私钥:商户申请商户API证书时,会生成商户私钥,并保存在本地证书文件夹的文件 apiclient_key.pem 中。
  • APIv3 密钥:为了保证安全性,微信支付在回调通知和平台证书下载接口中,对关键信息进行了 AES-256-GCM 加密。APIv3 密钥是加密时使用的对称密钥。

最终我们需要获取到以下信息:

  • appId:微信公众号或者小程序等的appId(登陆 微信公众平台,设置 -> 基本设置 -> 账号信息 -> AppID)
  • merchantId:微信支付商户号(首先关联商户号,功能 -> 微信支付 -> 商户号管理 -> 已关联商户号)
  • privateKeyPath:商户API私钥(商户 API 证书根据文档获取,将apiclient_key.pem文件复制至项目路径下。该文件在resource下路径)
  • merchantSerialNumber:商户证书序列号(登陆 商户平台,账户中心 -> API安全 -> API证书管理 -> 查看证书号)
  • apiV3key:商户APIv3密钥(登陆 商户平台,账户中心 -> API安全的页面 设置该密钥,请求才能通过微信支付的签名校验)
  • payNotifyUrl:支付回调通知地址(本项目的回调接口)

快速接入

项目结构如下,以及需要注意点
在这里插入图片描述

1、引入开发库

Gradle

implementation 'com.github.wechatpay-apiv3:wechatpay-java:0.2.10'

Maven

<dependency>
  <groupId>com.github.wechatpay-apiv3</groupId>
  <artifactId>wechatpay-java</artifactId>
  <version>0.2.10</version>
</dependency>

2、配置参数

yaml配置文件(这里只对支付参数讲解):

wechat:
  pay:
    # 微信公众号或者小程序等的appId
    appId: XXX
    # 微信支付商户号
    merchantId: XXX
    # 商户证书路径
    certPemPath: /apiclient_cert.pem
    # 商户API私钥路径
    privateKeyPath: /apiclient_key.pem
    # 商户APIv3密钥
    apiV3key: XXXX
    # 支付回调通知地址
    payNotifyUrl: https://XXXX/api/callback/wechat/pay/callback

微信支付配置类

package com.gw.pay.config;

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

/**
 * Description: 微信支付配置类
 *
 * @author LinHuiBa-YanAn
 * @date 2023/10/6 10:27
 */
@Data
@Component
@ConfigurationProperties(prefix = "wechat.pay")
public class WechatPayProperties {
    /**
     * 微信公众号或者小程序等的appId
     */
    private String appId;
    /**
     * 微信支付商户号
     */
    private String merchantId;
    /**
     * 商户证书路径
     */
    private String certPemPath;
    /**
     * 商户API私钥路径
     */
    private String privateKeyPath;
    /**
     * 商户APIv3密钥
     */
    private String apiV3key;
    /**
     * 支付回调通知地址
     */
    private String payNotifyUrl;
}

3、初始化商户配置

package com.gw.pay.config;

import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.cert.*;


/**
 * Description: 微信支付相关自动配置
 *
 * @author LinHuiBa-YanAn
 * @date 2023/10/6 10:50
 */
@Slf4j
@Configuration
public class WechatPayAutoConfiguration {

    @Autowired
    private WechatPayProperties properties;

    @Autowired
    private ResourceLoader resourceLoader;

    private static final String CLASS_PATH = "classpath:";


    /**
     * 自动更新证书
     *
     * @return RSAAutoCertificateConfig
     */
    @Bean
    public Config config() throws IOException {
        String path = CLASS_PATH + properties.getCertPemPath();
        Resource resourceCert = resourceLoader.getResource(path);
        X509Certificate certificate = getCertificate(resourceCert.getInputStream());
        String merchantSerialNumber = certificate.getSerialNumber().toString(16).toUpperCase();
        log.info("==========证书序列号:{},商户信息:{}", merchantSerialNumber, certificate.getSubjectDN());
        String privatePath = CLASS_PATH + properties.getPrivateKeyPath();
        Resource resourcePrivate = resourceLoader.getResource(privatePath);
        String privateKey = inputStreamToString(resourcePrivate.getInputStream());
        log.info("==========加载微信私钥配置:{}", privateKey);
        RSAAutoCertificateConfig config = new RSAAutoCertificateConfig.Builder()
                .merchantId(properties.getMerchantId())
                .privateKey(privateKey)
                .merchantSerialNumber(merchantSerialNumber)
                .apiV3Key(properties.getApiV3key())
                .build();
        return config;
    }

    /**
     * 微信支付对象
     * @param config Config
     * @return JsapiServiceExtension
     */
    @Bean
    public JsapiServiceExtension jsapiServiceExtension(Config config){
        log.info("==========加载微信支付对象");
        JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(config).build();
        return service;
    }

    /**
     * 微信回调对象
     *
     * @param config Config
     * @return NotificationParser
     */
    @Bean
    public NotificationParser notificationParser(Config config) {
        log.info("==========加载微信回调解析对象");
        NotificationParser parser = new NotificationParser((NotificationConfig) config);
        return parser;
    }

    /**
     * 读取私钥文件,将文件流读取成string
     *
     * @param inputStream
     * @return
     * @throws IOException
     */
    public String inputStreamToString(InputStream inputStream) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        StringBuilder stringBuilder = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            stringBuilder.append(line);
        }
        reader.close();
        return stringBuilder.toString();
    }

    /**
     * 获取证书 将文件流转成证书文件
     *
     * @param inputStream 证书文件
     * @return {@link X509Certificate} 获取证书
     */
    public static X509Certificate getCertificate(InputStream inputStream) {
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X509");
            X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
            cert.checkValidity();
            return cert;
        } catch (CertificateExpiredException e) {
            throw new RuntimeException("证书已过期", e);
        } catch (CertificateNotYetValidException e) {
            throw new RuntimeException("证书尚未生效", e);
        } catch (CertificateException e) {
            throw new RuntimeException("无效的证书", e);
        }
    }

}

4、微信支付对接

在接口中定义了提交预支付请求付款、查询状态、取消订单三类核心方法,以及回调信息转换方法。几乎满足最基本的微信支付对接~

package com.gw.pay.external;

import com.gw.pay.external.request.CreateOrderPayRequest;
import com.gw.pay.external.request.WechatPayCallBackRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
import com.wechat.pay.java.service.payments.model.Transaction;

/**
 * Description: 微信支付对接 V2(基于JSAPI 支付的扩展类实现)
 *
 * @author LinHuiBa-YanAn
 * @date 2023/10/7 11:38
 */
public interface WechatPayExternalService {

    /**
     * 提交预支付请求付款
     * @param createOrderPay 订单请求体
     * @return PrepayWithRequestPaymentResponse 预付费与请求付款响应
     */
    PrepayWithRequestPaymentResponse prepayWithRequestPayment(CreateOrderPayRequest createOrderPay);

    /**
     * 查询状态
     *
     * @param outTradeNo 商户支付no
     * @return 状态信息
     */
    Transaction queryStatus(String outTradeNo);

    /**
     * 取消订单
     *
     * @param outTradeNo
     */
    void closeOrder(String outTradeNo);

    /**
     * 回调信息转换 这些都是微信的回调信息,可以封装成对象传入
     * 官网地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml
     *
     * @param wechatPayCallBackRequest
     * @param clazz
     * @param <T>
     * @return
     */
    <T> T payCallBack(WechatPayCallBackRequest wechatPayCallBackRequest, Class<T> clazz);

}

具体实现如下:

package com.gw.pay.external.impl;

import com.gw.pay.config.WechatPayProperties;
import com.gw.pay.external.WechatPayExternalService;
import com.gw.pay.external.request.CreateOrderPayRequest;
import com.gw.pay.external.request.WechatPayCallBackRequest;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.cipher.PrivacyEncryptor;
import com.wechat.pay.java.core.exception.HttpException;
import com.wechat.pay.java.core.exception.MalformedMessageException;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.model.Transaction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * Description: 微信支付对接(基于JSAPI 支付的扩展类实现)
 *
 * @author LinHuiBa-YanAn
 * @date 2023/10/7 11:49
 */
@Slf4j
@Service
public class WechatPayExternalServiceImpl implements WechatPayExternalService {

    @Resource
    private Config config;

    @Resource
    private WechatPayProperties properties;

    @Resource
    private JsapiServiceExtension jsapiServiceExtension;

    @Resource
    private NotificationParser notificationParser;

    @Override
    public PrepayWithRequestPaymentResponse prepayWithRequestPayment(CreateOrderPayRequest createOrderPay) {
        log.info("prepayWithRequestPayment");
        PrepayRequest request = new PrepayRequest();
        Amount amount = new Amount();
        BigDecimal payMoney = createOrderPay.getPayMoney();
        BigDecimal amountTotal = payMoney.multiply(new BigDecimal("100").setScale(0, RoundingMode.DOWN));
        amount.setTotal(amountTotal.intValue());
        request.setAmount(amount);
        Payer payer = new Payer();
        payer.setOpenid(createOrderPay.getOpenId());
        request.setPayer(payer);
        request.setTimeExpire(getExpiredTimeStr());
        request.setAppid(properties.getAppId());
        request.setMchid(properties.getMerchantId());
        request.setAttach(String.valueOf(createOrderPay.getId()));
        request.setDescription(createOrderPay.getPayContent());
        request.setNotifyUrl(properties.getPayNotifyUrl());
        //这里生成流水号,后续用这个流水号与微信交互,查询订单状态
        request.setOutTradeNo(createOrderPay.getOutTradeNo());
        PrepayWithRequestPaymentResponse result;
        try {
            result = jsapiServiceExtension.prepayWithRequestPayment(request);
        } catch (HttpException e) {
            log.error("微信下单发送HTTP请求失败,错误信息:{}", e.getHttpRequest());
            throw new RuntimeException("微信下单发送HTTP请求失败", e);
        } catch (ServiceException e) {
            // 服务返回状态小于200或大于等于300,例如500
            log.error("微信下单服务状态错误,错误信息:{}", e.getErrorMessage());
            throw new RuntimeException("微信下单服务状态错误", e);
        } catch (MalformedMessageException e) {
            // 服务返回成功,返回体类型不合法,或者解析返回体失败
            log.error("服务返回成功,返回体类型不合法,或者解析返回体失败,错误信息:{}", e.getMessage());
            throw new RuntimeException("服务返回成功,返回体类型不合法,或者解析返回体失败", e);
        }
        log.info("prepayWithRequestPayment end");
        return result;
    }

    @Override
    public Transaction queryStatus(String outTradeNo) {
        QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
        request.setMchid(properties.getMerchantId());
        request.setOutTradeNo(outTradeNo);
        try {
            return jsapiServiceExtension.queryOrderByOutTradeNo(request);
        } catch (ServiceException e) {
            log.error("订单查询失败,返回码:{},返回信息:{}", e.getErrorCode(), e.getErrorMessage());
            throw new RuntimeException("订单查询失败", e);
        }
    }

    @Override
    public void closeOrder(String outTradeNo) {
        log.info("closeOrder");
        CloseOrderRequest closeRequest = new CloseOrderRequest();
        closeRequest.setMchid(properties.getMerchantId());
        closeRequest.setOutTradeNo(outTradeNo);
        try {
            //方法没有返回值,意味着成功时API返回204 No Content
            jsapiServiceExtension.closeOrder(closeRequest);
        } catch (ServiceException e) {
            log.error("订单关闭失败,返回码:{},返回信息:{}", e.getErrorCode(), e.getErrorMessage());
            throw new RuntimeException("订单关闭失败", e);
        }
    }

    @Override
    public <T> T payCallBack(WechatPayCallBackRequest wechatPayCallBackRequest, Class<T> clazz) {
        log.info("payCallBack");
        PrivacyEncryptor privacyEncryptor = config.createEncryptor();
        String weChatPayCertificateSerialNumber = privacyEncryptor.getWechatpaySerial();
        if (!wechatPayCallBackRequest.getSerial().equals(weChatPayCertificateSerialNumber)) {
            log.error("证书不一致");
            throw new RuntimeException("证书不一致");
        }
        RequestParam requestParam = new RequestParam.Builder()
                .serialNumber(wechatPayCallBackRequest.getSerial())
                .nonce(wechatPayCallBackRequest.getNonce())
                .signType(wechatPayCallBackRequest.getSignatureType())
                .signature(wechatPayCallBackRequest.getSignature())
                .timestamp(wechatPayCallBackRequest.getTimestamp())
                .body(wechatPayCallBackRequest.getBody())
                .build();
        return notificationParser.parse(requestParam, clazz);
    }


    /**
     * 获取失效时间
     */
    private String getExpiredTimeStr() {
        //失效时间,10分钟
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime expiredTime = now.plusMinutes(10);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        return formatter.format(expiredTime);
    }
}

其中使用到的 提交预支付请求付款请求体 实体类:

/**
 * Description: 提交预支付请求付款请求体
 *
 * @author LinHuiBa-YanAn
 * @date 2023/10/6 17:06
 */
@Data
public class CreateOrderPayRequest {
    /**
     * 主键id
     */
    private Long id;

    /**
     * 商户支付no 和微信交互 查询订单使用(outTradeNo)
     */
    private String outTradeNo;

    /**
     * 用户openid
     */
    private String openId;

    /**
     * 支付金额
     */
    private BigDecimal payMoney;

    /**
     * 支付内容
     */
    private String payContent;
}

自测一下

@ActiveProfiles("local")
@SpringBootTest
class SpringbootWechatPayApplicationTests {

    @Resource
    private WechatPayExternalService wechatPayExternalService;


    @Test
    void prepayWithRequestPayment() {
        CreateOrderPayRequest createOrderRequest = new CreateOrderPayRequest();
        createOrderRequest.setId(1001L);
        createOrderRequest.setOutTradeNo("100000004");
        createOrderRequest.setOpenId("oKwQd5MtFfgnXyLBp7vC6Pe3HAJQ");
        createOrderRequest.setPayMoney(new BigDecimal("0.01"));
        createOrderRequest.setPayContent("商机直租会员续费");
        PrepayWithRequestPaymentResponse response = wechatPayExternalService.prepayWithRequestPayment(createOrderRequest);
        System.out.println(JSONObject.toJSONString(response));
    }

    @Test
    void queryStatus() {
        Transaction result = wechatPayExternalService.queryStatus("100000004");
        System.out.println(JSONObject.toJSONString(result));
        if (Transaction.TradeStateEnum.SUCCESS.equals(result.getTradeState())) {
            System.out.println("支付成功");
        } else {
            System.out.println("支付失败");
        }
    }

    @Test
    void closeOrderV2() {
        wechatPayExternalService.closeOrder("100000004");
    }

}

5、支付回调-支付通知API

微信支付通过支付通知接口将用户支付成功消息通知给商户,文档地址:pay.weixin.qq.com/wiki/doc/ap…

注意

  • 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
  • 如果在所有通知频率后没有收到微信侧回调,商户应调用查询订单接口确认订单状态。
package com.gw.pay.controller;

import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.gw.pay.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * Description: 微信支付回调接口
 *
 * @author LinHuiBa-YanAn
 * @date 2023/10/6 11:18
 */
@Slf4j
@RestController
@RequestMapping("/api/callback/wechat/pay")
public class WechatPayCallbackController {

    @Value("${config.bot.url}")
    private String botUrl;

    @Resource
    private OrderService orderService;

    /**
     * 回调接口
     *
     * @param request
     * @return
     * @throws IOException
     */
    @RequestMapping(value = "/callback", method = {RequestMethod.POST, RequestMethod.GET})
    public Map<String, String> payCallback(HttpServletRequest request, HttpServletResponse response) {
        log.info("------收到支付通知------");
        Map<String, String> result = new HashMap<>();
        try {
            orderService.payCallBack(request);
            result.put("code", "SUCCESS");
            result.put("message", "成功");
            return result;
        } catch (Exception e) {
            log.error("支付处理失败,req:{}", request, e);
            alarm();
            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            result.put("code", "FAIL");
            result.put("message", e.getMessage());
            return result;
        }
    }

    /**
     * 企业微信群告警
     */
    private void alarm() {
        JSONObject messageReq = new JSONObject();
        messageReq.put("msgtype", "text");
        JSONObject text = new JSONObject();
        text.put("content", "【商业直租】支付处理失败!" );
        messageReq.put("text", text);
        String url = botUrl;
        String reqStr = JSON.toJSONString(messageReq);
        HttpUtil.post(url, reqStr, 30000);
    }

}

我们在 OrderService 定义了微信支付回调方法,让controller看上去更简洁一点~

/**
 * Description: 订单管理
 *
 * @author LinHuiBa-YanAn
 * @date 2023/10/11 19:45
 */
public interface OrderService {
  
    /**
     * 微信支付回调
     * @param request HttpServletRequest
     */
    void payCallBack(HttpServletRequest request) throws Exception;

}

具体实现方法:

package com.gw.pay.service.impl;

import cn.hutool.core.convert.Convert;
import com.alibaba.fastjson.JSONObject;
import com.gw.pay.dao.OrderDao;
import com.gw.pay.dao.PayLogDao;
import com.gw.pay.entity.OrderPO;
import com.gw.pay.entity.PayLogPO;
import com.gw.pay.enums.OrderPayStatusEnum;
import com.gw.pay.enums.OrderStatusEnum;
import com.gw.pay.enums.WechatPayCallBackHeaderConstant;
import com.gw.pay.external.WechatPayExternalService;
import com.gw.pay.external.request.CreateOrderPayRequest;
import com.gw.pay.external.request.WechatPayCallBackRequest;
import com.gw.pay.service.OrderService;
import com.gw.pay.utils.NumberGenerate;
import com.gw.pay.vo.OrderPayVO;
import com.gw.pay.vo.StatusVO;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
import com.wechat.pay.java.service.payments.model.Transaction;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Objects;

/**
 * Description: 订单
 *
 * @author LinHuiBa-YanAn
 * @date 2023/10/11 19:46
 */
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
    @Resource
    private OrderDao orderDao;

    @Resource
    private PayLogDao payLogDao;

    @Resource
    private WechatPayExternalService wechatPayExternalService;

    @Resource
    private RedissonClient redissonClient;

    /**
     * 分布式锁
     */
    private final String LOCK_KEY = "WECHAT_PAY_LOCK:";

    @Override
    public void payCallBack(HttpServletRequest request) throws Exception {
        BufferedReader reader = request.getReader();
        String line;
        StringBuilder sb = new StringBuilder();
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }
        WechatPayCallBackRequest callBackRequest = new WechatPayCallBackRequest();
        callBackRequest.setBody(sb.toString());
        callBackRequest.setNonce(request.getHeader(WechatPayCallBackHeaderConstant.NONCE));
        callBackRequest.setSerial(request.getHeader(WechatPayCallBackHeaderConstant.SERIAL));
        callBackRequest.setSignature(request.getHeader(WechatPayCallBackHeaderConstant.SIGNATURE));
        callBackRequest.setSignatureType(request.getHeader(WechatPayCallBackHeaderConstant.SIGNATURE_TYPE));
        callBackRequest.setTimestamp(request.getHeader(WechatPayCallBackHeaderConstant.TIMESTAMP));
        log.info("验签参数{}", JSONObject.toJSONString(callBackRequest));
        Transaction transaction = wechatPayExternalService.payCallBack(callBackRequest, Transaction.class);
        log.info("验签成功!-支付回调结果:{}", transaction.toString());

        String lockKey = LOCK_KEY + transaction.getOutTradeNo();
        RLock lock = redissonClient.getLock(lockKey);
        try {
            boolean isLock = lock.tryLock();
            if (!isLock) {
                throw new RuntimeException("请勿重复操作");
            }
            log.info("开始用户支付后业务处理");
            processTransaction(transaction);
            log.info("用户支付后业务处理成功");
        } catch (Exception e) {
            log.error("用户支付后业务处理错误, e{}", e);
            throw e;
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    /**
     * 处理回调业务(需要保证事务操作哦)
     * @param transaction Transaction
     */
    private void processTransaction(Transaction transaction) {
        // 修改订单前,主动请求微信查询订单是否支付成功,防止恶意post
        transaction = wechatPayExternalService.queryStatus(transaction.getOutTradeNo());
        if (Transaction.TradeStateEnum.SUCCESS != transaction.getTradeState()) {
            log.info("内部订单号【{}】,微信支付订单号【{}】支付未成功", transaction.getOutTradeNo(), transaction.getTransactionId());
            throw new RuntimeException("订单支付未成功");
        }
        // 修改支付信息
        PayLogPO payLog = payLogDao.getByOutTradeNo(transaction.getOutTradeNo());
        if (OrderPayStatusEnum.PAY_SUCCESS.getCode().equals(payLog.getStatus())) {
            // 若订单状态已为支付成功则不处理
            return;
        }
        payLog.setTransactionId(transaction.getTransactionId());
        if (Objects.nonNull(transaction.getSuccessTime())) {
            payLog.setPayTime(LocalDateTime.parse(transaction.getSuccessTime(), DateTimeFormatter.ISO_OFFSET_DATE_TIME));
        }
        payLog.setStatus(OrderPayStatusEnum.PAY_SUCCESS.getCode());
        payLogDao.store(payLog);
        // 修改订单信息
        OrderPO order = orderDao.getById(payLog.getOrderId());
        order.setStatus(OrderStatusEnum.DELIVER_GOODS.getCode());
        orderDao.store(order);
        // 其他业务操作
    }
}

项目中基于redis实现分布式锁,保证幂等性和防止并发冲突。对于Redis实现分布式锁想要进一步了解的小伙伴可查看小编的另外一篇博文Redis分布式锁

  • 7
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
注意要点: 1,Topay里面的参数要填好:appid,appsecret,mch_id,partnerkey,spbill_create_ip 2,openid 需要微信授权获取到 3,每次支付orderNo要不同 openid参考实例: 1,授权链接地址:https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxba3445566677&redirect_uri=http://www.acc.com/weixin/pay/paydispatcher&response_type=code&scope=snsapi_userinfo&state=123#wechat_redirect 2,转向处理地址:通过第一个链接微信会把code传过来,之前参数获取就行 @RequestMapping(value = "/paydispatcher", method = { RequestMethod.GET }) public void payDispatcher(HttpServletRequest request, HttpServletResponse response) throws Exception { String code = request.getParameter("code"); String msg=""; if(code==null||code.equals("")){ msg="获取微信Code失败!"; request.setAttribute("msg" ,msg); request.getRequestDispatcher("/jsp/login.jsp").forward(request,response); }else{ WeixinUtil util = new WeixinUtil(); UserAccessToken token = (UserAccessToken) request.getSession().getAttribute("UserAccessToken"); if(null==token){ token = util.getAccessToken3(Constants.APPID, Constants.SECRET,code); request.getSession().setAttribute("UserAccessToken",token); } request.setAttribute("openid", token.getOpenid()); request.setAttribute("accessToken", token.getAccessToken()); request.setAttribute("refreshToken", token.getRefreshToken()); request.setAttribute("expiresIn", token.getExpiresIn()); request.getRequestDispatcher("/pay/index.jsp").forward(request,response); } } // 获取用户openid accesstoken public static UserAccessToken getAccessToken3(String appid , String appsecret,String code) { UserAccessToken accessToken = null; String requestUrl = Constants.GET_OPENID_ACCESSTOKEN_URL.replace("APPID" , appid).replace("APPSECRET" , appsecret).replace("CODE" , code); String json = httpRequest(requestUrl , "GET" , null); JSONObject jsonObject = JSONObject.fromObject(json); // 如果请求成功 if (null != jsonObject) { try { accessToken = new UserAccessToken(); accessToken.setAccessToken(jsonObject.getString("access_token")); accessToken.setRefreshToken(jsonObject.getString("refresh_token")); accessToken.setExpiresIn(jsonObject.getInt("expires_in")); accessToken.setOpenid(jsonObject.getString("openid")); accessToken.setScope(jsonObject.getString("scope")); } catch (Exception e) { accessToken = null; // 获取token失败 System.out.println("获取token失败 errcode:{} errmsg:{}"); } } return accessToken; }
基于GO的微信支付SDK - 商户支付-商户分账-服务商支付-服务商支付.zip 安装包 go get -u / 查看文档 // 执行命令 godoc -http=:8888 -play // 浏览器打开文档 http://127.0.0.1:8888/pkg/// 破坏性更新 1.3.0版本将API证书配置APIClientPath的证书路径由string改为[]byte类型,1.3.0之前的版本不受影响。 V2 版本下单接口 config := entity.PayConfig{ // 传入支付初始化参数 AppID string // 商户/服务商 AppId(公众号/小程序) MchID string // 商户/服务商 商户号 SubAppID string // 子商户公众号ID SubMchID string // 子商户商户号 PayNotify string // 支付结果回调地址 RefundNotify string // 退款结果回调地址 Secret string // 微信支付密钥 APIClientPath APIClientPath // API证书内容,使用V3接口必传 SerialNo string // 证书编号,使用V3接口必传 } wxpay := WXPay.Init(config) // 统一下单 if data, err := wxpay.V2.UnifiedOrder(V2.UnifiedOrder{/* 传入参数 */}); err == nil { } // 小程序支付 if data, err := wxpay.V2.WxAppPay(V2.UnifiedOrder{/* 传入参数 */}); err == nil { } // APP支付 if data, err := wxpay.V2.WxAppAppPay(V2.UnifiedOrder{/* 传入参数 */}); err == nil { } // H5支付 if data, err := wxpay.V2.WxH5Pay(V2.UnifiedOrder{/* 传入参数 */}); err == nil { } // 付款码支付 if data, err := wxpay.V2.Micropay(V2.Micropay{/* 传入参数 */}); err == nil { } // 关闭订单 if data, err := wxpay.V2.CloseOrder("1111"); err == nil { } // 撤销订单 if data, err := wxpay.V2.ReverseOrder(V2.ReverseOrder{/* 传入参数 */}); err == nil { } // 查询订单 if data, err := wxpay.V2.OrderQuery(V2.OrderQuery{/* 传入参数 */}); err == nil { } // 申请退款 if data, err := wxpay.V2.Refund(V2.Refund{/* 传入参数 */}); err == nil { } // 查询退款 if data, err := wxpay.V2.RefundQuery(V2.RefundQuery{/* 传入参数 */}); err == nil { } V2 版本分账接口 // 添加分账接收方 if data, err := wxpay.V2.ProfitSharingAddReceiver(V2.Receiver{/* 传入参数 */}); err == nil { } // 删除分账接收方 if data, err := wxpay.V2.ProfitSharingRemoveReceiver(V2.Receiver{/* 传入参数 */}); err == nil { } // 发起分账 第二个参数options为multi为多次分账 默认为单次 if data, err := wxpay.V2.ProfitSharing(V2.ProfitSharing{/* 传入参数 */},""); err == nil { } // 完成分账 if data, err := wxpay.V2.ProfitSharingFinish(V2.ProfitSharingFinish{/* 传入参数 */}); err ==
### 回答1: Uniapp是一种跨平台的开发框架,可以用于同时开发iOS、Android和H5应用。而H5是指在网页上运行的应用程序。微信支付JSAPI是微信提供的一种支付接口,通过调用JSAPI接口,可以在H5应用中实现微信支付功能。 在Uniapp中使用微信支付JSAPI,可以通过以下步骤进行操作: 1. 首先,在Uniapp项目中引入微信支付的相关配置文件和SDK库文件。 2. 在需要使用微信支付的页面中,引入微信支付的JS文件,并初始化微信支付参数。支付参数包括商户号、appid、签名等信息。 3. 创建统一下单接口的后端处理程序,用于生成预支付订单并返回支付预处理参数给前端。 4. 在支付页面中,调用微信支付JSAPI的方法,传入预支付处理参数和支付回调方法。 5. 当用户点击支付按钮后,会弹出微信支付界面,用户可以选择支付方式进行支付操作。 6. 支付成功后,微信会将支付结果返回给前端,并通过支付回调方法进行处理,可以展示支付成功的提示信息和更新订单状态等操作。 需要注意的是,使用微信支付JSAPI需要在微信开放平台上注册并申请相关的权限。同时,在使用过程中,还需要确保支付参数的正确性、支付安全性和业务逻辑的完善性。 总之,Uniapp可以很好地支持H5应用中的微信支付JSAPI,通过合理的配置和调用,可以方便地在H5应用中实现微信支付功能,为用户提供更加便捷的支付体验。 ### 回答2: Uniapp是一款跨平台的应用开发框架,可以让开发者使用Vue.js语法来开发同时兼容多个平台的应用程序。Uniapp支持在H5平台中使用微信支付JSAPI。 微信支付JSAPI是微信提供的一组用于在网页中实现微信支付功能的JavaScriptAPI接口。通过调用微信支付JSAPI,开发者可以在H5页面中调起微信支付功能,用户可以使用微信支付完成支付操作。 在Uniapp中使用微信支付JSAPI,首先需要引入微信支付的JS文件。可以在页面的头部引入:<script src="https://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script>。 然后,在需要使用微信支付的地方,可以通过uni.request方法向服务器请求获取支付参数。获取到支付参数后,可以使用wx.requestPayment方法调起微信支付界面,并传入支付参数。支付成功后,微信会返回支付结果给开发者的回调函数,开发者可以在回调函数中处理支付结果。 需要注意的是,在使用微信支付JSAPI的过程中需要保证支付参数的安全性,避免支付参数被恶意篡改。通常可以在服务器端生成支付参数,并通过服务器端返回给前端,以确保支付参数的安全性。 总结起来,Uniapp可以在H5平台中使用微信支付JSAPI来实现微信支付功能,开发者需要引入微信支付的JS文件,通过uni.request方法获取支付参数,调用wx.requestPayment方法调起支付,处理支付结果的回调函数,并确保支付参数的安全性。 ### 回答3: Uniapp 是一款跨平台开发框架,可以同时开发小程序、H5 和APP。Uniapp 提供了对微信支付的支持,可以使用微信支付的 JSAPI(JavaScript API)来进行支付功能的开发。 Uniapp H5 微信支付 JSAPI 的实现过程如下: 1. 首先,需要在微信支付商户平台上注册并获取到自己的商户号(mch_id),同时生成随机字符串(nonce_str)和当前时间戳(timestamp)。 2. 接下来,在前端页面中引入微信支付的 JS 文件,可以通过在页面头部添加以下代码进行引入: ``` <script src="https://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script> ``` 3. 在页面中设置要支付的订单信息,包括订单号(out_trade_no)、订单总金额(total_fee)等,并将这些信息以及商户号、随机字符串和时间戳等传递给后端服务器。 4. 后端服务器在接收到前端传递的支付信息后,根据微信支付 API 的要求,生成签名(sign)并返回给前端。 5. 前端页面收到后端返回的签名后,调用微信支付的 JSAPI 方法,传入订单信息和签名等参数,即可发起支付请求。 6. 微信客户端会弹出支付窗口,用户输入密码或进行指纹验证后,支付完成。 注意事项: - 在使用 Uniapp 进行微信支付开发时,需要确保页面已经获得了微信公众号的授权,因为微信支付是需要通过公众号的权限进行的。 - 需要合理处理支付结果的回调,根据支付结果进行相应的处理,如跳转到支付成功页面或给予支付失败的提示等。 通过使用 Uniapp H5 微信支付 JSAPI,我们可以方便地在 Uniapp 框架下进行微信支付的开发,实现支付功能的需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值