请求补差
更新时间:2024.11.04服务商下单的时候带上补差标识,微信订单支付成功并结算完成后,发起分账前,调用这个接口进行补差。
电商平台下单时传入补差金额,详见【合单下单API】文档中补差字段说明。
确保账户余额充足。
补差金额需要和下单的时候的补差金额保持一致(当订单发生用户退款时,该金额可以小于下单填写的补差金额)。
该接口支持重入,请求参数相同只会扣款一次,重入有效期180天。
系统异常(如返回SYSTEM_ERROR),请使用相同参数稍后重新调用,请务必用原参数来重入此接口,如更换金额重试,可能会导致重复扣款,系统会在1天后回退到原账户。
一笔订单只能补差一笔。
接口说明
支持商户:【普通服务商】
请求方式:【POST】/v3/ecommerce/subsidies/create
请求域名:【主域名】https://api.mch.weixin.qq.com 使用该域名将访问就近的接入点
【备域名】https://api2.mch.weixin.qq.com 使用该域名将访问异地的接入点 ,指引点击查看
请求参数
Header HTTP头参数
Authorization 必填 string
请参考签名认证生成认证信息
Accept 必填 string
请设置为application/json
Content-Type 必填 string
请设置为application/json
body 包体参数
sub_mchid 必填 string(32)
【电商平台二级商户号】补差的电商平台二级商户,填写微信支付分配的商户号
transaction_id 必填 string(64)
【微信订单号】微信支付订单号
amount 必填 integer
【补差金额】补差金额,单位为分,只能为整数,不能超过下单时候的最大补差金额
description 必填 string(80)
【补差描述】补差备注描述,查询的时候原样带回来
out_subsidy_no 选填 string
【商户补差单号】商户系统内部的补差单号,在商户系统内部唯一,同一补差单号多次请求等同一次。只能是数字、大小写字母_-|*@
请求示例
POST
1curl -X POST \ 2 https://api.mch.weixin.qq.com/v3/ecommerce/subsidies/create \ 3 -H "Authorization: WECHATPAY2-SHA256-RSA2048 mchid=\"1900000001\",..." \ 4 -H "Accept: application/json" \ 5 -H "Content-Type: application/json" \ 6 -d '{ 7 "sub_mchid" : "1900000109", 8 "transaction_id" : "4208450740201411110007820472", 9 "amount" : 10, 10 "description" : "测试备注", 11 "out_subsidy_no" : "P20150806125347" 12 }' 13
需配合微信支付工具库 WXPayUtility 使用,请参考Java
1package com.java.demo; 2 3import com.java.utils.WXPayUtility; // 引用微信支付工具库,参考:https://pay.weixin.qq.com/doc/v3/partner/4014985777 4 5import com.google.gson.annotations.SerializedName; 6import com.google.gson.annotations.Expose; 7import okhttp3.MediaType; 8import okhttp3.OkHttpClient; 9import okhttp3.Request; 10import okhttp3.RequestBody; 11import okhttp3.Response; 12 13import java.io.IOException; 14import java.io.UncheckedIOException; 15import java.security.PrivateKey; 16import java.security.PublicKey; 17import java.util.ArrayList; 18import java.util.HashMap; 19import java.util.List; 20import java.util.Map; 21 22/** 23 * 请求补差 24 */ 25public class CreateSubsidy { 26 private static String HOST = "https://api.mch.weixin.qq.com"; 27 private static String METHOD = "POST"; 28 private static String PATH = "/v3/ecommerce/subsidies/create"; 29 30 public static void main(String[] args) { 31 // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 32 CreateSubsidy client = new CreateSubsidy( 33 "19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 34 "1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 35 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 36 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 37 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 38 ); 39 40 SubsidiesCreateRequest request = new SubsidiesCreateRequest(); 41 request.subMchid = "1900000109"; 42 request.transactionId = "4208450740201411110007820472"; 43 request.amount = 10L; 44 request.description = "测试备注"; 45 request.outSubsidyNo = "P20150806125347"; 46 try { 47 SubsidiesCreateEntity response = client.run(request); 48 // TODO: 请求成功,继续业务逻辑 49 System.out.println(response); 50 } catch (WXPayUtility.ApiException e) { 51 // TODO: 请求失败,根据状态码执行不同的逻辑 52 e.printStackTrace(); 53 } 54 } 55 56 public SubsidiesCreateEntity run(SubsidiesCreateRequest request) { 57 String uri = PATH; 58 String reqBody = WXPayUtility.toJson(request); 59 60 Request.Builder reqBuilder = new Request.Builder().url(HOST + uri); 61 reqBuilder.addHeader("Accept", "application/json"); 62 reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId); 63 reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo,privateKey, METHOD, uri, reqBody)); 64 reqBuilder.addHeader("Content-Type", "application/json"); 65 RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody); 66 reqBuilder.method(METHOD, requestBody); 67 Request httpRequest = reqBuilder.build(); 68 69 // 发送HTTP请求 70 OkHttpClient client = new OkHttpClient.Builder().build(); 71 try (Response httpResponse = client.newCall(httpRequest).execute()) { 72 String respBody = WXPayUtility.extractBody(httpResponse); 73 if (httpResponse.code() >= 200 && httpResponse.code() < 300) { 74 // 2XX 成功,验证应答签名 75 WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey, 76 httpResponse.headers(), respBody); 77 78 // 从HTTP应答报文构建返回数据 79 return WXPayUtility.fromJson(respBody, SubsidiesCreateEntity.class); 80 } else { 81 throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers()); 82 } 83 } catch (IOException e) { 84 throw new UncheckedIOException("Sending request to " + uri + " failed.", e); 85 } 86 } 87 88 private final String mchid; 89 private final String certificateSerialNo; 90 private final PrivateKey privateKey; 91 private final String wechatPayPublicKeyId; 92 private final PublicKey wechatPayPublicKey; 93 94 public CreateSubsidy(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) { 95 this.mchid = mchid; 96 this.certificateSerialNo = certificateSerialNo; 97 this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath); 98 this.wechatPayPublicKeyId = wechatPayPublicKeyId; 99 this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath); 100 } 101 102 public static class SubsidiesCreateRequest { 103 @SerializedName("sub_mchid") 104 public String subMchid; 105 106 @SerializedName("transaction_id") 107 public String transactionId; 108 109 @SerializedName("amount") 110 public Long amount; 111 112 @SerializedName("description") 113 public String description; 114 115 @SerializedName("out_subsidy_no") 116 public String outSubsidyNo; 117 } 118 119 public static class SubsidiesCreateEntity { 120 @SerializedName("sub_mchid") 121 public String subMchid; 122 123 @SerializedName("transaction_id") 124 public String transactionId; 125 126 @SerializedName("subsidy_id") 127 public String subsidyId; 128 129 @SerializedName("description") 130 public String description; 131 132 @SerializedName("amount") 133 public Long amount; 134 135 @SerializedName("result") 136 public CreateStatus result; 137 138 @SerializedName("success_time") 139 public String successTime; 140 141 @SerializedName("out_subsidy_no") 142 public String outSubsidyNo; 143 } 144 145 public enum CreateStatus { 146 @SerializedName("SUCCESS") 147 SUCCESS, 148 @SerializedName("FAIL") 149 FAIL, 150 @SerializedName("REFUND") 151 REFUND 152 } 153 154} 155
需配合微信支付工具库 wxpay_utility 使用,请参考Go
1package main 2 3import ( 4 "bytes" 5 "demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/partner/4015119446 6 "encoding/json" 7 "fmt" 8 "net/http" 9 "net/url" 10) 11 12func main() { 13 // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 14 config, err := wxpay_utility.CreateMchConfig( 15 "19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 16 "1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924 17 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 18 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 19 "/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径 20 ) 21 if err != nil { 22 fmt.Println(err) 23 return 24 } 25 26 request := &SubsidiesCreateRequest{ 27 SubMchid: wxpay_utility.String("1900000109"), 28 TransactionId: wxpay_utility.String("4208450740201411110007820472"), 29 Amount: wxpay_utility.Int64(10), 30 Description: wxpay_utility.String("测试备注"), 31 OutSubsidyNo: wxpay_utility.String("P20150806125347"), 32 } 33 34 response, err := CreateSubsidy(config, request) 35 if err != nil { 36 fmt.Printf("请求失败: %+v\n", err) 37 // TODO: 请求失败,根据状态码执行不同的处理 38 return 39 } 40 41 // TODO: 请求成功,继续业务逻辑 42 fmt.Printf("请求成功: %+v\n", response) 43} 44 45func CreateSubsidy(config *wxpay_utility.MchConfig, request *SubsidiesCreateRequest) (response *SubsidiesCreateEntity, err error) { 46 const ( 47 host = "https://api.mch.weixin.qq.com" 48 method = "POST" 49 path = "/v3/ecommerce/subsidies/create" 50 ) 51 52 reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path)) 53 if err != nil { 54 return nil, err 55 } 56 reqBody, err := json.Marshal(request) 57 if err != nil { 58 return nil, err 59 } 60 httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody)) 61 if err != nil { 62 return nil, err 63 } 64 httpRequest.Header.Set("Accept", "application/json") 65 httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId()) 66 httpRequest.Header.Set("Content-Type", "application/json") 67 authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), reqBody) 68 if err != nil { 69 return nil, err 70 } 71 httpRequest.Header.Set("Authorization", authorization) 72 73 client := &http.Client{} 74 httpResponse, err := client.Do(httpRequest) 75 if err != nil { 76 return nil, err 77 } 78 respBody, err := wxpay_utility.ExtractResponseBody(httpResponse) 79 if err != nil { 80 return nil, err 81 } 82 if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 { 83 // 2XX 成功,验证应答签名 84 err = wxpay_utility.ValidateResponse( 85 config.WechatPayPublicKeyId(), 86 config.WechatPayPublicKey(), 87 &httpResponse.Header, 88 respBody, 89 ) 90 if err != nil { 91 return nil, err 92 } 93 response := &SubsidiesCreateEntity{} 94 if err := json.Unmarshal(respBody, response); err != nil { 95 return nil, err 96 } 97 98 return response, nil 99 } else { 100 return nil, wxpay_utility.NewApiException( 101 httpResponse.StatusCode, 102 httpResponse.Header, 103 respBody, 104 ) 105 } 106} 107 108type SubsidiesCreateRequest struct { 109 SubMchid *string `json:"sub_mchid,omitempty"` 110 TransactionId *string `json:"transaction_id,omitempty"` 111 Amount *int64 `json:"amount,omitempty"` 112 Description *string `json:"description,omitempty"` 113 OutSubsidyNo *string `json:"out_subsidy_no,omitempty"` 114} 115 116type SubsidiesCreateEntity struct { 117 SubMchid *string `json:"sub_mchid,omitempty"` 118 TransactionId *string `json:"transaction_id,omitempty"` 119 SubsidyId *string `json:"subsidy_id,omitempty"` 120 Description *string `json:"description,omitempty"` 121 Amount *int64 `json:"amount,omitempty"` 122 Result *CreateStatus `json:"result,omitempty"` 123 SuccessTime *string `json:"success_time,omitempty"` 124 OutSubsidyNo *string `json:"out_subsidy_no,omitempty"` 125} 126 127type CreateStatus string 128 129func (e CreateStatus) Ptr() *CreateStatus { 130 return &e 131} 132 133const ( 134 CREATESTATUS_SUCCESS CreateStatus = "SUCCESS" 135 CREATESTATUS_FAIL CreateStatus = "FAIL" 136 CREATESTATUS_REFUND CreateStatus = "REFUND" 137) 138
应答参数
|
sub_mchid 必填 string
【电商平台二级商户号】补差的电商平台二级商户,填写微信支付分配的商户号
transaction_id 必填 string
【微信订单号】微信支付订单号
subsidy_id 必填 string(64)
【微信补差单号】微信补差单号,微信系统返回的唯一标识
description 必填 string
【补差描述】补差描述
amount 选填 integer
【补差金额】补差金额
result 必填 string
【补差单结果】补差单结果
可选取值:
SUCCESS
: 补差成功
FAIL
: 补差失败
REFUND
: 全额回退补差
success_time 必填 string
【补差完成时间】补差完成时间,遵循RFC3339标准格式
out_subsidy_no 选填 string
【商户补差单号】商户系统内部的补差单号,在商户系统内部唯一,同一补差单号多次请求等同一次。只能是数字、大小写字母_-|*@
应答示例
200 OK
1{ 2 "sub_mchid" : "1900000109", 3 "transaction_id" : "4208450740201411110007820472", 4 "subsidy_id" : "3008450740201411110007820472", 5 "description" : "满减补差活动", 6 "amount" : 10, 7 "result" : "SUCCESS", 8 "success_time" : "2015-05-20T13:29:35.120+08:00", 9 "out_subsidy_no" : "P20150806125347" 10} 11
错误码
公共错误码
状态码 | 错误码 | 描述 | 解决方案 |
---|---|---|---|
400 | PARAM_ERROR | 参数错误 | 请根据错误提示正确传入参数 |
400 | INVALID_REQUEST | HTTP 请求不符合微信支付 APIv3 接口规则 | 请参阅 接口规则 |
401 | SIGN_ERROR | 验证不通过 | 请参阅 签名常见问题 |
500 | SYSTEM_ERROR | 系统异常,请稍后重试 | 请稍后重试 |
业务错误码
状态码 | 错误码 | 描述 | 解决方案 |
---|---|---|---|
429 | FREQUENCY_LIMITED | 频率限制 | 请降低频率后重试 |