微信支付 springboot java开发 商家转账V3最新版(2025年1月15日)

微信支付之商家转账功能(用于商家转账给微信用户),java可用详细版本,腾讯官方文档: https://pay.weixin.qq.com/doc/v3/merchant/4012711988
历史名称:企业付款到零钱 >> 商家转账到零钱 >> 商家转账,2025年1月15日后升级,调用方式也不一样了。本文介绍最新的调用方式(java代码),小白版本,大神勿喷…
在这里插入图片描述

在这里插入图片描述

一、准备工作,首先要把开发需要的材料都准备好,登录后台,找到API安全,把以下4项申请下来
在这里插入图片描述

  1. 商户API证书: 点击右上角<申请新证书>,按照腾讯指引下载新证书,证书下来是一个压缩包,解压出来有4个文件,需要用到的是apiclient_key.pem文件,4个可以放在代码static包里,或者放在服务器的文件夹里,只要java代码能访问到就可以。
    在这里插入图片描述
    在这里插入图片描述

  2. 商户APIv2秘钥: 自行设置32位秘钥,要记得,腾讯后台不会明文展示,如果忘记了只能重置。

  3. APIv3秘钥: 自行设置32位秘钥,要记得,腾讯后台不会明文展示,如果忘记了只能重置。

  4. 平台证书: 官方文档: https://pay.weixin.qq.com/doc/v3/merchant/4012068814这个比较麻烦一点,需要用到APIv3秘钥、商户ID、商户平台证书路径(步骤1下载下来的apiclient_key.pem文件所在的路径),申请方式:
    首先下载一个jar包,腾讯已经有放在github上了,如访问不了github,可以使用这个资源下载:https://download.youkuaiyun.com/download/weixin_42436080/90541583 下载后jar包放在本地文件夹,用cmd执行以下命令行: java -jar CertificateDownloader.jar -k ${apiV3key} -m ${mchId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath}

${apiV3key}: apiV3秘钥, 请使用自己的秘钥,如: OUiH6Z6kGxBHPclssO562OXkZnVYOWBX
${mchId}: 商户号,如16254032
${mchPrivateKeyFilePath}: 证书所在路径(步骤1下载的商户证书路径),: C:\apiclient_key.pem
${outputFilePath}: 申请后, 输出的证书路径,: F:\output

踩过的坑:申请的时候一直提示!Tag Matched,验签不通过,后来在后台重置了apiV3秘钥才行,可能是网络有延迟,如果确定命令、秘钥、路径都没问题,就在后台重置一下apiV3就行。
如果申请成功的话,F:\output文件夹会输出一个wechatpay_XXXXXXXXXXXXXXXXXXXXXXXXXXX.pem文件

二、材料都准备完成后,已经成功99%了,现在只需要复制拷贝就行。

  1. 新版的商家转账与之前的区别就是,原先是批量或者单个转账,直接到达用户微信零钱,新版需要调用接口生成账单,公众号、小程序或者H5再调用返回消息再进行用户收款。以下代码以小程序为例。

在这里插入图片描述

appId: 小程序的appID
secret: 小程序的秘钥
mchId: 商户号
apiV3Key: apiV3秘钥
mchSerialNo: 商户证书序列号,如下图
certUrl1: 商户证书地址,解压出来的文件,apiclient_key.pem所在的路径
certUrl2: 平台证书地址, 上一步骤申请的wechatpay_XXXXXXXXXXXXXXXXXXXXXXXXXXX.pem文件所在的路径
notifyUrl2: 回调地地址, 用户收款后的回调信息

在这里插入图片描述
2. 上代码
在这里插入图片描述

package com.ruoyi.common.wx;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;


/**
 * wxpay pay properties.
 *
 * @author Binary Wang
 */
@Data
@ConfigurationProperties(prefix = "wx.pay")
public class WxPayProperties {
  // 小程序appId
  private String appId;

  //小程序秘钥
  private String secret;
  
  // 商户ID
  private String mchId;

  // 商户秘钥 apiV2
  private String mchKey;
  
  // apiV3秘钥
  private String apiV3Key;
  
  // 证书序列号
  private String mchSerialNo;
  
  // 证书地址: 商户证书
  private String certUrl1;
  
  //证书地址: 平台证书
  private String certUrl2;

  // 回调地址: native收款
  private String notifyUrl1;
  
  //回调地址: 商家转账
  private String notifyUrl2;
}

Controller层代码

package com.ruoyi.web.controller.system;

 
import com.alibaba.fastjson.JSONObject;

import com.ruoyi.common.wx.WxPayProperties;

import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import io.swagger.annotations.Api;
import lombok.RequiredArgsConstructor;
import okhttp3.OkHttpClient;

import org.apache.commons.lang3.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.crypto.Cipher;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.util.Base64;
import java.util.HashMap;

import java.util.Random;

import com.wechat.pay.java.core.RSAAutoCertificateConfig;
 
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.http.DefaultHttpClientBuilder;
import com.wechat.pay.java.core.http.HttpClient;
import com.wechat.pay.java.core.http.HttpHeaders;
import com.wechat.pay.java.core.http.HttpMethod;
import com.wechat.pay.java.core.http.HttpRequest;
import com.wechat.pay.java.core.http.HttpResponse;
import com.wechat.pay.java.core.http.JsonRequestBody;
import com.wechat.pay.java.core.http.MediaType;

 
 

@Api(tags = "微信支付相关接口")
@RestController

