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
66 changes: 66 additions & 0 deletions docs/ELECTRONIC_INVOICE_API_COMPARISON.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# 服务商电子发票接口体系对比

## 结论

Gitee Issue `IDMETW` 补充的官方链接
[获取开通服务商电子发票能力邀请链接](https://pay.weixin.qq.com/doc/v3/partner/4015941495)
与 GitHub Issue [#4066](https://github.com/binarywang/WxJava/issues/4066) 给出的
[开发接入准备](https://pay.weixin.qq.com/doc/v3/partner/4015792554) 是**同一套**微信支付
V3 服务商电子发票产品文档:前者是 API 列表中的具体接口,后者是该产品的接入总览。

但是,当前 `weixin-java-mp` 的 `WxMpMerchantInvoiceService` 是另一套旧的公众号
`/card/invoice/*` 接口;不能把它当作这两个 Issue 所要求的支付 V3 能力实现。

## 当前仓库既有的公众号体系

- 当前代码将该能力归入公众号模块:
`weixin-java-mp` 的
`WxMpMerchantInvoiceService`。
- 该接口的 Javadoc 明确引用公众号官方文档:
[商户开票模式说明](https://developers.weixin.qq.com/doc/offiaccount/WeChat_Invoice/E_Invoice/Vendor_and_Invoicing_Platform_Mode_Instruction.html)
[商户开票接口列表](https://developers.weixin.qq.com/doc/offiaccount/WeChat_Invoice/E_Invoice/Vendor_API_List.html)。
后者当前重定向至
[新版服务号文档](https://developers.weixin.qq.com/doc/service/guide/product/weixin_invoice/E_Invoice/Vendor_API_List.html)。
- 代码实际请求公众号 API:`/card/invoice/getauthurl`、
`/card/invoice/getauthdata`、`/card/invoice/makeoutinvoice`、
`/card/invoice/clearoutinvoice`、`/card/invoice/queryinvoceinfo` 等;见
`WxMpApiUrl.Invoice`。这套接口围绕授权页、获取用户授权数据、开票、冲红和
查询发票信息。
- 历史提交 `058ce62a2b932633931e30762f44c561481dde5f` 将该服务及其请求/响应
Bean 加入 `weixin-java-mp`;提交说明关联 #1305。

> 说明:调研时 Gitee 页面/API 的 TLS 连接失败,未能独立读取 IDMETW 的评论;
> 但用户随后提供的 `4015941495` 链接已明确该 Issue 实际指向支付 V3 服务商电子发票。

## 两个 Issue 共同指向的支付 V3 体系

- #4066 链接的[开发接入准备](https://pay.weixin.qq.com/doc/v3/partner/4015792554)
属于
“微信电子发票”产品,要求在**微信支付服务商号**申请“服务商电子发票”权限,
并提到数电发票资源。
- IDMETW 链接的[获取开通服务商电子发票能力邀请链接](https://pay.weixin.qq.com/doc/v3/partner/4015941495)
位于相同产品的“API 列表”下,路径为
`GET /v3/new-tax-control-fapiao/fapiaomerchant/getspinviteurl`。
- 同一官方文档导航的 API 列表包含:邀请子商户开通、检查子商户开票状态、创建电子
发票卡券模板、配置开发选项、抬头填写链接/信息、各行业开票、冲红、查询、下载/上传
发票文件、插入用户卡包,以及多个异步通知。
- 例如“开具通用行业电子发票”接口为
`POST /v3/new-tax-control-fapiao/fapiao-applications/issue-general`,请求域名为
`https://api.mch.weixin.qq.com`,并要求微信支付 API 证书签名、服务商模式的
`sub_mchid` 和唯一开票申请单号 `fapiao_apply_id`;官方文档:
[开具通用行业电子发票](https://pay.weixin.qq.com/doc/v3/partner/4015792574)。
该页还规定敏感字段使用微信支付公钥或平台证书加密。

## 直接差异

| 维度 | 既有公众号能力 | 两个 Issue 指向的微信支付能力 |
| --- | --- | --- |
| SDK 模块 | `weixin-java-mp` | 应位于 `weixin-java-pay` |
| 官方文档产品线 | `developers.weixin.qq.com` 的公众号电子发票 | `pay.weixin.qq.com/doc/v3/partner` 的微信支付合作伙伴电子发票 |
| API 形态 | `/card/invoice/*` | `/v3/new-tax-control-fapiao/*` |
| 身份/鉴权上下文 | 公众号 access token、用户授权页/授权数据 | 微信支付服务商号、子商户号、V3 签名与敏感字段加密 |
| 覆盖范围 | 授权、开票、冲红、查询及公众号商户配置 | 子商户邀约/状态、模板/开发配置、行业开票、文件和卡包、通知 |

因此,两个 Issue 都是同一个微信支付 V3 服务商电子发票接入需求,当前代码库检索未
发现对应的 `new-tax-control-fapiao` API 实现。
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.github.binarywang.wxpay.bean.invoice;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import java.io.Serializable;
/** 电子发票购买方抬头信息。敏感字段 phone、email 由调用方按支付文档加密。 */
@Data public class BuyerInformation implements Serializable {
private static final long serialVersionUID = 1L;
private String type;
private String name;
@SerializedName("taxpayer_id") private String taxpayerId;
private String address;
private String telephone;
@SerializedName("bank_name") private String bankName;
@SerializedName("bank_account") private String bankAccount;
private String phone;
private String email;
private Integer amount;
@SerializedName("out_trade_no") private String outTradeNo;
@SerializedName("fapiao_bill_type") private String fapiaoBillType;
@SerializedName("user_apply_message") private String userApplyMessage;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.github.binarywang.wxpay.bean.invoice;

import com.google.gson.annotations.SerializedName;
import lombok.Data;
import java.io.Serializable;

/** 创建电子发票卡券模板请求。 */
@Data
public class CardTemplateRequest implements Serializable {
private static final long serialVersionUID = 1L;
@SerializedName("sub_mchid") private String subMchid;
@SerializedName("card_appid") private String cardAppid;
@SerializedName("card_template_information") private TemplateInformation cardTemplateInformation;
@Data public static class TemplateInformation implements Serializable {
private static final long serialVersionUID = 1L;
@SerializedName("payee_name") private String payeeName;
@SerializedName("logo_url") private String logoUrl;
@SerializedName("custom_cell") private CustomCell customCell;
}
@Data public static class CustomCell implements Serializable {
private static final long serialVersionUID = 1L;
private String words;
private String description;
@SerializedName("jump_url") private String jumpUrl;
@SerializedName("miniprogram_user_name") private String miniprogramUserName;
@SerializedName("miniprogram_path") private String miniprogramPath;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.github.binarywang.wxpay.bean.invoice;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import java.io.Serializable;
/** 电子发票卡券模板结果。 */
@Data public class CardTemplateResult implements Serializable {
private static final long serialVersionUID = 1L;
@SerializedName("card_appid") private String cardAppid;
@SerializedName("card_id") private String cardId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.github.binarywang.wxpay.bean.invoice;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import java.io.Serializable;
/** 服务商电子发票开发配置请求。 */
@Data public class DevelopmentConfigRequest implements Serializable {
private static final long serialVersionUID = 1L;
@SerializedName("callback_url") private String callbackUrl;
@SerializedName("sub_mch_code") private String subMchCode;
@SerializedName("show_fapiao_cell") private Boolean showFapiaoCell;
@SerializedName("support_vat_fapiao") private Boolean supportVatFapiao;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.github.binarywang.wxpay.bean.invoice;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import java.io.Serializable;
/** 服务商电子发票开发配置结果。 */
@Data public class DevelopmentConfigResult implements Serializable {
private static final long serialVersionUID = 1L;
@SerializedName("callback_url") private String callbackUrl;
@SerializedName("show_fapiao_cell") private Boolean showFapiaoCell;
@SerializedName("support_vat_fapiao") private Boolean supportVatFapiao;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.github.binarywang.wxpay.bean.invoice;

import com.google.gson.annotations.SerializedName;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

/**
* 服务商开具通用行业电子发票请求。
*
* @see <a href="https://pay.weixin.qq.com/doc/v3/partner/4015792574">开具通用行业电子发票</a>
*/
@Data
public class GeneralInvoiceRequest implements Serializable {
private static final long serialVersionUID = 1L;

@SerializedName("sub_mchid")
private String subMchid;
@SerializedName("fapiao_apply_id")
private String fapiaoApplyId;
@SerializedName("buyer_information")
private BuyerInformation buyerInformation;
@SerializedName("fapiao_information")
private FapiaoInformation fapiaoInformation;

@Data
public static class FapiaoInformation implements Serializable {
private static final long serialVersionUID = 1L;
@SerializedName("fapiao_id")
private String fapiaoId;
@SerializedName("total_amount")
private Integer totalAmount;
private List<InvoiceItem> items;
@SerializedName("export_business_policy_code")
private Integer exportBusinessPolicyCode;
@SerializedName("vat_refund_levy_code")
private Integer vatRefundLevyCode;
@SerializedName("billing_person_id")
private String billingPersonId;
@SerializedName("billing_person")
private String billingPerson;
@SerializedName("fapiao_bill_type")
private String fapiaoBillType;
@SerializedName("transaction_information")
private List<TransactionInformation> transactionInformation;
private String remark;
}

@Data
public static class InvoiceItem implements Serializable {
private static final long serialVersionUID = 1L;
@SerializedName("tax_code")
private String taxCode;
@SerializedName("goods_name")
private String goodsName;
private String specification;
private String unit;
private Integer quantity;
@SerializedName("total_amount")
private Integer totalAmount;
@SerializedName("tax_rate")
private Integer taxRate;
private Boolean discount;
@SerializedName("preferential_policy_code")
private Integer preferentialPolicyCode;
}

@Data
public static class TransactionInformation implements Serializable {
private static final long serialVersionUID = 1L;
@SerializedName("pay_channel")
private String payChannel;
@SerializedName("transaction_id")
private String transactionId;
@SerializedName("out_trade_no")
private String outTradeNo;
private Integer amount;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.github.binarywang.wxpay.bean.invoice;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import java.io.Serializable;
/** 行业电子发票请求;fapiaoInformation 对应不动产租赁或成品油官方请求对象。 */
@Data public class IndustryInvoiceRequest implements Serializable {
private static final long serialVersionUID = 1L;
@SerializedName("sub_mchid") private String subMchid;
@SerializedName("fapiao_apply_id") private String fapiaoApplyId;
@SerializedName("buyer_information") private BuyerInformation buyerInformation;
@SerializedName("fapiao_information") private Object fapiaoInformation;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.github.binarywang.wxpay.bean.invoice;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/** 将电子发票插入微信用户卡包请求。 */
@Data public class InsertCardRequest implements Serializable {
private static final long serialVersionUID = 1L;
@SerializedName("sub_mchid") private String subMchid;
private String scene;
@SerializedName("fapiao_apply_id") private String fapiaoApplyId;
@SerializedName("buyer_information") private BuyerInformation buyerInformation;
@SerializedName("fapiao_card_information") private List<Object> fapiaoCardInformation;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.github.binarywang.wxpay.bean.invoice;
import lombok.Data;
import java.io.Serializable;
/** 查询服务商邀请开通电子发票能力的商户条件。 */
@Data public class InviteMerchantQuery implements Serializable {
private static final long serialVersionUID = 1L;
private String queryTimeStart;
private String queryTimeEnd;
private Integer offset;
private Integer limit;
private String inviteCode;
private String mchInviteStatus;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.github.binarywang.wxpay.bean.invoice;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/** 服务商邀请商户查询结果。 */
@Data public class InviteMerchantResult implements Serializable {
private static final long serialVersionUID = 1L;
@SerializedName("total_count") private Integer totalCount;
private Integer offset;
private Integer limit;
@SerializedName("mch_invite_result_list") private List<Merchant> mchInviteResultList;
@Data public static class Merchant implements Serializable {
private static final long serialVersionUID = 1L;
@SerializedName("sub_mchid") private String subMchid;
@SerializedName("mch_invite_status") private String mchInviteStatus;
@SerializedName("ep_name") private String epName;
@SerializedName("tax_id") private String taxId;
@SerializedName("invite_code") private String inviteCode;
@SerializedName("operate_time") private String operateTime;
@SerializedName("invite_failed_code") private String inviteFailedCode;
@SerializedName("invite_failed_reason") private String inviteFailedReason;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.github.binarywang.wxpay.bean.invoice;

import lombok.Data;

import java.io.Serializable;
import java.util.List;

/** 获取服务商电子发票能力邀请链接请求。 */
@Data
public class InviteUrlRequest implements Serializable {
private static final long serialVersionUID = 1L;
private String subMchid;
private String operationType;
private String fapiaoMode;
private List<String> fapiaoAbilityTypeList;
private String inviteChannel;
private String operateUser;
private String inviteCode;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.github.binarywang.wxpay.bean.invoice;

import com.google.gson.annotations.SerializedName;
import lombok.Data;

import java.io.Serializable;

/**
* 服务商电子发票开通邀请链接。
*
* @author binarywang
*/
@Data
public class InviteUrlResult implements Serializable {
private static final long serialVersionUID = 1L;

@SerializedName("invite_url")
private String inviteUrl;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.github.binarywang.wxpay.bean.invoice;

import com.google.gson.annotations.SerializedName;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

/**
* 电子发票文件下载信息。
*
* @see <a href="https://pay.weixin.qq.com/doc/v3/partner/4015792576">获取发票下载信息</a>
*/
@Data
public class InvoiceFileResult implements Serializable {
private static final long serialVersionUID = 1L;

@SerializedName("fapiao_download_info_list")
private List<DownloadInfo> fapiaoDownloadInfoList;

@Data
public static class DownloadInfo implements Serializable {
private static final long serialVersionUID = 1L;

@SerializedName("fapiao_id")
private String fapiaoId;
@SerializedName("download_url")
private String downloadUrl;
private String status;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.github.binarywang.wxpay.bean.invoice;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import java.io.File;
import java.io.Serializable;
/** 上传电子发票 PDF 文件请求。digest 为官方要求的 SM3 十六进制摘要。 */
@Data public class InvoiceFileUploadRequest implements Serializable {
private static final long serialVersionUID = 1L;
private File file;
@SerializedName("sub_mchid") private String subMchid;
@SerializedName("file_type") private String fileType = "PDF";
@SerializedName("digest_alogrithm") private String digestAlgorithm = "SM3";
private String digest;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.github.binarywang.wxpay.bean.invoice;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import java.io.Serializable;
/** 上传电子发票文件结果。 */
@Data public class InvoiceFileUploadResult implements Serializable {
private static final long serialVersionUID = 1L;
@SerializedName("fapiao_media_id") private String fapiaoMediaId;
}
Loading