业务示例代码

更新时间:2026.04.23
||

1. 目标

通过本教程的学习,你应该可以:

2. 业务处理流程

2.1 提现流程

收付通平台预约提现/二级商户预约提现单状态流转

 

二级商户按日终余额预约提现单状态流转

平台自身提现

  • 调用收付通平台预约提现接口将平台自身的余额进行提现,系统将在次日发起提现,预计当日到账

  • 发起的预约提现时需通过 notify_url 设置回调地址(必须为 HTTPS 协议)

  • 微信支付会在提现状态发生变更时(成功、失败、退票、关单)异步推送结果

  • 商户系统不能仅依赖回调通知,需结合查询接口使用,如出现结果显示异常(提现失败、银行退票)可以调用「按日下载提现异常文件」获取信息

  • 当 API 返回错误码为 SYSTEM_ERROR 时: 务必不要换单重试,使用原单(所有请求参数保持不变)发起重试

申请收付通平台预约提现

收付通平台通过该接口可将其平台的收入进行预约提现,接口文档参考:收付通平台预约提现

1package withdraw
2
3import (
4    "demo/wxpay_utility" // 引用微信支付工具库,参考:https://pay.weixin.qq.com/doc/v3/partner/4015119446
5    "errors"
6    "fmt"
7    "time"
8)
9
10// NewMchConfig 创建商户配置
11func NewMchConfig() (*wxpay_utility.MchConfig, error) {
12    return wxpay_utility.CreateMchConfig(
13        "19xxxxxxxx",                 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340
14        "1DDE55AD98Exxxxxxxxxx",      // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924
15        "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
16        "PUB_KEY_ID_xxxxxxxxxxxxx",   // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589
17        "/path/to/wxp_pub.pem",       // 微信支付公钥文件路径,本地文件路径
18    )
19}
20
21// HandleCreateWithdraw 使用收付通平台预约提现接口:收付通平台预约提现,参考:https://pay.weixin.qq.com/doc/v3/partner/4012476670
22func HandleCreateWithdraw(config *wxpay_utility.MchConfig) error {
23    request := &CreateWithdrawRequest{
24        // 商户预约提现单号,由商户自定义生成,必须是字母数字
25        OutRequestNo: wxpay_utility.String("20190611222222222200000000012122"),
26        // 提现金额,单位:分,不能超过8亿元
27        Amount: wxpay_utility.Int64(1),
28        // 提现备注,商户对预约提现单的备注
29        Remark: wxpay_utility.String("交易提现"),
30        // 银行附言,展示在收款银行系统中的附言,由数字、字母、汉字组成(能否成功展示依赖银行系统支持)
31        BankMemo: wxpay_utility.String("xx平台提现"),
32        // 出款账户类型
33        AccountType: WITHDRAWACCOUNTTYPE_BASIC.Ptr(),
34        // 提现结果通知地址,异步接收提现结果通知的回调地址,必须为HTTPS协议
35        NotifyUrl: wxpay_utility.String("https://yourapp.com/notify"),
36    }
37
38    resp, err := CreateWithdraw(config, request)
39	if err != nil {
40        return handleError(err)
41    }
42
43    return handleStatus(resp)
44}
45
46func handleError(err error) error {
47    var apiError *wxpay_utility.ApiException
48    if errors.As(err, &apiError) {
49        fmt.Printf("状态码: %d\n", apiError.StatusCode())
50        fmt.Printf("错误码: %s\n", apiError.ErrorCode())
51        fmt.Printf("错误信息: %s\n", apiError.ErrorMessage())
52        switch apiError.ErrorCode() {
53        case "PARAM_ERROR":
54            // 错误:PARAM_ERROR
55            // 描述:参数错误
56            // 解决方式:请根据错误提示正确传入参数
57        case "INVALID_REQUEST":
58            // 错误:INVALID_REQUEST
59            // 描述:二级商户未开启预约提现权限 / 商户账户类型不存在或未开通 / HTTP请求不符合微信支付APIv3接口规则
60            // 解决方式:请确认电商平台商户号和二级商户商户号是否存在受理关系;请确认二级商户账户类型有效
61        case "SIGN_ERROR":
62            // 错误:SIGN_ERROR
63            // 描述:验证不通过
64            // 解决方式:请参阅签名常见问题
65        case "ACCOUNT_ERROR":
66            // 错误:ACCOUNT_ERROR
67            // 描述:二级商户未绑卡
68            // 解决方式:二级商户号没有绑定结算银行卡,绑定后重试
69        case "ACCOUNT_NOT_VERIFIED":
70            // 错误:ACCOUNT_NOT_VERIFIED
71            // 描述:二级商户下行打款未成功
72            // 解决方式:二级商户号结算银行卡信息有误,修改后重试
73        case "CONTRACT_NOT_CONFIRMED":
74            // 错误:CONTRACT_NOT_CONFIRMED
75            // 描述:二级商户未开启预约提现权限
76            // 解决方式:二级商户号预约提现权限已关闭,无法发起提现
77        case "NO_AUTH":
78            // 错误:NO_AUTH
79            // 描述:无接口使用权限
80            // 解决方式:请开通商户号相关权限
81        case "NOT_ENOUGH":
82            // 错误:NOT_ENOUGH
83            // 描述:二级商户号账户可用余额不足
84            // 解决方式:二级商户号账户可用余额不足
85        case "REQUEST_BLOCKED":
86            // 错误:REQUEST_BLOCKED
87            // 描述:二级商户未开启预约提现权限
88            // 解决方式:二级商户号预约提现权限被冻结,无法发起预约提现
89        case "ORDER_NOT_EXIST":
90            // 错误:ORDER_NOT_EXIST
91            // 描述:预约提现单号不存在
92            // 解决方式:请检查预约提现单号是否正确
93        case "FREQUENCY_LIMITED":
94            // 错误:FREQUENCY_LIMITED
95            // 描述:频率限制
96            // 解决方式:请降低频率后重试
97        case "SYSTEM_ERROR":
98            // 错误:SYSTEM_ERROR
99            // 描述:系统错误
100            // 解决方式:系统异常,请使用相同参数稍后重新调用
101        default:
102            // 其他类型错误
103        }
104    }
105    return err
106}
107
108func handleStatus(resp *CreateWithdrawResponse) error {
109    if resp.WithdrawId == nil {
110        return fmt.Errorf("withdraw_id is nil")
111    }
112    if resp.OutRequestNo == nil {
113        return fmt.Errorf("out_request_no is nil")
114    }
115    return nil
116}
查询收付通平台预约提现状态

收付通平台通过该接口查询平台自身预约提现单的当前状态,根据商户预约提现单号查询,接口文档参考:平台查询预约提现状态

