• 微信JSAPI支付对接


    简介

    JSAPI支付是指商户通过调用微信支付提供的JSAPI接口,在支付场景中调起微信支付模块完成收款。

    应用场景

    JSAPI支付适用于线下场所、公众号场景和PC网站场景。

    商户已有H5商城网站,用户通过消息或扫描二维码在微信内打开网页时,可以调用微信支付完成下单购买的流程。具体操作流程如下:

    1.商户下发图文消息或者通过自定义菜单吸引用户点击进入商户网页
    2.进入商户网页,用户选择购买,完成选购流程。
    3.调起微信支付控件,用户开始输入支付密码
    4.密码验证通过,支付成功。商户后台得到支付成功的通知
    5.返回商户页面,显示购买成功。该页面由商户自定义
    6.微信支付公众号下发支付凭证
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    接入前准备

    直接跳转微信支付商户平台 https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_1.shtml

    生成密钥文件:
    在这里插入图片描述

    配置文件

    wx:
      appId: appId
      keyPath: apiclient_key.pem
      certPath: apiclient_cert.pem
      certP12Path: 暂不用
      platformCertPath: platform_cert.pem
      mchId: mchId
      apiKey3: 暂不用
      apiKey: apiKey
      domain: https://hous.exchang.cn
      serialNo: 序列号
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    pom文件:

    <dependency>
         <groupId>com.github.wechatpay-apiv3groupId>
         <artifactId>wechatpay-javaartifactId>
         <version>0.2.2version>
     dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    代码:

    配置类:

    //获取yml中微信配置
    @Data
    @ConfigurationProperties(prefix = "wx")
    public class WechatProperties {
    
        private String appId;
    
        private String keyPath;
    
        private String certPath;
    
        private String platformCertPath;
    
        private String mchId;
    
        private String apiKey;
    
        private String domain;
    
        private String serialNo;
    }
    //配置类
    @Configuration
    @EnableConfigurationProperties(WechatProperties.class)
    public class WechatJsapiConfig {
    
    
        private final WechatProperties wechatProperties;
    
        public WechatJsapiConfig(WechatProperties wechatProperties) {
            this.wechatProperties = wechatProperties;
        }
    
        @Bean
        public RSAConfig rsaConfig() throws IOException {
            ClassPathResource keyResource = new ClassPathResource(wechatProperties.getKeyPath());
            String apiClientKey = keyResource.getFile().getPath();
            ClassPathResource certResource = new ClassPathResource(wechatProperties.getPlatformCertPath());
            String platformCertResourceKey = certResource.getFile().getPath();
            /*String apiClientKey = wechatProperties.getKeyPath();
    //        ClassPathResource certResource = new ClassPathResource(wechatProperties.getPlatformCertPath());
            String platformCertResourceKey = wechatProperties.getPlatformCertPath();*/
            return new RSAConfig.Builder()
                    .merchantId(wechatProperties.getMchId())
                    .privateKeyFromPath(apiClientKey)
                    .merchantSerialNumber(wechatProperties.getSerialNo())
                    //平台证书
                    .wechatPayCertificatesFromPath(platformCertResourceKey)
                    .build();
        }
    
        @Bean
        public JsapiServiceExtension jsapiService() throws IOException {
            return new JsapiServiceExtension.Builder()
                    .config(this.rsaConfig())
                    .build();
        }
    
        @Bean
        public NotificationParser notificationParser() throws IOException{
            ClassPathResource certResource = new ClassPathResource(wechatProperties.getPlatformCertPath());
            String platformCertResourceKey = certResource.getFile().getPath();
            //String platformCertResourceKey = wechatProperties.getPlatformCertPath();
            NotificationConfig config = new RSANotificationConfig.Builder()
                    .apiV3Key(wechatProperties.getApiKey())
                    .certificatesFromPath(platformCertResourceKey)
                    .build();
            // 初始化 NotificationParser
            return new NotificationParser(config);
    
        }
    
        @Bean(name = "payRefundService")
        public RefundService refundService() throws IOException {
            return new RefundService.Builder().config(this.rsaConfig()).build();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    创建支付单:

    @Slf4j
    @Service
    @EnableConfigurationProperties(WechatProperties.class)
    public class WechatJsapiPayServiceImpl implements BasePayService {
    
        @Resource
        private WechatProperties wechatProperties;
    
        @Resource
        private JsapiServiceExtension jsapiService;
    
        @Value("${spring.application.name}")
        private String serviceName;
    
        @Override
        public R<?> paymentOrder(List<Order> orderList) {
            BigDecimal total = orderList.stream().map(Order::getPayAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
            Order order = orderList.get(0);
            Long mId = order.getMId();
            Member member = memberDao.queryById(mId);
            Integer payTotal = total.multiply(BigDecimal.valueOf(100)).intValue();
            PrepayRequest prepayRequest = new PrepayRequest();
            prepayRequest.setAppid(wechatProperties.getAppId());
            prepayRequest.setMchid(wechatProperties.getMchId());
            prepayRequest.setDescription("供应链下单");
            prepayRequest.setOutTradeNo(order.getBatchSn());
            prepayRequest.setNotifyUrl(wechatProperties.getDomain() + "/callback/wechat/pay");
            prepayRequest.setAttach("1");
            prepayRequest.setTimeExpire(this.getTimeExpire());
            Amount amount = new Amount();
            amount.setTotal(payTotal);
            Payer payer = new Payer();
            payer.setOpenid(member.getOpenId());
            prepayRequest.setAmount(amount);
            prepayRequest.setPayer(payer);
            log.info("创建预支付单入参:【{}】", JSONObject.toJSONString(prepayRequest));
            try {
                PrepayWithRequestPaymentResponse response = jsapiService.prepayWithRequestPayment(prepayRequest);
                log.info("创建预支付单并生成二维码成功,出参:【{}】", JSONObject.toJSONString(response));
                return R.ok(response);
            } catch (HttpException e) { // 发送HTTP请求失败
                log.error("发送HTTP请求失败:{}", e.getHttpRequest());
                return R.fail("发送HTTP请求失败");
            } catch (ServiceException e) { // 服务返回状态小于200或大于等于300,例如500
                log.error("微信支付返回状态异常,{}", e.getResponseBody());
                return R.fail("微信支付返回状态异常");
            } catch (MalformedMessageException e) { // 服务返回成功,返回体类型不合法,或者解析返回体失败
                log.error("返回类型不合法:{}", e.getMessage());
                return R.fail("返回类型不合法");
            }
        }
    
        @Override
        public void closeOrder(String outTradeNo) {
            CloseOrderRequest closeOrderRequest = new CloseOrderRequest();
            closeOrderRequest.setMchid(wechatProperties.getMchId());
            closeOrderRequest.setOutTradeNo(outTradeNo);
            jsapiService.closeOrder(closeOrderRequest);
            log.info("订单关闭成功,入参:【{}】", JSONObject.toJSONString(closeOrderRequest));
        }
    
        @Override
        public Optional<Transaction> getOrderInfo(String outTradeNo) {
            QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
            request.setOutTradeNo(outTradeNo);
            request.setMchid(wechatProperties.getMchId());
            Transaction transaction = jsapiService.queryOrderByOutTradeNo(request);
            if (Objects.isNull(transaction)) {
                return Optional.empty();
            }
            return Optional.of(transaction);
        }
        
        private String getTimeExpire(){
            //过期时间  RFC 3339格式
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
            //支付订单过期时间
            return sdf.format(new Date(System.currentTimeMillis() + 1000 * 60 * 5));
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81

    创建退款单:

    @Slf4j
    @Service
    @EnableConfigurationProperties(WechatProperties.class)
    public class WechatRefundServiceImpl implements BaseRefundService {
    
        @Resource
        private RefundService refundService;
    
        @Resource
        private WechatProperties wechatProperties;
    
        @Value("${spring.application.name:scm-ofc-system}")
        private String serviceName;
    
        @Resource
        private OrderDao orderDao;
    
        @Resource
        private OrderItemDao orderItemDao;
    
        @Resource
        private ShopDao shopDao;
    
        @Override
        public R<?> refundOrder(OrderRefund orderRefund) {
            try {
                BigDecimal refundAmount = orderRefund.getRefundAmount();
                Long orderId = orderRefund.getOrderId();
                Order order = orderDao.queryById(orderId);
                //退款金额
                long refundTotal = refundAmount.multiply(BigDecimal.valueOf(100)).longValue();
                //原支付金额
                String batchSn = order.getBatchSn();
                OrderDTO params = new OrderDTO();
                params.setBatchSn(batchSn);
                List<Order> orders = orderDao.selectOrderList(params);
                BigDecimal officialReceipts = orders.stream().map(Order::getPayAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
                long payTotal = officialReceipts.multiply(BigDecimal.valueOf(100)).longValue();
                //创建微信退款单
                CreateRequest createRequest = new CreateRequest();
                AmountReq amountReq = new AmountReq();
                amountReq.setCurrency("CNY");
                amountReq.setTotal(payTotal);
                amountReq.setRefund(refundTotal);
                createRequest.setOutTradeNo(batchSn);
                createRequest.setAmount(amountReq);
                createRequest.setOutRefundNo(orderRefund.getRefundSn());
                createRequest.setNotifyUrl(wechatProperties.getDomain() + "/" + serviceName +  "/callback/wechat/refund");
                log.info("退款单入参:{}", JSONObject.toJSONString(createRequest));
                Refund refund = refundService.create(createRequest);
                log.info("创建退款单成功:{}", JSONObject.toJSONString(refund));
                if (Objects.isNull(refund)) {
                    log.error("退款异常,参数:{}", JSONObject.toJSONString(createRequest));
                    return R.fail(500, "退款异常,请求微信返回值为null:参数" + JSONObject.toJSONString(createRequest));
                }
                if (Objects.equals(refund.getStatus(), Status.SUCCESS)) {
                    return R.ok(refund);
                }
            } catch (Exception e) {
                log.error("退款异常:{}", e.getMessage());
                return R.fail(500, "退款异常,请求微信报错" + e.getMessage());
            }
            return R.ok();
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    支付与退款回调

    @Slf4j
    @Service
    public class CallbackServiceImpl implements CallbackService {
    
        @Resource
        private NotificationParser notificationParser;
    
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void wechatPayCallback(HttpServletRequest request, HttpServletResponse response) {
            try {
                log.info("=========================微信native支付回调通知============================");
                Transaction transaction = this.verifyAndDecrypt(request, Transaction.class);
                log.info("验证签名成功:{}", JSONObject.toJSONString(transaction));
                Transaction.TradeStateEnum tradeState = transaction.getTradeState();
                if (!Objects.equals(tradeState, Transaction.TradeStateEnum.SUCCESS)) {
                    return;
                }
                String outTradeNo = transaction.getOutTradeNo();
                
                log.info("支付回调执行成功,待支付->待发货");
            } catch (Exception e) {
                log.error("支付回调异常:{}", e.getMessage());
            }
        }
    
        @Override
        public void wechatRefundCallback(HttpServletRequest request, HttpServletResponse response) {
            log.info("=========================微信native退款回调通知============================");
            RefundNotification refundNotification = this.verifyAndDecrypt(request, RefundNotification.class);
            Status refundStatus = refundNotification.getRefundStatus();
            if (!Objects.equals(refundStatus, Status.SUCCESS)) {
                return;
            }
            //执行退款业务
        }
        
        /**
         * 获取供应链订单id
         * @param data
         * @param orderList
         * @return
         */
        private Map<Long, String> getOrderIdMap(OrderByThirdIdVO data,List<Order> orderList){
            List<OrderSkuDetailVO> skus = data.getSkus();
            Map<String, String> skuOrderIdMap = skus.stream()
                    .collect(Collectors.toMap(OrderSkuDetailVO::getSku, OrderSkuDetailVO::getOrderId));
            List<OrderItem> orderItems = orderList.stream().map(Order::getOrderItem).collect(Collectors.toList());
            return orderItems.stream().map(item -> {
                String originId = item.getOriginId();
                String orderId = skuOrderIdMap.get(originId);
                return new DefaultKeyValue<>(item.getOrderId(), orderId);
            }).collect(Collectors.toMap(DefaultKeyValue::getKey, DefaultKeyValue::getValue));
        }
    
    
        /**
         * 验证并解密报文
         *
         * @param request
         */
        private <T> T verifyAndDecrypt(HttpServletRequest request, Class<T> clazz) {
            String timestamp = request.getHeader("Wechatpay-Timestamp");
            String nonce = request.getHeader("Wechatpay-Nonce");
            String serialNo = request.getHeader("Wechatpay-Serial");
            String signature = request.getHeader("Wechatpay-Signature");
            String signType = request.getHeader("Wechatpay-Signature-Type");
            String body = this.getBody(request);
            log.info("\n请求头信息:\n" +
                            "Wechatpay-Timestamp:{},\n" +
                            "Wechatpay-Nonce:{},\n" +
                            "Wechatpay-Serial:{},\n" +
                            "Wechatpay-Signature:{},\n" +
                            "Wechatpay-Signature-Type:{},\n" +
                            "body: {}",
                    timestamp, nonce, serialNo, signature, signType, body);
            RequestParam requestParam = new RequestParam.Builder()
                    .serialNumber(serialNo)
                    .nonce(nonce)
                    .signature(signature)
                    .timestamp(timestamp)
                    .signType(signType)
                    .body(body)
                    .build();
            return notificationParser.parse(requestParam, clazz);
        }
    
        /**
         * 获取请求体内容
         *
         * @param request
         * @return
         */
        private String getBody(HttpServletRequest request) {
            BufferedReader reader;
            String body = "";
            try {
                reader = request.getReader();
                String line;
                StringBuilder inputString = new StringBuilder();
                while ((line = reader.readLine()) != null) {
                    inputString.append(line);
                }
                body = inputString.toString();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return body;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111

    以上就是微信jsapi支付的对接,仅供参考

  • 相关阅读:
    java学习--字符流
    Qt中文件夹的操作
    HPE脚本更新致京都大学77TB数据被删
    【我不熟悉的javascript】02. 新手菜鸟们,求你不要再用console.log了,console.table用起来,提升效率的小方法
    Vim的使用
    【SpringMVC拦截器】
    Google Earth Engine ——常用简单的简写和全程
    深度学习(PyTorch)——卷积神经网络(CNN)基础篇
    一键部署(dhcp、dns、pxe、raid、nfs+apache+expect、lvm、磁盘分区、监控资源)
    【QT开发(1)】基于c++17的代码项目模板:QT build with Cmake
  • 原文地址:https://blog.csdn.net/qq_26869339/article/details/134078576