Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@ public String toString() {

/**
* <pre>
* 字段名:商家批次单号
* 变量名:out_batch_no
* 字段名:商户转账单号
* 变量名:out_bill_no
* 是否必填:是
* 类型:string[5,32]
* 描述:
* 商户系统内部的商家批次单号,在商户系统内部唯一。需要电子回单的批次单号
* 商户系统内部的商户转账单号,在商户系统内部唯一。兼容旧字段out_batch_no
* 示例值:plfk2020042013
* </pre>
*/
@SerializedName(value = "out_batch_no")
@SerializedName(value = "out_bill_no", alternate = {"out_batch_no"})
private String outBatchNo;
Comment on lines +41 to 42

/**
Expand All @@ -58,17 +58,18 @@ public String toString() {
/**
* <pre>
* 字段名:电子回单状态
* 变量名:signature_status
* 变量名:state
* 是否必填:否
* 类型:string[1,10]
* 描述:
* 枚举值:
* ACCEPTED:已受理,电子签章已受理成功
* FINISHED:已完成。电子签章已处理完成
* 兼容旧字段signature_status
* 示例值:ACCEPTED
* </pre>
*/
@SerializedName(value = "signature_status")
@SerializedName(value = "state", alternate = {"signature_status"})
private String signatureStatus;
Comment on lines +72 to 73

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
/**
* 转账电子回单申请受理API
* <pre>
* 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_1.shtml
* 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012716452
* </pre>
*
* @author xiaoqiang
Expand All @@ -21,15 +21,15 @@ public class ReceiptBillRequest implements Serializable {
private static final long serialVersionUID = 1L;
/**
* <pre>
* 字段名:商家批次单号
* 变量名:out_batch_no
* 字段名:商户转账单号
* 变量名:out_bill_no
* 是否必填:是
* 类型:string[5, 32]
* 描述:
* body商户系统内部的商家批次单号,在商户系统内部唯一。需要电子回单的批次单号
* body商户系统内部的商户转账单号,在商户系统内部唯一。兼容旧字段out_batch_no
* 示例值:plfk2020042013
* </pre>
*/
@SerializedName(value = "out_batch_no")
@SerializedName(value = "out_bill_no", alternate = {"out_batch_no"})
private String outBatchNo;
Comment on lines 31 to 34
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ public class ElectronicBillApplyRequest implements Serializable {
private static final long serialVersionUID = -2121536206019844928L;
/**
* <pre>
* 字段名:商家批次单号
* 变量名:out_batch_no
* 字段名:商户转账单号
* 变量名:out_bill_no
* 是否必填:是
* 类型:string[5,32]
* 描述:
* body商户系统内部的商家批次单号,在商户系统内部唯一。需要电子回单的批次单号
* body商户系统内部的商户转账单号,在商户系统内部唯一。兼容旧字段out_batch_no
* 示例值:plfk2020042013
* </pre>
*/
@SerializedName("out_batch_no")
@SerializedName(value = "out_bill_no", alternate = {"out_batch_no"})
private String outBatchNo;
Comment on lines 33 to 37
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@ public class ElectronicBillResult implements Serializable {
private static final long serialVersionUID = 7528245102572829190L;
/**
* <pre>
* 字段名:商家批次单号
* 变量名:out_batch_no
* 字段名:商户转账单号
* 变量名:out_bill_no
* 是否必填:是
* 类型:string[5,32]
* 描述:
* body商户系统内部的商家批次单号,在商户系统内部唯一。需要电子回单的批次单号
* body商户系统内部的商户转账单号,在商户系统内部唯一。兼容旧字段out_batch_no
* 示例值:plfk2020042013
* </pre>
*/
@SerializedName("out_batch_no")
@SerializedName(value = "out_bill_no", alternate = {"out_batch_no"})
private String outBatchNo;
Comment on lines +36 to 37

/**
Expand All @@ -53,17 +53,18 @@ public class ElectronicBillResult implements Serializable {
/**
* <pre>
* 字段名:电子回单状态
* 变量名:signature_status
* 变量名:state
* 是否必填:否
* 类型:string[1,10]
* 描述:
* 枚举值:
* ACCEPTED:已受理,电子签章已受理成功
* FINISHED:已完成。电子签章已处理完成
* 兼容旧字段signature_status
* 示例值:ACCEPTED
* </pre>
*/
@SerializedName("signature_status")
@SerializedName(value = "state", alternate = {"signature_status"})
private String signatureStatus;
Comment on lines +67 to 68

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ public interface MerchantTransferService {
* 转账电子回单申请受理API
* <p>
* 适用对象:直连商户
* 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_3_7.shtml
* 请求URL:https://api.mch.weixin.qq.com/v3/transfer/bill-receipt
* 文档详见: https://pay.weixin.qq.com/doc/v3/merchant/4012716452
* 请求URL:https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/elecsign/out-bill-no
* 请求方式:POST
* 接口限频: 单个商户 20QPS,如果超过频率限制,会报错FREQUENCY_LIMITED,请降低频率请求。
*
Expand All @@ -106,15 +106,15 @@ public interface MerchantTransferService {
* 查询转账电子回单API
* <p>
* 适用对象:直连商户
* 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_3_8.shtml
* 请求URL:https://api.mch.weixin.qq.com/v3/transfer/bill-receipt/{out_batch_no}
* 文档详见: https://pay.weixin.qq.com/doc/v3/merchant/4012716436
* 请求URL:https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/elecsign/out-bill-no/{out_bill_no}
* 请求方式:GET
*
* @param outBatchNo the out batch no
* @param outBillNo 商户转账单号
* @return electronic bill result
* @throws WxPayException the wx pay exception
*/
ElectronicBillResult queryElectronicBill(String outBatchNo) throws WxPayException;
ElectronicBillResult queryElectronicBill(String outBillNo) throws WxPayException;

/**
* 转账明细电子回单受理API
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,11 @@ public interface PartnerTransferService {
* 转账电子回单申请受理API
* 接口说明
* 适用对象:直连商户 服务商
* 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_1.shtml
* 请求URL:https://api.mch.weixin.qq.com/v3/transfer/bill-receipt
* 文档详见: https://pay.weixin.qq.com/doc/v3/merchant/4012716452
* 请求URL:https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/elecsign/out-bill-no
* 请求方式:POST
*
* @param request 商家批次单号
* @param request 商户转账单号
* @return 返回数据 fund balance result
* @throws WxPayException the wx pay exception
*/
Expand All @@ -114,15 +114,15 @@ public interface PartnerTransferService {
* 查询转账电子回单API
* 接口说明
* 适用对象:直连商户 服务商
* 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_2.shtml
* 请求URL:https://api.mch.weixin.qq.com/v3/transfer/bill-receipt/{out_batch_no}
* 文档详见: https://pay.weixin.qq.com/doc/v3/merchant/4012716436
* 请求URL:https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/elecsign/out-bill-no/{out_bill_no}
* 请求方式:GET
*
* @param outBatchNo 商家批次单号
* @param outBillNo 商户转账单号
* @return 返回数据 fund balance result
* @throws WxPayException the wx pay exception
*/
BillReceiptResult queryBillReceipt(String outBatchNo) throws WxPayException;
BillReceiptResult queryBillReceipt(String outBillNo) throws WxPayException;

/**
* 转账明细电子回单受理API
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,15 @@ public DetailsQueryResult queryMerchantDetails(MerchantDetailsQueryRequest reque

@Override
public ElectronicBillResult applyElectronicBill(ElectronicBillApplyRequest request) throws WxPayException {
String url = String.format("%s/v3/transfer/bill-receipt", this.wxPayService.getPayBaseUrl());
String url = String.format("%s/v3/fund-app/mch-transfer/elecsign/out-bill-no", this.wxPayService.getPayBaseUrl());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve batch-transfer receipt endpoint

When this service is used for the existing batch-transfer APIs, applyElectronicBill still receives an outBatchNo from ElectronicBillApplyRequest, but this line now sends it to the new single-transfer out_bill_no endpoint. The current WeChat docs keep batch receipt applications on /v3/transfer/bill-receipt with out_batch_no (https://pay.wechatpay.cn/doc/v3/merchant/4012458946), while the new endpoint is for ordinary-merchant user-confirm transfer bills and requires a transfer bill number (https://pay.wechatpay.cn/doc/v3/merchant/4012716452). Existing callers applying/querying receipts for batch transfers will therefore get parameter/not-found errors instead of their batch receipt.

Useful? React with 👍 / 👎.

String response = wxPayService.postV3(url, GSON.toJson(request));
return GSON.fromJson(response, ElectronicBillResult.class);
}

@Override
public ElectronicBillResult queryElectronicBill(String outBatchNo) throws WxPayException {
String url = String.format("%s/v3/transfer/bill-receipt/%s", this.wxPayService.getPayBaseUrl(), outBatchNo);
public ElectronicBillResult queryElectronicBill(String outBillNo) throws WxPayException {
String url = String.format("%s/v3/fund-app/mch-transfer/elecsign/out-bill-no/%s",
this.wxPayService.getPayBaseUrl(), outBillNo);
String response = wxPayService.getV3(url);
Comment on lines +101 to 104
return GSON.fromJson(response, ElectronicBillResult.class);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,17 +186,17 @@ public BatchDetailsResult queryBatchDetailByMch(String outBatchNo, String outDet
* 转账电子回单申请受理API
* 接口说明
* 适用对象:直连商户 服务商
* 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_1.shtml
* 请求URL:https://api.mch.weixin.qq.com/v3/transfer/bill-receipt
* 文档详见: https://pay.weixin.qq.com/doc/v3/merchant/4012716452
* 请求URL:https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/elecsign/out-bill-no
* 请求方式:POST
*
* @param request 商家批次单号
* @param request 商户转账单号
* @return 返回数据 fund balance result
* @throws WxPayException the wx pay exception
*/
@Override
public BillReceiptResult receiptBill(ReceiptBillRequest request) throws WxPayException {
String url = String.format("%s/v3/transfer/bill-receipt", this.payService.getPayBaseUrl());
String url = String.format("%s/v3/fund-app/mch-transfer/elecsign/out-bill-no", this.payService.getPayBaseUrl());
String response = this.payService.postV3(url, GSON.toJson(request));
return GSON.fromJson(response, BillReceiptResult.class);
}
Expand All @@ -206,17 +206,18 @@ public BillReceiptResult receiptBill(ReceiptBillRequest request) throws WxPayExc
* 查询转账电子回单API
* 接口说明
* 适用对象:直连商户 服务商
* 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transfer/chapter4_2.shtml
* 请求URL:https://api.mch.weixin.qq.com/v3/transfer/bill-receipt/{out_batch_no}
* 文档详见: https://pay.weixin.qq.com/doc/v3/merchant/4012716436
* 请求URL:https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/elecsign/out-bill-no/{out_bill_no}
* 请求方式:GET
*
* @param outBatchNo 商家批次单号
* @param outBillNo 商户转账单号
* @return 返回数据 fund balance result
* @throws WxPayException the wx pay exception
*/
@Override
public BillReceiptResult queryBillReceipt(String outBatchNo) throws WxPayException {
String url = String.format("%s/v3/transfer/bill-receipt/%s", this.payService.getPayBaseUrl(), outBatchNo);
public BillReceiptResult queryBillReceipt(String outBillNo) throws WxPayException {
String url = String.format("%s/v3/fund-app/mch-transfer/elecsign/out-bill-no/%s",
this.payService.getPayBaseUrl(), outBillNo);
String response = this.payService.getV3(url);
return GSON.fromJson(response, BillReceiptResult.class);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package com.github.binarywang.wxpay.service.impl;

import com.github.binarywang.wxpay.bean.marketing.transfer.BillReceiptResult;
import com.github.binarywang.wxpay.bean.marketing.transfer.ReceiptBillRequest;
import com.github.binarywang.wxpay.bean.merchanttransfer.ElectronicBillApplyRequest;
import com.github.binarywang.wxpay.bean.merchanttransfer.ElectronicBillResult;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.google.gson.Gson;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

@Test
public class TransferReceiptApiCompatibilityTest {

private static final String BASE_URL = "https://api.mch.weixin.qq.com";

/**
* 验证直连商户电子回单接口已切换到新版fund-app路径。
*/
public void shouldUseNewMerchantTransferElecsignApiPath() throws WxPayException {
RequestCaptureHandler handler = new RequestCaptureHandler();
WxPayService wxPayService = handler.createWxPayService();
MerchantTransferServiceImpl merchantTransferService = new MerchantTransferServiceImpl(wxPayService);

merchantTransferService.applyElectronicBill(new ElectronicBillApplyRequest().setOutBatchNo("plfk2020042013"));
Assert.assertEquals(handler.lastPostUrl, BASE_URL + "/v3/fund-app/mch-transfer/elecsign/out-bill-no");
Assert.assertTrue(handler.lastPostBody.contains("\"out_bill_no\""));
Assert.assertFalse(handler.lastPostBody.contains("\"out_batch_no\""));

merchantTransferService.queryElectronicBill("plfk2020042013");
Assert.assertEquals(handler.lastGetUrl, BASE_URL + "/v3/fund-app/mch-transfer/elecsign/out-bill-no/plfk2020042013");
}

/**
* 验证服务商电子回单接口已切换到新版fund-app路径。
*/
public void shouldUseNewPartnerTransferElecsignApiPath() throws WxPayException {
RequestCaptureHandler handler = new RequestCaptureHandler();
WxPayService wxPayService = handler.createWxPayService();
PartnerTransferServiceImpl partnerTransferService = new PartnerTransferServiceImpl(wxPayService);

ReceiptBillRequest request = new ReceiptBillRequest();
request.setOutBatchNo("plfk2020042013");
partnerTransferService.receiptBill(request);
Assert.assertEquals(handler.lastPostUrl, BASE_URL + "/v3/fund-app/mch-transfer/elecsign/out-bill-no");
Assert.assertTrue(handler.lastPostBody.contains("\"out_bill_no\""));
Assert.assertFalse(handler.lastPostBody.contains("\"out_batch_no\""));

partnerTransferService.queryBillReceipt("plfk2020042013");
Assert.assertEquals(handler.lastGetUrl, BASE_URL + "/v3/fund-app/mch-transfer/elecsign/out-bill-no/plfk2020042013");
}

/**
* 验证新版字段名能够正确反序列化到现有结果对象。
*/
public void shouldDeserializeNewResponseFieldNames() {
Gson gson = new Gson();
BillReceiptResult billReceiptResult =
gson.fromJson("{\"out_bill_no\":\"plfk2020042013\",\"state\":\"FINISHED\"}", BillReceiptResult.class);
Assert.assertEquals(billReceiptResult.getOutBatchNo(), "plfk2020042013");
Assert.assertEquals(billReceiptResult.getSignatureStatus(), "FINISHED");

ElectronicBillResult electronicBillResult =
gson.fromJson("{\"out_bill_no\":\"plfk2020042013\",\"state\":\"FINISHED\"}", ElectronicBillResult.class);
Assert.assertEquals(electronicBillResult.getOutBatchNo(), "plfk2020042013");
Assert.assertEquals(electronicBillResult.getSignatureStatus(), "FINISHED");
}

/**
* 通过动态代理拦截WxPayService请求并记录URL/请求体,便于断言接口路径和参数。
*/
private static class RequestCaptureHandler implements InvocationHandler {
private String lastPostUrl;
private String lastPostBody;
private String lastGetUrl;

private WxPayService createWxPayService() {
return (WxPayService) Proxy.newProxyInstance(
WxPayService.class.getClassLoader(),
new Class<?>[]{WxPayService.class},
this
);
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) {
if ("getPayBaseUrl".equals(method.getName())) {
return BASE_URL;
}
if ("postV3".equals(method.getName())) {
this.lastPostUrl = (String) args[0];
this.lastPostBody = (String) args[1];
return "{}";
}
if ("getV3".equals(method.getName())) {
this.lastGetUrl = (String) args[0];
return "{}";
}
if ("toString".equals(method.getName())) {
return "MockWxPayService";
}
Class<?> returnType = method.getReturnType();
if (boolean.class.equals(returnType)) {
return false;
}
if (int.class.equals(returnType)) {
return 0;
}
if (long.class.equals(returnType)) {
return 0L;
}
if (double.class.equals(returnType)) {
return 0D;
}
if (float.class.equals(returnType)) {
return 0F;
}
if (short.class.equals(returnType)) {
return (short) 0;
}
if (byte.class.equals(returnType)) {
return (byte) 0;
}
if (char.class.equals(returnType)) {
return (char) 0;
}
return null;
}
}
}