1package withdraw
2
3import (
4    "demo/wxpay_utility" // 引用微信支付工具库,参考:https://pay.weixin.qq.com/doc/v3/partner/4015119446
5    "errors"
6    "fmt"
7)
8
9// NewMchConfig 创建商户配置
10func NewMchConfig() (*wxpay_utility.MchConfig, error) {
11    return wxpay_utility.CreateMchConfig(
12        "19xxxxxxxx",                 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340
13        "1DDE55AD98Exxxxxxxxxx",      // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924
14        "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
15        "PUB_KEY_ID_xxxxxxxxxxxxx",   // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589
16        "/path/to/wxp_pub.pem",       // 微信支付公钥文件路径,本地文件路径
17    )
18}
19
20// HandleQueryWithdrawByOutRequestNo 使用平台查询预约提现状态接口:收付通平台查询预约提现状态(根据商户预约提现单号查询),参考:https://pay.weixin.qq.com/doc/v3/partner/4012476672
21func HandleQueryWithdrawByOutRequestNo(config *wxpay_utility.MchConfig) error {
22    request := &QueryWithdrawByOutRequestNoRequest{
23        // 商户预约提现单号,由商户自定义生成,必须是字母数字
24        OutRequestNo: wxpay_utility.String("20190611222222222200000000012122"),
25    }
26
27    resp, err := QueryWithdrawByOutRequestNo(config, request)
28    if err != nil {
29        return handleError(err)
30    }
31
32    return handleStatus(resp)
33}
34
35func handleError(err error) error {
36    var apiError *wxpay_utility.ApiException
37    if errors.As(err, &apiError) {
38        fmt.Printf("状态码: %d\n", apiError.StatusCode())
39        fmt.Printf("错误码: %s\n", apiError.ErrorCode())
40        fmt.Printf("错误信息: %s\n", apiError.ErrorMessage())
41        switch apiError.ErrorCode() {
42        case "PARAM_ERROR":
43            // 错误:PARAM_ERROR
44            // 描述:参数错误
45            // 解决方式:请根据错误提示正确传入参数
46        case "INVALID_REQUEST":
47            // 错误:INVALID_REQUEST
48            // 描述:二级商户未开启预约提现权限 / HTTP请求不符合微信支付APIv3接口规则
49            // 解决方式:请确认电商平台商户号和二级商户商户号是否存在受理关系
50        case "SIGN_ERROR":
51            // 错误:SIGN_ERROR
52            // 描述:验证不通过
53            // 解决方式:请参阅签名常见问题
54        case "ACCOUNT_ERROR":
55            // 错误:ACCOUNT_ERROR
56            // 描述:二级商户未绑卡
57            // 解决方式:二级商户号没有绑定结算银行卡,绑定后重试
58        case "ACCOUNT_NOT_VERIFIED":
59            // 错误:ACCOUNT_NOT_VERIFIED
60            // 描述:二级商户下行打款未成功
61            // 解决方式:二级商户号结算银行卡信息有误,修改后重试
62        case "CONTRACT_NOT_CONFIRMED":
63            // 错误:CONTRACT_NOT_CONFIRMED
64            // 描述:二级商户未开启预约提现权限
65            // 解决方式:二级商户号提现权限已关闭,无法发起提现
66        case "NO_AUTH":
67            // 错误:NO_AUTH
68            // 描述:无接口使用权限
69            // 解决方式:请开通商户号相关权限
70        case "NOT_ENOUGH":
71            // 错误:NOT_ENOUGH
72            // 描述:二级商户号账户可用余额不足
73            // 解决方式:二级商户号账户可用余额不足
74        case "REQUEST_BLOCKED":
75            // 错误:REQUEST_BLOCKED
76            // 描述:二级商户未开启预约提现权限
77            // 解决方式:二级商户号预约提现权限被冻结,无法发起预约提现
78        case "ORDER_NOT_EXIST":
79            // 错误:ORDER_NOT_EXIST
80            // 描述:预约提现单号不存在
81            // 解决方式:请检查预约提现单号是否正确
82        case "FREQUENCY_LIMITED":
83            // 错误:FREQUENCY_LIMITED
84            // 描述:频率限制
85            // 解决方式:请降低频率后重试
86        case "SYSTEM_ERROR":
87            // 错误:SYSTEM_ERROR
88            // 描述:系统错误
89            // 解决方式:系统异常,请使用相同参数稍后重新调用
90        default:
91            // 其他类型错误
92        }
93    }
94    return err
95}
96
97func handleStatus(resp *Withdraw) error {
98    if resp.Status == nil {
99        return fmt.Errorf("status is nil")
100    }
101    switch *resp.Status {
102    case WITHDRAWSTATUS_INIT:
103        // 业务单已创建,请使用原单(所有请求参数保持不变)发起重试
104    case WITHDRAWSTATUS_CREATE_SUCCESS:
105        // 受理成功,等待银行返回最终结果,请重试查询或接入商户回调等待后续状态通知
106    case WITHDRAWSTATUS_SUCCESS:
107        // 提现成功,但不代表银行入账一定成功,部分情况下可能发生银行退票
108    case WITHDRAWSTATUS_FAIL:
109        // 提现失败
110    case WITHDRAWSTATUS_REFUND:
111        // 提现退票,请检查登记的账户信息是否有误,退票后资金会自动退回发起时的商户账户内
112    case WITHDRAWSTATUS_CLOSE:
113        // 关单
114    default:
115        return fmt.Errorf("unknown status: %s", *resp.Status)
116    }
117    if resp.WithdrawId == nil {
118        return fmt.Errorf("withdraw_id is nil")
119    }
120    return nil
121}

 

二级商户提现

  • 调用二级商户预约提现接口帮助二级商户发起提现申请,系统将在次日发起提现,预计当日到账

  • 发起的预约提现时需通过 notify_url 设置回调地址(必须为 HTTPS 协议)

  • 微信支付会在提现状态发生变更时(成功、失败、退票、关单)异步推送结果

  • 商户系统不能仅依赖回调通知,需结合查询接口使用,如出现结果显示异常(提现失败、银行退票)可以调用「按日下载提现异常文件」获取信息

  • 当 API 返回错误码为 SYSTEM_ERROR 时: 务必不要换单重试,使用原单(所有请求参数保持不变)发起重试

申请二级商户预约提现

收付通平台通过预约提现API帮助二级商户发起账户预约提现申请,完成账户提现,接口文档参考:二级商户预约提现

