Pago V3 en modo de proveedor de servicios de pago de WeChat (se puede usar directamente)

 La diferencia entre el modelo comercial de conexión directa y el modelo de proveedor de servicios:

        Conectado directamente con los comerciantes: por ejemplo, Zhang San abrió un pequeño programa y luego alguien compra algo en este pequeño programa. Al pagar, el dinero se envía directamente a la cuenta de Zhang San.

        Modelo de proveedor de servicios: por ejemplo, Zhang San abrió un pequeño programa, y ​​luego este pequeño programa tiene la función de abrir una sucursal, y luego otros compran cosas en la sucursal, y al pagar, el dinero se transfiere directamente a la cuenta de el responsable de la sucursal.
        Este artículo trata sobre el modelo de proveedor de servicios (consulte este artículo para conocer el modo de conexión directa: Conexión directa de Wechat al pago V3 de comerciantes (se puede usar directamente)_Blog de devolución no olvidado de Longling-Blog de CSDN )

Lógica de pago de WeChat (énfasis):

44ce6b79061943038deae8091cf7e087.png


        El front-end hace clic en el botón de pago y, antes de iniciar la página de pago de WeChat, se envía una solicitud al back-end. El back-end es responsable de llamar a la interfaz de "pedido unificado" de WeChat. Al llamar a esta interfaz, el el número de orden local también será Envíelo juntos, luego obtendrá un prepay_id, y luego hará un algoritmo para el prepay_id y algunos parámetros para obtener el valor de firma correspondiente, y luego lo devolverá al front-end, y luego el front-end puede llame al pago de acuerdo con estos valores de retorno, y puede pagar

        Si el pago es exitoso, está bien, porque el número de pedido local se pasó a WeChat, por lo que es equivalente al número de pedido local vinculado al pedido en WeChat, por lo que siempre que el pago se realice mediante un pedido de Tencent, es es equivalente a completar el pedido local.



1. Solicite un certificado y configure la clave secreta V3 (este paso lo realiza la cuenta del proveedor de servicios)

 

 2. Configure la administración de la cuenta APPid (este paso lo realiza el proveedor de servicios)

La cuenta del proveedor de servicios está asociada con el mini programa a pagar

 3. Agregue un nuevo subcomerciante (este subcomerciante es en realidad la persona principal a cargo de la sucursal)

Se puede agregar manualmente o usando la interfaz

4. Configurar sub-comerciante

 Haz clic aquí y salta aquí:

Continúe haciendo clic en: configurar 

 Configure el applet o la identificación de la cuenta oficial para pagar

5. dirección experta

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

6. Interfaz de parámetros públicos

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";

}

La clave del certificado correspondiente al número de serie del certificado mercantil se encuentra  en este lugar del certificado descargado:


bb485ad4bd2e4018a7e4eab0dddb767e.png

 

El recuadro rojo es la clave del certificado correspondiente al número de serie del certificado mercantil:


28fc165bfdcb4540895fa84db76897d2.png

7. Pedir herramientas

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 [email protected]
 */
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.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);// 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. Clase de herramienta de firma de devolución de llamada

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 [email protected]
 */
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. Controlador de pedidos

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. Documentos oficiales:

Descripción general de las capacidades del producto | Centro de documentación de la plataforma del proveedor de servicios de pago de WeChat

Supongo que te gusta

Origin blog.csdn.net/qq_26112725/article/details/131684591
Recomendado
Clasificación