Skip to content

feat: 支持微信支付服务商电子发票 - #4070

Merged
binarywang merged 6 commits into
developfrom
codex/partner-invoice-clean
Jul 18, 2026
Merged

binarywang merged 6 commits into
developfrom
codex/partner-invoice-clean

Conversation

@binarywang

@binarywang binarywang commented Jul 18, 2026 •

Copy link
Copy Markdown
Owner

内容

实现微信支付 V3 服务商电子发票 API:

  • 服务商邀请、邀请商户查询与子商户能力状态
  • 卡券模板、开发配置、用户抬头链接及查询
  • 通用/不动产租赁/成品油开票、查询、冲红、下载信息、文件上传及插卡

Closes #4066。

同时对应 Gitee Issue IDMETW(其文档链接为同一服务商电子发票产品)。

验证

  • mvn -pl weixin-java-pay -am -DskipTests compile
  • TestNG:PartnerInvoiceServiceImplTest 6/6、GeneralInvoiceRequestTest 1/1

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

该 PR 在 weixin-java-pay 模块新增了微信支付 V3「服务商电子发票」能力的 SDK 封装,补齐服务商侧邀请/能力状态、模板与开发配置、抬头链接与查询、开票/查询/冲红、文件上传与下载、插卡等相关接口,并增加了基础的单元测试与调研对比文档,用于响应 Issue #4066 的需求。

Changes:

  • 新增 PartnerInvoiceService 及其实现 PartnerInvoiceServiceImpl,并将其挂载到 WxPayService/BaseWxPayServiceImpl。
  • 新增服务商电子发票相关的请求/响应 Bean(invoice 包)以及文件上传请求构造扩展(WechatPayUploadHttpPost)。
  • 新增接口调用与序列化字段名的单元测试,并补充公众号旧体系与支付 V3 体系的对比说明文档。

Reviewed changes

Copilot reviewed 27 out of 27 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/PartnerInvoiceServiceImplTest.java 覆盖服务商电子发票核心接口的 URL/请求体拼装行为测试
weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/invoice/GeneralInvoiceRequestTest.java 验证开票请求对象序列化字段名与微信支付字段一致
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/WechatPayUploadHttpPost.java 扩展支持电子发票文件上传的自定义 meta multipart 构造
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java 在顶层服务接口新增服务商电子发票 service 获取入口
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PartnerInvoiceService.java 新增服务商电子发票 API 服务接口定义
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PartnerInvoiceServiceImpl.java 新增服务商电子发票 API 的具体 v3 调用实现
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java 将 PartnerInvoiceServiceImpl 注入/暴露到 WxPayService 实现中
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/invoice/TitleUrlResult.java 新增“抬头填写小程序跳转信息”响应对象
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/invoice/TitleUrlRequest.java 新增“获取用户抬头填写链接”请求对象
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/invoice/SubMerchantInvoiceStatus.java 新增子商户电子发票能力状态响应对象
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/invoice/ReverseInvoiceRequest.java 新增冲红电子发票请求对象
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/invoice/InvoiceResult.java 新增查询电子发票响应对象
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/invoice/InvoiceFileUploadResult.java 新增上传电子发票文件响应对象
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/invoice/InvoiceFileUploadRequest.java 新增上传电子发票 PDF 文件请求对象
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/invoice/InvoiceFileResult.java 新增发票文件下载信息响应对象
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/invoice/InviteUrlResult.java 新增服务商邀请链接响应对象
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/invoice/InviteMerchantResult.java 新增邀请商户查询响应对象
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/invoice/InviteMerchantQuery.java 新增邀请商户查询条件对象
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/invoice/InsertCardRequest.java 新增“插卡到用户卡包”请求对象
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/invoice/IndustryInvoiceRequest.java 新增行业开票请求对象(不动产租赁/成品油)
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/invoice/GeneralInvoiceRequest.java 新增通用行业开票请求对象
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/invoice/DevelopmentConfigResult.java 新增开发配置响应对象
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/invoice/DevelopmentConfigRequest.java 新增开发配置请求对象
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/invoice/CardTemplateResult.java 新增卡券模板创建响应对象
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/invoice/CardTemplateRequest.java 新增卡券模板创建请求对象
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/invoice/BuyerInformation.java 新增购买方抬头信息对象
docs/ELECTRONIC_INVOICE_API_COMPARISON.md 补充公众号旧体系与支付 V3 服务商电子发票体系对比说明

@augmentcode

augmentcode Bot commented Jul 18, 2026

Copy link
Copy Markdown
🤖 Augment PR Summary

Summary: 本 PR 在 weixin-java-pay 模块新增微信支付 V3「服务商电子发票(new-tax-control-fapiao)」能力封装,覆盖邀约、抬头、开票、冲红、文件上传/下载等核心接口。

