微信支付的服务商模式V3支付(可直接使用)

微信支付的服务商模式V3支付(可直接使用)直连商户 例如张三开了一个小程序 然后别人在这个小程序买东西 结账的时候 钱是直接打到张三的账号上的

大家好,欢迎来到IT知识分享网。

 直连商户模式和服务商模式区别:

        直连商户:例如张三开了一个小程序,然后别人在这个小程序买东西,结账的时候,钱是直接打到张三的账号上的。

微信支付逻辑(重点):

44ce6b79061943038deae8091cf7e087.png

        如果支付成功,那就ok了,因为有把本地的订单号一起传过去给微信那边,所以就相当于这个本地的订单号跟微信那边的订单绑定了,所以只要支付了腾讯那边的订单,那就相当于完成了本地订单。



1.申请证书,设置V3秘钥(这一步是服务商账号操作的)

微信支付的服务商模式V3支付(可直接使用)

 2.设置APPid账号管理(这一步是服务商操作的)

微信支付的服务商模式V3支付(可直接使用)

服务商账号关联要支付的小程序

 3.新增子商户(这个子商户实际上就是分店的主负责人)

微信支付的服务商模式V3支付(可直接使用)

可以手动添加,也可以使用接口来添加

4.配置子商户

微信支付的服务商模式V3支付(可直接使用)

 点击这个,然后跳转到这里:

微信支付的服务商模式V3支付(可直接使用)

继续点击:配置 

微信支付的服务商模式V3支付(可直接使用)

 把要支付的小程序或者公众号id配置进去

5.maven地址

 <dependency> <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-apache-httpclient</artifactId> <version>0.4.7</version> </dependency>

6.公共参数接口

