🆕 #4070【微信支付】新增 V3服务商电子发票能力封装,覆盖邀约、抬头、开票、冲红、文件上传/下载等核心接口
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
package com.github.binarywang.wxpay.bean.invoice;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* {@link GeneralInvoiceRequest} 单元测试。
|
||||
*/
|
||||
public class GeneralInvoiceRequestTest {
|
||||
|
||||
@Test
|
||||
public void shouldSerializeGeneralInvoiceRequestUsingWechatPayFieldNames() {
|
||||
GeneralInvoiceRequest request = new GeneralInvoiceRequest();
|
||||
request.setSubMchid("1900000109");
|
||||
request.setFapiaoApplyId("apply_20260718_001");
|
||||
|
||||
BuyerInformation buyer = new BuyerInformation();
|
||||
buyer.setType("ORGANIZATION");
|
||||
buyer.setName("示例企业");
|
||||
buyer.setTaxpayerId("91440300TEST00001");
|
||||
request.setBuyerInformation(buyer);
|
||||
|
||||
GeneralInvoiceRequest.InvoiceItem item = new GeneralInvoiceRequest.InvoiceItem();
|
||||
item.setTaxCode("3010101020203000000");
|
||||
item.setGoodsName("技术服务");
|
||||
item.setTotalAmount(100);
|
||||
item.setTaxRate(600);
|
||||
item.setDiscount(false);
|
||||
|
||||
GeneralInvoiceRequest.FapiaoInformation invoice = new GeneralInvoiceRequest.FapiaoInformation();
|
||||
invoice.setFapiaoId("invoice_20260718_001");
|
||||
invoice.setTotalAmount(100);
|
||||
invoice.setItems(Collections.singletonList(item));
|
||||
invoice.setBillingPersonId("billing_person_001");
|
||||
invoice.setFapiaoBillType("COMM_FAPIAO");
|
||||
|
||||
GeneralInvoiceRequest.TransactionInformation transaction = new GeneralInvoiceRequest.TransactionInformation();
|
||||
transaction.setPayChannel("WECHAT_PAY");
|
||||
transaction.setOutTradeNo("order_20260718_001");
|
||||
transaction.setAmount(100);
|
||||
invoice.setTransactionInformation(Collections.singletonList(transaction));
|
||||
request.setFapiaoInformation(invoice);
|
||||
|
||||
JsonObject json = new Gson().toJsonTree(request).getAsJsonObject();
|
||||
assertThat(json.get("sub_mchid").getAsString()).isEqualTo("1900000109");
|
||||
assertThat(json.get("fapiao_apply_id").getAsString()).isEqualTo("apply_20260718_001");
|
||||
assertThat(json.getAsJsonObject("buyer_information").get("taxpayer_id").getAsString())
|
||||
.isEqualTo("91440300TEST00001");
|
||||
assertThat(json.getAsJsonObject("fapiao_information").getAsJsonArray("items").get(0)
|
||||
.getAsJsonObject().get("tax_code").getAsString()).isEqualTo("3010101020203000000");
|
||||
assertThat(json.getAsJsonObject("fapiao_information").getAsJsonArray("transaction_information").get(0)
|
||||
.getAsJsonObject().get("out_trade_no").getAsString()).isEqualTo("order_20260718_001");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package com.github.binarywang.wxpay.service.impl;
|
||||
|
||||
import com.github.binarywang.wxpay.service.WxPayService;
|
||||
import com.github.binarywang.wxpay.bean.invoice.GeneralInvoiceRequest;
|
||||
import com.github.binarywang.wxpay.bean.invoice.InvoiceResult;
|
||||
import com.github.binarywang.wxpay.bean.invoice.ReverseInvoiceRequest;
|
||||
import com.github.binarywang.wxpay.bean.invoice.InvoiceFileResult;
|
||||
import com.github.binarywang.wxpay.bean.invoice.SubMerchantInvoiceStatus;
|
||||
import com.github.binarywang.wxpay.bean.invoice.TitleUrlRequest;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* {@link PartnerInvoiceServiceImpl} 测试。
|
||||
*/
|
||||
public class PartnerInvoiceServiceImplTest {
|
||||
|
||||
@Test
|
||||
public void shouldRequestInviteUrlWithOptionalSubMchId() throws Exception {
|
||||
AtomicReference<String> requestedUrl = new AtomicReference<>();
|
||||
WxPayService payService = (WxPayService) Proxy.newProxyInstance(
|
||||
getClass().getClassLoader(), new Class[]{WxPayService.class}, (proxy, method, args) -> {
|
||||
if ("getPayBaseUrl".equals(method.getName())) {
|
||||
return "https://api.mch.weixin.qq.com";
|
||||
}
|
||||
if ("getV3".equals(method.getName())) {
|
||||
requestedUrl.set((String) args[0]);
|
||||
return "{\"invite_url\":\"https://wxacurl.cn?xxx=\"}";
|
||||
}
|
||||
throw new UnsupportedOperationException(method.getName());
|
||||
});
|
||||
|
||||
PartnerInvoiceServiceImpl service = new PartnerInvoiceServiceImpl(payService);
|
||||
|
||||
Assert.assertEquals(service.getInviteUrl("19998278783").getInviteUrl(), "https://wxacurl.cn?xxx=");
|
||||
Assert.assertEquals(requestedUrl.get(),
|
||||
"https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/fapiaomerchant/getspinviteurl?sub_mchid=19998278783");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldPostGeneralInvoiceToV3Endpoint() throws Exception {
|
||||
AtomicReference<String> requestedUrl = new AtomicReference<>();
|
||||
AtomicReference<String> requestedBody = new AtomicReference<>();
|
||||
WxPayService payService = (WxPayService) Proxy.newProxyInstance(
|
||||
getClass().getClassLoader(), new Class[]{WxPayService.class}, (proxy, method, args) -> {
|
||||
if ("getPayBaseUrl".equals(method.getName())) {
|
||||
return "https://api.mch.weixin.qq.com";
|
||||
}
|
||||
if ("postV3".equals(method.getName())) {
|
||||
requestedUrl.set((String) args[0]);
|
||||
requestedBody.set((String) args[1]);
|
||||
return "";
|
||||
}
|
||||
throw new UnsupportedOperationException(method.getName());
|
||||
});
|
||||
GeneralInvoiceRequest request = new GeneralInvoiceRequest();
|
||||
request.setSubMchid("1900000109");
|
||||
request.setFapiaoApplyId("invoice-001");
|
||||
|
||||
new PartnerInvoiceServiceImpl(payService).issueGeneralInvoice(request);
|
||||
|
||||
Assert.assertEquals(requestedUrl.get(),
|
||||
"https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/fapiao-applications/issue-general");
|
||||
Assert.assertTrue(requestedBody.get().contains("\"fapiao_apply_id\":\"invoice-001\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldQueryInvoiceWithRequiredSubMchId() throws Exception {
|
||||
AtomicReference<String> requestedUrl = new AtomicReference<>();
|
||||
WxPayService payService = (WxPayService) Proxy.newProxyInstance(
|
||||
getClass().getClassLoader(), new Class[]{WxPayService.class}, (proxy, method, args) -> {
|
||||
if ("getPayBaseUrl".equals(method.getName())) {
|
||||
return "https://api.mch.weixin.qq.com";
|
||||
}
|
||||
if ("getV3".equals(method.getName())) {
|
||||
requestedUrl.set((String) args[0]);
|
||||
return "{\"total_count\":1,\"fapiao_information\":[{\"fapiao_id\":\"fapiao-001\",\"status\":\"ISSUED\"}]}";
|
||||
}
|
||||
throw new UnsupportedOperationException(method.getName());
|
||||
});
|
||||
|
||||
InvoiceResult result = new PartnerInvoiceServiceImpl(payService)
|
||||
.getInvoice("apply-001", "1900000109", "fapiao-001");
|
||||
|
||||
Assert.assertEquals(requestedUrl.get(), "https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/fapiao-applications/apply-001?sub_mchid=1900000109&fapiao_id=fapiao-001");
|
||||
Assert.assertEquals(result.getTotalCount(), Integer.valueOf(1));
|
||||
Assert.assertEquals(result.getFapiaoInformation().get(0).getStatus(), "ISSUED");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReverseInvoiceUsingPathApplyId() throws Exception {
|
||||
AtomicReference<String> requestedUrl = new AtomicReference<>();
|
||||
AtomicReference<String> requestedBody = new AtomicReference<>();
|
||||
WxPayService payService = (WxPayService) Proxy.newProxyInstance(
|
||||
getClass().getClassLoader(), new Class[]{WxPayService.class}, (proxy, method, args) -> {
|
||||
if ("getPayBaseUrl".equals(method.getName())) {
|
||||
return "https://api.mch.weixin.qq.com";
|
||||
}
|
||||
if ("postV3".equals(method.getName())) {
|
||||
requestedUrl.set((String) args[0]);
|
||||
requestedBody.set((String) args[1]);
|
||||
return "";
|
||||
}
|
||||
throw new UnsupportedOperationException(method.getName());
|
||||
});
|
||||
ReverseInvoiceRequest request = new ReverseInvoiceRequest();
|
||||
request.setFapiaoApplyId("apply-001");
|
||||
request.setSubMchid("1900000109");
|
||||
request.setReverseReason("SALES_RETURN");
|
||||
|
||||
new PartnerInvoiceServiceImpl(payService).reverseInvoice(request);
|
||||
|
||||
Assert.assertEquals(requestedUrl.get(),
|
||||
"https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/fapiao-applications/apply-001/reverse");
|
||||
Assert.assertTrue(requestedBody.get().contains("\"sub_mchid\":\"1900000109\""));
|
||||
Assert.assertFalse(requestedBody.get().contains("fapiao_apply_id"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetInvoiceFileDownloadInfo() throws Exception {
|
||||
AtomicReference<String> requestedUrl = new AtomicReference<>();
|
||||
WxPayService payService = (WxPayService) Proxy.newProxyInstance(
|
||||
getClass().getClassLoader(), new Class[]{WxPayService.class}, (proxy, method, args) -> {
|
||||
if ("getPayBaseUrl".equals(method.getName())) {
|
||||
return "https://api.mch.weixin.qq.com";
|
||||
}
|
||||
if ("getV3".equals(method.getName())) {
|
||||
requestedUrl.set((String) args[0]);
|
||||
return "{\"fapiao_download_info_list\":[{\"fapiao_id\":\"fapiao-001\",\"download_url\":\"https://download.example.com/file\",\"status\":\"ISSUED\"}]}";
|
||||
}
|
||||
throw new UnsupportedOperationException(method.getName());
|
||||
});
|
||||
|
||||
InvoiceFileResult result = new PartnerInvoiceServiceImpl(payService)
|
||||
.getInvoiceFileDownloadInfo("apply-001", "1900000109", "fapiao-001");
|
||||
|
||||
Assert.assertEquals(requestedUrl.get(), "https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/fapiao-applications/apply-001/fapiao-files?sub_mchid=1900000109&fapiao_id=fapiao-001");
|
||||
Assert.assertEquals(result.getFapiaoDownloadInfoList().get(0).getDownloadUrl(), "https://download.example.com/file");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCheckSubMerchantInvoiceStatus() throws Exception {
|
||||
AtomicReference<String> requestedUrl = new AtomicReference<>();
|
||||
WxPayService payService = (WxPayService) Proxy.newProxyInstance(
|
||||
getClass().getClassLoader(), new Class[]{WxPayService.class}, (proxy, method, args) -> {
|
||||
if ("getPayBaseUrl".equals(method.getName())) {
|
||||
return "https://api.mch.weixin.qq.com";
|
||||
}
|
||||
if ("getV3".equals(method.getName())) {
|
||||
requestedUrl.set((String) args[0]);
|
||||
return "{\"sub_mchid\":\"1900000109\",\"third_mode\":{\"status\":\"ENABLED\"}}";
|
||||
}
|
||||
throw new UnsupportedOperationException(method.getName());
|
||||
});
|
||||
|
||||
SubMerchantInvoiceStatus result = new PartnerInvoiceServiceImpl(payService).getSubMerchantInvoiceStatus("1900000109");
|
||||
|
||||
Assert.assertEquals(requestedUrl.get(), "https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/merchant/1900000109/check-status");
|
||||
Assert.assertEquals(result.getThirdMode().getStatus(), "ENABLED");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEncodeTitleUrlParametersAndIncludeOptionalFields() throws Exception {
|
||||
AtomicReference<String> requestedUrl = new AtomicReference<>();
|
||||
WxPayService payService = (WxPayService) Proxy.newProxyInstance(
|
||||
getClass().getClassLoader(), new Class[]{WxPayService.class}, (proxy, method, args) -> {
|
||||
if ("getPayBaseUrl".equals(method.getName())) {
|
||||
return "https://api.mch.weixin.qq.com";
|
||||
}
|
||||
if ("getV3".equals(method.getName())) {
|
||||
requestedUrl.set((String) args[0]);
|
||||
return "{}";
|
||||
}
|
||||
throw new UnsupportedOperationException(method.getName());
|
||||
});
|
||||
TitleUrlRequest request = new TitleUrlRequest();
|
||||
request.setSubMchid("1900000109");
|
||||
request.setFapiaoApplyId("apply|001");
|
||||
request.setSource("WEB");
|
||||
request.setAppid("wx123");
|
||||
request.setOpenid("openid");
|
||||
request.setTotalAmount(100);
|
||||
request.setSellerName("测试 商户");
|
||||
request.setShowPhoneCell(true);
|
||||
|
||||
new PartnerInvoiceServiceImpl(payService).getUserTitleUrl(request);
|
||||
|
||||
Assert.assertTrue(requestedUrl.get().contains("fapiao_apply_id=apply%7C001"));
|
||||
Assert.assertTrue(requestedUrl.get().contains("seller_name=%E6%B5%8B%E8%AF%95+%E5%95%86%E6%88%B7"));
|
||||
Assert.assertTrue(requestedUrl.get().contains("show_phone_cell=true"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user