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

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

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

        服务商模式:例如张三开了一个小程序,然后这个小程序中有一个开分店的功能,然后别人在分店购买东西,在结账的时候,钱是直接打到分店的负责人的账号上的。
        本文章说的是服务商模式(直连模式看这篇文章:微信直连商户V3支付(可直接使用)_流连勿忘返的博客-CSDN博客

微信支付逻辑(重点):

44ce6b79061943038deae8091cf7e087.png


        前端点击支付按钮,在调起微信自带支付页面之前,要往后端发一个请求,后端先是负责调用微信的 "统一下单" 接口,在调用这个接口的时候,会把本地订单号也一起发过去,然后会得到一个 prepay_id ,然后再针对 prepay_id 和一些参数做一个算法,得到相对应的签名值,然后返回给前端,然后前端就可以根据这些返回值调用支付,就可以支付了

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



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

 

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

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

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

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

4.配置子商户

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

继续点击:配置 

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

5.maven地址

        com.github.wechatpay-apiv3wechatpay-apache-httpclient0.4.7

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 = "";     // ApIdString 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";}

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


bb485ad4bd2e4018a7e4eab0dddb767e.png

 

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


28fc165bfdcb4540895fa84db76897d2.png

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 liaozan8888@163.com*/
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));// 从证书管理器中获取verifierverifier = 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.qq.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);// openidtry {objectMapper.writeValue(bos, rootNode);httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));//获取预支付IDCloseableHttpResponse 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();// Appidbuilder.append(ServiceProvider.sp_appid).append("\n");// 时间戳builder.append(timestamp).append("\n");// 随机字符串builder.append(nonce).append("\n");JsonNode jsonNode = objectMapper.readTree(bodyAsString);// 预支付会话IDbuilder.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 liaozan8888@163.com*/
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 stringStringHashMap = new HashMap<>();stringStringHashMap.put("code","200");stringStringHashMap.put("message","返回成功");// 返回这个说明应答成功return stringStringHashMap;}}

10.官方文档:

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


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部