1package withdraw
2import (
3    "demo/wxpay_utility" // 引用微信支付工具库,参考:https://pay.weixin.qq.com/doc/v3/partner/4015119446
4    "errors"
5    "fmt"
6    "time"
7)
8// NewMchConfig 创建商户配置
9func NewMchConfig() (*wxpay_utility.MchConfig, error) {
10    return wxpay_utility.CreateMchConfig(
11        "19xxxxxxxx",                 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340
12        "1DDE55AD98Exxxxxxxxxx",      // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924
13        "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
14        "PUB_KEY_ID_xxxxxxxxxxxxx",   // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589
15        "/path/to/wxp_pub.pem",       // 微信支付公钥文件路径,本地文件路径
16    )
17}
18// HandleCreateSubMerchantWithdraw 使用二级商户预约提现接口:二级商户预约提现,参考:https://pay.weixin.qq.com/doc/v3/partner/4012476652
19func HandleCreateSubMerchantWithdraw(config *wxpay_utility.MchConfig) error {
20    request := &CreateSubMerchantWithdrawRequest{
21        // 二级商户号,收付通平台二级商户号,由微信支付生成并下发
22        SubMchid: wxpay_utility.String("1900000109"),
23        // 商户预约提现单号,由商户自定义生成,必须是字母数字
24        OutRequestNo: wxpay_utility.String("20190611222222222200000000012122"),
25        // 提现金额,单位:分,不能超过8亿元
26        Amount: wxpay_utility.Int64(1),
27        // 提现备注,商户对预约提现单的备注
28        Remark: wxpay_utility.String("交易提现"),
29        // 银行附言,展示在收款银行系统中的附言,由数字、字母、汉字组成(能否成功展示依赖银行系统支持)
30        BankMemo: wxpay_utility.String("xx平台提现"),
31        // 出款账户类型
32        AccountType: WITHDRAWACCOUNTTYPE_BASIC.Ptr(),
33        // 提现结果通知地址,异步接收提现结果通知的回调地址,必须为HTTPS协议
34        NotifyUrl: wxpay_utility.String("https://yourapp.com/notify"),
35    }
36
37  	resp, err := CreateSubMerchantWithdraw(config, request)
38    if err != nil {
39        return handleError(err)
40    }
41    return handleStatus(resp)
42}
43func handleError(err error) error {
44    var apiError *wxpay_utility.ApiException
45    if errors.As(err, &apiError) {
46        fmt.Printf("状态码: %d\n", apiError.StatusCode())
47        fmt.Printf("错误码: %s\n", apiError.ErrorCode())
48        fmt.Printf("错误信息: %s\n", apiError.ErrorMessage())
49        switch apiError.ErrorCode() {
50        case "PARAM_ERROR":
51            // 错误:PARAM_ERROR
52            // 描述:参数错误
53            // 解决方式:请根据错误提示正确传入参数
54        case "INVALID_REQUEST":
55            // 错误:INVALID_REQUEST
56            // 描述:二级商户未开启预约提现权限 / 商户账户类型不存在或未开通 / HTTP请求不符合微信支付APIv3接口规则
57            // 解决方式:请确认电商平台商户号和二级商户商户号是否存在受理关系;请确认二级商户账户类型有效
58        case "SIGN_ERROR":
59            // 错误:SIGN_ERROR
60            // 描述:验证不通过
61            // 解决方式:请参阅签名常见问题
62        case "ACCOUNT_ERROR":
63            // 错误:ACCOUNT_ERROR
64            // 描述:二级商户未绑卡
65            // 解决方式:二级商户号没有绑定结算银行卡,绑定后重试
66        case "ACCOUNT_NOT_VERIFIED":
67            // 错误:ACCOUNT_NOT_VERIFIED
68            // 描述:二级商户下行打款未成功
69            // 解决方式:二级商户号结算银行卡信息有误,修改后重试
70        case "CONTRACT_NOT_CONFIRMED":
71            // 错误:CONTRACT_NOT_CONFIRMED
72            // 描述:二级商户未开启预约提现权限
73            // 解决方式:二级商户号预约提现权限已关闭,无法发起预约提现
74        case "NO_AUTH":
75            // 错误:NO_AUTH
76            // 描述:无接口使用权限
77            // 解决方式:请开通商户号相关权限
78        case "NOT_ENOUGH":
79            // 错误:NOT_ENOUGH
80            // 描述:二级商户号账户可用余额不足
81            // 解决方式:二级商户号账户可用余额不足
82        case "REQUEST_BLOCKED":
83            // 错误:REQUEST_BLOCKED
84            // 描述:二级商户未开启预约提现权限
85            // 解决方式:二级商户号预约提现权限被冻结,无法发起预约提现
86        case "ORDER_NOT_EXIST":
87            // 错误:ORDER_NOT_EXIST
88            // 描述:预约提现单号不存在
89            // 解决方式:请检查订单号是否正确
90        case "FREQUENCY_LIMITED":
91            // 错误:FREQUENCY_LIMITED
92            // 描述:频率限制
93            // 解决方式:请降低频率后重试
94        case "SYSTEM_ERROR":
95            // 错误:SYSTEM_ERROR
96            // 描述:系统错误
97            // 解决方式:系统异常,请使用相同参数稍后重新调用
98        default:
99            // 其他类型错误
100        }
101    }
102    return err
103}
104func handleStatus(resp *CreateSubMerchantWithdrawResponse) error {
105    if resp.WithdrawId == nil {
106        return fmt.Errorf("withdraw_id is nil")
107    }
108    if resp.SubMchid == nil {
109        return fmt.Errorf("sub_mchid is nil")
110    }
111    if resp.OutRequestNo == nil {
112        return fmt.Errorf("out_request_no is nil")
113    }
114    return nil
115}
查询二级商户预约提现状态

收付通平台通过该接口查询二级商户预约提现单的当前状态,根据商户预约提现单号查询,接口文档参考:二级商户查询预约提现状态

