🆕 #4070【微信支付】新增 V3服务商电子发票能力封装,覆盖邀约、抬头、开票、冲红、文件上传/下载等核心接口
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
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/4015792567">查询电子发票</a>
|
||||
*/
|
||||
@Data
|
||||
public class InvoiceResult implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@SerializedName("total_count")
|
||||
private Integer totalCount;
|
||||
@SerializedName("fapiao_information")
|
||||
private List<InvoiceInformation> fapiaoInformation;
|
||||
|
||||
@Data
|
||||
public static class InvoiceInformation implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@SerializedName("fapiao_id")
|
||||
private String fapiaoId;
|
||||
private String status;
|
||||
@SerializedName("blue_fapiao")
|
||||
private Fapiao blueFapiao;
|
||||
@SerializedName("red_fapiao")
|
||||
private Fapiao redFapiao;
|
||||
@SerializedName("total_amount")
|
||||
private Integer totalAmount;
|
||||
@SerializedName("tax_amount")
|
||||
private Integer taxAmount;
|
||||
private Integer amount;
|
||||
@SerializedName("fapiao_error_code")
|
||||
private String fapiaoErrorCode;
|
||||
@SerializedName("fapiao_error_message")
|
||||
private String fapiaoErrorMessage;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Fapiao implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@SerializedName("fapiao_code")
|
||||
private String fapiaoCode;
|
||||
@SerializedName("fapiao_number")
|
||||
private String fapiaoNumber;
|
||||
@SerializedName("check_code")
|
||||
private String checkCode;
|
||||
private String password;
|
||||
@SerializedName("fapiao_time")
|
||||
private String fapiaoTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.github.binarywang.wxpay.bean.invoice;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
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/4015792575">冲红电子发票</a>
|
||||
*/
|
||||
@Data
|
||||
public class ReverseInvoiceRequest implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@SerializedName("sub_mchid")
|
||||
private String subMchid;
|
||||
@Expose(serialize = false)
|
||||
@SerializedName("fapiao_apply_id")
|
||||
private String fapiaoApplyId;
|
||||
@SerializedName("reverse_reason")
|
||||
private String reverseReason;
|
||||
@SerializedName("fapiao_information")
|
||||
private List<InvoiceInfo> fapiaoInformation;
|
||||
|
||||
@Data
|
||||
public static class InvoiceInfo implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@SerializedName("fapiao_id")
|
||||
private String fapiaoId;
|
||||
@SerializedName("fapiao_code")
|
||||
private String fapiaoCode;
|
||||
@SerializedName("fapiao_number")
|
||||
private String fapiaoNumber;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
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 SubMerchantInvoiceStatus implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@SerializedName("sub_mchid")
|
||||
private String subMchid;
|
||||
@SerializedName("third_mode")
|
||||
private Mode thirdMode;
|
||||
@SerializedName("digital_tax_mode")
|
||||
private DigitalTaxMode digitalTaxMode;
|
||||
|
||||
@Data
|
||||
public static class Mode implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String status;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class DigitalTaxMode extends Mode {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@SerializedName("billing_person_info")
|
||||
private List<BillingPerson> billingPersonInfo;
|
||||
@SerializedName("access_time")
|
||||
private String accessTime;
|
||||
@SerializedName("expired_time")
|
||||
private String expiredTime;
|
||||
@SerializedName("access_fail_reason")
|
||||
private String accessFailReason;
|
||||
@SerializedName("ability_info")
|
||||
private List<Ability> abilityInfo;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class BillingPerson implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String id;
|
||||
private String name;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Ability implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String type;
|
||||
private String status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.github.binarywang.wxpay.bean.invoice;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.Data;
|
||||
import java.io.Serializable;
|
||||
/** 获取用户抬头填写链接请求。 */
|
||||
@Data public class TitleUrlRequest implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@SerializedName("sub_mchid") private String subMchid;
|
||||
@SerializedName("fapiao_apply_id") private String fapiaoApplyId;
|
||||
private String source;
|
||||
private String appid;
|
||||
private String openid;
|
||||
@SerializedName("total_amount") private Integer totalAmount;
|
||||
@SerializedName("seller_name") private String sellerName;
|
||||
@SerializedName("show_phone_cell") private Boolean showPhoneCell;
|
||||
@SerializedName("must_input_phone") private Boolean mustInputPhone;
|
||||
@SerializedName("show_email_cell") private Boolean showEmailCell;
|
||||
@SerializedName("must_input_email") private Boolean mustInputEmail;
|
||||
}
|
||||
@@ -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 TitleUrlResult implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@SerializedName("title_url") private String titleUrl;
|
||||
@SerializedName("miniprogram_appid") private String miniprogramAppid;
|
||||
@SerializedName("miniprogram_path") private String miniprogramPath;
|
||||
@SerializedName("miniprogram_user_name") private String miniprogramUserName;
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.github.binarywang.wxpay.service;
|
||||
|
||||
import com.github.binarywang.wxpay.bean.invoice.InviteUrlResult;
|
||||
import com.github.binarywang.wxpay.bean.invoice.InviteUrlRequest;
|
||||
import com.github.binarywang.wxpay.bean.invoice.GeneralInvoiceRequest;
|
||||
import com.github.binarywang.wxpay.bean.invoice.InvoiceResult;
|
||||
import com.github.binarywang.wxpay.bean.invoice.InvoiceFileResult;
|
||||
import com.github.binarywang.wxpay.bean.invoice.ReverseInvoiceRequest;
|
||||
import com.github.binarywang.wxpay.bean.invoice.SubMerchantInvoiceStatus;
|
||||
import com.github.binarywang.wxpay.bean.invoice.CardTemplateRequest;
|
||||
import com.github.binarywang.wxpay.bean.invoice.CardTemplateResult;
|
||||
import com.github.binarywang.wxpay.bean.invoice.DevelopmentConfigRequest;
|
||||
import com.github.binarywang.wxpay.bean.invoice.DevelopmentConfigResult;
|
||||
import com.github.binarywang.wxpay.bean.invoice.TitleUrlRequest;
|
||||
import com.github.binarywang.wxpay.bean.invoice.TitleUrlResult;
|
||||
import com.github.binarywang.wxpay.bean.invoice.BuyerInformation;
|
||||
import com.github.binarywang.wxpay.bean.invoice.IndustryInvoiceRequest;
|
||||
import com.github.binarywang.wxpay.bean.invoice.InsertCardRequest;
|
||||
import com.github.binarywang.wxpay.bean.invoice.InviteMerchantQuery;
|
||||
import com.github.binarywang.wxpay.bean.invoice.InviteMerchantResult;
|
||||
import com.github.binarywang.wxpay.bean.invoice.InvoiceFileUploadRequest;
|
||||
import com.github.binarywang.wxpay.bean.invoice.InvoiceFileUploadResult;
|
||||
import java.io.IOException;
|
||||
import com.github.binarywang.wxpay.exception.WxPayException;
|
||||
|
||||
/**
|
||||
* 微信支付服务商电子发票 API。
|
||||
*
|
||||
* @author binarywang
|
||||
*/
|
||||
public interface PartnerInvoiceService {
|
||||
|
||||
/**
|
||||
* 获取开通服务商电子发票能力邀请链接。
|
||||
*
|
||||
* @param subMchId 可选,指定要邀请开通能力的子商户号
|
||||
* @return 邀请链接
|
||||
* @throws WxPayException 微信支付异常
|
||||
* @see <a href="https://pay.weixin.qq.com/doc/v3/partner/4015941495">官方文档</a>
|
||||
*/
|
||||
InviteUrlResult getInviteUrl(String subMchId) throws WxPayException;
|
||||
|
||||
InviteUrlResult getInviteUrl(InviteUrlRequest request) throws WxPayException;
|
||||
|
||||
/**
|
||||
* 开具通用行业电子发票。接口受理成功后,请通过查询电子发票接口获取处理结果。
|
||||
*
|
||||
* @param request 开票申请
|
||||
* @throws WxPayException 微信支付异常
|
||||
* @see <a href="https://pay.weixin.qq.com/doc/v3/partner/4015792574">官方文档</a>
|
||||
*/
|
||||
void issueGeneralInvoice(GeneralInvoiceRequest request) throws WxPayException;
|
||||
|
||||
/**
|
||||
* 查询电子发票。
|
||||
*
|
||||
* @param fapiaoApplyId 开票申请单号
|
||||
* @param subMchId 子商户号
|
||||
* @param fapiaoId 可选,商户发票单号
|
||||
* @return 发票信息
|
||||
* @throws WxPayException 微信支付异常
|
||||
*/
|
||||
InvoiceResult getInvoice(String fapiaoApplyId, String subMchId, String fapiaoId) throws WxPayException;
|
||||
|
||||
/**
|
||||
* 冲红电子发票。
|
||||
*
|
||||
* @param request 冲红申请
|
||||
* @throws WxPayException 微信支付异常
|
||||
*/
|
||||
void reverseInvoice(ReverseInvoiceRequest request) throws WxPayException;
|
||||
|
||||
/**
|
||||
* 获取发票文件下载信息。
|
||||
*
|
||||
* @param fapiaoApplyId 开票申请单号
|
||||
* @param subMchId 子商户号
|
||||
* @param fapiaoId 可选,商户发票单号
|
||||
* @return 发票文件下载信息
|
||||
* @throws WxPayException 微信支付异常
|
||||
*/
|
||||
InvoiceFileResult getInvoiceFileDownloadInfo(String fapiaoApplyId, String subMchId, String fapiaoId) throws WxPayException;
|
||||
|
||||
/**
|
||||
* 检查子商户开票功能状态。
|
||||
*
|
||||
* @param subMchId 子商户号
|
||||
* @return 子商户电子发票能力状态
|
||||
* @throws WxPayException 微信支付异常
|
||||
*/
|
||||
SubMerchantInvoiceStatus getSubMerchantInvoiceStatus(String subMchId) throws WxPayException;
|
||||
|
||||
CardTemplateResult createCardTemplate(CardTemplateRequest request) throws WxPayException;
|
||||
|
||||
DevelopmentConfigResult updateDevelopmentConfig(DevelopmentConfigRequest request) throws WxPayException;
|
||||
|
||||
TitleUrlResult getUserTitleUrl(TitleUrlRequest request) throws WxPayException;
|
||||
|
||||
BuyerInformation getUserTitle(String subMchId, String scene, String fapiaoApplyId) throws WxPayException;
|
||||
|
||||
void issueRealEstateLeasingInvoice(IndustryInvoiceRequest request) throws WxPayException;
|
||||
|
||||
void issueRefinedOilInvoice(IndustryInvoiceRequest request) throws WxPayException;
|
||||
|
||||
void insertCards(InsertCardRequest request) throws WxPayException;
|
||||
|
||||
InviteMerchantResult listInviteMerchants(InviteMerchantQuery query) throws WxPayException;
|
||||
|
||||
InvoiceFileUploadResult uploadInvoiceFile(InvoiceFileUploadRequest request) throws WxPayException, IOException;
|
||||
}
|
||||
@@ -408,6 +408,13 @@ public interface WxPayService {
|
||||
*/
|
||||
MerchantLimitationService getMerchantLimitationService();
|
||||
|
||||
/**
|
||||
* 获取服务商电子发票服务类。
|
||||
*
|
||||
* @return 服务商电子发票服务
|
||||
*/
|
||||
PartnerInvoiceService getPartnerInvoiceService();
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 查询订单.
|
||||
|
||||
@@ -148,6 +148,9 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
|
||||
@Getter
|
||||
private final MerchantLimitationService merchantLimitationService = new MerchantLimitationServiceImpl(this);
|
||||
|
||||
@Getter
|
||||
private final PartnerInvoiceService partnerInvoiceService = new PartnerInvoiceServiceImpl(this);
|
||||
|
||||
protected Map<String, WxPayConfig> configMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
package com.github.binarywang.wxpay.service.impl;
|
||||
|
||||
import com.github.binarywang.wxpay.bean.invoice.InviteUrlResult;
|
||||
import com.github.binarywang.wxpay.bean.invoice.InviteUrlRequest;
|
||||
import com.github.binarywang.wxpay.bean.invoice.GeneralInvoiceRequest;
|
||||
import com.github.binarywang.wxpay.bean.invoice.InvoiceResult;
|
||||
import com.github.binarywang.wxpay.bean.invoice.InvoiceFileResult;
|
||||
import com.github.binarywang.wxpay.bean.invoice.ReverseInvoiceRequest;
|
||||
import com.github.binarywang.wxpay.bean.invoice.SubMerchantInvoiceStatus;
|
||||
import com.github.binarywang.wxpay.bean.invoice.CardTemplateRequest;
|
||||
import com.github.binarywang.wxpay.bean.invoice.CardTemplateResult;
|
||||
import com.github.binarywang.wxpay.bean.invoice.DevelopmentConfigRequest;
|
||||
import com.github.binarywang.wxpay.bean.invoice.DevelopmentConfigResult;
|
||||
import com.github.binarywang.wxpay.bean.invoice.TitleUrlRequest;
|
||||
import com.github.binarywang.wxpay.bean.invoice.TitleUrlResult;
|
||||
import com.github.binarywang.wxpay.bean.invoice.BuyerInformation;
|
||||
import com.github.binarywang.wxpay.bean.invoice.IndustryInvoiceRequest;
|
||||
import com.github.binarywang.wxpay.bean.invoice.InsertCardRequest;
|
||||
import com.github.binarywang.wxpay.bean.invoice.InviteMerchantQuery;
|
||||
import com.github.binarywang.wxpay.bean.invoice.InviteMerchantResult;
|
||||
import com.github.binarywang.wxpay.bean.invoice.InvoiceFileUploadRequest;
|
||||
import com.github.binarywang.wxpay.bean.invoice.InvoiceFileUploadResult;
|
||||
import com.github.binarywang.wxpay.v3.WechatPayUploadHttpPost;
|
||||
import com.github.binarywang.wxpay.exception.WxPayException;
|
||||
import com.github.binarywang.wxpay.service.PartnerInvoiceService;
|
||||
import com.github.binarywang.wxpay.service.WxPayService;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
|
||||
|
||||
/**
|
||||
* 微信支付服务商电子发票 API 实现。
|
||||
*
|
||||
* @author binarywang
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class PartnerInvoiceServiceImpl implements PartnerInvoiceService {
|
||||
private static final com.google.gson.Gson GSON = WxGsonBuilder.create();
|
||||
private static final String INVITE_URL_PATH = "/v3/new-tax-control-fapiao/fapiaomerchant/getspinviteurl";
|
||||
private static final String ISSUE_GENERAL_PATH = "/v3/new-tax-control-fapiao/fapiao-applications/issue-general";
|
||||
private static final String FAPIAO_APPLICATIONS_PATH = "/v3/new-tax-control-fapiao/fapiao-applications/";
|
||||
|
||||
private final WxPayService payService;
|
||||
|
||||
@Override
|
||||
public InviteUrlResult getInviteUrl(String subMchId) throws WxPayException {
|
||||
InviteUrlRequest request = new InviteUrlRequest();
|
||||
request.setSubMchid(subMchId);
|
||||
return getInviteUrl(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InviteUrlResult getInviteUrl(InviteUrlRequest request) throws WxPayException {
|
||||
String url = this.payService.getPayBaseUrl() + INVITE_URL_PATH;
|
||||
if (StringUtils.isNotBlank(request.getSubMchid())) {
|
||||
url += "?" + query("sub_mchid", request.getSubMchid());
|
||||
}
|
||||
url = appendQuery(url, "operation_type", request.getOperationType());
|
||||
url = appendQuery(url, "fapiao_mode", request.getFapiaoMode());
|
||||
if (request.getFapiaoAbilityTypeList() != null && !request.getFapiaoAbilityTypeList().isEmpty()) {
|
||||
url = appendQuery(url, "fapiao_ability_type_list", String.join(",", request.getFapiaoAbilityTypeList()));
|
||||
}
|
||||
url = appendQuery(url, "invite_channel", request.getInviteChannel());
|
||||
url = appendQuery(url, "operate_user", request.getOperateUser());
|
||||
url = appendQuery(url, "invite_code", request.getInviteCode());
|
||||
String response = this.payService.getV3(url);
|
||||
return GSON.fromJson(response, InviteUrlResult.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void issueGeneralInvoice(GeneralInvoiceRequest request) throws WxPayException {
|
||||
String url = this.payService.getPayBaseUrl() + ISSUE_GENERAL_PATH;
|
||||
this.payService.postV3(url, GSON.toJson(request));
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvoiceResult getInvoice(String fapiaoApplyId, String subMchId, String fapiaoId) throws WxPayException {
|
||||
String url = this.payService.getPayBaseUrl() + FAPIAO_APPLICATIONS_PATH + encode(fapiaoApplyId) + "?sub_mchid=" + encode(subMchId);
|
||||
if (StringUtils.isNotBlank(fapiaoId)) {
|
||||
url += "&fapiao_id=" + encode(fapiaoId);
|
||||
}
|
||||
return GSON.fromJson(this.payService.getV3(url), InvoiceResult.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reverseInvoice(ReverseInvoiceRequest request) throws WxPayException {
|
||||
String url = this.payService.getPayBaseUrl() + FAPIAO_APPLICATIONS_PATH + encode(request.getFapiaoApplyId()) + "/reverse";
|
||||
JsonObject body = GSON.toJsonTree(request).getAsJsonObject();
|
||||
body.remove("fapiao_apply_id");
|
||||
this.payService.postV3(url, GSON.toJson(body));
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvoiceFileResult getInvoiceFileDownloadInfo(String fapiaoApplyId, String subMchId, String fapiaoId) throws WxPayException {
|
||||
String url = this.payService.getPayBaseUrl() + FAPIAO_APPLICATIONS_PATH + encode(fapiaoApplyId) + "/fapiao-files?sub_mchid=" + encode(subMchId);
|
||||
if (StringUtils.isNotBlank(fapiaoId)) {
|
||||
url += "&fapiao_id=" + encode(fapiaoId);
|
||||
}
|
||||
return GSON.fromJson(this.payService.getV3(url), InvoiceFileResult.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubMerchantInvoiceStatus getSubMerchantInvoiceStatus(String subMchId) throws WxPayException {
|
||||
String url = this.payService.getPayBaseUrl() + "/v3/new-tax-control-fapiao/merchant/" + encode(subMchId) + "/check-status";
|
||||
return GSON.fromJson(this.payService.getV3(url), SubMerchantInvoiceStatus.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CardTemplateResult createCardTemplate(CardTemplateRequest request) throws WxPayException {
|
||||
String url = this.payService.getPayBaseUrl() + "/v3/new-tax-control-fapiao/card-template";
|
||||
return GSON.fromJson(this.payService.postV3(url, GSON.toJson(request)), CardTemplateResult.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DevelopmentConfigResult updateDevelopmentConfig(DevelopmentConfigRequest request) throws WxPayException {
|
||||
String url = this.payService.getPayBaseUrl() + "/v3/new-tax-control-fapiao/merchant/development-config";
|
||||
return GSON.fromJson(this.payService.patchV3(url, GSON.toJson(request)), DevelopmentConfigResult.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TitleUrlResult getUserTitleUrl(TitleUrlRequest request) throws WxPayException {
|
||||
String url = this.payService.getPayBaseUrl() + "/v3/new-tax-control-fapiao/user-title/title-url?"
|
||||
+ query("sub_mchid", request.getSubMchid()) + "&" + query("fapiao_apply_id", request.getFapiaoApplyId())
|
||||
+ "&" + query("source", request.getSource()) + "&" + query("appid", request.getAppid())
|
||||
+ "&" + query("openid", request.getOpenid()) + "&" + query("total_amount", request.getTotalAmount());
|
||||
url = appendQuery(url, "seller_name", request.getSellerName());
|
||||
url = appendQuery(url, "show_phone_cell", request.getShowPhoneCell());
|
||||
url = appendQuery(url, "must_input_phone", request.getMustInputPhone());
|
||||
url = appendQuery(url, "show_email_cell", request.getShowEmailCell());
|
||||
url = appendQuery(url, "must_input_email", request.getMustInputEmail());
|
||||
return GSON.fromJson(this.payService.getV3(url), TitleUrlResult.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BuyerInformation getUserTitle(String subMchId, String scene, String fapiaoApplyId) throws WxPayException {
|
||||
String url = this.payService.getPayBaseUrl() + "/v3/new-tax-control-fapiao/user-title?" + query("sub_mchid", subMchId)
|
||||
+ "&" + query("scene", scene) + "&" + query("fapiao_apply_id", fapiaoApplyId);
|
||||
return GSON.fromJson(this.payService.getV3(url), BuyerInformation.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void issueRealEstateLeasingInvoice(IndustryInvoiceRequest request) throws WxPayException {
|
||||
this.payService.postV3(this.payService.getPayBaseUrl() + "/v3/new-tax-control-fapiao/fapiao-applications/real-estate-leasing", GSON.toJson(request));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void issueRefinedOilInvoice(IndustryInvoiceRequest request) throws WxPayException {
|
||||
this.payService.postV3(this.payService.getPayBaseUrl() + "/v3/new-tax-control-fapiao/fapiao-applications/issue-refined-oil", GSON.toJson(request));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertCards(InsertCardRequest request) throws WxPayException {
|
||||
String url = this.payService.getPayBaseUrl() + FAPIAO_APPLICATIONS_PATH + encode(request.getFapiaoApplyId()) + "/insert-cards";
|
||||
JsonObject body = GSON.toJsonTree(request).getAsJsonObject();
|
||||
body.remove("fapiao_apply_id");
|
||||
this.payService.postV3(url, GSON.toJson(body));
|
||||
}
|
||||
|
||||
@Override
|
||||
public InviteMerchantResult listInviteMerchants(InviteMerchantQuery query) throws WxPayException {
|
||||
String url = this.payService.getPayBaseUrl() + "/v3/new-tax-control-fapiao/fapiaomerchant/listspinvitemchinfo"
|
||||
+ "?" + query("query_time_start", query.getQueryTimeStart()) + "&" + query("query_time_end", query.getQueryTimeEnd())
|
||||
+ "&" + query("offset", query.getOffset()) + "&" + query("limit", query.getLimit()) + "&" + query("mch_invite_status", query.getMchInviteStatus());
|
||||
if (StringUtils.isNotBlank(query.getInviteCode())) {
|
||||
url += "&invite_code=" + encode(query.getInviteCode());
|
||||
}
|
||||
return GSON.fromJson(this.payService.getV3(url), InviteMerchantResult.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvoiceFileUploadResult uploadInvoiceFile(InvoiceFileUploadRequest request) throws WxPayException, IOException {
|
||||
String url = this.payService.getPayBaseUrl() + FAPIAO_APPLICATIONS_PATH + "upload-fapiao-file";
|
||||
JsonObject meta = new JsonObject();
|
||||
meta.addProperty("sub_mchid", request.getSubMchid());
|
||||
meta.addProperty("file_type", request.getFileType());
|
||||
// 微信支付官方接口字段即为 digest_alogrithm(文档中的既定拼写)。
|
||||
meta.addProperty("digest_alogrithm", request.getDigestAlgorithm());
|
||||
meta.addProperty("digest", request.getDigest());
|
||||
try (FileInputStream inputStream = new FileInputStream(request.getFile())) {
|
||||
WechatPayUploadHttpPost post = new WechatPayUploadHttpPost.Builder(URI.create(url))
|
||||
.withFapiaoFile(request.getFile().getName(), GSON.toJson(meta), inputStream).buildFapiaoFile();
|
||||
return GSON.fromJson(this.payService.postV3(url, post), InvoiceFileUploadResult.class);
|
||||
}
|
||||
}
|
||||
|
||||
private static String query(String key, Object value) {
|
||||
return key + "=" + encode(value);
|
||||
}
|
||||
|
||||
private static String appendQuery(String url, String key, Object value) {
|
||||
return value == null ? url : url + (url.contains("?") ? "&" : "?") + query(key, value);
|
||||
}
|
||||
|
||||
private static String encode(Object value) {
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("微信支付接口必填参数不能为空");
|
||||
}
|
||||
try {
|
||||
return URLEncoder.encode(String.valueOf(value), StandardCharsets.UTF_8.name());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,7 +138,7 @@ public class WxPayServiceApacheHttpImpl extends BaseWxPayServiceImpl {
|
||||
responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) {
|
||||
if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode || HttpStatus.SC_ACCEPTED == statusCode) {
|
||||
this.logRequestAndResponse(url, requestStr, responseString);
|
||||
return responseString;
|
||||
}
|
||||
@@ -210,7 +210,7 @@ public class WxPayServiceApacheHttpImpl extends BaseWxPayServiceImpl {
|
||||
responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) {
|
||||
if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode || HttpStatus.SC_ACCEPTED == statusCode) {
|
||||
log.info("\n【请求地址】:{}\n【响应数据】:{}", url, responseString);
|
||||
return responseString;
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ public class WxPayServiceHttpComponentsImpl extends BaseWxPayServiceImpl {
|
||||
responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) {
|
||||
if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode || HttpStatus.SC_ACCEPTED == statusCode) {
|
||||
this.logRequestAndResponse(url, requestStr, responseString);
|
||||
return responseString;
|
||||
}
|
||||
@@ -207,7 +207,7 @@ public class WxPayServiceHttpComponentsImpl extends BaseWxPayServiceImpl {
|
||||
responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) {
|
||||
if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode || HttpStatus.SC_ACCEPTED == statusCode) {
|
||||
log.info("\n【请求地址】:{}\n【响应数据】:{}", url, responseString);
|
||||
return responseString;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ public class WechatPayUploadHttpPost extends HttpPost {
|
||||
private InputStream fileInputStream;
|
||||
private ContentType fileContentType;
|
||||
private URI uri;
|
||||
private String customMeta;
|
||||
|
||||
public Builder(URI uri) {
|
||||
this.uri = uri;
|
||||
@@ -58,6 +59,32 @@ public class WechatPayUploadHttpPost extends HttpPost {
|
||||
return withMedia(fileName, fileSha256, inputStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务商电子发票文件上传。该接口签名和 multipart 的 meta 均使用业务元信息。
|
||||
*/
|
||||
public Builder withFapiaoFile(String fileName, String meta, InputStream inputStream) {
|
||||
this.fileName = fileName;
|
||||
this.fileInputStream = inputStream;
|
||||
this.customMeta = meta;
|
||||
String mimeType = URLConnection.guessContentTypeFromName(fileName);
|
||||
this.fileContentType = ContentType.create(mimeType == null ? "application/octet-stream" : mimeType);
|
||||
return this;
|
||||
}
|
||||
|
||||
public WechatPayUploadHttpPost buildFapiaoFile() {
|
||||
if (fileName == null || fileInputStream == null || customMeta == null || uri == null) {
|
||||
throw new IllegalArgumentException("缺少电子发票文件上传信息");
|
||||
}
|
||||
WechatPayUploadHttpPost request = new WechatPayUploadHttpPost(uri, customMeta);
|
||||
MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();
|
||||
entityBuilder.setMode(HttpMultipartMode.RFC6532)
|
||||
.addBinaryBody("file", fileInputStream, fileContentType, fileName)
|
||||
.addTextBody("meta", customMeta, ContentType.APPLICATION_JSON);
|
||||
request.setEntity(entityBuilder.build());
|
||||
request.addHeader("Accept", ContentType.APPLICATION_JSON.toString());
|
||||
return request;
|
||||
}
|
||||
|
||||
public WechatPayUploadHttpPost build() {
|
||||
if (fileName == null || fileSha256 == null || fileInputStream == null) {
|
||||
throw new IllegalArgumentException("缺少待上传文件信息");
|
||||
|
||||
@@ -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