微信支付(Native支付/服务商模式)

 微信支付有多种模式,这里阐述的是服务商模式下的Native支付,适用场景为用户在服务商或子商户(也叫特约商户)前端选择微信支付后,服务商调起支付并接收回调的全过程。

特别提醒:本代码只有后端请求,服务商模式和商户模式有所不同,请根据自身需求自行选择。

框架:SpringBoot \ jdk1.8

可参考文档:Native下单_Native支付|微信支付合作伙伴文档中心https://pay.weixin.qq.com/doc/v3/partner/4012738659

一、所需实体类

@Data
public class Amount {
    private Integer total;
    private String currency;

    // Getters and Setters
}
@Data
public class WechatPayNotifyRequest {

    @JsonProperty("id")
    private String id;

    @JsonProperty("create_time")
    private Date createTime;

    @JsonProperty("resource_type")
    private String resourceType;

    @JsonProperty("event_type")
    private String eventType;

    @JsonProperty("summary")
    private String summary;

    @JsonProperty("resource")
    private Resource resource;

    // Getters and Setters
}
@Data
public class TransactionInfo {

    @JsonProperty("transaction_id")
    private String transactionId;

    private Amount amount;

    @JsonProperty("sub_mchid")
    private String subMchId;

    @JsonProperty("trade_state")
    private String tradeState;

    @JsonProperty("bank_type")
    private String bankType;

    @JsonProperty("promotion_detail")
    private List<PromotionDetail> promotionDetail;

    @JsonProperty("success_time")
    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX")
    private Date successTime;

    private Payer payer;

    @JsonProperty("out_trade_no")
    private String outTradeNo;

    private String appid;

    @JsonProperty("trade_state_desc")
    private String tradeStateDesc;

    @JsonProperty("trade_type")
    private String tradeType;

    private String attach;

    @JsonProperty("scene_info")
    private SceneInfo sceneInfo;

    @Data
    public static class Amount {
        @JsonProperty("payer_total")
        private Integer payerTotal;

        private Integer total;

        private String currency;

        @JsonProperty("payer_currency")
        private String payerCurrency;
    }

    @Data
    public static class PromotionDetail {
        private Integer amount;

        @JsonProperty("wechatpay_contribute")
        private Integer wechatpayContribute;

        @JsonProperty("coupon_id")
        private String couponId;

        private String scope;

        @JsonProperty("merchant_contribute")
        private Integer merchantContribute;

        private String name;

        @JsonProperty("other_contribute")
        private Integer otherContribute;

        private String currency;

        @JsonProperty("stock_id")
        private String stockId;

        @JsonProperty("goods_detail")
        private List<GoodsDetail> goodsDetail;
    }

    @Data
    public static class GoodsDetail {
        @JsonProperty("goods_remark")
        private String goodsRemark;

        private Integer quantity;

        @JsonProperty("discount_amount")
        private Integer discountAmount;

        @JsonProperty("goods_id")
        private String goodsId;

        @JsonProperty("unit_price")
        private Integer unitPrice;
    }

    @Data
    public static class Payer {
        private String openid;
    }

    @Data
    public static class SceneInfo {
        @JsonProperty("device_id")
        private String deviceId;
    }
}

 

 二、Native下单

1.微信请求签名头生成方法

/**
 * 微信支付 V3 签名工具类
 */
public class WechatPaySignUtil {
    private static final ObjectMapper objectMapper = new ObjectMapper();

    static {
        // 禁用缩进输出,确保紧凑格式
        objectMapper.configure(SerializationFeature.INDENT_OUTPUT, false);

        // 可选:忽略 null 字段(根据业务需求决定是否启用)
        // objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    }

    /**
     * 加载商户私钥(PKCS#8 格式)
     *
     * @param privateKeyPEMContent PEM 格式的私钥内容
     * @return PrivateKey
     * @throws Exception
     */
    public static PrivateKey loadPrivateKey(String privateKeyPEMContent) throws Exception {
        String pem = privateKeyPEMContent
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s+", "");

        byte[] pkcs8Bytes = Base64.getDecoder().decode(pem);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePrivate(keySpec);
    }

    /**
     * 生成请求所需的 Authorization 头
     *
     * @param method HTTP 方法,如 POST/GET
     * @param urlPath 请求路径,如 /v3/pay/transactions/native
     * @param body 请求体 JSON 字符串(POST 请求),GET 请求传 ""
     * @param merchantId 商户号 mchid
     * @param serialNo 证书序列号
     * @param privateKey 商户私钥
     * @return 完整的 Authorization Header
     * @throws Exception
     */
    public static String generateAuthorizationHeader(
            String method,
            String urlPath,
            String body,
            String merchantId,
            String serialNo,
            PrivateKey privateKey) throws Exception {

        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        String nonceStr = UUID.randomUUID().toString().replaceAll("-", "");

        String message = buildMessage(method, urlPath, timestamp, nonceStr, body);

        String signature = sign(message, privateKey);

        String format = String.format("WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%s\",serial_no=\"%s\",signature=\"%s\"",
                merchantId, nonceStr, timestamp, serialNo, signature);
        System.out.println(format);

        return format;
    }

    /**
     * 构建签名原文
     */
    private static String buildMessage(String method, String urlPath, String timestamp, String nonceStr, String body) {
        return method.toUpperCase() + "\n" +
                urlPath + "\n" +
                timestamp + "\n" +
                nonceStr + "\n" +
                body + "\n";
    }

    /**
     * 使用私钥对消息进行签名
     */
    private static String sign(String message, PrivateKey privateKey) throws Exception {
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(message.getBytes(StandardCharsets.UTF_8));
        byte[] signedBytes = signature.sign();
        return Base64.getEncoder().encodeToString(signedBytes);
    }

    /**
     * 将对象转换为 JSON 字符串
     */
    public static String toJson(Object obj) throws IOException {
        return objectMapper.writeValueAsString(obj);
    }
}

2.Native下单接口

@Service
public class WeChatPayGet {