1package withdraw
2
3import (
4    "demo/wxpay_utility" // 引用微信支付工具库,参考:https://pay.weixin.qq.com/doc/v3/partner/4015119446
5    "errors"
6    "fmt"
7)
8
9// NewMchConfig 创建商户配置
10func NewMchConfig() (*wxpay_utility.MchConfig, error) {
11    return wxpay_utility.CreateMchConfig(
12        "19xxxxxxxx",                 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340
13        "1DDE55AD98Exxxxxxxxxx",      // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924
14        "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
15        "PUB_KEY_ID_xxxxxxxxxxxxx",   // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589
16        "/path/to/wxp_pub.pem",       // 微信支付公钥文件路径,本地文件路径
17    )
18}
19
20// HandleQuerySubMerchantByOutRequestNo 使用二级商户查询预约提现状态接口:二级商户查询预约提现状态(根据商户预约提现单号查询),参考:https://pay.weixin.qq.com/doc/v3/partner/4012476656
21func HandleQuerySubMerchantByOutRequestNo(config *wxpay_utility.MchConfig) error {
22    request := &QuerySubMerchantByOutRequestNoRequest{
23        // 二级商户号,收付通平台二级商户号,由微信支付生成并下发
24        SubMchid: wxpay_utility.String("1900000109"),
25        // 商户预约提现单号,由商户自定义生成,必须是字母数字
26        OutRequestNo: wxpay_utility.String("20190611222222222200000000012122"),
27    }
28
29    resp, err := QuerySubMerchantByOutRequestNo(config, request)
30    if err != nil {
31        return handleError(err)
32    }
33
34    return handleStatus(resp)
35}
36
37func handleError(err error) error {
38    var apiError *wxpay_utility.ApiException
39    if errors.As(err, &apiError) {
40        fmt.Printf("状态码: %d\n", apiError.StatusCode())
41        fmt.Printf("错误码: %s\n", apiError.ErrorCode())
42        fmt.Printf("错误信息: %s\n", apiError.ErrorMessage())
43        switch apiError.ErrorCode() {
44        case "PARAM_ERROR":
45            // 错误:PARAM_ERROR
46            // 描述:参数错误
47            // 解决方式:请根据错误提示正确传入参数
48        case "INVALID_REQUEST":
49            // 错误:INVALID_REQUEST
50            // 描述:二级商户未开启预约提现权限 / HTTP请求不符合微信支付APIv3接口规则
51            // 解决方式:请确认电商平台商户号和二级商户商户号是否存在受理关系
52        case "SIGN_ERROR":
53            // 错误:SIGN_ERROR
54            // 描述:验证不通过
55            // 解决方式:请参阅签名常见问题
56        case "ACCOUNT_ERROR":
57            // 错误:ACCOUNT_ERROR
58            // 描述:二级商户未绑卡
59            // 解决方式:二级商户号没有绑定结算银行卡,绑定后重试
60        case "ACCOUNT_NOT_VERIFIED":
61            // 错误:ACCOUNT_NOT_VERIFIED
62            // 描述:二级商户下行打款未成功
63            // 解决方式:二级商户号结算银行卡信息有误,修改后重试
64        case "CONTRACT_NOT_CONFIRMED":
65            // 错误:CONTRACT_NOT_CONFIRMED
66            // 描述:二级商户未开启预约提现权限
67            // 解决方式:二级商户号提现权限已关闭,无法发起提现
68        case "NO_AUTH":
69            // 错误:NO_AUTH
70            // 描述:无接口使用权限
71            // 解决方式:请开通商户号相关权限
72        case "NOT_ENOUGH":
73            // 错误:NOT_ENOUGH
74            // 描述:二级商户号账户可用余额不足
75            // 解决方式:二级商户号账户可用余额不足
76        case "REQUEST_BLOCKED":
77            // 错误:REQUEST_BLOCKED
78            // 描述:二级商户未开启预约提现权限
79            // 解决方式:二级商户号预约提现权限被冻结,无法发起预约提现
80        case "ORDER_NOT_EXIST":
81            // 错误:ORDER_NOT_EXIST
82            // 描述:预约提现单号不存在
83            // 解决方式:请检查预约提现单号是否正确
84        case "FREQUENCY_LIMITED":
85            // 错误:FREQUENCY_LIMITED
86            // 描述:频率限制
87            // 解决方式:请降低频率后重试
88        case "SYSTEM_ERROR":
89            // 错误:SYSTEM_ERROR
90            // 描述:系统错误
91            // 解决方式:系统异常,请使用相同参数稍后重新调用
92        default:
93            // 其他类型错误
94        }
95    }
96    return err
97}
98
99func handleStatus(resp *SubMerchantWithdraw) error {
100    if resp.Status == nil {
101        return fmt.Errorf("status is nil")
102    }
103    switch *resp.Status {
104    case WITHDRAWSTATUS_INIT:
105        // 业务单已创建,请使用原单(所有请求参数保持不变)发起重试
106    case WITHDRAWSTATUS_CREATE_SUCCESS:
107        // 受理成功,等待银行返回最终结果,请重试查询或接入商户回调等待后续状态通知
108    case WITHDRAWSTATUS_SUCCESS:
109        // 提现成功,但不代表银行入账一定成功,部分情况下可能发生银行退票
110    case WITHDRAWSTATUS_FAIL:
111        // 提现失败
112    case WITHDRAWSTATUS_REFUND:
113        // 提现退票,请检查登记的账户信息是否有误,退票后资金会自动退回发起时的商户账户内
114    case WITHDRAWSTATUS_CLOSE:
115        // 关单
116    default:
117        return fmt.Errorf("unknown status: %s", *resp.Status)
118    }
119    if resp.WithdrawId == nil {
120        return fmt.Errorf("withdraw_id is nil")
121    }
122    return nil
123}
申请二级商户按日终余额预约提现

收付通平台通过二级商户按日终余额预约提现API帮助二级商户发起基本户提现,提现金额按前一天日终余额计算,建议在10点以后发起接口文档参考:二级商户按日终余额预约提现