package com.example.demo.zhifu; / * @Description: * @Author sk * @Date: 2023/7/5 14:31 */ / * 服务商模式 */ public interface ServiceProvider { String NOTIFY_URL = ""; //回调地址 String sp_mchid = ""; //服务商的商户号 String sub_mchid = "xxx"; // 子商户号,在这里写固定的,用于测试 String MCH_SERIAL_NO = ""; // 服务商的商户证书序列号 String API_3KEY = ""; // 服务商的V3的密钥 String sp_appid = ""; // ApId String privateKey = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDAgPXTRI0OFMEk" + "yf4+OSHs0K7wpDKfChB4xchJHJ39WwSS+A/fsyIEzC547D0NbUeiRby4ybAIfroa" + "zQCXRjRr0x6typGVY2ul9khWhSeC/CZHd0JrfcOCDHa3uJR01MElrGBIwgGSINrk" + "luW+jYveIVtc+uI1DSZrUOFxj8dg7//dvlhWluClwUbQiv9OG131Bi1j/fivUhI2" + "hiPy8zWADiCqTv5xzH3RBIbRJgNO/eIxUvfzGgyPECQ9C6XN4uxKxVWHOcg/vAD7" + "vQJHFO5sZ4/Z5pisHlUNr3aclTWVQg9n+ReOb8ztlmoqU4bvkh+3QveqsScDCqWl" + "A0CZ0Nr/AgMBAAECggEBALoMKQltaFIiluSiYBjtCK+ipGCooM/6Xx8KL98RTFQv" + "YkVUf6r4qrkuSP/PedX/NstLUPDa5EnhiKYcWSTa0hEfsrfOXlOeCc0VMKaF/EDo" + "x2oshcHzgz+uIhK/zqL3eFCbv1ayQehj3oosmJBIptQhMvay9mrFccsoGSqzBcPV" + "nwg04jlqZK"; } 

其中的 商户证书序列号对应的证书秘钥 在下载好的证书的这个地方:

 

其中红色框起来的就是商户证书序列号对应的证书秘钥:

7.下单工具类

import cn.hutool.core.util.RandomUtil; import com.alibaba.fastjson2.JSONObject; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder; import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner; import com.wechat.pay.contrib.apache.httpclient.auth.Verifier; import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials; import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator; import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager; import com.wechat.pay.contrib.apache.httpclient.util.PemUtil; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import java.security.*; import java.util.Base64; / * 服务商的下单工具类 * @author  */ public class PayMerchantUtil { private CloseableHttpClient httpClient; private CertificatesManager certificatesManager; private Verifier verifier; / * App下单 具体下单场景查看官方文档 * * @param total * @param description * @return * @throws Exception */ public String requestwxChatPay(String orderSn, int total, String description,String openid) throws Exception { PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(ServiceProvider.privateKey.getBytes("utf-8"))); // 获取证书管理器实例 certificatesManager = CertificatesManager.getInstance(); // 向证书管理器增加需要自动更新平台证书的商户信息 certificatesManager.putMerchant(ServiceProvider.sp_mchid, new WechatPay2Credentials(ServiceProvider.sp_mchid, new PrivateKeySigner(ServiceProvider.MCH_SERIAL_NO, merchantPrivateKey)), ServiceProvider.API_3KEY.getBytes(StandardCharsets.UTF_8)); // 从证书管理器中获取verifier verifier = certificatesManager.getVerifier(ServiceProvider.sp_mchid); httpClient = WechatPayHttpClientBuilder.create() .withMerchant(ServiceProvider.sp_mchid, ServiceProvider.MCH_SERIAL_NO, merchantPrivateKey) .withValidator(new WechatPay2Validator(certificatesManager.getVerifier(ServiceProvider.sp_mchid))) .build(); HttpPost httpPost = new HttpPost("https://api.mch.weixin..com/v3/pay/partner/transactions/jsapi"); httpPost.addHeader("Accept", "application/json"); httpPost.addHeader("Content-type", "application/json; charset=utf-8"); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectMapper objectMapper = new ObjectMapper(); //组合请求参数JSON格式 ObjectNode rootNode = objectMapper.createObjectNode(); rootNode.put("sp_appid", ServiceProvider.sp_appid) .put("sp_mchid", ServiceProvider.sp_mchid) .put("sub_mchid", ServiceProvider.sub_mchid) // 回调地址 .put("notify_url", ServiceProvider.NOTIFY_URL + "returnNotify") .put("description", description) .put("out_trade_no", orderSn); rootNode.putObject("amount") // total:金额,以分为单位,假如是10块钱,那就要写 1000 .put("total", total) .put("currency", "CNY"); rootNode.putObject("payer") .put("sp_openid", openid);// openid try { objectMapper.writeValue(bos, rootNode); httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8")); //获取预支付ID CloseableHttpResponse response = httpClient.execute(httpPost); String bodyAsString = EntityUtils.toString(response.getEntity()); //微信成功响应 int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == 200) { //时间戳 String timestamp = System.currentTimeMillis() / 1000 + ""; //随机字符串 String nonce = RandomUtil.randomString(32); StringBuilder builder = new StringBuilder(); // Appid builder.append(ServiceProvider.sp_appid).append("\n"); // 时间戳 builder.append(timestamp).append("\n"); // 随机字符串 builder.append(nonce).append("\n"); JsonNode jsonNode = objectMapper.readTree(bodyAsString); // 预支付会话ID builder.append("prepay_id=").append(jsonNode.get("prepay_id").textValue()).append("\n"); //获取签名 String sign = this.sign(builder.toString().getBytes("utf-8"), merchantPrivateKey); JSONObject jsonMap = new JSONObject(); jsonMap.put("noncestr", nonce); jsonMap.put("timestamp", timestamp); jsonMap.put("prepayid", jsonNode.get("prepay_id").textValue()); jsonMap.put("sign", sign); jsonMap.put("appid", ServiceProvider.sp_appid); jsonMap.put("partnerid", ServiceProvider.sp_mchid); return jsonMap.toJSONString();//响应签名数据,前端拿着响应数据调起微信SDK } } catch (Exception e) { e.printStackTrace(); } return null; } / * 计算签名 * * @param message * @param yourPrivateKey * @return */ private String sign(byte[] message, PrivateKey yourPrivateKey) { try { Signature sign = Signature.getInstance("SHA256withRSA"); sign.initSign(yourPrivateKey); sign.update(message); return Base64.getEncoder().encodeToString(sign.sign()); } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) { e.printStackTrace(); } return ""; }

8.回调签名工具类

package com.example.demo.zhifu; / * @Description: * @Author sk * @Date: 2023/7/5 14:31 */ import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Base64; / * 回调签名配置 * @author  */ public class AesUtil { static final int KEY_LENGTH_BYTE = 32; static final int TAG_LENGTH_BIT = 128; private final byte[] aesKey; public AesUtil(byte[] key) { if (key.length != KEY_LENGTH_BYTE) { throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节"); } this.aesKey = key; } public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) throws GeneralSecurityException, IOException { try { Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); SecretKeySpec key = new SecretKeySpec(aesKey, "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); } } } 

9.下单的controller

package com.example.demo.zhifu; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import org.springframework.web.bind.annotation.*; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; / * @Description: * @Author sk * @Date: 2023/7/5 19:10 */ @RestController @RequestMapping(value = "/pay") public class payController { / * 预支付下单 * @param orderSn 订单号 * @param total 分 * @param description 描述 * @return */ @GetMapping(value = "/getPay") public String getPay(String orderSn,int total , String description) { PayMerchantUtil payMerchantUtil = new PayMerchantUtil(); try { return payMerchantUtil.requestwxChatPay(orderSn, total, description, "oYgFI91D00GpCwccdnKDR4KNxI4k"); } catch (Exception e) { throw new RuntimeException(e); } } // 支付回调 @PostMapping(value = "/returnNotify") public Map returnNotify(@RequestBody JSONObject jsonObject) { // v3 私钥 String key = "xxxxx"; String json = jsonObject.toString(); String associated_data = (String) JSONUtil.getByPath(JSONUtil.parse(json), "resource.associated_data"); String ciphertext = (String) JSONUtil.getByPath(JSONUtil.parse(json), "resource.ciphertext"); String nonce = (String) JSONUtil.getByPath(JSONUtil.parse(json), "resource.nonce"); try { String decryptData = new AesUtil(key.getBytes(StandardCharsets.UTF_8)).decryptToString(associated_data.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext); System.out.println("decryptData = " + decryptData); //TODO 业务校验 } catch (Exception e) { e.printStackTrace(); } HashMap<String, String> stringStringHashMap = new HashMap<>(); stringStringHashMap.put("code","200"); stringStringHashMap.put("message","返回成功"); // 返回这个说明应答成功 return stringStringHashMap; } } 

10.官方文档:

产品能力概览 | 微信支付服务商平台文档中心

11.注意事项 

        1.有位兄弟看了博客之后,在使用的过程中,出现了签名不正确的问题,原因如下:

        业务不同,当前的服务商模式的业务是:所有的商户用的都是同一个小程序,所以在预支付下单的接口中,并没有传递 sub_appid 这个参数。
        而那位兄弟的业务是:服务商与特约商户,两个主体不一致,服务商有自己的服务号appid ,小程序是另外一个主体公司,然后开发的小程序,所以要传递 sub_appid 参数

        2.经常出现{“code”:”PARAM_ERROR”,”detail”:{“location”:”body”,”value”:0},”message”:”输入源“/body/reason”映射到值字段“退款原因”字符串规则校验失败,字符数 0,小于最小值 1″},是因为我退款原因没写上,写上之后就没有这个问题了

        3.支付回调跟退款的回调,必须是外网可以访问的,这推荐使用内网穿透




uni.requestPayment({ provider: 'wxpay', timeStamp: this.timeStamp, nonceStr: this.nonceStr, package: this.package, signType: 'RSA', paySign: this.paySign, success: function(res) { console.log('支付成功:' + JSON.stringify(res)); uni.switchTab({ url:'/pages/index/index' }) }, fail: function(err) { uni.showToast({ title:"支付失败,请重新支付", icon:'none' }) console.log('支付失败:' + JSON.stringify(err)); } });

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/125909.html

(0)
上一篇 2025-09-23 13:45
下一篇 2025-09-23 14:00

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信