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