1package withdraw
2import (
3    "demo/wxpay_utility" // 引用微信支付工具库,参考:https://pay.weixin.qq.com/doc/v3/partner/4015119446
4    "errors"
5    "fmt"
6    "time"
7)
8// NewMchConfig 创建商户配置
9func NewMchConfig() (*wxpay_utility.MchConfig, error) {
10    return wxpay_utility.CreateMchConfig(
11        "19xxxxxxxx",                 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340
12        "1DDE55AD98Exxxxxxxxxx",      // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924
13        "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
14        "PUB_KEY_ID_xxxxxxxxxxxxx",   // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589
15        "/path/to/wxp_pub.pem",       // 微信支付公钥文件路径,本地文件路径
16    )
17}
18// HandlePlatCreateDayEndBalanceWithdraw 使用二级商户按日终余额预约提现接口:二级商户按日终余额预约提现,参考:https://pay.weixin.qq.com/doc/v3/partner/4013328143
19func HandlePlatCreateDayEndBalanceWithdraw(config *wxpay_utility.MchConfig) error {
20    request := &PlatCreateDayEndBalanceWithdrawRequest{
21        // 二级商户号,收付通平台二级商户号,由微信支付生成并下发
22        SubMchid: wxpay_utility.String("1900000109"),
23        // 商户提现单号,由商户自定义生成,必须是字母数字
24        OutRequestNo: wxpay_utility.String("T20240611222222222200"),
25        // 计算提现金额方式,仅当前余额少于日终余额时才有区别,否则均以日终余额计算提现金额
26        CalculateAmountType: CALCULATEWITHDRAWAMOUNTTYPE_ONLY_DAY_END_BALANCE.Ptr(),
27        // 提现备注,商户对预约提现单的备注
28        Remark: wxpay_utility.String("交易提现"),
29        // 银行附言,展示在收款银行系统中的附言,由数字、字母、汉字组成(能否成功展示依赖银行系统支持)
30        BankMemo: wxpay_utility.String("xx平台提现"),
31        // 提现结果通知地址,异步接收提现结果通知的回调地址,必须为HTTPS协议
32        NotifyUrl: wxpay_utility.String("https://yourapp.com/notify"),
33    }
34
35	resp, err := PlatCreateDayEndBalanceWithdraw(config, request)
36	if err != nil {
37        return handleError(err)
38    }
39
40    return handleStatus(resp)
41}
42func handleError(err error) error {
43    var apiError *wxpay_utility.ApiException
44    if errors.As(err, &apiError) {
45        fmt.Printf("状态码: %d\n", apiError.StatusCode())
46        fmt.Printf("错误码: %s\n", apiError.ErrorCode())
47        fmt.Printf("错误信息: %s\n", apiError.ErrorMessage())
48        switch apiError.ErrorCode() {
49        case "PARAM_ERROR":
50            // 错误:PARAM_ERROR
51            // 描述:参数错误
52            // 解决方式:请根据错误提示正确传入参数
53        case "INVALID_REQUEST":
54            // 错误:INVALID_REQUEST
55            // 描述:二级商户未开启预约提现权限 / 商户账户类型不存在或未开通 / HTTP请求不符合微信支付APIv3接口规则
56            // 解决方式:请确认电商平台商户号和二级商户商户号是否存在受理关系;请确认二级商户账户类型有效
57        case "SIGN_ERROR":
58            // 错误:SIGN_ERROR
59            // 描述:验证不通过
60            // 解决方式:请参阅签名常见问题
61        case "ACCOUNT_ERROR":
62            // 错误:ACCOUNT_ERROR
63            // 描述:二级商户未绑卡
64            // 解决方式:二级商户号没有绑定结算银行卡,绑定后重试
65        case "ACCOUNT_NOT_VERIFIED":
66            // 错误:ACCOUNT_NOT_VERIFIED
67            // 描述:二级商户下行打款未成功
68            // 解决方式:二级商户号结算银行卡信息有误,修改后重试
69        case "CONTRACT_NOT_CONFIRMED":
70            // 错误:CONTRACT_NOT_CONFIRMED
71            // 描述:二级商户未开启预约提现权限
72            // 解决方式:二级商户号预约提现权限已关闭,无法发起预约提现
73        case "NO_AUTH":
74            // 错误:NO_AUTH
75            // 描述:无接口使用权限
76            // 解决方式:请开通商户号相关权限
77        case "NOT_ENOUGH":
78            // 错误:NOT_ENOUGH
79            // 描述:二级商户号账户可用余额不足
80            // 解决方式:二级商户号账户可用余额不足
81        case "REQUEST_BLOCKED":
82            // 错误:REQUEST_BLOCKED
83            // 描述:二级商户未开启预约提现权限
84            // 解决方式:二级商户号预约提现权限被冻结,无法发起预约提现
85        case "ORDER_NOT_EXIST":
86            // 错误:ORDER_NOT_EXIST
87            // 描述:预约提现单号不存在
88            // 解决方式:请检查订单号是否正确
89        case "FREQUENCY_LIMITED":
90            // 错误:FREQUENCY_LIMITED
91            // 描述:频率限制
92            // 解决方式:请降低频率后重试
93        case "SYSTEM_ERROR":
94            // 错误:SYSTEM_ERROR
95            // 描述:系统错误
96            // 解决方式:系统异常,请使用相同参数稍后重新调用
97        default:
98            // 其他类型错误
99        }
100    }
101    return err
102}
103func handleStatus(resp *WithdrawEntity) error {
104    if resp.Status == nil {
105        return fmt.Errorf("status is nil")
106    }
107    switch *resp.Status {
108        // 返回正常时不存在‘已创建’状态
109    case WITHDRAWENTITYSTATUS_PROCESSING:
110        // 处理中,等待银行返回结果,最终结果请重试查询
111    case WITHDRAWENTITYSTATUS_FINISHED:
112        // 已完成,向银行付款完成且收到银行反馈为成功,但不代表银行入账一定成功,部分情况下可能发生银行退票
113    case WITHDRAWENTITYSTATUS_ABNORMAL:
114        // 提现异常,存在向银行付款失败或银行退票情况
115    default:
116        return fmt.Errorf("unknown status: %s", *resp.Status)
117    }
118    if resp.WithdrawId == nil {
119        return fmt.Errorf("withdraw_id is nil")
120    }
121    return nil
122}
查询二级商户按日终余额预约提现状态

收付通平台通过该接口查询二级商户按日终余额预约提现单的当前状态,接口文档参考:查询二级商户按日终余额预约提现状态

