From edf8e18c5be3d0f170e478de98fd7da186a714a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E6=9E=AB?= <954649679@qq.com> Date: Fri, 7 Aug 2020 17:39:38 +0800 Subject: [PATCH] =?UTF-8?q?=20:new:=20#1706=20=E5=BE=AE=E4=BF=A1=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E5=A2=9E=E5=8A=A0=E7=89=B9=E7=BA=A6=E5=95=86=E6=88=B7?= =?UTF-8?q?=E8=BF=9B=E4=BB=B6=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 实现特约商户进件相关接口 --- .../applyment/ApplymentStateQueryResult.java | 87 ++ .../applyment/ModifySettlementRequest.java | 53 ++ .../bean/applyment/SettlementInfoResult.java | 51 + .../WxPayApplyment4SubCreateRequest.java | 883 ++++++++++++++++++ .../applyment/WxPayApplymentCreateResult.java | 31 + .../bean/applyment/enums/AccountTypeEnum.java | 18 + .../applyment/enums/ApplymentStateEnum.java | 42 + .../applyment/enums/BankAccountTypeEnum.java | 18 + .../bean/applyment/enums/CertTypeEnum.java | 60 ++ .../bean/applyment/enums/IdTypeEnum.java | 29 + .../applyment/enums/SalesScenesTypeEnum.java | 32 + .../enums/SettlementVerifyResultEnum.java | 23 + .../bean/applyment/enums/SubjectTypeEnum.java | 28 + .../binarywang/wxpay/config/WxPayConfig.java | 10 + .../wxpay/service/Applyment4SubService.java | 67 ++ .../wxpay/service/WxPayService.java | 12 + .../impl/Applyment4SubServiceImpl.java | 68 ++ .../impl/WxPayServiceApacheHttpImpl.java | 36 + .../impl/WxPayServiceJoddHttpImpl.java | 5 + .../binarywang/wxpay/v3/SpecEncrypt.java | 16 + .../wxpay/v3/util/RsaCryptoUtil.java | 99 ++ .../impl/Applyment4SubServiceImplTest.java | 87 ++ 22 files changed, 1755 insertions(+) create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/ApplymentStateQueryResult.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/ModifySettlementRequest.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/SettlementInfoResult.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/WxPayApplyment4SubCreateRequest.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/WxPayApplymentCreateResult.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/AccountTypeEnum.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/ApplymentStateEnum.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/BankAccountTypeEnum.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/CertTypeEnum.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/IdTypeEnum.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/SalesScenesTypeEnum.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/SettlementVerifyResultEnum.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/SubjectTypeEnum.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/Applyment4SubService.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/Applyment4SubServiceImpl.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/SpecEncrypt.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/RsaCryptoUtil.java create mode 100644 weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/Applyment4SubServiceImplTest.java diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/ApplymentStateQueryResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/ApplymentStateQueryResult.java new file mode 100644 index 000000000..41954a2aa --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/ApplymentStateQueryResult.java @@ -0,0 +1,87 @@ +package com.github.binarywang.wxpay.bean.applyment; + +import com.github.binarywang.wxpay.bean.applyment.enums.ApplymentStateEnum; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 查询申请单状态返回对象信息 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +public class ApplymentStateQueryResult { + + /** + * 业务申请编号 + */ + @SerializedName("business_code") + private String businessCode; + /** + * 微信支付申请单号 + */ + @SerializedName("applyment_id") + private String applymentId; + /** + * 特约商户号 + */ + @SerializedName("sub_mchid") + private String subMchid; + /** + * 超级管理员签约链接 + */ + @SerializedName("sign_url") + private String signUrl; + + /** + * 申请单状态 + * + */ + @SerializedName("applyment_state") + private ApplymentStateEnum applymentState; + /** + * 申请状态描述 + */ + @SerializedName("applyment_state_msg") + private String applymentStateMsg; + /** + * 驳回原因详情 + */ + @SerializedName("audit_detail") + private List auditDetail; + + /** + * 驳回原因详情 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Accessors(chain = true) + public static class AuditDetail { + /** + * 字段名 + */ + @SerializedName("field") + private String field; + /** + * 字段名称 + */ + @SerializedName("field_name") + private String fieldName; + /** + * 驳回原因 + */ + @SerializedName("reject_reason") + private String rejectReason; + + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/ModifySettlementRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/ModifySettlementRequest.java new file mode 100644 index 000000000..e31af2aed --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/ModifySettlementRequest.java @@ -0,0 +1,53 @@ +package com.github.binarywang.wxpay.bean.applyment; + +import com.github.binarywang.wxpay.bean.applyment.enums.AccountTypeEnum; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import com.github.binarywang.wxpay.v3.SpecEncrypt; + +/** + * 修改结算账户请求对象 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +public class ModifySettlementRequest { + /** + *账户类型 + */ + @SerializedName("account_type") + private AccountTypeEnum accountType; + /** + *开户银行 + */ + @SerializedName("account_bank") + private String accountBank; + /** + *开户银行省市编码 + */ + @SerializedName("bank_address_code") + private String bankAddressCode; + /** + *开户银行全称(含支行) + */ + @SerializedName("bank_name") + private String bankName; + /** + *开户银行联行号 + */ + @SerializedName("bank_branch_id") + private String bankBranchId; + + /** + *银行账号 + */ + @SpecEncrypt + @SerializedName("account_number") + private String accountNumber; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/SettlementInfoResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/SettlementInfoResult.java new file mode 100644 index 000000000..b284f7926 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/SettlementInfoResult.java @@ -0,0 +1,51 @@ +package com.github.binarywang.wxpay.bean.applyment; + +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * 查询结算账户返回对象信息 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +public class SettlementInfoResult { + + /** + * 账户类型 + */ + @SerializedName("account_type") + private String accountType; + /** + * 开户银行 + */ + @SerializedName("account_bank") + private String accountBank; + /** + * 开户银行全称(含支行] + */ + @SerializedName("bank_name") + private String bankName; + /** + * 开户银行联行号 + */ + @SerializedName("bank_branch_id") + private String bankBranchId; + /** + * 银行账号 + */ + @SerializedName("account_number") + private String accountNumber; + /** + * 汇款验证结果 + * @see com.github.binarywang.wxpay.bean.applyment.enums.SettlementVerifyResultEnum + */ + @SerializedName("verify_result") + private String verifyResult; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/WxPayApplyment4SubCreateRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/WxPayApplyment4SubCreateRequest.java new file mode 100644 index 000000000..cfdbdd8ed --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/WxPayApplyment4SubCreateRequest.java @@ -0,0 +1,883 @@ +package com.github.binarywang.wxpay.bean.applyment; + +import com.github.binarywang.wxpay.bean.applyment.enums.*; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import com.github.binarywang.wxpay.v3.SpecEncrypt; + +import java.io.Serializable; +import java.util.List; + +/** + * 特约商户进件 提交申请对象 + * + * @author zhouyongshen + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +public class WxPayApplyment4SubCreateRequest implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 业务申请编号 + */ + @SerializedName("business_code") + private String businessCode; + /** + * 超级管理员信息 + */ + @SerializedName("contact_info") + @SpecEncrypt + private ContactInfo contactInfo; + + /** + * 主体资料 + */ + @SerializedName("subject_info") + @SpecEncrypt + private SubjectInfo subjectInfo; + + /** + * 经营资料 + */ + @SerializedName("business_info") + private BusinessInfo businessInfo; + + /** + * 结算规则 + */ + @SerializedName("settlement_info") + private SettlementInfo settlementInfo; + + /** + * 结算银行账户 + */ + @SerializedName("bank_account_info") + @SpecEncrypt + private BankAccountInfo bankAccountInfo; + + /** + * 结算银行账户 + */ + @SerializedName("addition_info") + private AdditionInfo additionInfo; + + /** + * 超级管理员需在开户后进行签约,并接收日常重要管理信息和进行资金操作,请确定其为商户法定代表人或负责人。 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Accessors(chain = true) + public static class ContactInfo implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 超级管理员姓名 + */ + @SerializedName("contact_name") + @SpecEncrypt + private String contactName; + + /** + * 超级管理员身份证件号码 + * 1、“超级管理员身份证号码”与“超级管理员微信openid”,二选一必填。 + * 2、超级管理员签约时,校验微信号绑定的银行卡实名信息,是否与该证件号码一致。 + * 3、可传身份证、来往内地通行证、来往大陆通行证、护照等证件号码。 + */ + @SerializedName("contact_id_number") + @SpecEncrypt + private String contactIdNumber; + + /** + * 超级管理员微信openid + * 1、“超级管理员身份证件号码”与“超级管理员微信openid”,二选一必填。 + * 2、超级管理员签约时,校验微信号是否与该微信openid一致。 + */ + @SerializedName("openid") + private String openid; + + /** + * 1、11位数字。 + * 2、用于接收微信支付的重要管理信息及日常操作验证码。 + */ + @SerializedName("mobile_phone") + @SpecEncrypt + private String mobilePhone; + + /** + * 1、用于接收微信支付的开户邮件及日常业务通知。 + * 2、需要带@,遵循邮箱格式校验,该字段需进行加密处理, + */ + @SerializedName("contact_email") + @SpecEncrypt + private String contactEmail; + + } + + /** + * 主体资料 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Accessors(chain = true) + public static class SubjectInfo implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 主体类型 + */ + @SerializedName("subject_type") + private SubjectTypeEnum subjectType; + + /** + * 营业执照 + */ + @SerializedName("business_license_info") + private BusinessLicenseInfo businessLicenseInfo; + /** + * 登记证书 + */ + @SerializedName("certificate_info") + private CertificateInfo certificateInfo; + + /** + * 组织机构代码证 + */ + @SerializedName("organization_info") + private OrganizationInfo organizationInfo; + + /** + * 单位证明函照片 + */ + @SerializedName("certificate_letter_copy") + private String certificateLetterCopy; + + /** + * 经营者/法人身份证件 + */ + @SerializedName("identity_info") + @SpecEncrypt + private IdentityInfo identityInfo; + + /** + * 最终受益人信息(UBO] + */ + @SerializedName("ubo_info") + @SpecEncrypt + private UboInfo uboInfo; + + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Accessors(chain = true) + public static class BusinessLicenseInfo { + /** + * 营业执照照片 + */ + @SerializedName("license_copy") + private String licenseCopy; + /** + * 注册号/统一社会信用代码 + */ + @SerializedName("license_number") + private String licenseNumber; + /** + * 商户名称 + */ + @SerializedName("merchant_name") + private String merchantName; + /** + * 个体户经营者/法人姓名 + */ + @SerializedName("legal_person") + private String legalPerson; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Accessors(chain = true) + public static class CertificateInfo { + + /** + * 登记证书照片 + */ + @SerializedName("cert_copy") + private String certCopy; + + /** + * 登记证书类型 + */ + @SerializedName("cert_type") + private CertTypeEnum certType; + + + /** + * 证书号 + */ + @SerializedName("cert_number") + private String certNumber; + + + /** + * 商户名称 + */ + @SerializedName("merchant_name") + private String merchantName; + + + /** + * 注册地址 + */ + @SerializedName("company_address") + private String companyAddress; + + + /** + * 法人姓名 + */ + @SerializedName("legal_person") + private String legalPerson; + + + /** + * 有效期限开始日期 + */ + @SerializedName("period_begin") + private String periodBegin; + + + /** + * 有效期限结束日期 + */ + @SerializedName("period_end") + private String periodEnd; + + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Accessors(chain = true) + public static class OrganizationInfo { + /** + * 组织机构代码证照片 + */ + @SerializedName("organization_copy") + private String organizationCopy; + /** + * 组织机构代码 + */ + @SerializedName("organization_code") + private String organizationCode; + /** + * 组织机构代码证有效期开始日期 + */ + @SerializedName("org_period_begin") + private String orgPeriodBegin; + /** + * 组织机构代码证有效期结束日期 + */ + @SerializedName("org_period_end") + private String orgPeriodEnd; + + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Accessors(chain = true) + public static class IdentityInfo { + + /** + * 证件类型 + */ + @SerializedName("id_doc_type") + private IdTypeEnum idDocType; + + /** + * 身份证信息 + */ + @SerializedName("id_card_info") + @SpecEncrypt + private IdCardInfo idCardInfo; + + /** + * 其他类型证件信息 + */ + @SerializedName("id_doc_info") + @SpecEncrypt + private IdDocInfo idDocInfo; + + /** + * 经营者/法人是否为受益人 + */ + @SerializedName("owner") + private boolean owner; + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Accessors(chain = true) + public static class IdCardInfo { + /** + * 身份证人像面照片 + */ + @SerializedName("id_card_copy") + private String idCardCopy; + /** + * 身份证国徽面照片 + */ + @SerializedName("id_card_national") + private String idCardNational; + + /** + * 身份证姓名 + */ + @SerializedName("id_card_name") + @SpecEncrypt + private String idCardName; + /** + * 身份证号码 + */ + @SerializedName("id_card_number") + @SpecEncrypt + private String idCardNumber; + /** + * 身份证有效期开始时间 + */ + @SerializedName("card_period_begin") + private String cardPeriodBegin; + /** + * 身份证有效期结束时间 + */ + @SerializedName("card_period_end") + private String cardPeriodEnd; + + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Accessors(chain = true) + public static class IdDocInfo { + /** + * 证件照片 + */ + @SerializedName("id_doc_copy") + private String idDocCopy; + + /** + * 证件姓名 + */ + @SerializedName("id_doc_name") + @SpecEncrypt + private String idDocName; + + /** + * 证件号码 + */ + @SerializedName("id_doc_number") + @SpecEncrypt + private String idDocNumber; + /** + * 证件有效期开始时间 + */ + @SerializedName("doc_period_begin") + private String docPeriodBegin; + /** + * 证件有效期结束时间 + */ + @SerializedName("doc_period_end") + private String docPeriodEnd; + } + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Accessors(chain = true) + public static class UboInfo { + /** + * 证件类型 + */ + @SerializedName("id_type") + private IdTypeEnum idType; + /** + * 身份证人像面照片 + */ + @SerializedName("id_card_copy") + private String idCardCopy; + /** + * 身份证国徽面照片 + */ + @SerializedName("id_card_national") + private String idCardNational; + /** + * 证件照片 + */ + @SerializedName("id_doc_copy") + private String idDocCopy; + /** + * 受益人姓名 + */ + @SerializedName("name") + @SpecEncrypt + private String name; + /** + * 证件号码 + */ + @SerializedName("id_number") + @SpecEncrypt + private String idNumber; + /** + * 证件有效期开始时间 + */ + @SerializedName("id_period_begin") + private String idPeriodBegin; + /** + * 证件有效期结束时间 + */ + @SerializedName("id_period_end") + private String idPeriodEnd; + } + + } + + /** + * 经营资料 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Accessors(chain = true) + public static class BusinessInfo implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 商户简称 + */ + @SerializedName("merchant_shortname") + private String merchantShortname; + + /** + * 客服电话 + */ + @SerializedName("service_phone") + private String servicePhone; + + /** + * 经营场景 + */ + @SerializedName("sales_info") + private SalesInfo salesInfo; + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Accessors(chain = true) + public static class SalesInfo { + + /** + * 经营场景类型 + */ + @SerializedName("sales_scenes_type") + private List salesScenesType; + + /** + * 线下门店场景 + */ + @SerializedName("biz_store_info") + private BizStoreInfo bizStoreInfo; + + /** + * 公众号场景 + */ + @SerializedName("mp_info") + private MpInfo mpInfo; + + /** + * 小程序场景 + */ + @SerializedName("mini_program_info") + private MiniProgramInfo miniProgramInfo; + + /** + * APP场景 + */ + @SerializedName("app_info") + private AppInfo appInfo; + + /** + * 互联网网站场景 + */ + @SerializedName("web_info") + private WebInfo webInfo; + + /** + * 企业微信场景 + */ + @SerializedName("wework_info") + private WeworkInfo weworkInfo; + + /** + * 线下门店场景 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Accessors(chain = true) + public static class BizStoreInfo { + + /** + * 门店名称 + */ + @SerializedName("biz_store_name") + private String bizStoreName; + + /** + * 门店省市编码 + */ + @SerializedName("biz_address_code") + private String bizAddressCode; + + /** + * 门店地址 + */ + @SerializedName("biz_store_address") + private String bizStoreAddress; + + /** + * 门店门头照片 + */ + @SerializedName("store_entrance_pic") + private List storeEntrancePic; + + /** + * 店内环境照片 + */ + @SerializedName("indoor_pic") + private List indoorPic; + + /** + * 线下场所对应的商家APPID + */ + @SerializedName("biz_sub_appid") + private String bizSubAppid; + + } + + /** + * 公众号场景 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Accessors(chain = true) + public static class MpInfo { + + /** + * 服务商公众号APPID + */ + @SerializedName("mp_appid") + private String mpAppid; + + /** + * 商家公众号APPID + */ + @SerializedName("mp_sub_appid") + private String mpSubAppid; + + /** + * 公众号页面截图 + */ + @SerializedName("mp_pics") + private List mpPics; + + } + + /** + * 小程序场景 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Accessors(chain = true) + public static class MiniProgramInfo { + + /** + * 服务商小程序APPID + */ + @SerializedName("mini_program_appid") + private String miniProgramAppid; + + /** + * 商家小程序APPID + */ + @SerializedName("mini_program_sub_appid") + private String miniProgramSubAppid; + + /** + * 小程序截图 + */ + @SerializedName("mini_program_pics") + private List miniProgramPics; + + + } + + /** + * APP场景 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Accessors(chain = true) + public static class AppInfo { + + /** + * 服务商应用APPID + */ + @SerializedName("app_appid") + private String appAppid; + + /** + * 商家应用APPID + */ + @SerializedName("app_sub_appid") + private String appSubAppid; + + /** + * APP截图 + */ + @SerializedName("app_pics") + private List appPics; + + } + + /** + * 互联网网站场景 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Accessors(chain = true) + public static class WebInfo { + + /** + * 互联网网站域名 + */ + @SerializedName("domain") + private String domain; + + /** + * 网站授权函 + */ + @SerializedName("web_authorisation") + private String webAuthorisation; + + /** + * 互联网网站对应的商家APPID + */ + @SerializedName("web_appid") + private String webAppid; + + } + + /** + * 企业微信场景 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Accessors(chain = true) + public static class WeworkInfo { + + /** + * 商家企业微信CorpID + */ + @SerializedName("sub_corp_id") + private String subCorpId; + + /** + * 企业微信页面截图 + */ + @SerializedName("wework_pics") + private List weworkPics; + + } + } + } + + /** + * 结算规则 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Accessors(chain = true) + public static class SettlementInfo implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 入驻结算规则ID + */ + @SerializedName("settlement_id") + private String settlementId; + + /** + * 所属行业 + */ + @SerializedName("qualification_type") + private String qualificationType; + + /** + * 特殊资质图片 + */ + @SerializedName("qualifications") + private List qualifications; + + /** + * 优惠费率活动ID + */ + @SerializedName("activities_id") + private String activitiesId; + + /** + * 优惠费率活动值 + */ + @SerializedName("activities_rate") + private String activitiesRate; + + /** + * 优惠费率活动补充材料 + */ + @SerializedName("activities_additions") + private List activitiesAdditions; + + } + + /** + * 结算银行账户 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Accessors(chain = true) + public static class BankAccountInfo implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 账户类型 + */ + @SerializedName("bank_account_type") + private BankAccountTypeEnum bankAccountType; + + /** + * 开户名称 + */ + @SerializedName("account_name") + @SpecEncrypt + private String accountName; + + /** + * 开户银行 + */ + @SerializedName("account_bank") + private String accountBank; + + /** + * 开户银行省市编码 + */ + @SerializedName("bank_address_code") + private String bankAddressCode; + + /** + * 开户银行联行号 + */ + @SerializedName("bank_branch_id") + private String bankBranchId; + + /** + * 开户银行全称(含支行] + */ + @SerializedName("bank_name") + private String bankName; + + /** + * 银行账号 + */ + @SerializedName("account_number") + @SpecEncrypt + private String accountNumber; + + } + + + /** + * 补充材料 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Accessors(chain = true) + public static class AdditionInfo implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 法人开户承诺函 + */ + @SerializedName("legal_person_commitment") + private String legalPersonCommitment; + + /** + * 法人开户意愿视频 + */ + @SerializedName("legal_person_video") + private String legalPersonVideo; + + /** + * 补充材料 + */ + @SerializedName("business_addition_pics") + private List businessAdditionPics; + + /** + * 补充说明 + */ + @SerializedName("business_addition_msg") + private String businessAdditionMsg; + + } + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/WxPayApplymentCreateResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/WxPayApplymentCreateResult.java new file mode 100644 index 000000000..d68ba0ce8 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/WxPayApplymentCreateResult.java @@ -0,0 +1,31 @@ +package com.github.binarywang.wxpay.bean.applyment; + +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.Serializable; + + +/** + * 特约商户进件 提交申请结果响应 + * + * @author zhouyongshen + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +public class WxPayApplymentCreateResult implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 微信支付申请单号 + */ + @SerializedName("applyment_id") + private String applymentId; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/AccountTypeEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/AccountTypeEnum.java new file mode 100644 index 000000000..3239709b6 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/AccountTypeEnum.java @@ -0,0 +1,18 @@ +package com.github.binarywang.wxpay.bean.applyment.enums; + + +/** + * 银行结算账户枚举类 + */ +public enum AccountTypeEnum { + /** + * 对公银行账户 + */ + ACCOUNT_TYPE_BUSINESS, + + /** + * 经营者个人银行卡 + */ + ACCOUNT_TYPE_PRIVATE, + ; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/ApplymentStateEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/ApplymentStateEnum.java new file mode 100644 index 000000000..50affe877 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/ApplymentStateEnum.java @@ -0,0 +1,42 @@ +package com.github.binarywang.wxpay.bean.applyment.enums; + +/** + * 申请单状态枚举类 + * + * @author zhouyongshen + */ +public enum ApplymentStateEnum { + /** + * (编辑中):提交申请发生错误导致,请尝试重新提交。 + */ + APPLYMENT_STATE_EDITTING, + /** + * (审核中):申请单正在审核中,超级管理员用微信打开“签约链接”,完成绑定微信号后,申请单进度将通过微信公众号通知超级管理员,引导完成后续步骤。 + */ + APPLYMENT_STATE_AUDITING, + /** + * (已驳回):请按照驳回原因修改申请资料,超级管理员用微信打开“签约链接”,完成绑定微信号,后续申请单进度将通过微信公众号通知超级管理员。 + */ + APPLYMENT_STATE_REJECTED, + /** + * (待账户验证):请超级管理员使用微信打开返回的“签约链接”,根据页面指引完成账户验证。 + */ + APPLYMENT_STATE_TO_BE_CONFIRMED, + /** + * (待签约):请超级管理员使用微信打开返回的“签约链接”,根据页面指引完成签约。 + */ + APPLYMENT_STATE_TO_BE_SIGNED, + /** + * (开通权限中):系统开通相关权限中,请耐心等待。 + */ + APPLYMENT_STATE_SIGNING, + /** + * (已完成):商户入驻申请已完成。 + */ + APPLYMENT_STATE_FINISHED, + /** + * (已作废):申请单已被撤销。 + */ + APPLYMENT_STATE_CANCELED + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/BankAccountTypeEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/BankAccountTypeEnum.java new file mode 100644 index 000000000..5d566702e --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/BankAccountTypeEnum.java @@ -0,0 +1,18 @@ +package com.github.binarywang.wxpay.bean.applyment.enums; + + +/** + * 银行结算账户枚举类 + */ +public enum BankAccountTypeEnum { + /** + * 对公银行账户 + */ + BANK_ACCOUNT_TYPE_CORPORATE, + + /** + * 经营者个人银行卡 + */ + BANK_ACCOUNT_TYPE_PERSONAL, + ; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/CertTypeEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/CertTypeEnum.java new file mode 100644 index 000000000..89ca59d19 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/CertTypeEnum.java @@ -0,0 +1,60 @@ +package com.github.binarywang.wxpay.bean.applyment.enums; + +/** + * 登记证书的类型枚举 + */ +public enum CertTypeEnum { + /** + * 事业单位法人证书 + */ + CERTIFICATE_TYPE_2388, + /** + * 统一社会信用代码证书 + */ + CERTIFICATE_TYPE_2389, + /** + * 有偿服务许可证(军队医院适用) + */ + CERTIFICATE_TYPE_2390, + /** + * 医疗机构执业许可证(军队医院适用) + */ + CERTIFICATE_TYPE_2391, + /** + * 企业营业执照(挂靠企业的党组织适用) + */ + CERTIFICATE_TYPE_2392, + /** + * 组织机构代码证(政府机关适用) + */ + CERTIFICATE_TYPE_2393, + /** + * 社会团体法人登记证书 + */ + CERTIFICATE_TYPE_2394, + /** + * 民办非企业单位登记证书 + */ + CERTIFICATE_TYPE_2395, + /** + * 基金会法人登记证书 + */ + CERTIFICATE_TYPE_2396, + /** + * 慈善组织公开募捐资格证书 + */ + CERTIFICATE_TYPE_2397, + /** + * 农民专业合作社法人营业执照 + */ + CERTIFICATE_TYPE_2398, + /** + * 宗教活动场所登记证 + */ + CERTIFICATE_TYPE_2399, + /** + * 其他证书/批文/证明 + */ + CERTIFICATE_TYPE_2400, + ; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/IdTypeEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/IdTypeEnum.java new file mode 100644 index 000000000..7bf88e6b5 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/IdTypeEnum.java @@ -0,0 +1,29 @@ +package com.github.binarywang.wxpay.bean.applyment.enums; + +/** + * 个体户/企业/党政、机关及事业单位/其他组织:可选择任一证件类型。 + * 枚举值 + */ +public enum IdTypeEnum { + /** + * 中国大陆居民-身份证 + */ + IDENTIFICATION_TYPE_IDCARD, + /** + * 其他国家或地区居民-护照 + */ + IDENTIFICATION_TYPE_OVERSEA_PASSPORT, + /** + * 中国香港居民-来往内地通行证 + */ + IDENTIFICATION_TYPE_HONGKONG_PASSPORT, + /** + * 中国澳门居民-来往内地通行证 + */ + IDENTIFICATION_TYPE_MACAO_PASSPORT, + /** + * 中国台湾居民-来往大陆通行证 + */ + IDENTIFICATION_TYPE_TAIWAN_PASSPORT, + ; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/SalesScenesTypeEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/SalesScenesTypeEnum.java new file mode 100644 index 000000000..baf4d1839 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/SalesScenesTypeEnum.java @@ -0,0 +1,32 @@ +package com.github.binarywang.wxpay.bean.applyment.enums; + +/** + * 经营场景类型枚举值 + */ +public enum SalesScenesTypeEnum { + /** + * 线下门店 + */ + SALES_SCENES_STORE, + /** + * 公众号 + */ + SALES_SCENES_MP, + /** + * 小程序 + */ + SALES_SCENES_MINI_PROGRAM, + /** + * 互联网 + */ + SALES_SCENES_WEB, + /** + * APP + */ + SALES_SCENES_APP, + /** + * 企业微信 + */ + SALES_SCENES_WEWORK, + ; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/SettlementVerifyResultEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/SettlementVerifyResultEnum.java new file mode 100644 index 000000000..f26e6c09f --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/SettlementVerifyResultEnum.java @@ -0,0 +1,23 @@ +package com.github.binarywang.wxpay.bean.applyment.enums; + +/** + * 返回特约商户的结算账户-汇款验证结果枚举类 + * + * @author zhouyognshen + */ +public enum SettlementVerifyResultEnum { + /** + * 系统汇款验证中,商户可发起提现尝试。 + */ + VERIFYING, + /** + * 系统成功汇款,该账户可正常发起提现。 + */ + VERIFY_SUCCESS, + /** + * 系统汇款失败,该账户无法发起提现,请检查修改。 + */ + VERIFY_FAIL, + ; + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/SubjectTypeEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/SubjectTypeEnum.java new file mode 100644 index 000000000..c1555b7c5 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/applyment/enums/SubjectTypeEnum.java @@ -0,0 +1,28 @@ +package com.github.binarywang.wxpay.bean.applyment.enums; + +/** + * 主体类型枚举类 + *
+ *     商户申请接入时如何选择主体类型? https://kf.qq.com/faq/180910IBZVnQ180910naQ77b.html
+ * 
+ * @author zhouyongshen + */ +public enum SubjectTypeEnum { + /** + * (个体户):营业执照上的主体类型一般为个体户、个体工商户、个体经营; + */ + SUBJECT_TYPE_INDIVIDUAL, + /** + * (企业):营业执照上的主体类型一般为有限公司、有限责任公司; + */ + SUBJECT_TYPE_ENTERPRISE, + /** + * (党政、机关及事业单位):包括国内各级、各类政府机构、事业单位等(如:公安、党团、司法、交通、旅游、工商税务、市政、医疗、教育、学校等机构); + */ + SUBJECT_TYPE_INSTITUTIONS, + /** + * (其他组织):不属于企业、政府/事业单位的组织机构(如社会团体、民办非企业、基金会),要求机构已办理组织机构代码证。 + */ + SUBJECT_TYPE_OTHERS,; + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java index 4ee1ed707..f5cd91c6c 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java @@ -6,6 +6,7 @@ import com.github.binarywang.wxpay.v3.auth.*; import com.github.binarywang.wxpay.v3.util.PemUtils; import jodd.util.ResourcesUtil; import lombok.Data; +import lombok.SneakyThrows; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.RegExUtils; import org.apache.commons.lang3.StringUtils; @@ -169,6 +170,15 @@ public class WxPayConfig { return this.payBaseUrl; } + @SneakyThrows + public Verifier getVerifier() { + if (verifier == null) { + //当改对象为null时,初始化api v3的请求头 + initApiV3HttpClient(); + } + return verifier; + } + /** * 初始化ssl. * diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/Applyment4SubService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/Applyment4SubService.java new file mode 100644 index 000000000..2187de0c0 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/Applyment4SubService.java @@ -0,0 +1,67 @@ +package com.github.binarywang.wxpay.service; + +import com.github.binarywang.wxpay.bean.applyment.*; +import com.github.binarywang.wxpay.exception.WxPayException; + +/** + * 特约商户进件 + * 产品介绍:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/tool/applyment4sub/chapter1_1.shtml + * + * @author zhouyongshen + */ +public interface Applyment4SubService { + /** + * 提交申请单API + * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/tool/applyment4sub/chapter3_1.shtml + * 接口链接:https://api.mch.weixin.qq.com/v3/applyment4sub/applyment/ + * + * @param request 请求对象 + * @return WxPayApplymentCreateResult 响应结果 + * @throws WxPayException the wx pay exception + */ + WxPayApplymentCreateResult createApply(WxPayApplyment4SubCreateRequest request) throws WxPayException; + + + + /** + * 通过业务申请编号查询申请状态 + * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/tool/applyment4sub/chapter3_2.shtml + * 接口链接:https://api.mch.weixin.qq.com/v3/applyment4sub/applyment/business_code/{business_code} + * + * @param businessCode 业务申请编号 + * 1、只能由数字、字母或下划线组成,建议前缀为服务商商户号。 + * 2、服务商自定义的唯一编号。 + * 3、每个编号对应一个申请单,每个申请单审核通过后生成一个微信支付商户号。 + * 4、若申请单被驳回,可填写相同的“业务申请编号”,即可覆盖修改原申请单信息。 + * 示例值:1900013511_10000 + */ + ApplymentStateQueryResult queryApplyStatusByBusinessCode(String businessCode) throws WxPayException; + + /** + * 通过申请单号查询申请状态 + * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/tool/applyment4sub/chapter3_2.shtml + * 接口链接:https://api.mch.weixin.qq.com/v3/applyment4sub/applyment/applyment_id/{applyment_id} + * + * @param applymentId 微信支付分的申请单号。示例值:2000001234567890 + */ + ApplymentStateQueryResult queryApplyStatusByApplymentId(String applymentId) throws WxPayException; + + /** + * 通过申请单号查询申请状态 + * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/tool/applyment4sub/chapter3_4.shtml + * 接口链接:https://api.mch.weixin.qq.com/v3/apply4sub/sub_merchants/{sub_mchid}/settlement + * + * @param subMchid 本服务商进件、已签约的特约商户号。 + */ + SettlementInfoResult querySettlementBySubMchid(String subMchid) throws WxPayException; + + /** + * 修改结算帐号 + * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/tool/applyment4sub/chapter3_3.shtml + * 接口链接:https://api.mch.weixin.qq.com/v3/apply4sub/sub_merchants/{sub_mchid}/modify-settlement + * @param subMchid 特约商户号 + * @param request 修改结算账户请求对象信息 + */ + void modifySettlement(String subMchid,ModifySettlementRequest request) throws WxPayException; + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java index 565db9c6a..da7652ef6 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java @@ -66,6 +66,18 @@ public interface WxPayService { */ String postV3(String url, String requestStr) throws WxPayException; + /** + * 发送post请求,得到响应字符串. + * + * 部分字段会包含敏感信息,所以在提交前需要在请求头中会包含"Wechatpay-Serial"信息 + * + * @param url 请求地址 + * @param requestStr 请求信息 + * @return 返回请求结果字符串 string + * @throws WxPayException the wx pay exception + */ + String postV3WithWechatpaySerial(String url, String requestStr) throws WxPayException; + /** * 发送post请求,得到响应字符串. * diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/Applyment4SubServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/Applyment4SubServiceImpl.java new file mode 100644 index 000000000..defb01a9f --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/Applyment4SubServiceImpl.java @@ -0,0 +1,68 @@ +package com.github.binarywang.wxpay.service.impl; + +import com.github.binarywang.wxpay.bean.applyment.*; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.Applyment4SubService; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.v3.util.RsaCryptoUtil; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.net.URI; +import java.security.cert.X509Certificate; + +@Slf4j +@RequiredArgsConstructor +public class Applyment4SubServiceImpl implements Applyment4SubService { + + private static final Gson GSON = new GsonBuilder().create(); + private final WxPayService payService; + + private void encryptFiled(Object request) throws WxPayException { + + X509Certificate validCertificate = payService.getConfig().getVerifier().getValidCertificate(); + + RsaCryptoUtil.encryptFields(request, validCertificate); + } + + + @Override + public WxPayApplymentCreateResult createApply(WxPayApplyment4SubCreateRequest request) throws WxPayException { + String url = String.format("%s/v3/applyment4sub/applyment/", this.payService.getPayBaseUrl()); + + encryptFiled(request); + + String result = payService.postV3WithWechatpaySerial(url, GSON.toJson(request)); + return GSON.fromJson(result, WxPayApplymentCreateResult.class); + } + + @Override + public ApplymentStateQueryResult queryApplyStatusByBusinessCode(String businessCode) throws WxPayException { + String url = String.format("%s/v3/applyment4sub/applyment/business_code/%s", this.payService.getPayBaseUrl(), businessCode); + String result = payService.getV3(URI.create(url)); + return GSON.fromJson(result, ApplymentStateQueryResult.class); + } + + @Override + public ApplymentStateQueryResult queryApplyStatusByApplymentId(String applymentId) throws WxPayException { + String url = String.format("%s/v3/applyment4sub/applyment/applyment_id/%s", this.payService.getPayBaseUrl(), applymentId); + String result = payService.getV3(URI.create(url)); + return GSON.fromJson(result, ApplymentStateQueryResult.class); + } + + @Override + public SettlementInfoResult querySettlementBySubMchid(String subMchid) throws WxPayException { + String url = String.format("%s/v3/apply4sub/sub_merchants/%s/settlement", this.payService.getPayBaseUrl(), subMchid); + String result = payService.getV3(URI.create(url)); + return GSON.fromJson(result, SettlementInfoResult.class); + } + + @Override + public void modifySettlement(String subMchid,ModifySettlementRequest request) throws WxPayException { + String url = String.format("%s/v3/apply4sub/sub_merchants/%s/modify-settlement", this.payService.getPayBaseUrl(), subMchid); + encryptFiled(request); + String result = payService.postV3WithWechatpaySerial(url, GSON.toJson(request)); + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java index 7c4f2021a..7923c4723 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java @@ -6,6 +6,7 @@ import com.google.gson.JsonObject; import jodd.util.Base64; import me.chanjar.weixin.common.util.json.GsonParser; import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpStatus; import org.apache.http.auth.AuthScope; @@ -26,6 +27,7 @@ import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import javax.net.ssl.SSLContext; +import java.math.BigInteger; import java.net.URI; import java.nio.charset.StandardCharsets; @@ -116,6 +118,40 @@ public class WxPayServiceApacheHttpImpl extends BaseWxPayServiceImpl { } + @Override + public String postV3WithWechatpaySerial(String url, String requestStr) throws WxPayException { + CloseableHttpClient httpClient = this.createApiV3HttpClient(); + HttpPost httpPost = this.createHttpPost(url, requestStr); + httpPost.addHeader("Accept", "application/json"); + httpPost.addHeader("Content-Type", "application/json"); + String serialNumber = getConfig().getVerifier().getValidCertificate().getSerialNumber().toString(16).toUpperCase(); + httpPost.addHeader("Wechatpay-Serial", serialNumber); + try (CloseableHttpResponse response = httpClient.execute(httpPost)) { + //v3已经改为通过状态码判断200 204 成功 + int statusCode = response.getStatusLine().getStatusCode(); + String responseString="{}"; + HttpEntity entity = response.getEntity(); + if(entity!=null){ + responseString= EntityUtils.toString(entity, StandardCharsets.UTF_8); + } + + if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) { + this.log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, responseString); + return responseString; + } else { + //有错误提示信息返回 + JsonObject jsonObject = GsonParser.parse(responseString); + throw new WxPayException(jsonObject.get("message").getAsString()); + } + } catch (Exception e) { + this.log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage()); + e.printStackTrace(); + throw new WxPayException(e.getMessage(), e); + } finally { + httpPost.releaseConnection(); + } + } + @Override public String postV3(String url, HttpPost httpPost) throws WxPayException { diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java index 91292e810..b7ef11695 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java @@ -66,6 +66,11 @@ public class WxPayServiceJoddHttpImpl extends BaseWxPayServiceImpl { return null; } + @Override + public String postV3WithWechatpaySerial(String url, String requestStr) throws WxPayException { + return null; + } + @Override public String postV3(String url, HttpPost httpPost) throws WxPayException { return null; diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/SpecEncrypt.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/SpecEncrypt.java new file mode 100644 index 000000000..4f1eb9e58 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/SpecEncrypt.java @@ -0,0 +1,16 @@ +package com.github.binarywang.wxpay.v3; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 敏感信息字段 + * @author zhouyognshen + **/ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface SpecEncrypt { + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/RsaCryptoUtil.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/RsaCryptoUtil.java new file mode 100644 index 000000000..b6ab92316 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/RsaCryptoUtil.java @@ -0,0 +1,99 @@ +package com.github.binarywang.wxpay.v3.util; + +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.v3.SpecEncrypt; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.Base64; + +/** + * 微信支付敏感信息加密 + * 文档见: https://wechatpay-api.gitbook.io/wechatpay-api-v3/qian-ming-zhi-nan-1/min-gan-xin-xi-jia-mi + * + * @author zhouyongshen + **/ +public class RsaCryptoUtil { + + + static String JAVA_LANG_STRING = "java.lang.String"; + + public static void encryptFields(Object encryptObject, X509Certificate certificate) throws WxPayException { + try { + encryptField(encryptObject, certificate); + } catch (IllegalAccessException | IllegalBlockSizeException e) { + throw new WxPayException("敏感信息加密失败", e); + } catch (Exception e2) { + throw new WxPayException("敏感信息加密失败", e2); + } + } + + private static void encryptField(Object encryptObject, X509Certificate certificate) throws IllegalAccessException, IllegalBlockSizeException { + Class infoClass = encryptObject.getClass(); + Field[] infoFieldArray = infoClass.getDeclaredFields(); + for (Field field : infoFieldArray) { + if (field.isAnnotationPresent(SpecEncrypt.class)) { + //字段使用了@SpecEncrypt进行标识 + if (field.getType().getTypeName().equals(JAVA_LANG_STRING)) { + field.setAccessible(true); + Object oldValue = field.get(encryptObject); + if (oldValue != null) { + String oldStr = (String) oldValue; + if (!oldStr.trim().equals("'")) { + field.set(encryptObject, encryptOAEP(oldStr, certificate)); + } + } + } else { + field.setAccessible(true); + Object obj = field.get(encryptObject); + if (obj != null) { + encryptField(field.get(encryptObject), certificate); + } + } + } + } + } + + public static String encryptOAEP(String message, X509Certificate certificate) + throws IllegalBlockSizeException { + try { + Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding"); + cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey()); + + byte[] data = message.getBytes(StandardCharsets.UTF_8); + byte[] ciphertext = cipher.doFinal(data); + return Base64.getEncoder().encodeToString(ciphertext); + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e); + } catch (InvalidKeyException e) { + throw new IllegalArgumentException("无效的证书", e); + } catch (IllegalBlockSizeException | BadPaddingException e) { + throw new IllegalBlockSizeException("加密原串的长度不能超过214字节"); + } + } + + public static String decryptOAEP(String ciphertext, PrivateKey privateKey) + throws BadPaddingException { + try { + Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding"); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + + byte[] data = Base64.getDecoder().decode(ciphertext); + return new String(cipher.doFinal(data), StandardCharsets.UTF_8); + } catch (NoSuchPaddingException | NoSuchAlgorithmException e) { + throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e); + } catch (InvalidKeyException e) { + throw new IllegalArgumentException("无效的私钥", e); + } catch (BadPaddingException | IllegalBlockSizeException e) { + throw new BadPaddingException("解密失败"); + } + } +} diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/Applyment4SubServiceImplTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/Applyment4SubServiceImplTest.java new file mode 100644 index 000000000..da268ce9e --- /dev/null +++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/Applyment4SubServiceImplTest.java @@ -0,0 +1,87 @@ +package com.github.binarywang.wxpay.service.impl; + +import com.github.binarywang.wxpay.bean.applyment.ModifySettlementRequest; +import com.github.binarywang.wxpay.bean.applyment.WxPayApplyment4SubCreateRequest; +import com.github.binarywang.wxpay.bean.applyment.WxPayApplymentCreateResult; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.Applyment4SubService; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.testbase.ApiTestModule; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.inject.Inject; +import lombok.extern.slf4j.Slf4j; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +@Slf4j +@Test +@Guice(modules = ApiTestModule.class) +public class Applyment4SubServiceImplTest { + @Inject + private WxPayService wxPayService; + + private static final Gson GSON = new GsonBuilder().create(); + + @Test + public void testCreateApply() throws WxPayException { + Applyment4SubService applyment4SubService=new Applyment4SubServiceImpl(wxPayService); + String requestParamStr="{}"; + /* + {"business_code":"1596785690732","contact_info":{"contact_name":"张三","contact_id_number":"110110202001011234","mobile_phone":"13112345678","contact_email":"abc@qq.com"},"subject_info":{"subject_type":"SUBJECT_TYPE_ENTERPRISE","business_license_info":{"license_copy":"mxX07DyfM-bJyGJYCTyW-4wrXpJ5fq_bgYfWkIZZgjenf6Ct1gKV_FpkzgyQrf5ETVEyOWhC_0cbhOATODuLBAkxGl6Cvj31lh6OFAIHnwI","license_number":"123456789012345678","merchant_name":"腾讯科技有限公司","legal_person":"张三"},"identity_info":{"id_doc_type":"IDENTIFICATION_TYPE_IDCARD","id_card_info":{"id_card_copy":"mxX07DyfM-bJyGJYCTyW-4wrXpJ5fq_bgYfWkIZZgjenf6Ct1gKV_FpkzgyQrf5ETVEyOWhC_0cbhOATODuLBAkxGl6Cvj31lh6OFAIHnwI","id_card_national":"mxX07DyfM-bJyGJYCTyW-4wrXpJ5fq_bgYfWkIZZgjenf6Ct1gKV_FpkzgyQrf5ETVEyOWhC_0cbhOATODuLBAkxGl6Cvj31lh6OFAIHnwI","id_card_name":"张三","id_card_number":"110110202001011234","card_period_begin":"2016-06-06","card_period_end":"2026-06-06"},"owner":false},"ubo_info":{"id_type":"IDENTIFICATION_TYPE_IDCARD","id_card_copy":"mxX07DyfM-bJyGJYCTyW-4wrXpJ5fq_bgYfWkIZZgjenf6Ct1gKV_FpkzgyQrf5ETVEyOWhC_0cbhOATODuLBAkxGl6Cvj31lh6OFAIHnwI","id_card_national":"mxX07DyfM-bJyGJYCTyW-4wrXpJ5fq_bgYfWkIZZgjenf6Ct1gKV_FpkzgyQrf5ETVEyOWhC_0cbhOATODuLBAkxGl6Cvj31lh6OFAIHnwI","id_doc_copy":"mxX07DyfM-bJyGJYCTyW-4wrXpJ5fq_bgYfWkIZZgjenf6Ct1gKV_FpkzgyQrf5ETVEyOWhC_0cbhOATODuLBAkxGl6Cvj31lh6OFAIHnwI","name":"张三","id_number":"110110202001011234","id_period_begin":"2016-06-06","id_period_end":"2026-06-06"}},"business_info":{"merchant_shortname":"商户简称","service_phone":"13212345678","sales_info":{"sales_scenes_type":["SALES_SCENES_MINI_PROGRAM"],"mini_program_info":{"mini_program_appid":"wxe5f52902cf4de896"}}},"settlement_info":{"settlement_id":"716","qualification_type":"餐饮"}} + */ + requestParamStr="{\"business_code\":\"1596785690732\",\"contact_info\":{\"contact_name\":\"张三\",\"contact_id_number\":\"110110202001011234\",\"mobile_phone\":\"13112345678\",\"contact_email\":\"abc@qq.com\"},\"subject_info\":{\"subject_type\":\"SUBJECT_TYPE_ENTERPRISE\",\"business_license_info\":{\"license_copy\":\"mxX07DyfM-bJyGJYCTyW-4wrXpJ5fq_bgYfWkIZZgjenf6Ct1gKV_FpkzgyQrf5ETVEyOWhC_0cbhOATODuLBAkxGl6Cvj31lh6OFAIHnwI\",\"license_number\":\"123456789012345678\",\"merchant_name\":\"腾讯科技有限公司\",\"legal_person\":\"张三\"},\"identity_info\":{\"id_doc_type\":\"IDENTIFICATION_TYPE_IDCARD\",\"id_card_info\":{\"id_card_copy\":\"mxX07DyfM-bJyGJYCTyW-4wrXpJ5fq_bgYfWkIZZgjenf6Ct1gKV_FpkzgyQrf5ETVEyOWhC_0cbhOATODuLBAkxGl6Cvj31lh6OFAIHnwI\",\"id_card_national\":\"mxX07DyfM-bJyGJYCTyW-4wrXpJ5fq_bgYfWkIZZgjenf6Ct1gKV_FpkzgyQrf5ETVEyOWhC_0cbhOATODuLBAkxGl6Cvj31lh6OFAIHnwI\",\"id_card_name\":\"张三\",\"id_card_number\":\"110110202001011234\",\"card_period_begin\":\"2016-06-06\",\"card_period_end\":\"2026-06-06\"},\"owner\":false},\"ubo_info\":{\"id_type\":\"IDENTIFICATION_TYPE_IDCARD\",\"id_card_copy\":\"mxX07DyfM-bJyGJYCTyW-4wrXpJ5fq_bgYfWkIZZgjenf6Ct1gKV_FpkzgyQrf5ETVEyOWhC_0cbhOATODuLBAkxGl6Cvj31lh6OFAIHnwI\",\"id_card_national\":\"mxX07DyfM-bJyGJYCTyW-4wrXpJ5fq_bgYfWkIZZgjenf6Ct1gKV_FpkzgyQrf5ETVEyOWhC_0cbhOATODuLBAkxGl6Cvj31lh6OFAIHnwI\",\"id_doc_copy\":\"mxX07DyfM-bJyGJYCTyW-4wrXpJ5fq_bgYfWkIZZgjenf6Ct1gKV_FpkzgyQrf5ETVEyOWhC_0cbhOATODuLBAkxGl6Cvj31lh6OFAIHnwI\",\"name\":\"张三\",\"id_number\":\"110110202001011234\",\"id_period_begin\":\"2016-06-06\",\"id_period_end\":\"2026-06-06\"}},\"business_info\":{\"merchant_shortname\":\"商户简称\",\"service_phone\":\"13212345678\",\"sales_info\":{\"sales_scenes_type\":[\"SALES_SCENES_MINI_PROGRAM\"],\"mini_program_info\":{\"mini_program_appid\":\"wxe5f52902cf4de896\"}}},\"settlement_info\":{\"settlement_id\":\"716\",\"qualification_type\":\"餐饮\"}}"; + + WxPayApplyment4SubCreateRequest request=GSON.fromJson(requestParamStr,WxPayApplyment4SubCreateRequest.class); + String businessCode = String.valueOf(System.currentTimeMillis()); + request.setBusinessCode(businessCode); + + WxPayApplymentCreateResult apply = applyment4SubService.createApply(request); + String applymentId = apply.getApplymentId(); + log.info("businessCode:[{}],applymentId:[{}]",businessCode,applymentId); + + } + + @Test + public void testQueryApplyStatusByBusinessCode() throws WxPayException { + Applyment4SubService applyment4SubService=new Applyment4SubServiceImpl(wxPayService); + String businessCode="businessCode"; + + applyment4SubService.queryApplyStatusByBusinessCode(businessCode); + + + } + + @Test + public void testQueryApplyStatusByApplymentId() throws WxPayException { + Applyment4SubService applyment4SubService=new Applyment4SubServiceImpl(wxPayService); + String applymentId="applymentId"; + + applyment4SubService.queryApplyStatusByApplymentId(applymentId); + + } + + @Test + public void testQuerySettlementBySubMchid() throws WxPayException { + Applyment4SubService applyment4SubService=new Applyment4SubServiceImpl(wxPayService); + String subMchid="subMchid"; + + applyment4SubService.querySettlementBySubMchid(subMchid); + + } + + @Test + public void testModifySettlement() throws WxPayException { + Applyment4SubService applyment4SubService=new Applyment4SubServiceImpl(wxPayService); + String subMchid="subMchid"; + ModifySettlementRequest modifySettlementRequest = new ModifySettlementRequest(); + + applyment4SubService.modifySettlement(subMchid,modifySettlementRequest); + } + + + + + + +}