【微信支付V3】商家转账 - 发起转账

1、微信文档

https://pay.weixin.qq.com/doc/v3/merchant/4012716434

2、接口说明

  • 支持商户:
    【普通商户】
  • 请求方式:
    【POST】/v3/fund-app/mch-transfer/transfer-bills
  • 请求域名:
    【主域名】https://api.mch.weixin.qq.com

3、代码实现

import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.http.*;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import org.apache.commons.lang3.StringUtils;

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.util.Base64;
import java.util.HashMap;

@Slf4j
public class WxPayUtils {

    /**
     * 发起转账
     *
     * @param outBillNo 商户单号
     * @param openId    openId
     * @param userName  用户实名
     * @param amount    转账金额(单位:分)
     */
    public void createTransferBills(String outBillNo, String openId, String userName, Long amount) {
        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", "商户API证书序列号");

        HashMap<Object, Object> map = new HashMap<>();
        // 商户AppID
        map.put("appid", "小程序APPID");
        // 商户单号
        map.put("out_bill_no", outBillNo);
        // 转账场景ID
        map.put("transfer_scene_id", "1001");
        // 收款用户OpenID
        map.put("openid", openId);
        // 收款用户姓名(需要加密传入)
        if (StringUtils.isNotBlank(userName)) {
            map.put("user_name", rsaEncryptOAEP(userName));
        }
        // 转账金额(单位:分)
        map.put("transfer_amount", amount);
        // 转账备注
        map.put("transfer_remark", "2020年4月报销");
        // 回调通知地址
        map.put("notify_url", "https://www.xxx.com/wechat/notify/transferNotify");
        // 用户收款感知
        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();
            log.info("发起转账返回:{}", responseBody.toJSONString());
        } catch (ServiceException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * API安全加密配置
     */
    private RSAAutoCertificateConfig rsaAutoCertificateConfig() {
        return new RSAAutoCertificateConfig.Builder()
                // 商户号
                .merchantId("xxxxxxxxxxx")
                // 商户API证书私钥的存放路径
                .privateKeyFromPath("v3/apiclient_key.pem")
                // 商户API证书序列号
                .merchantSerialNumber("xxxxxxxxxxx")
                // APIv3密钥
                .apiV3Key("xxxxxxxxxxx")
                .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("v3/platform_cert.pem")) {
            if (in == null) {
                throw new IOException("Resource not found: v3/platform_cert.pem");
            }
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            return (X509Certificate) cf.generateCertificate(in);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

请求响应得到:

{
    "body": {
        "out_bill_no": "plfk2020042013",
        "transfer_bill_no": "1330000071100999991182020050700019480001",
        "create_time": "2015-05-20T13:29:35.120+08:00",
        "state": "ACCEPTED",
        "fail_reason": "PAYEE_ACCOUNT_ABNORMAL",
        "package_info": "affffddafdfafddffda=="
    }
}
  • state,单据状态,商家转账订单状态

ACCEPTED: 转账已受理
PROCESSING: 转账锁定资金中。如果一直停留在该状态,建议检查账户余额是否足够,如余额不足,可充值后再原单重试。
WAIT_USER_CONFIRM: 待收款用户确认,可拉起微信收款确认页面进行收款确认
TRANSFERING: 转账中,可拉起微信收款确认页面再次重试确认收款
SUCCESS: 转账成功
FAIL: 转账失败
CANCELING: 商户撤销请求受理成功,该笔转账正在撤销中
CANCELLED: 转账撤销完成

返回小程序端需要的参数

{
    "mchId ": "商户号",
    "appId": "商户AppID",
    "package": "跳转页面的package信息"
}

小程序端就可以拉起确认收款的界面了

如您在阅读中发现不足,欢迎留言!!!

### RAID5 和 RAIDZ 的读写速度性能对比 #### 顺序读取性能 对于顺序读取操作,RAID5 和 RAIDZ 都表现出良好的性能。两者都利用了条带化技术,在多个磁盘上并行读取数据,从而提高了整体吞吐量。然而,由于 RAIDZ 使用的是 ZFS 文件系统的优化算法,其实际表现可能会优于传统 RAID5 实现[^3]。 #### 顺序写入性能 在顺序写入方面,两种配置同样依赖于多块磁盘的同时工作以提升效率。但是需要注意的是,每次写入都需要更新奇偶校验信息,这增加了额外开销。特别是当遇到大量小文件频繁写入的情况时,这种差异更加明显。理论上讲,如果底层硬件相同,则二者在此项上的差距不大;但在某些特定场景下(比如高并发环境),可能因具体实现而有所不同[^1]。 #### 随机读取性能 随机访问模式下的读取效能也较为接近。得益于分布式布局的数据结构以及高效的缓存机制,无论是 RAID5 还是 RAIDZ ,都能较好地应对这类请求。不过值得注意的一点在于,随着负载增加或系统老化,可能出现轻微波动,但这通常不会造成显著影响。 #### 随机写入性能 这是最能体现两者区别的地方之一。由于 RAID5 只有一份冗余保护措施——即单一奇偶校验位,所以在处理单次写入事务时相对简单快捷。相比之下,尽管 RAIDZ 同样基于类似的原理运作,但由于它属于更为先进的解决方案,并集成了更多智能化特性(例如自适应替换策略等),因此即便是在面对复杂的随机写入任务时也能保持较好的响应速度和稳定性。 ```bash # 测试命令示例 (假设已安装 fio 工具) fio --name=test --rw=randwrite --bs=4k --size=2G --numjobs=8 --runtime=60 --group_reporting --filename=/mnt/raid{5,z}/testfile ``` 上述脚本可用于评估不同类型的 RAID 架构之间的 I/O 表现差异。请注意调整参数以匹配具体的测试需求。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Asurplus

学如逆水行舟,不进则退

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

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

打赏作者

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

抵扣说明:

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

余额充值