V2版接口和V3版接口实际上是基于两种接口标准设计的两套接口。 目前大部分接口已升级为V3接口,其余V2接口后续也将逐步升级为V3接口
总的来说,v3支付是对v2版本的升级,不过目前来看,v3的支付对接和实现对开发者更加友好,并且支付更加安全。
微信支付官方文档
请大家花点时间认真看下文档。指引文档,更新日志,sdk,api字典等,这些都很重要,避免在开发过程中,遇到很多不必要的麻烦。
v2的微信小程序支付我之前已经实现过一次了,可以看看 微信小程序支付详解
文章。
交互图
https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_4&index=3 请看交互图,其实交互是一样的,只是v3的支付接口不一致、大致的实现流程就这些,后台的大致流程为:
请求微信统一下单接口,获取预支付交易会话标识(prepay_id)
拿prepay_id 拼接参数,生成签名,给到前端拉起支付(JSAPI调起支付API)
支付成功后,处理支付回调方法
这里就是后端需要做处理的流程,中间会涉及到 数字证书,秘钥,签名等加密解密出来,以及自己业务处理。
代码实现
官方提供了工具和SDK。先获取官方的SDK,java版本的来做项目开发。
https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay6_0.shtml
wechatpay-apache-httpclient,适用于使用Apache HttpClient处理HTTP的Java开发者。
wechatpay-php(推荐)、wechatpay-guzzle-middleware,适用于PHP开发者。
wechatpay-go,适用于Go开发者
大家根据自己的语言去下载对应的SDK。并且好好看看sdk的使用方式。
maven依赖
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.2.2</version>
</dependency>
配置类:
@Bean
public HttpClient wxHttpClient(
@Value("${mchid}") String mchid,
@Value("${mchSerialNo}") String mchSerialNo,
PrivateKey merchantPrivateKey,
AutoUpdateCertificatesVerifier autoUpdateCertificatesVerifier) {
return WechatPayHttpClientBuilder.create()
.withMerchant(mchid, mchSerialNo, merchantPrivateKey)
.withValidator(new WechatPay2Validator(autoUpdateCertificatesVerifier)).build();
}
/**
* 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
*/
@Bean
public AutoUpdateCertificatesVerifier autoUpdateCertificatesVerifier(
PrivateKey merchantPrivateKey,
@Value("${mchid}") String mchid,
@Value("${mchSerialNo}") String mchSerialNo,
@Value("${v3Key}") String v3Key) throws UnsupportedEncodingException {
return new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(mchid, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),
v3Key.getBytes("utf-8"));
}
/**
* 加载商户私钥(privateKey:私钥字符串)
*/
@Bean
public PrivateKey merchantPrivateKey(@Value("${privateKey}") String privateKey)
throws UnsupportedEncodingException {
return PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
}
static final int KEY_LENGTH_BYTE = 32;
static final int TAG_LENGTH_BIT = 128;
@Value("${v3Key}")
private String weChatV3Key;
public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext)
throws GeneralSecurityException, IOException {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec key = new SecretKeySpec(weChatV3Key.getBytes(StandardCharsets.UTF_8), "AES");
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData);
return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException(e);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException(e);
}
}
请求下单
/**
* 请求统一下单逻辑处理:
*/
private WxPayOrderResult unifiedOrder(WxPayOrderRequest wxPayOrderRequest) throws IOException {
HttpPost httpPost = new HttpPost(PAYUNIFIEDORDERURL);
// 请求body参数
StringEntity entity = new StringEntity(new Gson().toJson(wxPayOrderRequest), "UTF-8");
entity.setContentType("application/json;charset=UTF-8");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json;charset=UTF-8");
HttpResponse response = httpClient.execute(httpPost);
int code = response.getStatusLine().getStatusCode();
String body = response.getEntity() == null ? null : EntityUtils.toString(response.getEntity());
if (code != 200) {
log.error("请求微信统一下单异常: code={}, body={}", code, body);
throw new RuntimeException("请求微信统一下单异常");
}
return new Gson().fromJson(body,
WxPayOrderResult.class);
}
获取下单的预支付id生成签名
@Override
public String payTransactionsJsapi(WxPayOrderRequest wxPayOrderRequest) throws IOException {
wxPayOrderRequest.setAppid(appid);
wxPayOrderRequest.setMchid(mchid);
WxPayOrderResult wxPayUnifiedOrderResult = this.unifiedOrder(wxPayOrderRequest);
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
WxPayMpOrderResult wxPayMpOrderResult = new WxPayMpOrderResult().setAppId(
wxPayOrderRequest.getAppid())
.setNonceStr(wxPayOrderRequest.getOut_trade_no())
.setPackageValue("prepay_id=" + wxPayUnifiedOrderResult.getPrepay_id())
.setSignType("RSA")
.setTimeStamp(timestamp);
/**
* jsapi 拉起支付文档 : https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_4.shtml
* 一定要注意签名方式哪里
*/
String paySign = sign(getSignStr(wxPayOrderRequest.getAppid(), timestamp,
wxPayOrderRequest.getOut_trade_no(), "prepay_id=" + wxPayUnifiedOrderResult.getPrepay_id())
, privateKey);
wxPayMpOrderResult.setPaySign(paySign);
/**
* 这里是为了做兼容 代码不允许有 package 的处理,所以才做转换,如果可以的话,让前端去做处理也行
*/
Gson gson = new Gson();
Map<String, Object> map = gson.fromJson(gson.toJson(wxPayMpOrderResult), Map.class);
map.put("package", map.get("packageValue"));
map.remove("packageValue");
return gson.toJson(map);
}
签名方法
private String getSignStr(String appId, String timeStamp, String nonceStr, String packageValue) {
return String.format("%s\n%s\n%s\n%s\n", appId, timeStamp, nonceStr, packageValue);
}
public static String sign(String string, PrivateKey privateKey) {
try {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(privateKey);
sign.update(string.getBytes());
return Base64.getEncoder().encodeToString(sign.sign());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持SHA256withRSA", e);
} catch (SignatureException e) {
throw new RuntimeException("签名计算失败", e);
} catch (InvalidKeyException e) {
throw new RuntimeException("无效的私钥", e);
}
}
这里可以看看官方文档对签名的介绍:
回调处理:
@PostMapping("weChat")
public String acceptNotify(@RequestBody WeChatNotify notify) throws Exception {
log.info("接收到JSAPI支付通知 {}", notify.toString());
Gson gson = new Gson();
String decrypt = weChatAesUtil.decryptToString(notify.getResource()
.getAssociated_data()
.getBytes(StandardCharsets.UTF_8),
notify.getResource().getNonce().getBytes(StandardCharsets.UTF_8),
notify.getResource().getCiphertext());
//获取微信通知数据,接下来,自己去做业务处理:
WeChatDecryptNotifyContent content = gson
.fromJson(decrypt, WeChatDecryptNotifyContent.class);
return gson.toJson(new NotifyBody("SUCCESS", "成功"));
}
https://gitee.com/QuRenNen/spring-boot-integrate
spring-boot-wx-miniapp-integrat 项目中感兴趣可以看
————————————————
版权声明:本文为CSDN博主「码小呆~」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_41971087/article/details/122058874
请帮助我理解范围运算符...和..之间的区别,作为Ruby中使用的“触发器”。这是PragmaticProgrammersguidetoRuby中的一个示例:a=(11..20).collect{|i|(i%4==0)..(i%3==0)?i:nil}返回:[nil,12,nil,nil,nil,16,17,18,nil,20]还有:a=(11..20).collect{|i|(i%4==0)...(i%3==0)?i:nil}返回:[nil,12,13,14,15,16,17,18,nil,20] 最佳答案 触发器(又名f/f)是
我正在检查一个Rails项目。在ERubyHTML模板页面上,我看到了这样几行:我不明白为什么不这样写:在这种情况下,||=和ifnil?有什么区别? 最佳答案 在这种特殊情况下没有区别,但可能是出于习惯。每当我看到nil?被使用时,它几乎总是使用不当。在Ruby中,很少有东西在逻辑上是假的,只有文字false和nil是。这意味着像if(!x.nil?)这样的代码几乎总是更好地表示为if(x)除非期望x可能是文字false。我会将其切换为||=false,因为它具有相同的结果,但这在很大程度上取决于偏好。唯一的缺点是赋值会在每次运行
我正在阅读一本关于Ruby的书,作者在编写类初始化定义时使用的形式与他在本书前几节中使用的形式略有不同。它看起来像这样:classTicketattr_accessor:venue,:datedefinitialize(venue,date)self.venue=venueself.date=dateendend在本书的前几节中,它的定义如下:classTicketattr_accessor:venue,:datedefinitialize(venue,date)@venue=venue@date=dateendend在第一个示例中使用setter方法与在第二个示例中使用实例变量之间是
前言一般来说,前端根据后台返回code码展示对应内容只需要在前台判断code值展示对应的内容即可,但要是匹配的code码比较多或者多个页面用到时,为了便于后期维护,后台就会使用字典表让前端匹配,下面我将在微信小程序中通过wxs的方法实现这个操作。为什么要使用wxs?{{method(a,b)}}可以看到,上述代码是一个调用方法传值的操作,在vue中很常见,多用于数据之间的转换,但由于微信小程序诸多限制的原因,你并不能优雅的这样操作,可能有人会说,为什么不用if判断实现呢?但是if判断的局限性在于如果存在数据量过大时,大量重复性操作和if判断会让你的代码显得异常冗余。wxswxs相当于是一个独立
项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU
@作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors 1、什么是behaviors 2、behaviors的工作方式 3、创建behavior 4、导入并使用behavior 5、behavior中所有可用的节点 6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors 1、什么是behaviorsbehaviors是小程序中,用于实现
转自:spring.profiles.active和spring.profiles.include的使用及区别说明下文笔者讲述spring.profiles.active和spring.profiles.include的区别简介说明,如下所示我们都知道,在日常开发中,开发|测试|生产环境都拥有不同的配置信息如:jdbc地址、ip、端口等此时为了避免每次都修改全部信息,我们则可以采用以上的属性处理此类异常spring.profiles.active属性例:配置文件,可使用以下方式定义application-${profile}.properties开发环境配置文件:application-dev
打印1:defsum(i)i=i+[2]end$x=[1]sum($x)print$x打印12:defsum(i)i.push(2)end$x=[1]sum($x)print$x后者是修改全局变量$x。为什么它在第二个例子中被修改而不是在第一个例子中?类Array的任何方法(不仅是push)都会发生这种情况吗? 最佳答案 变量范围在这里无关紧要。在第一段代码中,您仅使用赋值运算符=为变量i赋值,而在第二段代码中,您正在修改$x(也称为i)使用破坏性方法push。赋值从不修改任何对象。它只是提供一个名称来引用一个对象。方法要么是破坏性
Ruby中的Fixnum方法.next和.succ有什么区别?看起来它的工作原理是一样的:1.next=>21.succ=>2如果有什么不同,为什么有两种方法做同样的事情? 最佳答案 它们是等价的。Fixnum#succ只是Fixnum#next的同义词。他们甚至在thereferencemanual中共享同一block. 关于ruby-Ruby中.next和.succ的区别,我们在StackOverflow上找到一个类似的问题: https://stacko
我明白了defa(&block)block.call(self)end和defa()yieldselfend导致相同的结果,如果我假设有这样一个blocka{}。我的问题是-因为我偶然发现了一些这样的代码,它是否有任何区别或者是否有任何优势(如果我不使用变量/引用block):defa(&block)yieldselfend这是一个我不理解&block用法的具体案例:defrule(code,name,&block)@rules=[]if@rules.nil?@rules 最佳答案 我能想到的唯一优点就是自省(introspecti