    public Map<String, Object> submitNativeOrder() {
        try {

            // 1. 构造请求参数对象
            WechatPayNativeRequest request = new WechatPayNativeRequest();
            request.setSp_appid("appID");
            request.setSp_mchid("服务商商户号");
            request.setSub_mchid("商户商户号");
            request.setDescription("订单简介");
            request.setOut_trade_no("订单唯一编号(系统内唯一)");
            request.setNotify_url("回调接口接收地址");

            //获取收费金额
            Amount amount = getAmount();
            request.setAmount(amount);


            // 2. 序列化为 JSON 字符串
            String requestBody = WechatPaySignUtil.toJson(request);

            // 3. 加载商户私钥
            String privateKeyPEM = getKeyLoader();
            PrivateKey privateKey = WechatPaySignUtil.loadPrivateKey(privateKeyPEM);

            // 4. 生成签名头
            String authorization = WechatPaySignUtil.generateAuthorizationHeader(
                    "POST",
                    "/v3/pay/partner/transactions/native",
                    requestBody,
                    "商户号", 
                    "证书编号", 
                    privateKey);

            // 5. 发送请求
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
            headers.set("Authorization", authorization);

            HttpEntity<String> entity = new HttpEntity<>(requestBody, headers);

            RestTemplate restTemplate = new RestTemplate();
            ResponseEntity<String> response = restTemplate.postForEntity(
                    "https://api.mch.weixin.qq.com/v3/pay/partner/transactions/native",
                    entity,
                    String.class
            );

            ObjectMapper objectMapper = new ObjectMapper();
            Map<String, Object> map = objectMapper.readValue(response.getBody(), Map.class);
            Map<String, Object> result = new HashMap<>();
           
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("获取微信支付出错,请联系系统管理员");
        }
    }

    private @NotNull Amount getAmount() {
        Amount amount = new Amount();
       amount.setTotal("订单金额");
        amount.setCurrency("CNY");
        return amount;
    }


    private static String getKeyLoader() {
        try {
            return loadKey("私钥地址");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

 三、微信支付回调请求接收

1.请求接收接口

public class WechatPayNotifyController {

    /**
     * 微信支付回调
     */

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private WeChatPayResult weChatPayResult;

    private Certificate wechatCertificate;

    @PostConstruct
    public void init() throws Exception {
        this.wechatCertificate = loadCertificate();
    }

    @PostMapping("/notify")
    public String handleWechatPayNotify(@RequestBody String requestBody, HttpServletRequest request) {
        System.out.println("微信支付回调成功");
        try {
            // 1. 验证签名
            validateWechatSign(request, requestBody);
            System.out.println("验签成功");

            // 2. 反序列化通知内容
            WechatPayNotifyRequest notifyRequest = objectMapper.readValue(requestBody, WechatPayNotifyRequest.class);
            System.out.println("反序列化成功");

            // 3. 获取商户私钥
            String privateKey = getKeyLoader();
            String cleanedKey = privateKey.replace("\uFEFF", "").trim();
            System.out.println("私钥已获取");

            // 4. 解密 resource 数据
            AesUtil aesUtil = new AesUtil(cleanedKey.getBytes(StandardCharsets.UTF_8));
            String decryptData = aesUtil.decryptToString(
                    notifyRequest.getResource().getAssociatedData().getBytes(StandardCharsets.UTF_8),
                    notifyRequest.getResource().getNonce().getBytes(StandardCharsets.UTF_8),
                    notifyRequest.getResource().getCiphertext()
            );
            System.out.println("数据已解密");

            // 5. 转换为交易信息
            TransactionInfo transactionInfo = objectMapper.readValue(decryptData, TransactionInfo.class);
            System.out.println("交易信息转换成功,进行业务逻辑判断");
            // 6. 判断是否已处理过该订单(幂等性)
            boolean check = "自己的检测方法";
            if (check) {
                // 7. 没处理过,执行业务逻辑
                weChatPayResult.controllerAll(transactionInfo);
            }else {
                System.out.println("系统内已有该订单");
            }
            // 8. 返回成功响应
            return buildSuccessResponse();
        } catch (JsonProcessingException e) {
            return buildFailResponse("JSON解析失败");
        } catch (Exception e) {
            return buildFailResponse("系统内部错误");
        }
    }

    private void validateWechatSign(HttpServletRequest request, String body) throws Exception {
        String signature = request.getHeader("Wechatpay-Signature");
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonce = request.getHeader("Wechatpay-Nonce");

        String message = timestamp + "\n" + nonce + "\n" + body + "\n";
        Signature verifier = Signature.getInstance("SHA256WithRSA");
        verifier.initVerify(wechatCertificate);
        verifier.update(message.getBytes(StandardCharsets.UTF_8));


    }

    private Certificate loadCertificate() throws Exception {
        String pemData = getPublicLoader();
        StringReader reader = new StringReader(pemData.trim());
        PemReader pemReader = new PemReader(reader);
        PemObject pemObject = pemReader.readPemObject();
        pemReader.close();

        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
        return certificateFactory.generateCertificate(new ByteArrayInputStream(pemObject.getContent()));
    }

    private String buildSuccessResponse() {
        return "<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>";
    }

    private String buildFailResponse(String reason) {
        return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[" + reason + "]]></return_msg></xml>";
    }

    private static String getKeyLoader() {
        try {
            return loadKey("公钥地址");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static String getPublicLoader() {
        try {
            return loadKey("证书地址");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咚咚响咚呛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值