第三十五章 微信支付Native订单API测试实战和签名流程解读
第1集 微信支付-快速验证参数配置方法和统一下单接口开发
简介:微信支付-快速验证参数配置方法和统一下单接口开发
- 接口文档
- https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtml
- 编码实战
- 配置接口地址
public class WechatPayApi {
/**
* 微信支付域名
*/
public static final String HOST = "https://api.mch.weixin.qq.com";
/**
* Native下单
*/
public static final String NATIVE_ORDER = HOST+"/v3/pay/transactions/native";
}
- 通用测试代码(快速验证参数是否过期和失效)
@Test
public void testWechatPayNativeOrder() throws IOException {
//过期时间 RFC 3339格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
//支付订单过期时间
String timeExpire = sdf.format(new Date(System.currentTimeMillis() + 6000 * 60 * 1000));
String outTradeNo = CommonUtil.getStringNumRandom(32);
JSONObject amountObj = new JSONObject();
JSONObject payObj = new JSONObject();
payObj.put("mchid", payConfig.getMchId());
payObj.put("out_trade_no", outTradeNo);
payObj.put("appid", payConfig.getWxPayAppid());
payObj.put("description", "小滴课堂海量数据项目大课");
payObj.put("notify_url", payConfig.getCallbackUrl());
payObj.put("time_expire", timeExpire);
//微信支付需要以分为单位
int amount = 100;
amountObj.put("total", amount);
amountObj.put("currency", "CNY");
payObj.put("amount", amountObj);
//附属参数,可以用在回调携带
payObj.put("attach", "{\"accountNo\":" + 8888 + "}");
// 处理请求body参数
String body = payObj.toJSONString();
log.info("请求参数:{}", payObj);
StringEntity entity = new StringEntity(body, "utf-8");
entity.setContentType("application/json");
//调用统一下单API
HttpPost httpPost = new HttpPost(WechatPayApi.NATIVE_ORDER);
httpPost.setHeader("Accept", "application/json");
httpPost.setEntity(entity);
//httpClient自动进行参数签名
try (CloseableHttpResponse response = wechatPayClient.execute(httpPost)) {
String responseStr = EntityUtils.toString(response.getEntity());
//响应体
int statusCode = response.getStatusLine().getStatusCode();
log.info("统一下单响应码={},响应体={}", statusCode, responseStr);
} catch (Exception e) {
e.printStackTrace();
}
}
第2集 微信支付-查询订单支付状态开发链路验证
简介:微信支付-查询订单支付状态开发验证
- 接口文档
- https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_2.shtml
/**
* Native订单查询,根据商户订单号查询,out_trade_no
*/
public static final String NATIVE_QUERY = HOST+"/v3/pay/transactions/out-trade-no/%s?mchid=%s";
-
编码实战
@Test public void testWechatPayNativeQuery() throws IOException { String outTradeNo = "99lHMc8uO2ZZ03KSV24XEFaLRfSMfqwD"; String url = String.format(WechatPayApi.NATIVE_QUERY, outTradeNo, payConfig.getMchId()); HttpGet httpGet = new HttpGet(url); httpGet.setHeader("Accept", "application/json"); //httpClient自动进行参数签名 try (CloseableHttpResponse response = wechatPayClient.execute(httpGet)) { String responseStr = EntityUtils.toString(response.getEntity()); int statusCode = response.getStatusLine().getStatusCode(); log.info("查询订单响应码={},响应体={}", statusCode, responseStr); } catch (Exception e) { e.printStackTrace(); } }
第3集 微信支付-超时关闭订单API链路验证
简介:微信支付-超时关闭订API单链路验证
-
接口文档
- https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_3.shtml
/** * 关闭订单,根据 out_trade_no */ public static final String NATIVE_CLOSE_ORDER = HOST+"/v3/pay/transactions/out-trade-no/%s/close";
-
编码实战
@Test public void testWechatPayNativeCloseOrder() throws IOException { String outTradeNo = "99lHMc8uO2ZZ03KSV24XEFaLRfSMfqwD"; String url = String.format(WechatPayApi.NATIVE_CLOSE_ORDER, outTradeNo, payConfig.getMchId()); HttpPost httpPost = new HttpPost(url); //组装json JSONObject payObj = new JSONObject(); payObj.put("mchid", payConfig.getMchId()); String body = payObj.toJSONString(); log.info("请求参数={}", body); //将请求参数设置到请求对象中 StringEntity entity = new StringEntity(body, "utf-8"); entity.setContentType("application/json"); httpPost.setEntity(entity); httpPost.setHeader("Accept", "application/json"); //httpClient自动进行参数签名 try (CloseableHttpResponse response = wechatPayClient.execute(httpPost);) { int statusCode = response.getStatusLine().getStatusCode();//响应状态码 log.info("关闭订单响应码={},无响应体", statusCode); } catch (Exception e) { e.printStackTrace(); } }
第4集 微信支付-SDK自动完成支付签名流程解读
简介:微信支付-SDK自动完成支付签名流程解读
-
微信支付签名规则文档
-
https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_0.shtml
-
配置日志级别
-
logging.level.root=debug
-
-
第5集 微信支付-申请订单退款API链路开发实战
简介:微信支付-申请订单退款API链路开发实战
- 接口文档
- 二维码生成工具:https://cli.im/
- https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_9.shtml
- 注意事项
1、交易时间超过一年的订单无法提交退款
2、微信支付退款支持单笔交易分多次退款(不超50次),多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号
3、错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过6次
4、每个支付订单的部分退款次数不能超过50次
5、如果同一个用户有多笔退款,建议分不同批次进行退款,避免并发退款导致退款失败
6、申请退款接口的返回仅代表业务的受理情况,具体退款是否成功,需要通过退款查询接口获取结果
7、一个月之前的订单申请退款频率限制为:5000/min
8、同一笔订单多次退款的请求需相隔1分钟
/**
* {"amount":{"currency":"CNY","discount_refund":0,"from":[],"payer_refund":10,
* "payer_total":100,"refund":10,"settlement_refund":10,"settlement_total":100,"total":100},
* "channel":"ORIGINAL","create_time":"2022-01-18T13:14:46+08:00",
* "funds_account":"AVAILABLE","out_refund_no":"Pe9rWbRpUDu51PFvo8L17LJZHm6dpbj7",
* "out_trade_no":"6xYsHV3UziDINu06B0XeuzmNvOedjhY5","promotion_detail":[],
* "refund_id":"50302000542022011816569235991","status":"PROCESSING",
* "transaction_id":"4200001390202201189710793189",
* "user_received_account":"民生银行信用卡5022"}
*
* @throws IOException
*/
@Test
public void testNativeRefundOrder() throws IOException {
String outTradeNo = "iIYGHoBTO95YaZu68n7BWXsxyaaNxK6q";
String refundNo = CommonUtil.getStringNumRandom(32);
//调用统一下单API
String url = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
HttpPost httpPost = new HttpPost(url);
// 请求body参数
JSONObject refundObj = new JSONObject();
//订单号
refundObj.put("out_trade_no", outTradeNo);
//退款单编号,商户系统内部的退款单号,商户系统内部唯一,
// 只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔
refundObj.put("out_refund_no", refundNo);
refundObj.put("reason","商品已售完");
refundObj.put("notify_url", payConfig.getCallbackUrl());
JSONObject amountObj = new JSONObject();
amountObj.put("refund", 10);
amountObj.put("total", 100);
amountObj.put("currency", "CNY");
refundObj.put("amount", amountObj);
String body = refundObj.toJSONString();
log.info("请求参数:{}",body);
StringEntity entity = new StringEntity(body,"utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
try(CloseableHttpResponse response = wechatPayClient.execute(httpPost)){
//响应码
int statusCode = response.getStatusLine().getStatusCode();
//响应体
String responseStr = EntityUtils.toString(response.getEntity());
log.info("退款响应码:{},响应体:{}",statusCode,responseStr);
}catch (Exception e){
e.printStackTrace();
}
}
第6集 微信支付-订单退款状态查询API链路实战
简介:微信支付-订单退款状态查询API链路实战
- 接口文档
- 二维码工具:https://cli.im/
- https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_10.shtml
- 怎么知道退款状态呢?
- 微信回调通知
- 主动查询
- 查询API编码实战
/**
* {"amount":{"currency":"CNY","discount_refund":0,"from":[],"payer_refund":10,
* "payer_total":100,"refund":10,"settlement_refund":10,
* "settlement_total":100,"total":100},"channel":"ORIGINAL",
* "create_time":"2022-01-18T13:14:46+08:00","funds_account":"AVAILABLE",
* "out_refund_no":"Pe9rWbRpUDu51PFvo8L17LJZHm6dpbj7",
* "out_trade_no":"6xYsHV3UziDINu06B0XeuzmNvOedjhY5","promotion_detail":[],
* "refund_id":"50302000542022011816569235991","status":"SUCCESS",
* "success_time":"2022-01-18T13:14:55+08:00","transaction_id":"4200001390202201189710793189",
* "user_received_account":"民生银行信用卡5022"}
* @throws IOException
*/
@Test
public void testNativeRefundQuery() throws IOException {
String refundNo = "Pe9rWbRpUDu51PFvo8L17LJZHm6dpbj7";
String url = String.format("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/%s",refundNo);
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept","application/json");
try(CloseableHttpResponse response = wechatPayClient.execute(httpGet)){
//响应码
int statusCode = response.getStatusLine().getStatusCode();
//响应体
String responseStr = EntityUtils.toString(response.getEntity());
log.info("查询退款响应码:{},响应体:{}",statusCode,responseStr);
}catch (Exception e){
e.printStackTrace();
}
}
第三十六章 设计模式在多渠道支付里面的设计+编码实战
第1集 软件架构设计-设计模式的六大原则你知道多少
简介:讲解设计模式的六大设A计原则
- 设计模式是站在设计原则的基础之上的,软件架构也一样,有必要对这些设计原则先做一下了解
- 软件设计开发原则
- 为了让的代码更好重用性,可读性,可靠性,可维护性
- 诞生出了很多软件设计的原则,这6大设计原则是我们要掌握的
- 将六大原则的英文首字母拼在一起就是SOLID(稳定的),所以也称之为SOLID原则
-
单一职责原则
- 一个类只负责一个功能领域中的相应职责,就一个类而言,应该只有一个引起它变化的原因
- 是实现高内聚、低耦合的指导方针
- 解释:
- 高内聚
- 尽可能类的每个成员方法只完成一件事(最大限度的聚合)
- 模块内部的代码, 相互之间的联系越强,内聚就越高, 模块的独立性就越好
- 低耦合: 减少类内部,一个成员方法调用另一个成员方法, 不要有牵一发动全身
- 高内聚
开闭原则
- 对扩展开放,对修改关闭,在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果
里氏替换原则LSP
- 任何基类可以出现的地方,子类一定可以出现
- 在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象
- controller->service->dao
依赖倒转原则
- 是开闭原则的基础,针对接口编程,依赖于抽象而不依赖于具体
- 高层模块不应该依赖低层模块,二者都应该依赖其抽象
接口隔离原则
-
客户端不应该依赖那些它不需要的接口
-
使用多个隔离的接口,比使用单个接口要好,降低类之间的耦合度
-
迪米特法则
- 最少知道原则,一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立
- 类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及
- 通过引入一个合理的第三者来降低现有对象之间的耦合度
第2集 设计模式最佳实践-第三方支付对接-工厂模式回顾
简介:设计模式知识回顾-工厂模式
-
工厂模式介绍:
- 它提供了一种创建对象的最佳方式,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象
-
工厂模式有 3 种不同的实现方式
- 简单工厂模式:通过传入相关的类型来返回相应的类,这种方式比较单 一 , 可扩展性相对较差;
- 工厂方法模式:通过实现类实现相应的方法来决定相应的返回结果,这种方式的可扩展性比较强;
- 抽象工厂模式:基于上述两种模式的拓展,且支持细化产品
-
例子:
- 需要购买一辆车,不用管车辆如何组装,且可以购买不同类型的比如轿车、SUV、跑车,直接去4s店购买就行(4s店就是工厂)
- 工厂生产电脑,除了A品牌、还可以生产B、C、D品牌电脑
- 业务开发中,支付很常见,里面有统一下单和支付接口,具体的支付实现可以微信、支付宝、银行卡等
-
简单工厂模式
- 又称静态工厂方法, 可以根据参数的不同返回不同类的实例,专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类
- 由于工厂方法是静态方法,可通过类名直接调用,而且只需要传入简单的参数即可
-
优点:
- 将对象的创建和对象本身业务处理分离可以降低系统的耦合度,使得两者修改起来都相对容易。
-
缺点
- 工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,这一点与开闭原则是相违背
- 即开闭原则(Open Close Principle)对扩展开放,对修改关闭,程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果
- 将会增加系统中类的个数,在一定程度上增加了系统的复杂度和理解难度,不利于系统的扩展和维护
-
项目里面的应用
第3集 设计模式最佳实践-第三方支付对接-策略模式回顾
简介:设计模式知识回顾-策略模式
-
策略模式(Strategy Pattern)
- 定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换
- 淘宝天猫双十一,正在搞活动有打折的、有满减的、有返利的等等,这些算法只是一种策略,并且是随时都可能互相替换的, 我们就可以定义一组算法,将每个算法都封装起来,并且使它们之间可以互换
-
应用场景
- 老王计划外出旅游,选择骑自行车、坐汽车、飞机等,每一种旅行方式都是一个策略
- Java AWT中的LayoutManager,即布局管理器
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么可以使用策略模式
- 不希望暴露复杂的、与算法有关的数据结构,那么可以使用策略模式来封装算法
- 对接第三方支付里面,微信支付、支付宝支付等都可以是一种策略
-
角色
- Context上下文:屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化
- Strategy策略角色:抽象策略角色,是对策略、算法家族的抽象,定义每个策略或算法必须具有的方法和属性
- ConcreteStrategy具体策略角色:用于实现抽象策略中的操作,即实现具体的算法
第4集 多渠道支付对接-策略模式+工厂模式编码实战
简介:多渠道支付对接-策略模式+工厂模式编码实战
- 策略接口开发 PayStrategy
- 策略上下文 PayStrategyContext开发
- 具体支付策略开发 AlipayStrategy、WechatPayStrategy
- 简单工厂类开发
第5集 策略设计模式-微信支付下单编码实战整合
简介:微信支付策略编码实战
-
下单策略编码实战
@Override public String unifiedOrder(PayInfoVO payInfoVO) { //过期时间 RFC 3339格式 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); //支付订单过期时间 String timeExpire = sdf.format(new Date(System.currentTimeMillis() + payInfoVO.getOrderPayTimeoutMills())); JSONObject amountObj = new JSONObject(); //数据库存储是double比如,100.99元,微信支付需要以分为单位 int amount = payInfoVO.getPayFee().multiply(BigDecimal.valueOf(100)).intValue(); amountObj.put("total", amount); amountObj.put("currency", "CNY"); JSONObject payObj = new JSONObject(); payObj.put("mchid", payConfig.getMchId()); payObj.put("out_trade_no", payInfoVO.getOutTradeNo()); payObj.put("appid", payConfig.getWxPayAppid()); payObj.put("description", payInfoVO.getTitle()); payObj.put("notify_url", payConfig.getCallbackUrl()); payObj.put("time_expire", timeExpire); payObj.put("amount", amountObj); //回调携带 payObj.put("attach", "{\"accountNo\":" + payInfoVO.getAccountNo() + "}"); // 处理请求body参数 String body = payObj.toJSONString(); log.info("请求参数:{}", body); StringEntity entity = new StringEntity(body, "utf-8"); entity.setContentType("application/json"); HttpPost httpPost = new HttpPost(WechatPayApi.NATIVE_ORDER); httpPost.setHeader("Accept", "application/json"); httpPost.setEntity(entity); String result = ""; try (CloseableHttpResponse response = wechatPayClient.execute(httpPost)) { //响应码 int statusCode = response.getStatusLine().getStatusCode(); //响应体 String responseStr = EntityUtils.toString(response.getEntity()); log.info("微信支付响应:resp code={},return body={}", statusCode, responseStr); //处理成功 if (statusCode == HttpStatus.OK.value()) { JSONObject jsonObject = JSONObject.parseObject(responseStr); if (jsonObject.containsKey("code_url")) { result = jsonObject.getString("code_url"); } } else { log.error("微信支付响应失败:resp code={},return body={}", statusCode, responseStr); } } catch (Exception e) { log.error("微信支付响应异常信息:{}", e); } return result; }
第6集 服务端OR客户端生成二维码优缺点分析
简介:新冠健康码-服务端OR客户端生成二维码优缺点分析
-
偶尔的能听到某某健康码崩溃的新闻
- 我没参与过这类的研发,简单说下【码】的生成是服务端还是客户端的优缺点
- 当然,再牛逼的公司也都有服务崩溃的时候:谷歌、FB、亚马逊、阿里、腾讯、微信都在其中
-
服务端生成二维码
- 优点
- 引入二维码jar包统一生成
- 图片生成兼容性好,传输相对安全不易被篡改
- 缺点
- 占据服务端CPU内存/资源、消耗传输带宽,性能较差
- 没法一并返回其他参数,定制化相对弱
- 优点
-
客户端生成二维码
- 优点
- 在客户端生成二维码会降低服务器的运算与存储压力
- 选择框架要考虑兼容性,客户端根据js框架生成二维码
- 后端返回 二维码文本值,还可以携带其他参数(比如订单号,可以轮训支付状态)
- 缺点
- 客户端需要引入二维码生成js插件
- 需要考虑浏览器兼容性
- 优点
第7集 流量包商品下单-微信支付链路测试
简介:流量包商品下单-微信支付链路测试
- 下单
{
"productId":2,
"buyNum":1,
"clientType":"PC",
"payType":"WECHAT_PAY",
"totalAmount":1,
"payAmount":1,
"billType":"NO_BILL",
"billHeader":"",
"billContent":"",
"billReceiverPhone":"",
"billReceiverEmail":""
}
第8集 策略设计模式-微信支付查询和关闭订单编码实战整合
简介:策略设计模式-微信支付查询和关闭订单编码实战整合
- 查询微信支付状态
@Override
public String queryPayStatus(PayInfoVO payInfoVO) {
String outTradeNo = payInfoVO.getOutTradeNo();
String url = String.format(WechatPayApi.NATIVE_QUERY,outTradeNo,payConfig.getMchId());
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept","application/json");
String result = "";
try(CloseableHttpResponse response = wechatPayClient.execute(httpGet)){
//响应码
int statusCode = response.getStatusLine().getStatusCode();
//响应体
String responseStr = EntityUtils.toString(response.getEntity());
log.debug("查询响应码:{},响应体:{}",statusCode,responseStr);
if(statusCode == HttpStatus.OK.value()){
JSONObject jsonObject = JSONObject.parseObject(responseStr);
if(jsonObject.containsKey("trade_state")){
result = jsonObject.getString("trade_state");
}
}else {
log.error("查询支付状态响应失败:{},响应体:{}",statusCode,responseStr);
}
}catch (Exception e){
log.error("微信支付响应异常:{}",e);
}
return result;
}
- 关闭微信支付订单
@Override
public String closeOrder(PayInfoVO payInfoVO) {
String outTradeNo = payInfoVO.getOutTradeNo();
JSONObject payObj = new JSONObject();
payObj.put("mchid",payConfig.getMchId());
String body = payObj.toJSONString();
log.debug("请求参数:{}",body);
//将请求参数设置到请求对象中
StringEntity entity = new StringEntity(body,"utf-8");
entity.setContentType("application/json");
String url = String.format(WechatPayApi.NATIVE_CLOSE_ORDER,outTradeNo);
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Accept","application/json");
httpPost.setEntity(entity);
String result = "";
try(CloseableHttpResponse response = wechatPayClient.execute(httpPost)){
//响应码
int statusCode = response.getStatusLine().getStatusCode();
log.debug("关闭订单响应码:{},无响应体",statusCode);
if(statusCode == HttpStatus.NO_CONTENT.value()){
result = "CLOSE_SUCCESS";
}
}catch (Exception e){
log.error("微信支付响应异常:{}",e);
}
return result;
}
第三十七章 微信支付-域名映射和回调-验证签名开发实战
第1集 微信开发常用工具-内网穿透映射介绍和使用
简介:微信开发常用工具-内网穿透映射介绍和使用
- 什么是内网穿透
支付成功需要配置回调通知应用服务器订单支付成功,需要配置对应的域名
在本地电脑开发,微信、支付宝没法回调,所以需要配置个地址映射,就是外部服务器
可以通过这个地址访问当前开发电脑的地址
微信登录、授权、支付等都是需要域名映射工具配合
-
工具
-
ngrock https://ngrok.com/
-
花生壳 https://hsk.oray.com/
-
小米球 http://ngrok.ciqiuwl.cn/
-
natapp(采用) https://natapp.cn/
-
-
账号申请()
1、注册
2、实名制
3、购买
4、授权(仔细看文档,这个是第三方工具,经常改规则,所以务必看文档操作)
-
启动
- ./natapp -authtoken=b1824af621e40514
-
访问测试
第2集 微信支付V3版本回调+验签流程梳理
简介:微信支付V3版本回调+验签流程梳理
-
回调验签流程
- 文档
- https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_5.shtml
- https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml
- 注意
- 同样的通知可能会多次发送给商户系统,商户系统必须能够正确处理重复的通知
- 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理,如果未处理,则再进行处理;如果已处理,则直接返回结果成功。
- 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
- 如果在所有通知频率后没有收到微信侧回调,商户应调用查询订单接口确认订单状态
- 确保回调URL是外部可正常访问的,且不能携带后缀参数
- 文档
-
微信回调通知重复问题(不一定准确按照时间间隔推送,需要保证幂等性处理)
- 重复通知的时候,微信的请求id是一样的,用这个做请求幂等性处理
- 响应给微信的内容不规范 或者 超过5秒没响应
- 测试的问题:如果有多个未响应的,则测试的请求id,可能有之前的请求继续回调过来
-
核心流程操作
- 获取报文
- 验证签名(确保是微信传输过来的)
- 解密(AES对称解密出原始数据)
- 处理业务逻辑
- 响应请求
加密不能保证通知请求来自微信,微信会对发送给商户的通知进行签名,并将签名值放在通知的HTTP头Wechatpay-Signature,商户应当验证签名,以确认请求来自微信,而不是其他的第三方
第3集 微信支付V3版本回调+验签开发实战《上》
简介:微信支付V3版本回调+验签开发实战《上》
- 编码实战-核心流程操作
- 获取报文
- 验证签名(确保是微信传输过来的)
- 解密(AES对称解密出原始数据)
- 处理业务逻辑
- 响应请求
@Controller
@RequestMapping("/api/callback/order/v1/")
@Slf4j
public class PayCallbackController {
@Autowired
private WechatPayConfig wechatPayConfig;
@Autowired
private ProductOrderService productOrderService;
@Autowired
private ScheduledUpdateCertificatesVerifier verifier;
/**
* * 获取报文
* <p>
* * 验证签名(确保是微信传输过来的)
* <p>
* * 解密(AES对称解密出原始数据)
* <p>
* * 处理业务逻辑
* <p>
* * 响应请求
*
* @param request
* @param response
* @return
*/
@RequestMapping("wechat")
public Map<String, String> wehcatPayCallback(HttpServletRequest request, HttpServletResponse response) {
//获取报文
String body = getRequestBody(request);
//随机串
String nonceStr = request.getHeader("Wechatpay-Nonce");
//微信传递过来的签名
String signature = request.getHeader("Wechatpay-Signature");
//证书序列号(微信平台)
String serialNo = request.getHeader("Wechatpay-Serial");
//时间戳
String timestamp = request.getHeader("Wechatpay-Timestamp");
//构造签名串
//应答时间戳\n
//应答随机串\n
//应答报文主体\n
String signStr = Stream.of(timestamp, nonceStr, body).collect(Collectors.joining("\n", "", "\n"));
Map<String, String> map = new HashMap<>(2);
try {
//验证签名是否通过
boolean result = verifiedSign(serialNo, signStr, signature);
//解密数据 TODO
//处理业务逻辑 TODO
//响应微信
map.put("code", "SUCCESS");
map.put("message", "成功");
} catch (Exception e) {
log.error("微信支付回调异常:{}", e);
}
return map;
}
/**
* 验证签名
*
* @param serialNo 微信平台-证书序列号
* @param signStr 自己组装的签名串
* @param signature 微信返回的签名
* @return
* @throws UnsupportedEncodingException
*/
private boolean verifiedSign(String serialNo, String signStr, String signature) throws UnsupportedEncodingException {
return verifier.verify(serialNo, signStr.getBytes("utf-8"), signature);
}
/**
* 读取请求数据流
*
* @param request
* @return
*/
private String getRequestBody(HttpServletRequest request) {
StringBuffer sb = new StringBuffer();
try (ServletInputStream inputStream = request.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
log.error("读取数据流异常:{}", e);
}
return sb.toString();
}
}
第三十八章 微信支付-订单状态更新和流量包权益发放解决方案
第1集 订单更新和流量包权益更新-解决方案分析《上》
简介:订单更新和流量包权益更新-解决方案分析《上》
-
接收微信推送的支付成功消息后,我们需要做啥?
- 更新订单状态
- 调用账号服务发放流量包
-
上述两步你能想到几种方案
- 方案一:更新数据库订单表,和 RPC调用发放流量包 然后再响应微信成功
- 考虑高并发性能问题
- 1-更新订单:数据库IO
- 2-RPC发放流量包:网络IO+数据库IO
- 3-最终才响应给微信
- 还需要考虑分布式事务问题,假如1和2步骤其中一个失败怎么办
第2集 订单更新和流量包权益更新-解决方案分析《下》
简介:订单更新和流量包权益更新-解决方案分析《下》
- 方式二:直接进入队列冗余双写,然后响应微信支付接口,再消费消息-更新数据库订单状态和发放流量包
- 方式三:折中方案,接收微信回调通知更新数据库,发送新增流量包MQ消息,响应微信,再消费流量包消息
第3集 微信支付回调通知-发送MQ业务消息开发《上》
简介: 微信支付回调通知-发送MQ业务消息开发《上》
- MQ配置
//=====================订单支付成功配置========================
/**
* 更新订单 队列
*/
private String orderUpdateQueue="order.update.queue";
/**
* 根据订单发放流量包 队列
*/
private String orderTrafficQueue="order.traffic.queue";
/**
* 微信回调通知routingKey,【发送消息使用】
*/
private String orderUpdateTrafficRoutingKey="order.update.traffic.routing.key";
/**
* topic类型的binding key,用于绑定队列和交换机,是用于 订单 消费者,更新订单状态
*/
private String orderUpdateBindingKey="order.update.*.routing.key";
/**
* topic类型的binding key,用于绑定队列和交换机,是用于 账号 消费者,发放流量包
*/
private String orderTrafficBindingKey="order.*.traffic.routing.key";
/**
* 订单更新队列和交换机的绑定关系建立
*/
@Bean
public Binding orderUpdateBinding(){
return new Binding(orderUpdateQueue,Binding.DestinationType.QUEUE, orderEventExchange,orderUpdateBindingKey,null);
}
/**
* 发放流量包队列和交换机的绑定关系建立
*/
@Bean
public Binding orderTrafficBinding(){
return new Binding(orderTrafficQueue,Binding.DestinationType.QUEUE, orderEventExchange,orderTrafficBindingKey,null);
}
/**
* 更新订单状态队列 普通队列,用于被监听
*/
@Bean
public Queue orderUpdateQueue(){
return new Queue(orderUpdateQueue,true,false,false);
}
/**
* 发放流量包队列 普通队列,用于被监听
*/
@Bean
public Queue orderTrafficQueue(){
return new Queue(orderTrafficQueue,true,false,false);
}
-
给用户新增发送流量包
-
由业务性能决定哪种方式,空间换时间,时间换空间
-
方式一:商品信息/订单信息 可以由消费者那边 远程调用feign进行获取,多了一次开销
-
方式二:商品信息进行快照存储到订单,支付通知回调,组装消息体进行发送(空间换时间,推荐)
-
第4集 微信支付回调通知-发送MQ业务消息开发《下》
简介: 微信支付回调通知-发送MQ业务消息开发《下》
- 发送业务逻辑开发
/***
* 支付通知结果更新订单状态
* @param alipay
* @param paramsMap
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public JsonData handlerOrderCallbackMsg(ProductOrderPayTypeEnum payType, Map<String, String> paramsMap) {
//获取商户订单号
String outTradeNo = paramsMap.get("out_trade_no");
//交易的状态
String tradeState = paramsMap.get("trade_state");
Long accountNo = Long.valueOf(paramsMap.get("accountNo"));
/**
* 由业务性能决定哪种方式,空间换时间,时间换空间
* 方式一:商品信息/订单信息 可以由消费者那边 远程调用feign进行获取,多了一次开销
* 方式三:订单生成,商品信息进行快照存储到订单(空间换时间,推荐)
*
*/
Map<String, Object> content = new HashMap<>(4);
ProductOrderDO productOrderDO = productOrderManager.findByOutTradeNoAndAccount(outTradeNo,accountNo);
ProductDO productDO = productManager.findById(productOrderDO.getProductId());
content.put("outTradeNo", outTradeNo);
content.put("buyNum", productOrderDO.getBuyNum());
content.put("accountNo", productOrderDO.getAccountNo());
content.put("product", JsonUtil.obj2Json(productDO));
EventMessage eventMessage = EventMessage.builder()
.bizId(outTradeNo)
.accountNo(accountNo)
.messageId(outTradeNo)
.content(JsonUtil.obj2Json(content))
.eventMessageType(EventMessageType.ORDER_PAY.name())
.build();
if (payType.name().equalsIgnoreCase(ProductOrderPayTypeEnum.ALI_PAY.name())) {
} else if (payType.name().equalsIgnoreCase(ProductOrderPayTypeEnum.WECHAT_PAY.name())) {
/**
* 交易状态,枚举值:
* SUCCESS:支付成功
* REFUND:转入退款
* NOTPAY:未支付
* CLOSED:已关闭
* REVOKED:已撤销(付款码支付)
* USERPAYING:用户支付中(付款码支付)
* PAYERROR:支付失败(其他原因,如银行返回失败)
*/
if ("SUCCESS".equalsIgnoreCase(tradeState)) {
rabbitTemplate.convertAndSend(rabbitMQConfig.getEventExchange(), rabbitMQConfig.getOrderPayRoutingKey(), eventMessage);
return JsonData.buildSuccess();
}
}
return JsonData.buildResult(BizCodeEnum.PAY_ORDER_CALLBACK_NOT_SUCCESS);
}
第5集 微信支付回调通知-MQ重复发送问题
简介: 微信支付回调通知-MQ重复发送问题
- 问题
- 微信支付成功,短时间重复发送问题
- 通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m
- 虽然给了文档,但是不按套路出牌,不一定准确按照时间间隔推送,需要保证幂等性处理
-
消息队列MQ面试核心
-
重复内容
- 投递
- 消费
-
任何消息队列产品不保证消息不重复,如果你的业务需要保证严格的不重复消息,需要你自己在业务端去重
- kafka、rocketmq、rabbitmq等都是一样的
- 接口幂等性保障 ,消费端处理业务消息要保持幂等性
- 幂等性,通俗点说,就一个数据或者一个请求,给你重复来多次,你得确保对应的数据是不会改变的
-
-
怎么保证MQ发送的幂等性
-
方案一:数据库
- 增加表记录,涉及到记录清除
- 多了一次数据库IO
-
方案二:Redis
-
set key value, 配置过期时间
-
解决方式
- spring-data-redis中的StringRedisTemplate 的 setIfAbsent 从2.1及以上开始支持设置过期时间
boolean flag = redisTemplate.opsForValue() .setIfAbsent(paramsMap.get("out_trade_no"),"OK",3,TimeUnit.DAYS); if(flag){ rabbitTemplate.convertAndSend(rabbitMQConfig.getOrderEventExchange(), rabbitMQConfig.getOrderUpdateTrafficRoutingKey(), eventMessage); }
- 也可以自己封装Lua脚本
第6集 微信支付回调-发布订阅模型链路验证
简介:微信支付回调-发布订阅模型链路验证
- 全链路验证
- 下单
- 支付
- 回调调试
- MQ消息查看
-
-
-
AY_ORDER_CALLBACK_NOT_SUCCESS);
}
#### 第5集 微信支付回调通知-MQ重复发送问题
**简介: 微信支付回调通知-MQ重复发送问题**
- 问题
- 微信支付成功,短时间重复发送问题
- 通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m
- 虽然给了文档,但是不按套路出牌,不一定准确按照时间间隔推送,需要保证幂等性处理
[外链图片转存中...(img-9fBSbBVK-1723204691379)]
- 消息队列MQ面试核心
- 重复内容
- 投递
- 消费
- 任何消息队列产品不保证消息不重复,如果你的业务需要保证严格的不重复消息,需要你自己在业务端去重
- kafka、rocketmq、rabbitmq等都是一样的
- 接口幂等性保障 ,消费端处理业务消息要保持幂等性
- 幂等性,通俗点说,就一个数据或者一个请求,给你重复来多次,你得确保对应的数据是不会改变的
- 怎么保证MQ发送的幂等性
- 方案一:数据库
- 增加表记录,涉及到记录清除
- 多了一次数据库IO
- 方案二:Redis
- set key value, 配置过期时间
- 解决方式
- spring-data-redis中的StringRedisTemplate 的 setIfAbsent 从2.1及以上开始支持设置过期时间
```java
boolean flag = redisTemplate.opsForValue()
.setIfAbsent(paramsMap.get("out_trade_no"),"OK",3,TimeUnit.DAYS);
if(flag){
rabbitTemplate.convertAndSend(rabbitMQConfig.getOrderEventExchange(), rabbitMQConfig.getOrderUpdateTrafficRoutingKey(), eventMessage);
}
```
- 也可以自己封装Lua脚本
第6集 微信支付回调-发布订阅模型链路验证
**简介:微信支付回调-发布订阅模型链路验证**
- 全链路验证
- 下单
- 支付
- 回调调试
- MQ消息查看