1package withdraw
2
3import (
4    "demo/wxpay_utility" // 引用微信支付工具库,参考:https://pay.weixin.qq.com/doc/v3/partner/4015119446
5    "errors"
6    "fmt"
7)
8
9// NewMchConfig 创建商户配置
10func NewMchConfig() (*wxpay_utility.MchConfig, error) {
11    return wxpay_utility.CreateMchConfig(
12        "19xxxxxxxx",                 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340
13        "1DDE55AD98Exxxxxxxxxx",      // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924
14        "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
15        "PUB_KEY_ID_xxxxxxxxxxxxx",   // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589
16        "/path/to/wxp_pub.pem",       // 微信支付公钥文件路径,本地文件路径
17    )
18}
19
20// HandlePlatQueryDayEndBalanceWithdraw 使用查询二级商户按日终余额预约提现状态接口:查询二级商户按日终余额预约提现状态,参考:https://pay.weixin.qq.com/doc/v3/partner/4013328163
21func HandlePlatQueryDayEndBalanceWithdraw(config *wxpay_utility.MchConfig) error {
22    request := &PlatQueryDayEndBalanceWithdrawRequest{
23        // 二级商户号,收付通平台二级商户号,由微信支付生成并下发
24        SubMchid: wxpay_utility.String("1900000109"),
25        // 商户提现单号,由商户自定义生成,必须是字母数字
26        OutRequestNo: wxpay_utility.String("T20240611222222222200"),
27    }
28
29    resp, err := PlatQueryDayEndBalanceWithdraw(config, request)
30    if err != nil {
31        return handleError(err)
32    }
33
34    return handleStatus(resp)
35}
36
37func handleError(err error) error {
38    var apiError *wxpay_utility.ApiException
39    if errors.As(err, &apiError) {
40        fmt.Printf("状态码: %d\n", apiError.StatusCode())
41        fmt.Printf("错误码: %s\n", apiError.ErrorCode())
42        fmt.Printf("错误信息: %s\n", apiError.ErrorMessage())
43        switch apiError.ErrorCode() {
44        case "PARAM_ERROR":
45            // 错误:PARAM_ERROR
46            // 描述:参数错误
47            // 解决方式:请根据错误提示正确传入参数
48        case "INVALID_REQUEST":
49            // 错误:INVALID_REQUEST
50            // 描述:二级商户未开启预约提现权限 / HTTP请求不符合微信支付APIv3接口规则
51            // 解决方式:请确认电商平台商户号和二级商户商户号是否存在受理关系
52        case "SIGN_ERROR":
53            // 错误:SIGN_ERROR
54            // 描述:验证不通过
55            // 解决方式:请参阅签名常见问题
56        case "ACCOUNT_ERROR":
57            // 错误:ACCOUNT_ERROR
58            // 描述:二级商户未绑卡
59            // 解决方式:二级商户号没有绑定结算银行卡,绑定后重试
60        case "ACCOUNT_NOT_VERIFIED":
61            // 错误:ACCOUNT_NOT_VERIFIED
62            // 描述:二级商户下行打款未成功
63            // 解决方式:二级商户号结算银行卡信息有误,修改后重试
64        case "CONTRACT_NOT_CONFIRMED":
65            // 错误:CONTRACT_NOT_CONFIRMED
66            // 描述:二级商户未开启预约提现权限
67            // 解决方式:二级商户号提现权限已关闭,无法发起提现
68        case "NO_AUTH":
69            // 错误:NO_AUTH
70            // 描述:无接口使用权限
71            // 解决方式:请开通商户号相关权限
72        case "NOT_ENOUGH":
73            // 错误:NOT_ENOUGH
74            // 描述:二级商户号账户可用余额不足
75            // 解决方式:二级商户号账户可用余额不足
76        case "REQUEST_BLOCKED":
77            // 错误:REQUEST_BLOCKED
78            // 描述:二级商户未开启预约提现权限
79            // 解决方式:二级商户号预约提现权限被冻结,无法发起预约提现
80        case "ORDER_NOT_EXIST":
81            // 错误:ORDER_NOT_EXIST
82            // 描述:预约提现单号不存在
83            // 解决方式:请检查预约提现单号是否正确
84        case "FREQUENCY_LIMITED":
85            // 错误:FREQUENCY_LIMITED
86            // 描述:频率限制
87            // 解决方式:请降低频率后重试
88        case "SYSTEM_ERROR":
89            // 错误:SYSTEM_ERROR
90            // 描述:系统错误
91            // 解决方式:系统异常,请使用相同参数稍后重新调用
92        default:
93            // 其他类型错误
94        }
95    }
96    return err
97}
98
99func handleStatus(resp *WithdrawEntity) error {
100    if resp.Status == nil {
101        return fmt.Errorf("status is nil")
102    }
103    switch *resp.Status {
104    case WITHDRAWENTITYSTATUS_CREATED:
105        // 已创建,该状态下请使用原单(所有请求参数保持不变)发起重试提现
106    case WITHDRAWENTITYSTATUS_PROCESSING:
107        // 处理中,等待银行返回结果,最终结果请重试查询
108    case WITHDRAWENTITYSTATUS_FINISHED:
109        // 已完成,向银行付款完成且收到银行反馈为成功,但不代表银行入账一定成功,部分情况下可能发生银行退票
110    case WITHDRAWENTITYSTATUS_ABNORMAL:
111        // 提现异常,存在向银行付款失败或银行退票情况
112    default:
113        return fmt.Errorf("unknown status: %s", *resp.Status)
114    }
115    if resp.WithdrawId == nil {
116        return fmt.Errorf("withdraw_id is nil")
117    }
118    return nil
119}

2.2 下载异常文件

按日下载提现异常文件

收付通平台通过「查询预约提现状态」接口或「商户提现状态变更通知」接口返回的结果显示异常(提现失败、银行退票)时可调用「按日下载提现异常文件」接口,通过该接口按日查询并下载提现状态为异常的提现单,提现异常包括提现失败和银行退票。每日09:00开始可以下载前一日的提现异常文件,支持下载90天内提现状态变为提现异常的提现单文件。接口文档参考:按日下载提现异常文件

