业务示例代码
更新时间:2026.04.231. 目标
通过本教程的学习,你应该可以:
通过收付通平台预约提现接口,将收付通平台自身的收入进行预约提现
通过二级商户预约提现接口,帮助收付通平台二级商户发起账户预约提现申请,完成账户提现
通过二级商户按日终余额预约提现接口,按前一天日终余额为二级商户发起基本户提现
通过平台查询预约提现状态接口,查询收付通平台自身的预约提现单状态
通过二级商户查询预约提现状态接口,查询二级商户的预约提现单状态
通过查询二级商户按日终余额预约提现状态接口,查询按日终余额预约提现单的状态
通过按日下载提现异常文件接口,按日查询并下载提现状态为异常的提现单
通过接收商户提现状态变更通知回调,异步获知预约提现单据的状态变更(提现成功、失败、退票、关单)
了解预约提现单据的状态流转规则,正确处理各种状态和错误码
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}

