从零玩转系列之微信支付实战PC端支付微信回调接口搭建2

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
日志服务 SLS,月写入数据量 50GB 1个月
简介: 从零玩转系列之微信支付实战PC端支付微信回调接口搭建

启动项目测试流程

开启内网穿透 映射你启动项目的端口 自己访问一下是否通

启动程序 请求下单接口 /api/wx-pay/native/native/{productId}

{productId} 查看商品表数据的ID

复制返回的微信二维码地址

进入 https://cli.im/url 生成扫描二维码 使用微信扫描

等待微信回调

f0d15a2af164763e31a3ccf5c57efee.png

ok我们可以正常的接收到微信的回调我们需要根据回调的数据来处理自己系统的业务

修改回调方法 新增 processOrder 业务传递报文

log.info("通知验签成功:{}", bodyMap);
// 通知回调 -> 更新订单状态逻辑
wxPayService.processOrder(bodyMap);
log.info("回调业务处理完毕");

修改 WxPayService 服务类

可以搞redsi分布式锁根据实际业务来我们只是个demo就不要那么严谨

/**
* 一个可重入互斥 锁
*/
private final ReentrantLock lock = new ReentrantLock();
/**
 * 通知回调-> 更新订单状态逻辑
 */
@Transactional(rollbackFor = Exception.class)
@Override
public void processOrder(Map<String, Object> bodyMap) throws GeneralSecurityException, InterruptedException {
    log.info("处理订单");
    //解密报文
    String plainText = decryptFromResource(bodyMap);
        // 将明文转换成map
        Map<String, Object> plainTextMap = JSONUtil.toBean(plainText, Map.class);
        String orderNo = (String) plainTextMap.get("out_trade_no");
    // 微信特别提醒:
    // 在对业务数据进行状态检查和处理之前,
    // 要采用数据锁进行并发控制,以避免函数重入造成的数据混乱.
    // 尝试获取锁:
    // 成功获取则立即返回true,获取失败则立即返回false。不必一直等待锁的释放.
    if (lock.tryLock()) {
        try {
            // 处理重复的通知
            // 接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的。
            OrderInfo orderInfo = orderInfoService.lambdaQuery().eq(OrderInfo::getOrderNo, (orderNo)).one();
            if (null != orderInfo && !OrderStatus.NOTPAY.getType().equals(orderInfo.getOrderStatus())) {
                log.info("重复的通知,已经支付成功啦");
                return;
            }
            // 模拟通知并发
            //TimeUnit.SECONDS.sleep(5);
            // 更新订单状态
            orderInfoService.lambdaUpdate().eq(OrderInfo::getOrderNo, orderNo).set(OrderInfo::getOrderStatus, OrderStatus.SUCCESS.getType()).update();
            log.info("更新订单状态,订单号:{},订单状态:{}", orderNo, OrderStatus.SUCCESS);
            // 记录支付日志
            paymentInfoService.createPaymentInfo(plainText);
        } finally {
            // 要主动释放锁
            lock.unlock();
        }
    }
}

扩展本次不运用到项目当中

