WeChat-Zahlungsdienstleistermodus V3-Zahlung (kann direkt verwendet werden)

 Der Unterschied zwischen dem direkt verbundenen Händlermodell und dem Service-Provider-Modell:

        Direkt mit Händlern verbunden: Beispielsweise hat Zhang San ein kleines Programm eröffnet, und dann kauft jemand etwas in diesem kleinen Programm. Beim Auschecken wird das Geld direkt auf das Konto von Zhang San überwiesen.

        Dienstleistermodell: Zum Beispiel hat Zhang San ein kleines Programm eröffnet, und dann hat dieses kleine Programm die Funktion, eine Filiale zu eröffnen, und dann kaufen andere Dinge in der Filiale, und beim Auschecken wird das Geld direkt auf das Konto von überwiesen der Verantwortliche der Filiale.
        In diesem Artikel geht es um das Dienstanbietermodell (siehe diesen Artikel für den direkten Verbindungsmodus: Wechat-Direktverbindung zur Händler-V3-Zahlung (kann direkt verwendet werden)_Longling Do Not Forget Return Blog-CSDN Blog )

WeChat-Zahlungslogik (Schwerpunkt):

44ce6b79061943038deae8091cf7e087.png


        Das Front-End klickt auf die Zahlungsschaltfläche und bevor die WeChat-Zahlungsseite gestartet wird, wird eine Anfrage an das Back-End gesendet. Das Back-End ist für den Aufruf der Schnittstelle „Einheitliche Bestellung“ von WeChat verantwortlich. Beim Aufruf dieser Schnittstelle wird die Die lokale Bestellnummer wird ebenfalls gesendet. Dann erhalten Sie eine prepay_id und führen dann einen Algorithmus für die prepay_id und einige Parameter aus, um den entsprechenden Signaturwert zu erhalten, und geben ihn dann an das Front-End zurück, und dann kann das Front-End Rufen Sie die Zahlung gemäß diesen Rückgabewerten auf und Sie können bezahlen

        Wenn die Zahlung erfolgreich ist, ist dies in Ordnung, da die lokale Bestellnummer an WeChat übergeben wurde. Sie entspricht also der lokalen Bestellnummer, die an die Bestellung auf WeChat gebunden ist. Solange die Zahlung per Tencent-Bestellung erfolgt, erfolgt sie entspricht dem Abschluss der lokalen Bestellung.



1. Beantragen Sie ein Zertifikat und legen Sie den geheimen V3-Schlüssel fest (dieser Schritt wird über das Konto des Dienstanbieters ausgeführt).

 

 2. Legen Sie die APPid-Kontoverwaltung fest (dieser Schritt wird vom Dienstanbieter durchgeführt).

Das Dienstanbieterkonto ist mit dem zu bezahlenden Miniprogramm verknüpft

 3. Fügen Sie einen neuen Unterhändler hinzu (dieser Unterhändler ist eigentlich der Hauptverantwortliche der Filiale)

Es kann manuell oder über die Schnittstelle hinzugefügt werden

4. Unterhändler konfigurieren

 Klicken Sie hier und springen Sie hierher:

Klicken Sie weiter auf: Konfigurieren 

 Konfigurieren Sie die zu zahlende Applet- oder offizielle Konto-ID

5. Maven-Adresse

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

6. Öffentliche Parameterschnittstelle

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

}

Der Zertifikatsschlüssel, der der Seriennummer des Händlerzertifikats entspricht, befindet sich  an dieser Stelle des heruntergeladenen Zertifikats:


bb485ad4bd2e4018a7e4eab0dddb767e.png

 

Das rote Kästchen ist der Zertifikatsschlüssel, der der Seriennummer des Händlerzertifikats entspricht:


28fc165bfdcb4540895fa84db76897d2.png

7. Werkzeuge bestellen

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. Callback-Signatur-Tool-Klasse

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. Auftragscontroller

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. Offizielle Dokumente:

Übersicht über die Produktfunktionen | Dokumentationszentrum der WeChat Payment Service Provider Platform

Ich denke du magst

Origin blog.csdn.net/qq_26112725/article/details/131684591
Empfohlen
Rangfolge