Changes:

  • 新增 PartnerInvoiceService 及 PartnerInvoiceServiceImpl,对接多条 /v3/new-tax-control-fapiao/* 端点
  • 新增电子发票相关请求/响应 Bean:邀请、子商户能力状态、卡券模板、开发配置、用户抬头、开票/冲红、文件上传/下载等
  • 在 WxPayService / BaseWxPayServiceImpl 中暴露 getPartnerInvoiceService()
  • 扩展 WechatPayUploadHttpPost:支持电子发票文件上传所需的自定义 meta 签名与 multipart 表单构造
  • 补充 TestNG 单测:验证通用开票请求序列化字段名、以及部分接口 URL/请求体拼装
  • 新增对比文档,澄清公众号旧电子发票接口与支付 V3 服务商电子发票体系的差异

🤖 Was this summary useful? React with 👍 or 👎

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review completed. 3 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7fd8effade

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@binarywang

Copy link
Copy Markdown
Owner Author

已处理本轮评审意见:

  • 所有 path/query 参数改为 UTF-8 URL 编码;必填参数为空时立即失败,避免发送 null。
  • getUserTitleUrl 补齐 seller_name 与手机号/邮箱开关等可选参数。
  • 复用公共 BuyerInformation,移除通用开票请求中的重复定义。
  • JSON 序列化统一使用 WxGsonBuilder.create()。
  • 上传文件 MIME 类型按扩展名推断,支持 OFD 等类型。
  • digest_alogrithm 保持不变:这是微信支付该接口官方文档及示例规定的字段拼写,已增加注释以避免误改。

验证:PartnerInvoiceServiceImplTest 7/7 通过,模块 Maven 构建通过。

@binarywang

Copy link
Copy Markdown
Owner Author

补充修复已推送:892c86fe7。

  • Apache HttpClient 与 HttpComponents 实现均接受 HTTP 202,支持电子发票异步受理成功。
  • 开发配置请求改为 PATCH。
  • 抬头结果新增 title_url。
  • 冲红信息补充 fapiao_code;子商户数电状态补充 ability_info。
  • 新增 InviteUrlRequest,支持完整邀请查询参数。

验证:模块构建成功,PartnerInvoiceServiceImplTest 7/7、GeneralInvoiceRequestTest 1/1 通过。

@binarywang
binarywang merged commit 2d496ed into develop Jul 18, 2026
1 check passed
@binarywang
binarywang deleted the codex/partner-invoice-clean branch July 18, 2026 12:54
@binarywang binarywang added this to the 4.8.5 milestone Jul 23, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor

代码审查(事后复审,PR 已合并)

结论:整体分层、命名和注册方式(WxPayService#getPartnerInvoiceService)与现有服务商能力一致,字段的 @SerializedName 映射也有测试覆盖。但含敏感字段的开票主流程目前无法走通(缺 Wechatpay-Serial),另有若干可选参数被当作必填、弱类型建模和 URL 编码问题,建议后续补丁跟进。

P1-1 敏感字段场景缺少 Wechatpay-Serial,含加密字段的开票请求无法成功

BuyerInformation 的 Javadoc 明确写明 phone、email 需由调用方按支付文档加密,但 PartnerInvoiceServiceImpl 的所有请求都走 payService.postV3(...) / getV3(...)。仓库中已有专用方法:

  • WxPayService#postV3WithWechatpaySerial(String, String):205
  • WxPayService#getV3WithWechatPaySerial(String):245

按微信支付 V3 规范,请求体含平台证书加密字段时必须带 Wechatpay-Serial 头,否则会被拒绝;开票(issueGeneralInvoice、行业发票)与获取用户抬头(getUserTitle,响应中的抬头字段为密文)都属于这一类。当前实现下:

  1. 调用方即使自行加密 phone/email,也没有任何途径让 SDK 带上该请求头;
  2. getUserTitle 直接把密文 BuyerInformation 返回,没有配套解密(可复用 RsaCryptoUtil)。

建议:涉及敏感字段的接口改用 postV3WithWechatpaySerial / getV3WithWechatPaySerial,并提供加解密辅助方法或在 Javadoc 中给出完整用法示例。

P2-2 可选查询参数被当成必填,且抛的是 IllegalArgumentException

PartnerInvoiceServiceImpl 中的 encode(Object) 在入参为 null 时直接 throw new IllegalArgumentException("微信支付接口必填参数不能为空"),而以下位置对可选参数也用了 query(...)(而非 appendQuery(...)):

  • listInviteMerchants:offset、limit、mch_invite_status 按官方文档为可选,未设置即抛异常;
  • getUserTitleUrl:source、appid、openid、total_amount 全部强制拼接,部分场景(如非 H5 来源)并不需要全部提供。

另外抛出的是 IllegalArgumentException,与本模块统一的 WxPayException 参数校验风格不一致(参见 BaseWxPayRequest#checkFields)。建议:必填项做显式校验并抛 WxPayException,可选项统一走 appendQuery。

P2-3 Object / List<Object> 弱类型建模

  • IndustryInvoiceRequest#fapiaoInformation 为 Object;
  • InsertCardRequest#fapiaoCardInformation 为 List<Object>。

这等于把接口契约交还给调用方:用户必须自己定义带 @SerializedName 的 Bean,没有编译期校验,也享受不到 SDK 的字段映射保证,与仓库中其他请求 Bean 的做法(完整建模)不一致。不动产租赁与成品油两个行业发票的字段结构差异不大,建议分别建模或抽公共父类。

P2-4 路径段使用 URLEncoder 编码

getInvoice、getInvoiceFileDownloadInfo、reverseInvoice、insertCards、getSubMerchantInvoiceStatus 都用 encode(...) 拼接 path 片段,而 URLEncoder.encode 是 application/x-www-form-urlencoded 规则:空格会变成 +,~、* 等字符的处理也与 RFC 3986 的 path 编码不同。V3 签名串包含 URL,路径编码与实际请求必须完全一致,一旦 ID 中出现特殊字符就可能验签失败。建议对 path 段使用严格的 percent-encoding(或明确约束 ID 字符集并在 Javadoc 说明)。

P3-5 @Expose(serialize = false) 实际不生效

ReverseInvoiceRequest#fapiaoApplyId 上的 @Expose(serialize = false) 只有在 Gson 开启 excludeFieldsWithoutExposeAnnotation() 时才有意义,而 WxGsonBuilder.create() 并未开启。真正起作用的是实现里的 body.remove("fapiao_apply_id")。这个注解会误导后续维护者(以为可以删掉手动 remove),建议移除。

P3-6 202 放宽是全局行为变更,且 Jodd 客户端下整个能力不可用

  • WxPayServiceApacheHttpImpl:141,213 与 WxPayServiceHttpComponentsImpl:138,210:postV3(String,String) 和 postV3(String,HttpPost) 现在把 202 Accepted 视为成功。这影响所有 V3 接口(此前 202 会抛异常),方向上合理,但属于跨接口的行为变更,建议在 PR 描述/发布说明中显式提及。
  • WxPayServiceJoddHttpImpl 的 V3 方法均为 return null(未实现)。在 Jodd 客户端下,GSON.fromJson(null, X.class) 会静默返回 null,issueGeneralInvoice 也会“无声成功”,比报错更难排查。这是既有限制而非本 PR 引入,但既然是新增能力,建议至少在 PartnerInvoiceService 的 Javadoc 中注明「仅支持 Apache HttpClient / HttpComponents」。

P3-7 uploadInvoiceFile 的异常签名与模块风格不一致

PartnerInvoiceService#uploadInvoiceFile 声明 throws WxPayException, IOException,其余方法只抛 WxPayException。建议把 FileInputStream 的 IO 异常包装成 WxPayException,避免把底层 IO 细节泄露到公共 API(该签名一旦发布就难以收回)。

文件流本身用 try-with-resources 正确释放,这点没问题。

P3-8 代码风格与 .editorconfig / 相邻代码不一致

  • 多个新增 Bean(BuyerInformation、CardTemplateResult、DevelopmentConfigRequest/Result、IndustryInvoiceRequest 等)package 声明后缺少空行,与同目录其他 Bean(CardTemplateRequest、GeneralInvoiceRequest)不一致;
  • PartnerInvoiceServiceImpl:29-36 中 java.* 与项目 import 交错(me.chanjar... 排在 java.io.UnsupportedEncodingException 之后);
  • PartnerInvoiceServiceImpl:45 用了全限定名 private static final com.google.gson.Gson GSON,而同文件已 import 其他 Gson 类型。

这些不影响功能,但建议在后续补丁中统一为模块现有风格。

P3-9 覆盖面建议

  • 未提供异步通知(开票/冲红结果回调)的解析 Bean,用户仍需自行解析;
  • docs/ELECTRONIC_INVOICE_API_COMPARISON.md 更像调研底稿,放在 docs/ 会随接口演进而失真,可考虑迁到 Wiki 或精简为使用指南;
  • 现有测试用 Proxy mock WxPayService 断言 URL 与 body,思路清晰;建议补充「可选参数缺省」「敏感字段加密」「文件上传 meta 与签名」的用例。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

服务商开电子发票功能是不是还没对接

2 participants