### ⚠️ 以上基本就ok了但是在订单服务是不是还缺少点什么?
#### 方式一
🔒 植入分布式锁 LOCK4J
```
<!--    lock4j mybatiplus 扩展-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
    <version>2.2.5</version>
</dependency>
<!--redisson-->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.21.1</version>
</dependency>
```
修改application.yml
```yml
# redis配置
---
# 分布式锁配置
#acquire-timeout 可以理解为排队时长,超过这个时才就退出排队,抛出获取锁超时异常。
#为什么必须要有这个参数?现实你会一直排队等下去吗?所有人都一直排队有没有问题 ?
#expire 锁过期时间 。 主要是防止死锁。 建议估计好你锁方法运行时常,正常没有复杂业务的增删改查最多几秒,
#留有一定冗余,10秒足够。 我们默认30秒是为了兼容绝大部分场景。
lock4j:
  acquire-timeout: 3000 #默认值3s,可不设置
  expire: 30000 #默认值30s,可不设置
  primary-executor: com.baomidou.lock.executor.RedissonLockExecutor
  lock-key-prefix: lock4j #锁key前缀, 默认值lock4j,可不设置
spring:
  data:
    redis:
      database: 6
      url: 127.0.0.1
      port: 6391
      password: '123456'
# 分布式redis配置
redisson:
  # redis key前缀
  keyPrefix: yby6
  # 线程池数量
  threads: 4
  # Netty线程池数量
  nettyThreads: 8
  # 单节点配置
  singleServerConfig:
    # 客户端名称
    clientName: yangbuyiya
    # 最小空闲连接数
    connectionMinimumIdleSize: 8
    # 连接池大小
    connectionPoolSize: 32
    # 连接空闲超时,单位:毫秒
    idleConnectionTimeout: 10000
    # 命令等待超时,单位:毫秒
    timeout: 3000
    # 发布和订阅连接池大小
    subscriptionConnectionPoolSize: 50
```
使用方式参考 https://gitee.com/baomidou/lock4j
> ⚠️ 上面分布式锁作为扩展知识点分布式锁要求唯一ID 比如当前登陆用户唯一ID 目前我们没有登陆功能则使用其他代替锁

参数解密

/**
 * 对称解密
 */
private String decryptFromResource(Map<String, Object> bodyMap) throws GeneralSecurityException {
    log.info("密文解密");
    //通知数据拿到 resource 节点
    Map<String, String> resourceMap = (Map) bodyMap.get("resource");
    //数据密文
    String ciphertext = resourceMap.get("ciphertext");
    //随机串
    String nonce = resourceMap.get("nonce");
    //附加数据
    String associatedData = resourceMap.get("associated_data");
    log.info("密文 ===> {}", ciphertext);
    AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
    // 使用key、nonce和associated_data,对数据密文resource.ciphertext进行解密,得到JSON形式的资源对象
    String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
    log.info("明文 ===> {}", plainText);
    return plainText;
}

记录支付日志

引入

/**
 * 支付日志
 */
private final PaymentInfoService paymentInfoService;

修改 PaymentInfoService

package com.yby6.service;
import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yby6.domain.PaymentInfo;
import com.yby6.enums.PayType;
import com.yby6.mapper.PaymentInfoMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Map;
@Slf4j
@Service
public class PaymentInfoService extends ServiceImpl<PaymentInfoMapper, PaymentInfo> {
    /**
     * 创建付款信息
     *
     * @param plainText 纯文本
     */
    @Transactional
    public void createPaymentInfo(String plainText) {
        log.info("记录支付日志: {}", plainText);
        Map<String, Object> plainTextMap = JSONUtil.toBean(plainText, Map.class);
        //订单号
        String orderNo = (String) plainTextMap.get("out_trade_no");
        //业务编号
        String transactionId = (String) plainTextMap.get("transaction_id");
        //支付类型
        String tradeType = (String) plainTextMap.get("trade_type");
        //交易状态
        String tradeState = (String) plainTextMap.get("trade_state");
        //用户实际支付金额
        Map<String, Object> amount = (Map<String, Object>) plainTextMap.get("amount");
        Integer payerTotal = MapUtil.getInt(amount, "payer_total");
        PaymentInfo paymentInfo = new PaymentInfo();
        paymentInfo.setOrderNo(orderNo);
        paymentInfo.setPaymentType(PayType.WXPAY.getType());
        paymentInfo.setTransactionId(transactionId);
        paymentInfo.setTradeType(tradeType);
        paymentInfo.setTradeState(tradeState);
        paymentInfo.setPayerTotal(payerTotal);
        paymentInfo.setContent(plainText);
        baseMapper.insert(paymentInfo);
    }
}

启动项目测试流程

开启内网穿透 映射你启动项目的端口 自己访问一下是否通