1package withdraw
2
3import (
4    "bytes"
5    "demo/wxpay_utility" // 引用微信支付工具库,参考:https://pay.weixin.qq.com/doc/v3/partner/4015119446
6    "errors"
7    "fmt"
8    "io"
9    "net/http"
10    "net/url"
11    "os"
12)
13
14// NewMchConfig 创建商户配置
15func NewMchConfig() (*wxpay_utility.MchConfig, error) {
16    return wxpay_utility.CreateMchConfig(
17        "19xxxxxxxx",                 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340
18        "1DDE55AD98Exxxxxxxxxx",      // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924
19        "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
20        "PUB_KEY_ID_xxxxxxxxxxxxx",   // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589
21        "/path/to/wxp_pub.pem",       // 微信支付公钥文件路径,本地文件路径
22    )
23}
24
25// HandleQueryWithdrawBill 使用按日下载提现异常文件接口:按日下载提现异常文件,参考:https://pay.weixin.qq.com/doc/v3/partner/4012476678
26func HandleQueryWithdrawBill(config *wxpay_utility.MchConfig) error {
27    request := &QueryWithdrawBillRequest{
28        // 账单日期,格式YYYY-MM-DD,建议输入昨日日期
29        BillDate: wxpay_utility.String("2019-08-17"),
30        // 账单类型,NO_SUCC:包括提现失败和提现退票账单
31        BillType: WITHDRAWBILLTYPE_NO_SUCC.Ptr(),
32        // 压缩格式,GZIP:返回格式为.gzip的压缩包账单
33        TarType: TARTYPE_GZIP.Ptr(),
34    }
35
36    resp, err := QueryWithdrawBill(config, request)
37    if err != nil {
38        return handleError(err)
39    }
40
41    return handleStatus(config, resp)
42}
43
44func handleError(err error) error {
45    var apiError *wxpay_utility.ApiException
46    if errors.As(err, &apiError) {
47        fmt.Printf("状态码: %d\n", apiError.StatusCode())
48        fmt.Printf("错误码: %s\n", apiError.ErrorCode())
49        fmt.Printf("错误信息: %s\n", apiError.ErrorMessage())
50        switch apiError.ErrorCode() {
51        case "PARAM_ERROR":
52            // 错误:PARAM_ERROR
53            // 描述:参数错误
54            // 解决方式:请根据错误提示正确传入参数
55        case "INVALID_REQUEST":
56            // 错误:INVALID_REQUEST
57            // 描述:请求的账单日期已过期 / HTTP请求不符合微信支付APIv3接口规则
58            // 解决方式:请检查bill_date,并重新调用
59        case "SIGN_ERROR":
60            // 错误:SIGN_ERROR
61            // 描述:验证不通过
62            // 解决方式:请参阅签名常见问题
63        case "NO_STATEMENT_EXIST":
64            // 错误:NO_STATEMENT_EXIST
65            // 描述:请求的账单文件不存在
66            // 解决方式:请检查当前商户号请求的微信支付账户在指定日期是否有资金操作
67        case "STATEMENT_CREATING":
68            // 错误:STATEMENT_CREATING
69            // 描述:请求的账单正在生成中
70            // 解决方式:请先检查当前商户号的微信支付账户在指定日期内是否有资金操作,若有,则在T+1日上午10点后再重新下载
71        case "NO_AUTH":
72            // 错误:NO_AUTH
73            // 描述:当前商户号没有使用该接口的权限
74            // 解决方式:请确认是否已经开通相关权限
75        case "SYSTEM_ERROR":
76            // 错误:SYSTEM_ERROR
77            // 描述:系统错误
78            // 解决方式:系统异常,请使用相同参数稍后重新调用
79        default:
80            // 其他类型错误
81        }
82    }
83    return err
84}
85
86func handleStatus(config *wxpay_utility.MchConfig, resp *QueryBillEntity) error {
87    if resp.DownloadUrl == nil {
88        return fmt.Errorf("download_url is nil")
89    }
90    if resp.HashValue == nil {
91        return fmt.Errorf("hash_value is nil")
92    }
93
94    // 使用 download_url 下载文件
95    data, err := downloadBillFile(config, *resp.DownloadUrl)
96    if err != nil {
97        return fmt.Errorf("下载账单文件失败: %w", err)
98    }
99
100    // 根据 hash_type 和 hash_value 校验文件内容
101    if err := verifyBillFileHash(data, resp.HashType, *resp.HashValue); err != nil {
102        return fmt.Errorf("账单文件校验失败: %w", err)
103    }
104
105    // 校验通过,将文件保存到本地(按需修改保存路径)
106    outputPath := "withdraw_bill.csv.gz"
107    if err := os.WriteFile(outputPath, data, 0644); err != nil {
108        return fmt.Errorf("保存账单文件失败: %w", err)
109    }
110    fmt.Printf("账单文件已保存到: %s\n", outputPath)
111
112    return nil
113}
114
115// downloadBillFile 通过 download_url 下载账单文件
116//
117// 下载账单需要进行二次签名:以V3接口规则对 download_url 生成签名信息,
118// 并通过 Authorization 请求头携带。响应头中不包含微信支付签名,跳过验签流程。
119// 参考:https://pay.weixin.qq.com/doc/v3/partner/4013080597
120func downloadBillFile(config *wxpay_utility.MchConfig, downloadUrl string) ([]byte, error) {
121    // 解析 download_url,提取用于签名的 path + query 部分
122    parsedURL, err := url.Parse(downloadUrl)
123    if err != nil {
124        return nil, fmt.Errorf("解析 download_url 失败: %w", err)
125    }
126    canonicalURL := parsedURL.RequestURI()
127
128    // 以V3接口规则生成签名(GET 请求 body 为空)
129    authorization, err := wxpay_utility.BuildAuthorization(
130        config.MchId(),
131        config.CertificateSerialNo(),
132        config.PrivateKey(),
133        http.MethodGet,
134        canonicalURL,
135        nil,
136    )
137    if err != nil {
138        return nil, fmt.Errorf("生成签名失败: %w", err)
139    }
140
141    httpReq, err := http.NewRequest(http.MethodGet, downloadUrl, nil)
142    if err != nil {
143        return nil, fmt.Errorf("构建HTTP请求失败: %w", err)
144    }
145    httpReq.Header.Set("Accept", "application/json")
146    httpReq.Header.Set("Authorization", authorization)
147    httpReq.Header.Set(wxpay_utility.WechatPaySerial, config.WechatPayPublicKeyId())
148
149    httpResp, err := http.DefaultClient.Do(httpReq)
150    if err != nil {
151        return nil, fmt.Errorf("HTTP请求失败: %w", err)
152    }
153    defer httpResp.Body.Close()
154
155    data, err := io.ReadAll(httpResp.Body)
156    if err != nil {
157        return nil, fmt.Errorf("读取响应体失败: %w", err)
158    }
159
160    if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
161        return nil, wxpay_utility.NewApiException(httpResp.StatusCode, httpResp.Header, data)
162    }
163    return data, nil
164}
165
166// verifyBillFileHash 根据 hash_type 校验文件内容的哈希值
167func verifyBillFileHash(data []byte, hashType *WithDrawBillHashType, expectedHash string) error {
168    var actualHash string
169    var err error
170
171    ht := WITHDRAWBILLHASHTYPE_SHA1
172    if hashType != nil {
173        ht = *hashType
174    }
175
176    switch ht {
177    case WITHDRAWBILLHASHTYPE_SHA1:
178        actualHash, err = wxpay_utility.GenerateSHA1FromStream(bytes.NewReader(data))
179    default:
180        return fmt.Errorf("不支持的哈希类型: %s", ht)
181    }
182    if err != nil {
183        return fmt.Errorf("计算哈希值失败: %w", err)
184    }
185
186    if actualHash != expectedHash {
187        return fmt.Errorf("哈希值不匹配: 期望 %s, 实际 %s", expectedHash, actualHash)
188    }
189    fmt.Println("文件哈希校验通过")
190    return nil
191}

2.3 回调通知处理

商户提现状态变更通知

收付通平台在调用预约提现API时通过 notify_url 参数设置回调地址,当提现状态发生变更(提现成功、失败、退票、关单)时,微信支付会通过 POST 请求向该地址发送回调通知;商户系统需在5秒内完成验签并应答,应当先应答成功(返回 200/204)再处理后续业务逻辑(推荐异步处理),以避免应答超时。回调文档参考:商户提现状态变更通知

