package com.doumee.config.wx; import com.alibaba.fastjson.JSONObject; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; import okhttp3.*; import org.springframework.beans.factory.annotation.Autowired; import java.io.IOException; import java.io.UncheckedIOException; import java.security.PrivateKey; import java.security.PublicKey; import java.util.ArrayList; import java.util.List; /** * Created by IntelliJ IDEA. * * @Author : Rk * @create 2025/7/17 9:19 */ public class TransferToUser { private static String HOST = "https://api.mch.weixin.qq.com"; private static String METHOD = "POST"; private static String PATH = "/v3/fund-app/mch-transfer/transfer-bills"; private static String CANCEL_PATH = "/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}/cancel"; public TransferToUser(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) { this.mchid = mchid; this.certificateSerialNo = certificateSerialNo; this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath); this.wechatPayPublicKeyId = wechatPayPublicKeyId; this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath); } public static void main(String[] args) { // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/merchant/4013070756 // TransferToUser client = new TransferToUser( // "1229817002", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756 // "3FE90C2F3D40A56E1C51926F31B8A8D22426CCE0", // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053 // "d://wechatApiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 // "PUB_KEY_ID_0112298170022025071700291836000600", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816 // "d://pub_key.pem" // 微信支付公钥文件路径,本地文件路径 // ); TransferToUser client = new TransferToUser( "1723326069", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756 "12C0F0DD0F3D2B565B45586D3FEA225EBF723BEC", // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053 "d://jinkuai/shanghu/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 "PUB_KEY_ID_0117233260692025072500181939000603", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816 "d://jinkuai/shanghu/pub_key.pem" // 微信支付公钥文件路径,本地文件路径 ); TransferToUserRequest request = new TransferToUserRequest(); request.appid = "wx332441ae5b12be7d"; //小程序id request.outBillNo = "plfk2020042016"; request.transferSceneId = "1005"; request.openid = "oFucG7Nu5teWNIZiWkAINfAE4glE"; // request.userName = client.encrypt("施旭辉"); request.transferAmount = 10L; request.transferRemark = "~~~"; // request.notifyUrl = "https://www.weixin.qq.com/wxpay/pay.php"; request.userRecvPerception = "劳务报酬"; request.transferSceneReportInfos = new ArrayList<>(); { TransferSceneReportInfo item0 = new TransferSceneReportInfo(); item0.infoType = "岗位类型"; item0.infoContent = "完成订单"; request.transferSceneReportInfos.add(item0); TransferSceneReportInfo item1 = new TransferSceneReportInfo(); item1.infoType = "报酬说明"; item1.infoContent = "订单报酬"; request.transferSceneReportInfos.add(item1); }; try { TransferToUserResponse response = client.testRun(request); // TODO: 请求成功,继续业务逻辑 System.out.println(JSONObject.toJSONString(response)); } catch (WXPayUtility.ApiException e) { // TODO: 请求失败,根据状态码执行不同的逻辑 e.printStackTrace(); } } private final String mchid; private final String certificateSerialNo; private final PrivateKey privateKey; private final String wechatPayPublicKeyId; private final PublicKey wechatPayPublicKey; public TransferToUserResponse testRun(TransferToUserRequest request) { String uri = PATH; String reqBody = WXPayUtility.toJson(request); Request.Builder reqBuilder = new Request.Builder().url(HOST + uri); reqBuilder.addHeader("Accept", "application/json"); reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId); reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo,privateKey, METHOD, uri, reqBody)); reqBuilder.addHeader("Content-Type", "application/json"); RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody); reqBuilder.method(METHOD, requestBody); Request httpRequest = reqBuilder.build(); // 发送HTTP请求 OkHttpClient client = new OkHttpClient.Builder().build(); try (Response httpResponse = client.newCall(httpRequest).execute()) { String respBody = WXPayUtility.extractBody(httpResponse); if (httpResponse.code() >= 200 && httpResponse.code() < 300) { // 2XX 成功,验证应答签名 WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey, httpResponse.headers(), respBody); // 从HTTP应答报文构建返回数据 return WXPayUtility.fromJson(respBody, TransferToUserResponse.class); } else { throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers()); } } catch (IOException e) { throw new UncheckedIOException("Sending request to " + uri + " failed.", e); } } public TransferToUserResponse run(TransferToUserRequest request,String name) { String uri = PATH; request.appid = WxMiniConfig.wxProperties.getSubAppId(); request.notifyUrl = WxMiniConfig.wxProperties.getWechatNotifyUrl(); request.transferSceneId = "1005"; request.userRecvPerception = "劳务报酬"; if(request.transferAmount >= 30){ request.userName = this.encrypt(name); } request.transferSceneReportInfos = new ArrayList<>(); { TransferSceneReportInfo item0 = new TransferSceneReportInfo(); item0.infoType = "岗位类型"; item0.infoContent = "完成订单"; request.transferSceneReportInfos.add(item0); TransferSceneReportInfo item1 = new TransferSceneReportInfo(); item1.infoType = "报酬说明"; item1.infoContent = "订单报酬"; request.transferSceneReportInfos.add(item1); }; String reqBody = WXPayUtility.toJson(request); Request.Builder reqBuilder = new Request.Builder().url(HOST + uri); reqBuilder.addHeader("Accept", "application/json"); reqBuilder.addHeader("Wechatpay-Serial", WxMiniConfig.wxProperties.getWechatPayPublicKeyId()); reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(WxMiniConfig.wxProperties.getSubMchId(), WxMiniConfig.wxProperties.getWechatSerialNumer(), WXPayUtility.loadPrivateKeyFromPath(WxMiniConfig.wxProperties.getWechatPrivateKeyPath()), METHOD, uri, reqBody)); // reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo,privateKey, METHOD, uri, reqBody)); reqBuilder.addHeader("Content-Type", "application/json"); RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody); reqBuilder.method(METHOD, requestBody); Request httpRequest = reqBuilder.build(); // 发送HTTP请求 OkHttpClient client = new OkHttpClient.Builder().build(); try (Response httpResponse = client.newCall(httpRequest).execute()) { String respBody = WXPayUtility.extractBody(httpResponse); if (httpResponse.code() >= 200 && httpResponse.code() < 300) { // 2XX 成功,验证应答签名 WXPayUtility.validateResponse(WxMiniConfig.wxProperties.getWechatPayPublicKeyId(), WXPayUtility.loadPublicKeyFromPath(WxMiniConfig.wxProperties.getWechatPubKeyPath()), httpResponse.headers(), respBody); // 从HTTP应答报文构建返回数据 TransferToUser.TransferToUserResponse response = WXPayUtility.fromJson(respBody, TransferToUserResponse.class); response.appId = request.appid; response.mchId = WxMiniConfig.wxProperties.getSubMchId(); return response; } else { throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers()); } } catch (IOException e) { throw new UncheckedIOException("Sending request to " + uri + " failed.", e); } } public TransferToUser.CancelTransferResponse cancelRun(TransferToUser.CancelTransferRequest request) { String uri = CANCEL_PATH; uri = uri.replace("{out_bill_no}", WXPayUtility.urlEncode(request.outBillNo)); Request.Builder reqBuilder = new Request.Builder().url(HOST + uri); reqBuilder.addHeader("Accept", "application/json"); reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId); reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(WxMiniConfig.wxProperties.getSubMchId(), WxMiniConfig.wxProperties.getWechatSerialNumer(), WXPayUtility.loadPrivateKeyFromPath(WxMiniConfig.wxProperties.getWechatPrivateKeyPath()), METHOD, uri, null)); reqBuilder.addHeader("Content-Type", "application/json"); RequestBody emptyBody = RequestBody.create(null, ""); reqBuilder.method(METHOD, emptyBody); Request httpRequest = reqBuilder.build(); // 发送HTTP请求 OkHttpClient client = new OkHttpClient.Builder().build(); try (Response httpResponse = client.newCall(httpRequest).execute()) { String respBody = WXPayUtility.extractBody(httpResponse); if (httpResponse.code() >= 200 && httpResponse.code() < 300) { // 2XX 成功,验证应答签名 WXPayUtility.validateResponse(WxMiniConfig.wxProperties.getWechatPayPublicKeyId(), WXPayUtility.loadPublicKeyFromPath(WxMiniConfig.wxProperties.getWechatPubKeyPath()), httpResponse.headers(), respBody); // 从HTTP应答报文构建返回数据 return WXPayUtility.fromJson(respBody, TransferToUser.CancelTransferResponse.class); } else { throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers()); } } catch (IOException e) { throw new UncheckedIOException("Sending request to " + uri + " failed.", e); } } public String encrypt(String plainText) { return WXPayUtility.encrypt(this.wechatPayPublicKey, plainText); } public static class TransferToUserResponse { @SerializedName("out_bill_no") public String outBillNo; @SerializedName("transfer_bill_no") public String transferBillNo; @SerializedName("create_time") public String createTime; @SerializedName("state") public TransferBillStatus state; @SerializedName("package_info") public String packageInfo; @SerializedName("mch_id") public String mchId; @SerializedName("app_id") public String appId; } public enum TransferBillStatus { @SerializedName("ACCEPTED") ACCEPTED, @SerializedName("PROCESSING") PROCESSING, @SerializedName("WAIT_USER_CONFIRM") WAIT_USER_CONFIRM, @SerializedName("TRANSFERING") TRANSFERING, @SerializedName("SUCCESS") SUCCESS, @SerializedName("FAIL") FAIL, @SerializedName("CANCELING") CANCELING, @SerializedName("CANCELLED") CANCELLED } public static class TransferSceneReportInfo { @SerializedName("info_type") public String infoType; @SerializedName("info_content") public String infoContent; } public static class TransferToUserRequest { @SerializedName("appid") public String appid; @SerializedName("out_bill_no") public String outBillNo; @SerializedName("transfer_scene_id") public String transferSceneId; @SerializedName("openid") public String openid; @SerializedName("user_name") public String userName; @SerializedName("transfer_amount") public Long transferAmount; @SerializedName("transfer_remark") public String transferRemark; @SerializedName("notify_url") public String notifyUrl; @SerializedName("user_recv_perception") public String userRecvPerception; @SerializedName("transfer_scene_report_infos") public List transferSceneReportInfos; } public static class CancelTransferResponse { @SerializedName("out_bill_no") public String outBillNo; @SerializedName("transfer_bill_no") public String transferBillNo; @SerializedName("state") public String state; @SerializedName("update_time") public String updateTime; } public static class CancelTransferRequest { @SerializedName("out_bill_no") @Expose(serialize = false) public String outBillNo; } }