| 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<TransferSceneReportInfo> 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; | 
|     } | 
|   | 
| } |