1package notify
2
3import (
4    "demo/wxpay_utility" // 引用微信支付工具库,参考:https://pay.weixin.qq.com/doc/v3/partner/4015119446
5    "encoding/json"
6    "fmt"
7    "io"
8    "net/http"
9)
10
11// WithdrawNotify 商户提现状态变更通知内容,参考:https://pay.weixin.qq.com/doc/v3/partner/4013049135
12// resource 中 ciphertext 解密后字段,覆盖收付通平台预约提现、二级商户预约提现、平台商户给二级商户按日终余额预约提现三种场景:
13//   - 平台预约提现:对应「平台查询预约提现状态」的返回 body
14//   - 二级商户预约提现:对应「二级商户查询预约提现状态」的返回 body
15//   - 平台商户给二级商户按日终余额预约提现:对应「平台商户查询二级商户的按日终余额预约提现状态」的返回 body
16type WithdrawNotify struct {
17    // 收付通平台商户号(仅二级商户、按日终余额预约提现场景返回)
18    SpMchid string `json:"sp_mchid,omitempty"`
19    // 二级商户号(仅二级商户、按日终余额预约提现场景返回)
20    SubMchid string `json:"sub_mchid,omitempty"`
21    // 预约提现单状态
22    // 普通预约提现状态:INIT-业务单已创建,CREATE_SUCCESS-受理成功,SUCCESS-提现成功,FAIL-提现失败,REFUND-提现退票,CLOSE-关单
23    // 按日终余额预约提现状态:CREATED-已创建,PROCESSING-处理中,FINISHED-已完成,ABNORMAL-提现异常
24    Status string `json:"status"`
25    // 微信支付预约提现单号
26    WithdrawId string `json:"withdraw_id"`
27    // 商户预约提现单号
28    OutRequestNo string `json:"out_request_no"`
29    // 提现金额,单位为「分」(普通预约提现场景)
30    Amount int64 `json:"amount,omitempty"`
31    // 提现总金额,单位为「分」(按日终余额预约提现场景)
32    TotalAmount int64 `json:"total_amount,omitempty"`
33    // 提现成功金额,单位为「分」(按日终余额预约提现场景)
34    SuccessAmount int64 `json:"success_amount,omitempty"`
35    // 提现失败金额,单位为「分」(按日终余额预约提现场景)
36    FailAmount int64 `json:"fail_amount,omitempty"`
37    // 提现退票金额,单位为「分」(按日终余额预约提现场景)
38    RefundAmount int64 `json:"refund_amount,omitempty"`
39    // 单据创建时间,遵循 rfc3339 标准格式
40    CreateTime string `json:"create_time"`
41    // 最后一次状态变更时间,遵循 rfc3339 标准格式
42    UpdateTime string `json:"update_time"`
43    // 提现失败/异常原因
44    Reason string `json:"reason,omitempty"`
45    // 提现备注
46    Remark string `json:"remark,omitempty"`
47    // 银行附言
48    BankMemo string `json:"bank_memo,omitempty"`
49    // 出款账户类型:BASIC-基本账户,FEES-手续费账户,OPERATION-运营账户
50    AccountType string `json:"account_type,omitempty"`
51    // 异常解决方案(平台预约提现场景)
52    Solution string `json:"solution,omitempty"`
53    // 出款账号(仅银行卡尾号)
54    AccountNumber string `json:"account_number,omitempty"`
55    // 出款账户银行名
56    AccountBank string `json:"account_bank,omitempty"`
57    // 出款账户开户行
58    BankName string `json:"bank_name,omitempty"`
59}
60
61// HandleWithdrawNotify 处理商户提现状态变更通知,参考:https://pay.weixin.qq.com/doc/v3/partner/4013049135
62// 当商户提现单据状态发生变更时(提现成功、失败、退票、关单),微信支付会通过此回调通知商户。
63func HandleWithdrawNotify(config *wxpay_utility.MchConfig, request *http.Request) error {
64    // 商户APIv3密钥,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4012081991
65    const apiv3Key = "your apiv3 key"
66
67    // 读取微信支付通知的请求体
68    notifyBody, err := io.ReadAll(request.Body) // 微信支付通知的原始字符串
69    if err != nil {
70        fmt.Println(err)
71        return err
72    }
73    headers := &request.Header
74    notification, err := wxpay_utility.ParseNotification(
75        config.WechatPayPublicKeyId(),
76        config.WechatPayPublicKey(),
77        apiv3Key,
78        headers,
79        notifyBody,
80    )
81    if err != nil {
82        fmt.Println(err)
83        return err
84    }
85
86    // 解析商户提现回调内容
87    withdrawNotify := &WithdrawNotify{}
88    if err = json.Unmarshal([]byte(notification.Plaintext), withdrawNotify); err != nil {
89        fmt.Println(err)
90        return err
91    }
92
93    // 处理商户提现的事件和单据状态
94    switch notification.EventType {
95    case "MCHWITHDRAW.CHANGE":
96        // 商户提现状态变更通知,处理单据状态
97        switch withdrawNotify.Status {
98        case "CREATE_SUCCESS":
99            // 受理成功,等待银行返回最终结果,请重试查询或接入商户回调等待后续状态通知
100            return nil
101        case "SUCCESS":
102            // 提现成功,但不代表银行入账一定成功,部分情况下可能发生银行退票
103            return nil
104        case "FAIL":
105            // 提现失败,请根据 reason 字段确认失败原因后再决定是否重新发起
106            return nil
107        case "REFUND":
108            // 提现退票,请检查登记的账户信息是否有误,退票后资金会自动退回发起时的商户账户内
109            return nil
110        case "CLOSE":
111            // 关单,提现单已被关闭
112            return nil
113
114        case "PROCESSING":
115            // 按日终余额预约提现处理中,请等待回调通知或主动查询结果
116            return nil
117        case "FINISHED":
118            // 按日终余额预约提现已完成
119            return nil
120        case "ABNORMAL":
121            // 按日终余额预约提现异常(包含提现失败/银行退票),请根据 reason 字段确认异常原因
122            return nil
123        default:
124            // 状态非法,报错返回
125            return fmt.Errorf("unknown status: %s", withdrawNotify.Status)
126        }
127    default:
128        // 不关心的事件类型,打印日志后忽略
129        fmt.Printf("unknown event type: %s, ignore\n", notification.EventType)
130        return nil
131    }
132}
133
134// WriteSuccessResponse 给微信支付回调返回成功应答
135// 验签通过后,返回HTTP状态码200,无需返回应答报文,参考:https://pay.weixin.qq.com/doc/v3/partner/4013049135
136func WriteSuccessResponse(w http.ResponseWriter) {
137    w.WriteHeader(http.StatusOK)
138}
139
140// WriteFailResponse 给微信支付回调返回失败应答
141// 验签不通过时,返回HTTP状态码4XX/5XX,并返回JSON格式的错误信息,参考:https://pay.weixin.qq.com/doc/v3/partner/4013049135
142func WriteFailResponse(w http.ResponseWriter, code string, message string) {
143    w.Header().Set("Content-Type", "application/json")
144    w.WriteHeader(http.StatusBadRequest)
145    resp := map[string]string{
146        "code":    code,
147        "message": message,
148    }
149    jsonBytes, err := json.Marshal(resp)
150    if err != nil {
151        fmt.Println(err)
152        return
153    }
154    if _, err = w.Write(jsonBytes); err != nil {
155        fmt.Println(err)
156    }
157}
158
159// WithdrawNotifyHandler 商户提现状态变更通知HTTP入口,参考:https://pay.weixin.qq.com/doc/v3/partner/4013049135
160// 应当先应答成功(返回200/204)再处理后续业务逻辑,推荐异步处理,以避免应答超时
161func WithdrawNotifyHandler(config *wxpay_utility.MchConfig) http.HandlerFunc {
162    return func(w http.ResponseWriter, r *http.Request) {
163        if err := HandleWithdrawNotify(config, r); err != nil {
164            WriteFailResponse(w, "FAIL", "处理通知失败")
165            return
166        }
167
168        // 先应答成功,再异步处理后续业务逻辑
169        WriteSuccessResponse(w)
170    }
171}