@Validated
@RequiredArgsConstructor
@RequestMapping("/pay")
public class WechatPayController {
    @Resource
    private WxPayProperties wxPayProperties;
 
    
    // 2025.01之后 商家转账功能
    @GetMapping("/transfer/bat")
    @ResponseBody
    public String test1()   {
		long currentTimestamp = Instant.now().getEpochSecond();
		Random random = new Random();
		long randomIntBound = random.nextInt(10000);
		String formattedNumber = String.format("%0" + 5 + "d", randomIntBound); // 不足5位, 补0
		String orderNo = currentTimestamp + "8" + formattedNumber; // 订单编号
		
    	createTransferBills(orderNo, "oqX1E7F6aH8TiFlIcAnmXagOzBf1", "", 50L);
    	return "aa" ;
    }
     
    // step1 生成账单
    public void createTransferBills(String orderNo, String openId, String userName, Long money) {
        OkHttpClient okHttpClient = new OkHttpClient();
        HttpClient httpClient = new DefaultHttpClientBuilder()
                .config(rsaAutoCertificateConfig())
                .okHttpClient(okHttpClient)
                .build();
        HttpHeaders headers = new HttpHeaders();
        headers.addHeader("Accept", MediaType.APPLICATION_JSON.getValue());
        headers.addHeader("Content-Type", MediaType.APPLICATION_JSON.getValue());
        headers.addHeader("Wechatpay-Serial", wxPayProperties.getMchSerialNo()); // 商户证书序列号

        HashMap<Object, Object> map = new HashMap<>();
        map.put("appid", wxPayProperties.getAppId()); //小程序 appId
        map.put("out_bill_no", orderNo); // 订单编号
        map.put("transfer_scene_id", "1000"); // 转账场景ID
        map.put("openid", openId);  // openid
        // 收款用户姓名(需要加密传入)
        if (StringUtils.isNotBlank(userName)) {
            map.put("user_name", rsaEncryptOAEP(userName));
        }
        map.put("transfer_amount", money);  //金额(分)
        map.put("transfer_remark", "货款结算: " + orderNo); // 转账备注
        map.put("notify_url", wxPayProperties.getNotifyUrl2());  //回调地址: 商家转账回调地址
        map.put("user_recv_perception", "现金奖励");  // 用户收款感知
        // 转账场景报备信息 
        JSONArray jsonArray = new JSONArray();
        jsonArray.add(new JSONObject().fluentPut("info_type", "活动名称").fluentPut("info_content", "货款回收"));
        jsonArray.add(new JSONObject().fluentPut("info_type", "奖励说明").fluentPut("info_content", "货款回收结算"));
        map.put("transfer_scene_report_infos", jsonArray);

        JsonRequestBody build = new JsonRequestBody.Builder()
                .body(JSONUtil.toJsonStr(map))
                .build();
        HttpRequest executeSendGetHttpRequest = new HttpRequest.Builder()
                .httpMethod(HttpMethod.POST)
                .url("https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills")
                .headers(headers)
                .body(build)
                .build();
        try {
			HttpResponse<JSONObject> execute = httpClient.execute(executeSendGetHttpRequest, JSONObject.class);
            JSONObject responseBody = execute.getServiceResponse();
          //  System.out.println(responseBody.toJSONString());
            System.out.println(responseBody.getString("package_info"));  // 需要用到的字段
        } catch (ServiceException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // API安全加密配置
    private RSAAutoCertificateConfig rsaAutoCertificateConfig() {
        return new RSAAutoCertificateConfig.Builder()
                .merchantId(wxPayProperties.getMchId())  // 商户号
                .privateKeyFromPath(wxPayProperties.getCertUrl1())  // 商户API证书私钥的存放路径
                .merchantSerialNumber(wxPayProperties.getMchSerialNo())  // 商户API证书序列号
                .apiV3Key(wxPayProperties.getApiV3Key())  // APIv3密钥
                .build();
    }

    // 敏感信息加密
    private String rsaEncryptOAEP(String message) {
        X509Certificate cert = getX509Certificate();
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, cert.getPublicKey());
            byte[] data = message.getBytes(StandardCharsets.UTF_8);
            byte[] cipherdata = cipher.doFinal(data);
            return Base64.getEncoder().encodeToString(cipherdata);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // 获取 X509Certificate
    private X509Certificate getX509Certificate() {
        ClassLoader classLoader = this.getClass().getClassLoader();
        try (InputStream in = classLoader.getResourceAsStream(wxPayProperties.getCertUrl2())) {
            if (in == null) {
                throw new IOException("Resource not found: " + wxPayProperties.getCertUrl2());
            }
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            return (X509Certificate) cf.generateCertificate(in);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    } 
}

调用成功会返回json包,需要用到的是package_info里面的数据,并且状态应该为"state":“WAIT_USER_CONFIRM”,等待用户收款
在这里插入图片描述

小程序代码,可以放在提现按钮里,用于用户收款,把java返回的package_info复制到小程序的package里面来

 if (!wx.canIUse('requestMerchantTransfer')) {
    wx.showModal({
      content: '你的微信版本过低,请更新至最新版本。',
      showCancel: false,
    });

    return
  }


  wx.requestMerchantTransfer({
    mchId: '10000000001', // 商户号
    appId: 'waaaaaxxxxxxxxxxxe', // appId
    package: "ABxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxPMuG9v4Ssg=", // 商家转账付款单跳转收款页package信息
    success: (res) => {
      // res.err_msg将在页面展示成功后返回应用时返回ok,并不代表付款成功
      console.log('success:', res);
    },
    fail: (res) => {
      console.log('fail:', res);
    },
  })

用户点击提现后,会弹出以下提示框,确定收款即可完成转账.
在这里插入图片描述
这样就完成了一次商家转账,如有多次,就多次调用,并替换package数据即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

weixin_42436080

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

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

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

打赏作者

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

抵扣说明:

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

余额充值