启动程序 请求下单接口 /api/wx-pay/native/native/{productId}

{productId} 查看商品表数据的ID

复制返回的微信二维码地址

进入 https://cli.im/url 生成扫描二维码 使用微信扫描

等待微信回调处理系统业务订单状态更改

0900657875c181146e0bedb1092baba.png

最后

本期结束咱们下次再见👋~

,关注我不迷路,如果本篇文章对你有所帮助,或者你有什么疑问,欢迎在评论区留言,我一般看到都会回复的。大家点赞支持一下哟~ 💗

【选题思路】

“技术源于生活” 为什么写微信支付这种项目的文章呢? 因为我看到市面上的文章都不全面不细节不小白话更加没有配套Demo!!!

从而我的从零玩转微信支付诞生啦~ 搭配PC端、Uniapp端的不同实现.

【写作提纲】

一、前言

通过前言表达我每次的文章内容是什么东西和注意事项

二、Native模式回调

介绍回调的思路、通知规则、通知报文、通知签名、签名验证、参数加解密、证书和回调包稳解密、支付通知、最后进行测试功能的集成!

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
2月前
|
Web App开发 移动开发 前端开发
H5微信外支付(移动端浏览器)
H5微信外支付(移动端浏览器)
45 1
 H5微信外支付(移动端浏览器)
|
2月前
|
移动开发 安全 API
微信H5支付--微信JS-SDK支付--点金计划
本文详细介绍了微信H5支付和JS-SDK支付的原理、配置和开发流程,涵盖了H5支付在移动端浏览器外唤起微信支付的细节,以及JS-SDK支付在微信内置浏览器中完成支付的相关注意事项。文章还针对微信支付常见问题,提供了解决方案和代码示例。最后,文章深入解析了微信支付点金计划,包括商家小票的自定义开发、API接口以及支付成功后的页面展示逻辑,为开发者提供了完整的开发参考。
85 0
微信H5支付--微信JS-SDK支付--点金计划
|
2月前
|
JavaScript 小程序 开发者
uni-app开发实战:利用Vue混入(mixin)实现微信小程序全局分享功能,一键发送给朋友、分享到朋友圈、复制链接
uni-app开发实战:利用Vue混入(mixin)实现微信小程序全局分享功能,一键发送给朋友、分享到朋友圈、复制链接
411 0
|
4月前
|
移动开发 前端开发 JavaScript
|
4月前
|
前端开发 JavaScript API
微信公众号项目,实现微信支付(具体流程和参数)
微信公众号项目,实现微信支付(具体流程和参数)
|
2月前
|
JSON 小程序 JavaScript
uni-app开发微信小程序的报错[渲染层错误]排查及解决
uni-app开发微信小程序的报错[渲染层错误]排查及解决
633 7
|
2月前
|
小程序 JavaScript 前端开发
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
724 1
|
2月前
|
小程序 前端开发 测试技术
微信小程序的开发完整流程是什么?
微信小程序的开发完整流程是什么?
130 7
ly~
|
3月前
|
存储 供应链 小程序
除了微信小程序,PHP 还可以用于开发哪些类型的小程序?
除了微信小程序,PHP 还可用于开发多种类型的小程序,包括支付宝小程序、百度智能小程序、抖音小程序、企业内部小程序及行业特定小程序。在电商、生活服务、资讯、工具、娱乐、营销等领域,PHP 能有效管理商品信息、订单处理、支付接口、内容抓取、复杂计算、游戏数据、活动规则等多种业务。同时,在企业内部,PHP 可提升工作效率,实现审批流程、文件共享、生产计划等功能;在医疗和教育等行业,PHP 能管理患者信息、在线问诊、课程资源、成绩查询等重要数据。
ly~
83 6
|
2月前
|
缓存 小程序 索引
uni-app开发微信小程序时vant组件van-tabs的使用陷阱及解决方案
uni-app开发微信小程序时vant组件van-tabs的使用陷阱及解决方案
224 1