From 1812b233bbcccd12944aade7188f042c622cf26e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=BE=E6=B5=A9?= Date: Mon, 17 Aug 2020 13:32:42 +0800 Subject: [PATCH 1/5] =?UTF-8?q?art:=E8=AF=81=E4=B9=A6=E7=B1=BB=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E8=AF=BB=E5=8F=96=E4=BC=98=E5=8C=96=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../binarywang/wxpay/config/WxPayConfig.java | 131 ++++++++---------- 1 file changed, 56 insertions(+), 75 deletions(-) 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 f5cd91c6c3..097a0fa0c7 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 @@ -115,8 +115,6 @@ public class WxPayConfig { * apiV3 证书序列号值 */ private String certSerialNo; - - /** * 微信支付分serviceId */ @@ -128,8 +126,10 @@ public class WxPayConfig { private String payScoreNotifyUrl; private CloseableHttpClient apiV3HttpClient; - - + /** + * 私钥信息 + */ + private PrivateKey privateKey; /** * p12证书文件内容的字节数组. */ @@ -197,44 +197,7 @@ public SSLContext initSSLContext() throws WxPayException { if (StringUtils.isBlank(this.getKeyPath())) { throw new WxPayException("请确保证书文件地址keyPath已配置"); } - - final String prefix = "classpath:"; - String fileHasProblemMsg = String.format(PROBLEM_MSG, this.getKeyPath()); - String fileNotFoundMsg = String.format(NOT_FOUND_MSG, this.getKeyPath()); - if (this.getKeyPath().startsWith(prefix)) { - String path = RegExUtils.removeFirst(this.getKeyPath(), prefix); - if (!path.startsWith("/")) { - path = "/" + path; - } - try { - inputStream = ResourcesUtil.getResourceAsStream(path); - if (inputStream == null) { - throw new WxPayException(fileNotFoundMsg); - } - } catch (Exception e) { - throw new WxPayException(fileNotFoundMsg, e); - } - } else if (this.getKeyPath().startsWith("http://") || this.getKeyPath().startsWith("https://")) { - try { - inputStream = new URL(this.keyPath).openStream(); - if (inputStream == null) { - throw new WxPayException(fileNotFoundMsg); - } - } catch (IOException e) { - throw new WxPayException(fileNotFoundMsg, e); - } - } else { - try { - File file = new File(this.getKeyPath()); - if (!file.exists()) { - throw new WxPayException(fileNotFoundMsg); - } - - inputStream = new FileInputStream(file); - } catch (IOException e) { - throw new WxPayException(fileHasProblemMsg, e); - } - } + inputStream = this.loadConfigInputStream(this.getKeyPath()); } try { @@ -275,39 +238,8 @@ public CloseableHttpClient initApiV3HttpClient() throws WxPayException { throw new WxPayException("请确保apiV3Key值已设置"); } - InputStream keyInputStream = null; - InputStream certInputStream = null; - final String prefix = "classpath:"; - if (privateKeyPath.startsWith(prefix)) { - String keypath = RegExUtils.removeFirst(privateKeyPath, prefix); - if (!keypath.startsWith("/")) { - keypath = "/" + keypath; - } - try { - keyInputStream = ResourcesUtil.getResourceAsStream(keypath); - if (keyInputStream == null) { - throw new WxPayException(String.format(NOT_FOUND_MSG, this.getPrivateKeyPath())); - } - } catch (Exception e) { - throw new WxPayException(String.format(NOT_FOUND_MSG, this.getPrivateKeyPath()), e); - } - } - - if (privateCertPath.startsWith(prefix)) { - String certpath = RegExUtils.removeFirst(privateCertPath, prefix); - if (!certpath.startsWith("/")) { - certpath = "/" + certpath; - } - try { - certInputStream = ResourcesUtil.getResourceAsStream(certpath); - if (certInputStream == null) { - throw new WxPayException(String.format(NOT_FOUND_MSG, this.getPrivateCertPath())); - } - } catch (Exception e) { - throw new WxPayException(String.format(NOT_FOUND_MSG, this.getPrivateCertPath()), e); - } - } - + InputStream keyInputStream = this.loadConfigInputStream(privateKeyPath); + InputStream certInputStream = this.loadConfigInputStream(privateCertPath); try { PrivateKey merchantPrivateKey = PemUtils.loadPrivateKey(keyInputStream); @@ -323,10 +255,59 @@ public CloseableHttpClient initApiV3HttpClient() throws WxPayException { .build(); this.apiV3HttpClient = httpClient; this.verifier=verifier; + this.privateKey = merchantPrivateKey; return httpClient; } catch (Exception e) { throw new WxPayException("v3请求构造异常!", e); } } + + /** + * 从配置路径 加载配置 信息(支持 classpath、本地路径、网络url) + * @param configPath 配置路径 + * @return + * @throws WxPayException + */ + private InputStream loadConfigInputStream(String configPath) throws WxPayException { + InputStream inputStream; + final String prefix = "classpath:"; + String fileHasProblemMsg = String.format(PROBLEM_MSG, configPath); + String fileNotFoundMsg = String.format(NOT_FOUND_MSG, configPath); + if (configPath.startsWith(prefix)) { + String path = RegExUtils.removeFirst(configPath, prefix); + if (!path.startsWith("/")) { + path = "/" + path; + } + try { + inputStream = ResourcesUtil.getResourceAsStream(path); + if (inputStream == null) { + throw new WxPayException(fileNotFoundMsg); + } + } catch (Exception e) { + throw new WxPayException(fileNotFoundMsg, e); + } + } else if (configPath.startsWith("http://") || configPath.startsWith("https://")) { + try { + inputStream = new URL(configPath).openStream(); + if (inputStream == null) { + throw new WxPayException(fileNotFoundMsg); + } + } catch (IOException e) { + throw new WxPayException(fileNotFoundMsg, e); + } + } else { + try { + File file = new File(configPath); + if (!file.exists()) { + throw new WxPayException(fileNotFoundMsg); + } + + inputStream = new FileInputStream(file); + } catch (IOException e) { + throw new WxPayException(fileHasProblemMsg, e); + } + } + return inputStream; + } } From 2b27ee9c9f04986060b4cd86d420eb1623a8ffa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=BE=E6=B5=A9?= Date: Mon, 17 Aug 2020 20:14:28 +0800 Subject: [PATCH 2/5] =?UTF-8?q?new:=E7=94=B5=E5=95=86=E6=94=B6=E4=BB=98?= =?UTF-8?q?=E9=80=9A=E4=BA=8C=E7=BA=A7=E5=95=86=E6=88=B7=E8=BF=9B=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bean/ecommerce/ApplymentsRequest.java | 796 ++++++++++++++++++ .../bean/ecommerce/ApplymentsResult.java | 43 + .../ecommerce/ApplymentsStatusResult.java | 308 +++++++ .../binarywang/wxpay/config/WxPayConfig.java | 7 +- .../wxpay/service/EcommerceService.java | 55 ++ .../wxpay/service/WxPayService.java | 6 + .../service/impl/BaseWxPayServiceImpl.java | 6 + .../service/impl/EcommerceServiceImpl.java | 52 ++ .../wxpay/v3/util/RsaCryptoUtil.java | 4 +- 9 files changed, 1272 insertions(+), 5 deletions(-) create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsRequest.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsResult.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsStatusResult.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsRequest.java new file mode 100644 index 0000000000..92e069f631 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsRequest.java @@ -0,0 +1,796 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.github.binarywang.wxpay.v3.SpecEncrypt; +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + + +/** + *
+ * 电商平台,可使用该接口,帮助其二级商户进件成为微信支付商户。
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/applyments/chapter3_1.shtml
+ * 
+ */ +@Data +@NoArgsConstructor +public class ApplymentsRequest implements Serializable { + /** + *
+	 * 字段名:业务申请编号
+	 * 变量名:out_request_no
+	 * 是否必填:是
+	 * 类型:string(124)
+	 * 描述:
+	 *  1、服务商自定义的商户唯一编号。
+	 *  2、每个编号对应一个申请单,每个申请单审核通过后会生成一个微信支付商户号。
+	 *  3、若申请单被驳回,可填写相同的“业务申请编号”,即可覆盖修改原申请单信息 。
+	 *  示例值:APPLYMENT_00000000001
+	 * 
+ */ + @SerializedName(value = "out_request_no") + private String outRequestNo; + + /** + *
+	 * 字段名:主体类型
+	 * 变量名:organization_type
+	 * 是否必填:是
+	 * 类型:string(4)
+	 * 描述:
+	 *  枚举值:
+	 *  2401:小微商户,指无营业执照的商户。
+	 *  4:个体工商户,营业执照上的主体类型一般为个体户、个体工商户、个体经营。
+	 *  2:企业,营业执照上的主体类型一般为有限公司、有限责任公司。
+	 *  3:党政、机关及事业单位,包括国内各级、各类政府机构、事业单位等(如:公安、党团、司法、交通、旅游、工商税务、市政、医疗、教育、学校等机构)。
+	 *  1708:其他组织,不属于企业、政府/事业单位的组织机构(如社会团体、民办非企业、基金会),要求机构已办理组织机构代码证。
+	 *  示例值:2401
+	 * 
+ */ + @SerializedName(value = "organization_type") + private String organizationType; + + /** + *
+	 * 字段名:+营业执照/登记证书信息
+	 * 变量名:business_license_info
+	 * 是否必填:条件选填
+	 * 类型:object
+	 * 描述:
+	 *  1、主体为“小微”时,不填。
+	 *  2、主体为“个体工商户/企业”时,请上传营业执照。
+	 *  3、主体为“党政、机关及事业单位/其他组织”时,请上传登记证书。
+	 * 
+ */ + @SerializedName(value = "business_license_info") + private BusinessLicenseInfo businessLicenseInfo; + + /** + *
+	 * 字段名:+组织机构代码证信息
+	 * 变量名:organization_cert_info
+	 * 是否必填:条件选填
+	 * 类型:object
+	 * 描述:主体为企业/党政、机关及事业单位/其他组织,且证件号码不是18位时必填。
+	 * 
+ */ + @SerializedName(value = "organization_cert_info") + private OrganizationCertInfo organizationCertInfo; + + /** + *
+	 * 字段名:经营者/法人证件类型
+	 * 变量名:id_doc_type
+	 * 是否必填:否
+	 * 类型:string(64)
+	 * 描述:
+	 *  1、主体为“小微”,只可选择:身份证。
+	 *  2、主体为“个体户/企业/党政、机关及事业单位/其他组织”,可选择:任一证件类型。
+	 *  3、若没有填写,系统默认选择:身份证。
+	 *  枚举值:
+	 *  IDENTIFICATION_TYPE_MAINLAND_IDCARD:中国大陆居民-身份证
+	 *  IDENTIFICATION_TYPE_OVERSEA_PASSPORT:其他国家或地区居民-护照
+	 *  IDENTIFICATION_TYPE_HONGKONG:中国香港居民–来往内地通行证
+	 *  IDENTIFICATION_TYPE_MACAO:中国澳门居民–来往内地通行证
+	 *  IDENTIFICATION_TYPE_TAIWAN:中国台湾居民–来往大陆通行证
+	 *  示例值:IDENTIFICATION_TYPE_MACAO
+	 * 
+ */ + @SerializedName(value = "id_doc_type") + private String idDocType; + + /** + *
+	 * 字段名:+经营者/法人身份证信息
+	 * 变量名:id_card_info
+	 * 是否必填:条件选填
+	 * 类型:object
+	 * 描述:
+	 *  请填写经营者/法人的身份证信息
+	 *  证件类型为“身份证”时填写。
+	 *
+	 * 
+ */ + @SerializedName(value = "id_card_info") + @SpecEncrypt + private IdCardInfo idCardInfo; + + /** + *
+	 * 字段名:+经营者/法人其他类型证件信息
+	 * 变量名:id_doc_info
+	 * 是否必填:条件选填
+	 * 类型:object
+	 * 描述:证件类型为“来往内地通行证、来往大陆通行证、护照”时填写。
+	 * 
+ */ + @SerializedName(value = "id_doc_info") + private IdDocInfo idDocInfo; + + /** + *
+	 * 字段名:是否填写结算账户信息
+	 * 变量名:need_account_info
+	 * 是否必填:是
+	 * 类型:bool
+	 * 描述:
+	 *  可根据实际情况,填写“true”或“false”。
+	 *  1、若为“true”,则需填写结算账户信息。
+	 *  2、若为“false”,则无需填写结算账户信息。
+	 *  示例值:true
+	 * 
+ */ + @SerializedName(value = "need_account_info") + private Boolean needAccountInfo; + + /** + *
+	 * 字段名:+结算账户信息
+	 * 变量名:account_info
+	 * 是否必填:条件选填
+	 * 类型:object
+	 * 描述:若"是否填写结算账户信息"填写为“true”, 则必填,填写为“false”不填 。
+	 * 
+ */ + @SerializedName(value = "account_info") + @SpecEncrypt + private AccountInfo accountInfo; + + /** + *
+	 * 字段名:+超级管理员信息
+	 * 变量名:contact_info
+	 * 是否必填:是
+	 * 类型:object
+	 * 描述:
+	 *  请填写店铺的超级管理员信息。
+	 *  超级管理员需在开户后进行签约,并可接收日常重要管理信息和进行资金操作,请确定其为商户法定代表人或负责人。
+	 * 
+ */ + @SerializedName(value = "contact_info") + @SpecEncrypt + private ContactInfo contactInfo; + + /** + *
+	 * 字段名:+店铺信息
+	 * 变量名:sales_scene_info
+	 * 是否必填:是
+	 * 类型:object
+	 * 描述:请填写店铺信息
+	 * 
+ */ + @SerializedName(value = "sales_scene_info") + private SalesSceneInfo salesSceneInfo; + + /** + *
+	 * 字段名:商户简称
+	 * 变量名:merchant_shortname
+	 * 是否必填:是
+	 * 类型:string(64)
+	 * 描述:
+	 *  UTF-8格式,中文占3个字节,即最多16个汉字长度。将在支付完成页向买家展示,需与商家的实际售卖商品相符 。
+	 *  示例值:腾讯
+	 * 
+ */ + @SerializedName(value = "merchant_shortname") + private String merchantShortname; + + /** + *
+	 * 字段名:特殊资质
+	 * 变量名:qualifications
+	 * 是否必填:否
+	 * 类型:string(1024)
+	 * 描述:
+	 *  1、若店铺业务包含互联网售药,则需上传特殊资质-《互联网药品交易服务证》。
+	 *  2、最多可上传5张照片,请填写通过图片上传接口预先上传图片生成好的MediaID 。
+	 *  示例值:[\"jTpGmxUX3FBWVQ5NJInE4d2I6_H7I4\"]
+	 * 
+ */ + @SerializedName(value = "qualifications") + private String qualifications; + + /** + *
+	 * 字段名:补充材料
+	 * 变量名:business_addition_pics
+	 * 是否必填:否
+	 * 类型:string(1024)
+	 * 描述:
+	 *   最多可上传5张照片,请填写通过图片上传接口预先上传图片生成好的MediaID 。
+	 *  示例值:[\"jTpGmg05InE4d2I6_H7I4\"]
+	 * 
+ */ + @SerializedName(value = "business_addition_pics") + private String businessAdditionPics; + + /** + *
+	 * 字段名:补充说明
+	 * 变量名:business_addition_desc
+	 * 是否必填:否
+	 * 类型:string(256)
+	 * 描述:
+	 *   可填写512字以内 。
+	 *  示例值:特殊情况,说明原因
+	 * 
+ */ + @SerializedName(value = "business_addition_desc") + private String businessAdditionDesc; + + @Data + @NoArgsConstructor + public static class BusinessLicenseInfo implements Serializable{ + /** + *
+		 * 字段名:证件扫描件
+		 * 变量名:business_license_copy
+		 * 是否必填:是
+		 * 类型:string(256)
+		 * 描述:
+		 *  1、主体为“个体工商户/企业”时,请上传营业执照的证件图片。
+		 *  2、主体为“党政、机关及事业单位/其他组织”时,请上传登记证书的证件图片。
+		 *  3、可上传1张图片,请填写通过图片上传接口预先上传图片生成好的MediaID 。
+		 *  4、图片要求:
+		 *  (1)请上传证件的彩色扫描件或彩色数码拍摄件,黑白复印件需加盖公章(公章信息需完整) 。
+		 *  (2)不得添加无关水印(非微信支付商户申请用途的其他水印)。
+		 *  (3)需提供证件的正面拍摄件,完整、照面信息清晰可见。信息不清晰、扭曲、压缩变形、反光、不完整均不接受。
+		 *  (4)不接受二次剪裁、翻拍、PS的证件照片。
+		 *  示例值: 47ZC6GC-vnrbEny__Ie_An5-tCpqxucuxi-vByf3Gjm7KE53JXvGy9tqZm2XAUf-4KGprrKhpVBDIUv0OF4wFNIO4kqg05InE4d2I6_H7I4
+		 * 
+ */ + @SerializedName(value = "business_license_copy") + private String businessLicenseCopy; + + /** + *
+		 * 字段名:证件注册号
+		 * 变量名:business_license_number
+		 * 是否必填:是
+		 * 类型:string(18)
+		 * 描述:
+		 *  1、主体为“个体工商户/企业”时,请填写营业执照上的注册号/统一社会信用代码,须为15位数字或 18位数字|大写字母。
+		 *  2、主体为“党政、机关及事业单位/其他组织”时,请填写登记证书的证书编号。
+		 *  示例值:123456789012345678
+		 *  特殊规则:长度最小15个字节
+		 * 
+ */ + @SerializedName(value = "business_license_number") + private String businessLicenseNumber; + + /** + *
+		 * 字段名:商户名称
+		 * 变量名:merchant_name
+		 * 是否必填:是
+		 * 类型:string(128)
+		 * 描述:
+		 *  1、请填写营业执照/登记证书的商家名称,2~110个字符,支持括号 。
+		 *  2、个体工商户/党政、机关及事业单位,不能以“公司”结尾。
+		 *  3、个体工商户,若营业执照上商户名称为空或为“无”,请填写"个体户+经营者姓名",如“个体户张三” 。
+		 *  示例值:腾讯科技有限公司
+		 * 
+ */ + @SerializedName(value = "merchant_name") + private String merchantName; + + /** + *
+		 * 字段名:经营者/法定代表人姓名
+		 * 变量名:legal_person
+		 * 是否必填:是
+		 * 类型:string(128)
+		 * 描述:
+		 *  请填写证件的经营者/法定代表人姓名
+		 *  示例值:张三
+		 * 
+ */ + @SerializedName(value = "legal_person") + private String legalPerson; + + /** + *
+		 * 字段名:注册地址
+		 * 变量名:company_address
+		 * 是否必填:条件选填
+		 * 类型:string(128)
+		 * 描述:
+		 *  主体为“党政、机关及事业单位/其他组织”时必填,请填写登记证书的注册地址。
+		 *  示例值:深圳南山区科苑路
+		 * 
+ */ + @SerializedName(value = "company_address") + private String companyAddress; + + /** + *
+		 * 字段名:营业期限
+		 * 变量名:business_time
+		 * 是否必填:条件选填
+		 * 类型:string(256)
+		 * 描述:
+		 *  1、主体为“党政、机关及事业单位/其他组织”时必填,请填写证件有效期。
+		 *  2、若证件有效期为长期,请填写:长期。
+		 *  3、结束时间需大于开始时间。
+		 *  4、有效期必须大于60天,即结束时间距当前时间需超过60天。
+		 *  示例值:[\"2014-01-01\",\"长期\"]
+		 * 
+ */ + @SerializedName(value = "business_time") + private String businessTime; + + } + + @Data + @NoArgsConstructor + public static class OrganizationCertInfo implements Serializable { + /** + *
+		 * 字段名:组织机构代码证照片
+		 * 变量名:organization_copy
+		 * 是否必填:是
+		 * 类型:string(256)
+		 * 描述:
+		 *  可上传1张图片,请填写通过图片上传接口预先上传图片生成好的MediaID。
+		 *  示例值:vByf3Gjm7KE53JXv\prrKhpVBDIUv0OF4wFNIO4kqg05InE4d2I6_H7I4
+		 * 
+ */ + @SerializedName(value = "organization_copy") + private String organizationCopy; + + /** + *
+		 * 字段名:组织机构代码
+		 * 变量名:organization_number
+		 * 是否必填:是
+		 * 类型:string(256)
+		 * 描述:
+		 *  1、请填写组织机构代码证上的组织机构代码。
+		 *  2、可填写9或10位 数字|字母|连字符。
+		 *  示例值:12345679-A
+		 * 
+ */ + @SerializedName(value = "organization_number") + private String organizationNumber; + + /** + *
+		 * 字段名:组织机构代码有效期限
+		 * 变量名:organization_time
+		 * 是否必填:是
+		 * 类型:string(256)
+		 * 描述:
+		 *  1、请填写组织机构代码证的有效期限,注意参照示例中的格式。
+		 *  2、若证件有效期为长期,请填写:长期。
+		 *  3、结束时间需大于开始时间。
+		 *  4、有效期必须大于60天,即结束时间距当前时间需超过60天。
+		 *  示例值:[\"2014-01-01\",\"长期\"]
+		 * 
+ */ + @SerializedName(value = "organization_time") + private String organizationTime; + + } + + @Data + @NoArgsConstructor + public static class IdCardInfo implements Serializable { + /** + *
+		 * 字段名:身份证人像面照片
+		 * 变量名:id_card_copy
+		 * 是否必填:是
+		 * 类型:string(256)
+		 * 描述:
+		 *  1、请上传经营者/法定代表人的身份证人像面照片。
+		 *  2、可上传1张图片,请填写通过图片上传接口预先上传图片生成好的MediaID。
+		 *  示例值:xpnFuAxhBTEO_PvWkfSCJ3zVIn001D8daLC-ehEuo0BJqRTvDujqhThn4ReFxikqJ5YW6zFQ
+		 * 
+ */ + @SerializedName(value = "id_card_copy") + private String idCardCopy; + + /** + *
+		 * 字段名:身份证国徽面照片
+		 * 变量名:id_card_national
+		 * 是否必填:是
+		 * 类型:string(256)
+		 * 描述:
+		 *  1、请上传经营者/法定代表人的身份证国徽面照片。
+		 *  2、可上传1张图片,请填写通过图片上传接口预先上传图片生成好的MediaID 。
+		 *  示例值:vByf3Gjm7KE53JXvGy9tqZm2XAUf-4KGprrKhpVBDIUv0OF4wFNIO4kqg05InE4d2I6_H7I4
+		 * 
+ */ + @SerializedName(value = "id_card_national") + private String idCardNational; + + /** + *
+		 * 字段名:身份证姓名
+		 * 变量名:id_card_name
+		 * 是否必填:是
+		 * 类型:string(256)
+		 * 描述:
+		 *  1、请填写经营者/法定代表人对应身份证的姓名,2~30个中文字符、英文字符、符号。
+		 *  2、该字段需进行加密处理,加密方法详见敏感信息加密说明。
+		 *  示例值:pVd1HJ6v/69bDnuC4EL5Kz4jBHLiCa8MRtelw/wDa4SzfeespQO/0kjiwfqdfg==
+		 *  字段加密:使用APIv3定义的方式加密
+		 * 
+ */ + @SerializedName(value = "id_card_name") + @SpecEncrypt + private String idCardName; + + /** + *
+		 * 字段名:身份证号码
+		 * 变量名:id_card_number
+		 * 是否必填:是
+		 * 类型:string(18)
+		 * 描述:
+		 *  1、请填写经营者/法定代表人对应身份证的号码。
+		 *  2、15位数字或17位数字+1位数字|X ,该字段需进行加密处理,加密方法详见敏感信息加密说明。
+		 *  示例值:zV+BEmytMNQCqQ8juwEc4P4TG5xzchG/5IL9DBd+Z0zZXkw==4
+		 *  特殊规则:长度最小15个字节
+		 * 
+ */ + @SerializedName(value = "id_card_number") + @SpecEncrypt + private String idCardNumber; + + /** + *
+		 * 字段名:身份证有效期限
+		 * 变量名:id_card_valid_time
+		 * 是否必填:是
+		 * 类型:string(128)
+		 * 描述:
+		 *  1、请填写身份证有效期的结束时间,注意参照示例中的格式。
+		 *  2、若证件有效期为长期,请填写:长期。
+		 *  3、证件有效期需大于60天。
+		 *  示例值:2026-06-06,长期
+		 * 
+ */ + @SerializedName(value = "id_card_valid_time") + private String idCardValidTime; + + } + + @Data + @NoArgsConstructor + public static class IdDocInfo implements Serializable { + /** + *
+		 * 字段名:证件姓名
+		 * 变量名:id_doc_name
+		 * 是否必填:是
+		 * 类型:string(128)
+		 * 描述:
+		 *  请填写经营者/法人姓名。
+		 *  示例值:jTpGmxUX3FBWVQ5NJTZvlKX_gdU4LC-ehEuo0BJqRTvDujqhThn4ReFxikqJ5YW6zFQ
+		 * 
+ */ + @SerializedName(value = "id_doc_name") + private String idDocName; + + /** + *
+		 * 字段名:证件号码
+		 * 变量名:id_doc_number
+		 * 是否必填:是
+		 * 类型:string(128)
+		 * 描述:
+		 *  7~11位 数字|字母|连字符 。
+		 *  示例值:jTpGmxUX3FBWVQ5NJTZvlKX_go0BJqRTvDujqhThn4ReFxikqJ5YW6zFQ
+		 * 
+ */ + @SerializedName(value = "id_doc_number") + private String idDocNumber; + + /** + *
+		 * 字段名:证件照片
+		 * 变量名:id_doc_copy
+		 * 是否必填:是
+		 * 类型:string(256)
+		 * 描述:
+		 *  1、可上传1张图片,请填写通过图片上传接口预先上传图片生成好的MediaID。
+		 *  2、2M内的彩色图片,格式可为bmp、png、jpeg、jpg或gif 。
+		 *  示例值:xi-vByf3Gjm7KE53JXvGy9tqZm2XAUf-4KGprrKhpVBDIUv0OF4wFNIO4kqg05InE4d2I6_H7I4
+		 * 
+ */ + @SerializedName(value = "id_doc_copy") + private String idDocCopy; + + /** + *
+		 * 字段名:证件结束日期
+		 * 变量名:doc_period_end
+		 * 是否必填:是
+		 * 类型:string(128)
+		 * 描述:
+		 *  1、请按照示例值填写。
+		 *  2、若证件有效期为长期,请填写:长期。
+		 *  3、证件有效期需大于60天 。
+		 *  示例值:2020-01-02
+		 * 
+ */ + @SerializedName(value = "doc_period_end") + private String docPeriodEnd; + + } + + @Data + @NoArgsConstructor + public static class AccountInfo implements Serializable { + /** + *
+		 * 字段名:账户类型
+		 * 变量名:bank_account_type
+		 * 是否必填:是
+		 * 类型:string(2)
+		 * 描述:
+		 *  1、若主体为企业/党政、机关及事业单位/其他组织,可填写:74-对公账户。
+		 *  2、若主体为小微,可填写:75-对私账户。
+		 *  3、若主体为个体工商户,可填写:74-对公账户或75-对私账户。
+		 *  示例值:75
+		 * 
+ */ + @SerializedName(value = "bank_account_type") + private String bankAccountType; + + /** + *
+		 * 字段名:开户银行
+		 * 变量名:account_bank
+		 * 是否必填:是
+		 * 类型:string(10)
+		 * 描述:
+		 *  详细参见开户银行对照表。
+		 *  示例值:工商银行
+		 * 
+ */ + @SerializedName(value = "account_bank") + private String accountBank; + + /** + *
+		 * 字段名:开户名称
+		 * 变量名:account_name
+		 * 是否必填:是
+		 * 类型:string(128)
+		 * 描述:
+		 *  1、选择经营者个人银行卡时,开户名称必须与身份证姓名一致。
+		 *  2、选择对公账户时,开户名称必须与营业执照上的“商户名称”一致。
+		 *  3、该字段需进行加密处理,加密方法详见敏感信息加密说明。
+		 *  示例值:AOZdYGISxo4yw96uY1Pk7Rq79Jtt7+I8juwEc4P4TG5xzchG/5IL9DBd+Z0zZXkw==
+		 * 
+ */ + @SerializedName(value = "account_name") + @SpecEncrypt + private String accountName; + + /** + *
+		 * 字段名:开户银行省市编码
+		 * 变量名:bank_address_code
+		 * 是否必填:是
+		 * 类型:string(12)
+		 * 描述:
+		 *  至少精确到市,详细参见省市区编号对照表。
+		 *  示例值:110000
+		 * 
+ */ + @SerializedName(value = "bank_address_code") + private String bankAddressCode; + + /** + *
+		 * 字段名:开户银行联行号
+		 * 变量名:bank_branch_id
+		 * 是否必填:条件选填
+		 * 类型:string(64)
+		 * 描述:
+		 *  1、17家直连银行无需填写,如为其他银行,开户银行全称(含支行)和开户银行联行号二选一。
+		 *  2、详细参见开户银行全称(含支行)对照表。
+		 *  示例值:402713354941
+		 * 
+ */ + @SerializedName(value = "bank_branch_id") + private String bankBranchId; + + /** + *
+		 * 字段名:开户银行全称 (含支行)
+		 * 变量名:bank_name
+		 * 是否必填:条件选填
+		 * 类型:string(128)
+		 * 描述:
+		 *  1、17家直连银行无需填写,如为其他银行,开户银行全称(含支行)和开户银行联行号二选一。
+		 *  2、需填写银行全称,如"深圳农村商业银行XXX支行" 。
+		 *  3、详细参见开户银行全称(含支行)对照表。
+		 *  示例值:施秉县农村信用合作联社城关信用社
+		 * 
+ */ + @SerializedName(value = "bank_name") + private String bankName; + + /** + *
+		 * 字段名:银行帐号
+		 * 变量名:account_number
+		 * 是否必填:是
+		 * 类型:string(128)
+		 * 描述:
+		 *  1、数字,长度遵循系统支持的对公/对私卡号长度要求表。
+		 *  2、该字段需进行加密处理,加密方法详见敏感信息加密说明。
+		 *  示例值: d+xT+MQCvrLHUVDWv/8MR/dB7TkXLVfSrUxMPZy6jWWYzpRrEEaYQE8ZRGYoeorwC+w==
+		 * 
+ */ + @SerializedName(value = "account_number") + @SpecEncrypt + private String accountNumber; + + } + + @Data + @NoArgsConstructor + public static class ContactInfo implements Serializable { + /** + *
+		 * 字段名:超级管理员类型
+		 * 变量名:contact_type
+		 * 是否必填:是
+		 * 类型:string(2)
+		 * 描述:
+		 *  1、小微商户,选择:65-法人/经营者。
+		 *  2、个体工商户/企业/党政、机关及事业单位/其他组织,可选择:65-法人/经营者、66- 负责人。 (负责人:经商户授权办理微信支付业务的人员,授权范围包括但不限于签约,入驻过程需完成账户验证)。
+		 *  示例值:65
+		 * 
+ */ + @SerializedName(value = "contact_type") + private String contactType; + + /** + *
+		 * 字段名:超级管理员姓名
+		 * 变量名:contact_name
+		 * 是否必填:是
+		 * 类型:string(256)
+		 * 描述:
+		 *  1、若管理员类型为“法人”,则该姓名需与法人身份证姓名一致。
+		 *  2、若管理员类型为“经办人”,则可填写实际经办人的姓名。
+		 *  3、该字段需进行加密处理,加密方法详见敏感信息加密说明。
+		 *  (后续该管理员需使用实名微信号完成签约)
+		 *  示例值: pVd1HJ6zyvPedzGaV+X3IdGdbDnuC4Eelw/wDa4SzfeespQO/0kjiwfqdfg==
+		 * 
+ */ + @SerializedName(value = "contact_name") + @SpecEncrypt + private String contactName; + + /** + *
+		 * 字段名:超级管理员身份证件号码
+		 * 变量名:contact_id_card_number
+		 * 是否必填:是
+		 * 类型:string(256)
+		 * 描述:
+		 *  1、若管理员类型为法人,则该身份证号码需与法人身份证号码一致。若管理员类型为经办人,则可填写实际经办人的身份证号码。
+		 *  2、可传身份证、来往内地通行证、来往大陆通行证、护照等证件号码。
+		 *  3、超级管理员签约时,校验微信号绑定的银行卡实名信息,是否与该证件号码一致。
+		 *  4、该字段需进行加密处理,加密方法详见敏感信息加密说明。
+		 *  示例值:pVd1HJ6zmty7/mYNxLMpRSvMRtelw/wDa4SzfeespQO/0kjiwfqdfg==
+		 * 
+ */ + @SerializedName(value = "contact_id_card_number") + @SpecEncrypt + private String contactIdCardNumber; + + /** + *
+		 * 字段名:超级管理员手机
+		 * 变量名:mobile_phone
+		 * 是否必填:是
+		 * 类型:string(256)
+		 * 描述:
+		 *  1、请填写管理员的手机号,11位数字, 用于接收微信支付的重要管理信息及日常操作验证码 。
+		 *  2、该字段需进行加密处理,加密方法详见敏感信息加密说明。
+		 *  示例值:pVd1HJ6zyvPedzGaV+X3qtmrq9bb9tPROvwia4ibL+F6mfjbzQIzfb3HHLEjZ4YiNWWNeespQO/0kjiwfqdfg==
+		 * 
+ */ + @SerializedName(value = "mobile_phone") + @SpecEncrypt + private String mobilePhone; + + /** + *
+		 * 字段名:超级管理员邮箱
+		 * 变量名:contact_email
+		 * 是否必填:是
+		 * 类型:string(256)
+		 * 描述:
+		 *  1、用于接收微信支付的开户邮件及日常业务通知。
+		 *  2、需要带@,遵循邮箱格式校验 。
+		 *  3、该字段需进行加密处理,加密方法详见敏感信息加密说明。
+		 *  示例值:pVd1HJ6zyvPedzGaV+X3qtmrq9bb9tPROvwia4ibL+FWWNUlw/wDa4SzfeespQO/0kjiwfqdfg==
+		 * 
+ */ + @SerializedName(value = "contact_email") + @SpecEncrypt + private String contactEmail; + + } + + @Data + @NoArgsConstructor + public static class SalesSceneInfo implements Serializable { + /** + *
+		 * 字段名:店铺名称
+		 * 变量名:store_name
+		 * 是否必填:是
+		 * 类型:string(256)
+		 * 描述:
+		 *  请填写店铺全称。
+		 *  示例值:爱烧烤
+		 * 
+ */ + @SerializedName(value = "store_name") + private String storeName; + + /** + *
+		 * 字段名:店铺链接
+		 * 变量名:store_url
+		 * 是否必填:二选一
+		 * 类型:string(1024)
+		 * 描述:
+		 *  1、店铺二维码or店铺链接二选一必填。
+		 *  2、请填写店铺主页链接,需符合网站规范。
+		 *  示例值:http://www.qq.com
+		 * 
+ */ + @SerializedName(value = "store_url") + private String storeUrl; + + /** + *
+		 * 字段名:店铺二维码
+		 * 变量名:store_qr_code
+		 * 是否必填:1、店铺二维码 or 店铺链接二选一必填。 2、若为电商小程序,可上传店铺页面的小程序二维码。 3、请填写通过图片上传接口预先上传图片生成好的MediaID,仅能上传1张图片 。 示例值:jTpGmxUX3FBWVQ5NJTZvlKX_gdU4cRz7z5NxpnFuAxhBTEO1D8daLC-ehEuo0BJqRTvDujqhThn4ReFxikqJ5YW6zFQ
+		 * 类型:string(256)
+		 * 描述:
+		 * 
+ */ + @SerializedName(value = "store_qr_code") + private String storeQrCode; + + } + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsResult.java new file mode 100644 index 0000000000..dd2d46122c --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsResult.java @@ -0,0 +1,43 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 二级商户进件 提交申请结果响应 + */ +@Data +@NoArgsConstructor +public class ApplymentsResult implements Serializable { + + /** + *
+   * 字段名:微信支付申请单号
+   * 变量名:applyment_id
+   * 是否必填:是
+   * 类型:uint64
+   * 描述:
+   *  微信支付分配的申请单号 。
+   *  示例值:2000002124775691
+   * 
+ */ + @SerializedName(value = "applyment_id") + private String applymentId; + + /** + *
+   * 字段名:业务申请编号
+   * 变量名:out_request_no
+   * 是否必填:是
+   * 类型:string(124)
+   * 描述:
+   *  服务商自定义的商户唯一编号。每个编号对应一个申请单,每个申请单审核通过后会生成一个微信支付商户号。
+   *  示例值:APPLYMENT_00000000001
+   * 
+ */ + @SerializedName(value = "out_request_no") + private String outRequestNo; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsStatusResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsStatusResult.java new file mode 100644 index 0000000000..00967cfa67 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsStatusResult.java @@ -0,0 +1,308 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +@Data +@NoArgsConstructor +public class ApplymentsStatusResult implements Serializable { + /** + *
+   * 字段名:申请状态
+   * 变量名:applyment_state
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  枚举值:
+   *  CHECKING:资料校验中
+   *  ACCOUNT_NEED_VERIFY:待账户验证
+   *  AUDITING:审核中
+   *  REJECTED:已驳回
+   *  NEED_SIGN:待签约
+   *  FINISH:完成
+   *  FROZEN:已冻结
+   *  示例值:FINISH
+   * 
+ */ + @SerializedName(value = "applyment_state") + private String applymentState; + + /** + *
+   * 字段名:申请状态描述
+   * 变量名:applyment_state_desc
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  申请状态描述
+   *  示例值:“审核中”
+   * 
+ */ + @SerializedName(value = "applyment_state_desc") + private String applymentStateDesc; + + /** + *
+   * 字段名:签约链接
+   * 变量名:sign_url
+   * 是否必填:否
+   * 类型:string(256)
+   * 描述:
+   *  1、当申请状态为NEED_SIGN时才返回。
+   *  2、建议将链接转为二维码展示,需让申请单-管理者用微信扫码打开,完成签约。
+   *  示例值:https://pay.weixin.qq.com/public/apply4ec_sign/s?applymentId=2000002126198476&sign=b207b673049a32c858f3aabd7d27c7ec
+   * 
+ */ + @SerializedName(value = "sign_url") + private String signUrl; + + /** + *
+   * 字段名:电商平台二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  当申请状态为NEED_SIGN或FINISH时才返回。
+   *  示例值:1542488631
+   * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+   * 字段名:+汇款账户验证信息
+   * 变量名:account_validation
+   * 是否必填:否
+   * 类型:object
+   * 描述:当申请状态为ACCOUNT_NEED_VERIFY 时有返回,可根据指引汇款,完成账户验证。
+   * 
+ */ + @SerializedName(value = "account_validation") + private AccountValidation accountValidation; + + /** + *
+   * 字段名:+驳回原因详情
+   * 变量名:audit_detail
+   * 是否必填:否
+   * 类型:array
+   * 描述:各项资料的审核情况。当申请状态为REJECTED或 FROZEN时才返回。
+   * 
+ */ + @SerializedName(value = "audit_detail") + private List auditDetail; + + /** + *
+   * 字段名:法人验证链接
+   * 变量名:legal_validation_url
+   * 是否必填:否
+   * 类型:string(256)
+   * 描述:
+   *  1、当申请状态为
+   *  ACCOUNT_NEED_VERIFY,且通过系统校验的申请单,将返回链接。
+   *  2、建议将链接转为二维码展示,让商户法人用微信扫码打开,完成账户验证。
+   *  示例值: https://pay.weixin.qq.com/public/apply4ec_sign/s?applymentId=2000002126198476&sign=b207b673049a32c858f3aabd7d27c7ec
+   * 
+ */ + @SerializedName(value = "legal_validation_url") + private String legalValidationUrl; + + /** + *
+   * 字段名:业务申请编号
+   * 变量名:out_request_no
+   * 是否必填:是
+   * 类型:string(124)
+   * 描述:
+   *  提交接口填写的业务申请编号。
+   *  示例值:APPLYMENT_00000000001
+   * 
+ */ + @SerializedName(value = "out_request_no") + private String outRequestNo; + + /** + *
+   * 字段名:微信支付申请单号
+   * 变量名:applyment_id
+   * 是否必填:否
+   * 类型:uint64
+   * 描述:
+   *  微信支付分配的申请单号。
+   *  示例值:2000002124775691
+   * 
+ */ + @SerializedName(value = "applyment_id") + private String applymentId; + + @Data + @NoArgsConstructor + public static class AccountValidation implements Serializable{ + /** + *
+     * 字段名:付款户名
+     * 变量名:account_name
+     * 是否必填:否
+     * 类型:uint64
+     * 描述:
+     *  需商户使用该户名的账户进行汇款。
+     *  示例值: rDdICA3ZYXshYqeOSslSjSMf+MhhC4oaujiISFzq3AE+as7mAEDJly+DgRuVs74msmKUH8pl+3oA==
+     * 
+ */ + @SerializedName(value = "account_name") + private String accountName; + + /** + *
+     * 字段名:付款卡号
+     * 变量名:account_no
+     * 是否必填:否
+     * 类型:string(128)
+     * 描述:
+     *  结算账户为对私时会返回,商户需使用该付款卡号进行汇款。
+     *  示例值:9nZYDEvBT4rDdICA3ZYXshYqeOSslSjSauAE+as7mAEDJly+DgRuVs74msmKUH8pl+3oA==
+     * 
+ */ + @SerializedName(value = "account_no") + private String accountNo; + + /** + *
+     * 字段名:汇款金额
+     * 变量名:pay_amount
+     * 是否必填:否
+     * 类型:string(32)
+     * 描述:
+     *  需要汇款的金额(单位:分)。
+     *  示例值:124
+     * 
+ */ + @SerializedName(value = "pay_amount") + private String payAmount; + + /** + *
+     * 字段名:收款卡号
+     * 变量名:destination_account_number
+     * 是否必填:否
+     * 类型:string(128)
+     * 描述:
+     *  收款账户的卡号
+     *  示例值:7222223333322332
+     * 
+ */ + @SerializedName(value = "destination_account_number") + private String destinationAccountNumber; + + /** + *
+     * 字段名:收款户名
+     * 变量名:destination_account_name
+     * 是否必填:否
+     * 类型:string(128)
+     * 描述:
+     *  收款账户名
+     *  示例值:财付通支付科技有限公司
+     * 
+ */ + @SerializedName(value = "destination_account_name") + private String destinationAccountName; + + /** + *
+     * 字段名:开户银行
+     * 变量名:destination_account_bank
+     * 是否必填:否
+     * 类型:string(128)
+     * 描述:
+     *  收款账户的开户银行名称。
+     *  示例值:招商银行威盛大厦支行
+     * 
+ */ + @SerializedName(value = "destination_account_bank") + private String destinationAccountBank; + + /** + *
+     * 字段名:省市信息
+     * 变量名:city
+     * 是否必填:否
+     * 类型:string(128)
+     * 描述:
+     *  收款账户的省市。
+     *  示例值:深圳
+     * 
+ */ + @SerializedName(value = "city") + private String city; + + /** + *
+     * 字段名:备注信息
+     * 变量名:remark
+     * 是否必填:否
+     * 类型:string(128)
+     * 描述:
+     *  商户汇款时,需要填写的备注信息。
+     *  示例值:入驻账户验证
+     * 
+ */ + @SerializedName(value = "remark") + private String remark; + + /** + *
+     * 字段名:汇款截止时间
+     * 变量名:deadline
+     * 是否必填:否
+     * 类型:string(20)
+     * 描述:
+     *  请在此时间前完成汇款。
+     *  示例值:2018-12-1017:09:01
+     * 
+ */ + @SerializedName(value = "deadline") + private String deadline; + + } + + @Data + @NoArgsConstructor + public static class AuditDetail implements Serializable{ + /** + *
+     * 字段名:参数名称
+     * 变量名:param_name
+     * 是否必填:否
+     * 类型:string(32)
+     * 描述:
+     *  提交申请单的资料项名称。
+     *  示例值:id_card_copy
+     * 
+ */ + @SerializedName(value = "param_name") + private String paramName; + + /** + *
+     * 字段名:驳回原因
+     * 变量名:reject_reason
+     * 是否必填:否
+     * 类型:string(32)
+     * 描述:
+     *  提交资料项被驳回原因。
+     *  示例值:身份证背面识别失败,请上传更清晰的身份证图片
+     * 
+ */ + @SerializedName(value = "reject_reason") + private String rejectReason; + + } +} 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 097a0fa0c7..cd07619f50 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 @@ -130,6 +130,10 @@ public class WxPayConfig { * 私钥信息 */ private PrivateKey privateKey; + /** + * 证书自动更新时间差(分钟),默认一分钟 + */ + private int certAutoUpdateTime = 60; /** * p12证书文件内容的字节数组. */ @@ -245,8 +249,7 @@ public CloseableHttpClient initApiV3HttpClient() throws WxPayException { AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier( new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey)), - apiV3Key.getBytes(StandardCharsets.UTF_8)); - + apiV3Key.getBytes(StandardCharsets.UTF_8), this.getCertAutoUpdateTime()); CloseableHttpClient httpClient = WxPayV3HttpClientBuilder.create() .withMerchant(mchId, certSerialNo, merchantPrivateKey) diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java new file mode 100644 index 0000000000..f1bdb2b3eb --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java @@ -0,0 +1,55 @@ +package com.github.binarywang.wxpay.service; + +import com.github.binarywang.wxpay.bean.ecommerce.ApplymentsRequest; +import com.github.binarywang.wxpay.bean.ecommerce.ApplymentsResult; +import com.github.binarywang.wxpay.bean.ecommerce.ApplymentsStatusResult; +import com.github.binarywang.wxpay.exception.WxPayException; + +/** + *
+ *  电商收付通相关服务类.
+ *  接口规则:https://wechatpay-api.gitbook.io/wechatpay-api-v3
+ * 
+ * + * @author cloudX + * @date 2020/08/17 + */ +public interface EcommerceService { + /** + *
+   * 二级商户进件API
+   * 接口地址: https://api.mch.weixin.qq.com/v3/ecommerce/applyments/
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/applyments/chapter3_1.shtml
+   *
+   * 
+ * + * @param request 请求对象 + * @return . + */ + ApplymentsResult createApply(ApplymentsRequest request) throws WxPayException; + + /** + *
+   * 查询申请状态API
+   * 请求URL: https://api.mch.weixin.qq.com/v3/ecommerce/applyments/{applyment_id}
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/applyments/chapter3_2.shtml
+   * 
+ * + * @param applymentId 申请单ID + * @return . + */ + ApplymentsStatusResult queryApplyStatusByApplymentId(String applymentId) throws WxPayException; + + /** + *
+   * 查询申请状态API
+   * 请求URL: https://api.mch.weixin.qq.com/v3/ecommerce/applyments/out-request-no/{out_request_no}
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/applyments/chapter3_2.shtml
+   * 
+ * + * @param outRequestNo 业务申请编号 + * @return . + */ + ApplymentsStatusResult queryApplyStatusByOutRequestNo(String outRequestNo) 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 da7652ef69..2324fba170 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 @@ -126,6 +126,12 @@ public interface WxPayService { */ PayScoreService getPayScoreService(); + /** + * 获取电商收付通服务类 + * @return + */ + EcommerceService getEcommerceService(); + /** * 设置企业付款服务类,允许开发者自定义实现类. * diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java index efe9e70e0a..3bd514a609 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java @@ -62,6 +62,7 @@ public abstract class BaseWxPayServiceImpl implements WxPayService { private ProfitSharingService profitSharingService = new ProfitSharingServiceImpl(this); private RedpackService redpackService = new RedpackServiceImpl(this); private PayScoreService payScoreService = new PayScoreServiceImpl(this); + private EcommerceService ecommerceService = new EcommerceServiceImpl(this); /** * The Config. @@ -88,6 +89,11 @@ public RedpackService getRedpackService() { return this.redpackService; } + @Override + public EcommerceService getEcommerceService() { + return ecommerceService; + } + @Override public void setEntPayService(EntPayService entPayService) { this.entPayService = entPayService; diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java new file mode 100644 index 0000000000..0c7e3821ba --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java @@ -0,0 +1,52 @@ +package com.github.binarywang.wxpay.service.impl; + +import com.github.binarywang.wxpay.bean.ecommerce.ApplymentsRequest; +import com.github.binarywang.wxpay.bean.ecommerce.ApplymentsResult; +import com.github.binarywang.wxpay.bean.ecommerce.ApplymentsStatusResult; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.EcommerceService; +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 java.net.URI; + +public class EcommerceServiceImpl implements EcommerceService { + + private static final Gson GSON = new GsonBuilder().create(); + private WxPayService payService; + + /** + * + * @param payService + */ + public EcommerceServiceImpl(WxPayService payService) { + this.payService = payService; + } + + @Override + public ApplymentsResult createApply(ApplymentsRequest request) throws WxPayException { + String url = String.format("%s/v3/ecommerce/applyments/", this.payService.getPayBaseUrl()); + RsaCryptoUtil.encryptFields(request, this.payService.getConfig().getVerifier().getValidCertificate()); + + String result = this.payService.postV3WithWechatpaySerial(url, GSON.toJson(request)); + return GSON.fromJson(result, ApplymentsResult.class); + } + + @Override + public ApplymentsStatusResult queryApplyStatusByApplymentId(String applymentId) throws WxPayException { + String url = String.format("%s/v3/ecommerce/applyments/%s", this.payService.getPayBaseUrl(), applymentId); + String result = this.payService.getV3(URI.create(url)); + return GSON.fromJson(result, ApplymentsStatusResult.class); + } + + @Override + public ApplymentsStatusResult queryApplyStatusByOutRequestNo(String outRequestNo) throws WxPayException { + String url = String.format("%s/v3/ecommerce/applyments/out-request-no/%s", this.payService.getPayBaseUrl(), outRequestNo); + String result = this.payService.getV3(URI.create(url)); + return GSON.fromJson(result, ApplymentsStatusResult.class); + } + + +} 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 index b6ab923168..d88c67e419 100644 --- 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 @@ -29,10 +29,8 @@ public class RsaCryptoUtil { public static void encryptFields(Object encryptObject, X509Certificate certificate) throws WxPayException { try { encryptField(encryptObject, certificate); - } catch (IllegalAccessException | IllegalBlockSizeException e) { + } catch (Exception e) { throw new WxPayException("敏感信息加密失败", e); - } catch (Exception e2) { - throw new WxPayException("敏感信息加密失败", e2); } } From 10e0ca568ee8d4efac6bf69ba5d4dd431e6759b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=BE=E6=B5=A9?= Date: Mon, 24 Aug 2020 20:12:34 +0800 Subject: [PATCH 3/5] =?UTF-8?q?art:=E5=BE=AE=E4=BF=A1=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=95=86=E9=85=8D=E7=BD=AE=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CombineTransactionsJsRequest.java | 407 ++++++++++++++++++ .../CombineTransactionsJsResult.java | 16 + .../binarywang/wxpay/config/WxPayConfig.java | 15 +- 3 files changed, 433 insertions(+), 5 deletions(-) create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsJsRequest.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsJsResult.java diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsJsRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsJsRequest.java new file mode 100644 index 0000000000..fa39f82974 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsJsRequest.java @@ -0,0 +1,407 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 合单支付 JSAPI支付 + */ +@Data +@NoArgsConstructor +public class CombineTransactionsJsRequest implements Serializable { + /** + *
+   * 字段名:合单商户appid
+   * 变量名:combine_appid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *   合单发起方的appid。
+   *  示例值:wxd678efh567hg6787
+   * 
+ */ + @SerializedName(value = "combine_appid") + private String combineAppid; + + /** + *
+   * 字段名:合单商户号
+   * 变量名:combine_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  合单发起方商户号。
+   *  示例值:1900000109
+   * 
+ */ + @SerializedName(value = "combine_mchid") + private String combineMchid; + + /** + *
+   * 字段名:合单商户订单号
+   * 变量名:combine_out_trade_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  合单支付总订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。
+   *  示例值:P20150806125346
+   * 
+ */ + @SerializedName(value = "combine_out_trade_no") + private String combineOutTradeNo; + + /** + *
+   * 字段名:+场景信息
+   * 变量名:scene_info
+   * 是否必填:否
+   * 类型:object
+   * 描述:支付场景信息描述
+   * 
+ */ + @SerializedName(value = "scene_info") + private SceneInfo sceneInfo; + + /** + *
+   * 字段名:+子单信息
+   * 变量名:sub_orders
+   * 是否必填:是
+   * 类型:array
+   * 描述:
+   *  最多支持子单条数:50
+   *
+   * 
+ */ + @SerializedName(value = "sub_orders") + private List subOrders; + + /** + *
+   * 字段名:+支付者
+   * 变量名:combine_payer_info
+   * 是否必填:是
+   * 类型:object
+   * 描述:支付者信息
+   * 
+ */ + @SerializedName(value = "combine_payer_info") + private CombinePayerInfo combinePayerInfo; + + /** + *
+   * 字段名:交易起始时间
+   * 变量名:time_start
+   * 是否必填:否
+   * 类型:string(14)
+   * 描述:
+   *  订单生成时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
+   *  示例值:2019-12-31T15:59:60+08:00
+   * 
+ */ + @SerializedName(value = "time_start") + private String timeStart; + + /** + *
+   * 字段名:交易结束时间
+   * 变量名:time_expire
+   * 是否必填:否
+   * 类型:string(14)
+   * 描述:
+   *  订单失效时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
+   *  示例值:2019-12-31T15:59:60+08:00
+   * 
+ */ + @SerializedName(value = "time_expire") + private String timeExpire; + + /** + *
+   * 字段名:通知地址
+   * 变量名:notify_url
+   * 是否必填:是
+   * 类型:string(256)
+   * 描述:
+   *  接收微信支付异步通知回调地址,通知url必须为直接可访问的URL,不能携带参数。
+   *  格式: URL
+   *  示例值:https://yourapp.com/notify
+   * 
+ */ + @SerializedName(value = "notify_url") + private String notifyUrl; + + /** + *
+   * 字段名:指定支付方式
+   * 变量名:limit_pay
+   * 是否必填:否
+   * 类型:array
+   * 描述:
+   *  指定支付方式
+   *  no_credit:指定不能使用信用卡支付
+   *  特殊规则:长度最大限制32个字节
+   *  示例值:no_credit
+   * 
+ */ + @SerializedName(value = "limit_pay") + private List limitPay; + + @Data + @NoArgsConstructor + public static class SceneInfo implements Serializable{ + /** + *
+     * 字段名:商户端设备号
+     * 变量名:device_id
+     * 是否必填:否
+     * 类型:string(16)
+     * 描述:
+     *  终端设备号(门店号或收银设备ID)。
+     *  特殊规则:长度最小7个字节
+     *  示例值:POS1:1
+     * 
+ */ + @SerializedName(value = "device_id") + private String deviceId; + + /** + *
+     * 字段名:用户终端IP
+     * 变量名:payer_client_ip
+     * 是否必填:是
+     * 类型:string(45)
+     * 描述:
+     *  用户端实际ip
+     *  格式: ip(ipv4+ipv6)
+     *  示例值:14.17.22.32
+     * 
+ */ + @SerializedName(value = "payer_client_ip") + private String payerClientIp; + + } + + @Data + @NoArgsConstructor + public static class SubOrders implements Serializable{ + /** + *
+     * 字段名:子单商户号
+     * 变量名:mchid
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  子单发起方商户号,必须与发起方appid有绑定关系。
+     *  示例值:1900000109
+     *  此处一般填写服务商商户号
+     * 
+ */ + @SerializedName(value = "mchid") + private String mchid; + + /** + *
+     * 字段名:附加信息
+     * 变量名:attach
+     * 是否必填:是
+     * 类型:string(128)
+     * 描述:
+     *  附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。
+     *  示例值:深圳分店
+     * 
+ */ + @SerializedName(value = "attach") + private String attach; + + /** + *
+     * 字段名:+订单金额
+     * 变量名:amount
+     * 是否必填:是
+     * 类型:object
+     * 描述:
+     * 
+ */ + @SerializedName(value = "amount") + private Amount amount; + + /** + *
+     * 字段名:子单商户订单号
+     * 变量名:out_trade_no
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。
+     *  特殊规则:最小字符长度为6
+     *  示例值:20150806125346
+     * 
+ */ + @SerializedName(value = "out_trade_no") + private String outTradeNo; + + /** + *
+     * 字段名:二级商户号
+     * 变量名:sub_mchid
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  二级商户商户号,由微信支付生成并下发。
+     *  注意:仅适用于电商平台 服务商
+     *  示例值:1900000109
+     * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+     * 字段名:商品详情
+     * 变量名:detail
+     * 是否必填:否
+     * 类型:string(6000)
+     * 描述:商品详细描述(商品列表)
+     * 
+ */ + @SerializedName(value = "detail") + private String detail; + + /** + *
+     * 字段名:是否指定分账
+     * 变量名:profit_sharing
+     * 是否必填:是
+     * 类型:bool
+     * 描述:
+     *  是否指定分账
+     *  true:是
+     *  false:否
+     *  示例值:true
+     * 
+ */ + @SerializedName(value = "profit_sharing") + private Boolean profitSharing; + + /** + *
+     * 字段名:商品描述
+     * 变量名:description
+     * 是否必填:是
+     * 类型:string(128)
+     * 描述:
+     *  商品简单描述。需传入应用市场上的APP名字-实际商品名称,例如:天天爱消除-游戏充值。
+     *  示例值:腾讯充值中心-QQ会员充值
+     * 
+ */ + @SerializedName(value = "description") + private String description; + + /** + *
+     * 字段名:+结算信息
+     * 变量名:settle_info
+     * 是否必填:否
+     * 类型:Object
+     * 描述:结算信息
+     * 
+ */ + @SerializedName(value = "settle_info") + private SettleInfo settleInfo; + + } + + @Data + @NoArgsConstructor + public static class CombinePayerInfo implements Serializable{ + /** + *
+     * 字段名:用户标识
+     * 变量名:openid
+     * 是否必填:是
+     * 类型:string(128)
+     * 描述:
+     *  使用合单appid获取的对应用户openid。是用户在商户appid下的唯一标识。
+     *  示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+     * 
+ */ + @SerializedName(value = "openid") + private String openid; + + } + + @Data + @NoArgsConstructor + public static class Amount implements Serializable { + /** + *
+     * 字段名:标价金额
+     * 变量名:total_amount
+     * 是否必填:是
+     * 类型:int64
+     * 描述:
+     *  子单金额,单位为分。
+     *  示例值:100
+     * 
+ */ + @SerializedName(value = "total_amount") + private Integer totalAmount; + + /** + *
+     * 字段名:标价币种
+     * 变量名:currency
+     * 是否必填:是
+     * 类型:string(8)
+     * 描述:
+     *  符合ISO 4217标准的三位字母代码,人民币:CNY。
+     *  示例值:CNY
+     * 
+ */ + @SerializedName(value = "currency") + private String currency; + + } + + @Data + @NoArgsConstructor + public static class SettleInfo implements Serializable{ + /** + *
+     * 字段名:是否指定分账
+     * 变量名:profit_sharing
+     * 是否必填:否
+     * 类型:bool
+     * 描述:
+     *  是否分账,与外层profit_sharing同时存在时,以本字段为准。
+     *  true:是
+     *  false:否
+     *  示例值:true
+     * 
+ */ + @SerializedName(value = "profit_sharing") + private Boolean profitSharing; + + /** + *
+     * 字段名:补差金额
+     * 变量名:subsidy_amount
+     * 是否必填:否
+     * 类型:int64
+     * 描述:
+     *  SettleInfo.profit_sharing为true时,该金额才生效。
+     *  示例值:10
+     * 
+ */ + @SerializedName(value = "subsidy_amount") + private Integer subsidyAmount; + + } + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsJsResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsJsResult.java new file mode 100644 index 0000000000..f8f826cbea --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsJsResult.java @@ -0,0 +1,16 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Data +@NoArgsConstructor +public class CombineTransactionsJsResult implements Serializable { + + @SerializedName("prepay_id") + private String prepayId; + +} 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 2769e22018..1f012ed2f7 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 @@ -19,6 +19,7 @@ import java.nio.charset.StandardCharsets; import java.security.KeyStore; import java.security.PrivateKey; +import java.security.cert.X509Certificate; import java.util.Collections; /** @@ -229,7 +230,7 @@ public SSLContext initSSLContext() throws WxPayException { public CloseableHttpClient initApiV3HttpClient() throws WxPayException { String privateKeyPath = this.getPrivateKeyPath(); String privateCertPath = this.getPrivateCertPath(); - String certSerialNo = this.getCertSerialNo(); + String serialNo = this.getCertSerialNo(); String apiV3Key = this.getApiV3Key(); if (StringUtils.isBlank(privateKeyPath)) { throw new WxPayException("请确保privateKeyPath已设置"); @@ -237,9 +238,9 @@ public CloseableHttpClient initApiV3HttpClient() throws WxPayException { if (StringUtils.isBlank(privateCertPath)) { throw new WxPayException("请确保privateCertPath已设置"); } - if (StringUtils.isBlank(certSerialNo)) { - throw new WxPayException("请确保certSerialNo证书序列号已设置"); - } +// if (StringUtils.isBlank(certSerialNo)) { +// throw new WxPayException("请确保certSerialNo证书序列号已设置"); +// } if (StringUtils.isBlank(apiV3Key)) { throw new WxPayException("请确保apiV3Key值已设置"); } @@ -248,6 +249,10 @@ public CloseableHttpClient initApiV3HttpClient() throws WxPayException { InputStream certInputStream = this.loadConfigInputStream(privateCertPath); try { PrivateKey merchantPrivateKey = PemUtils.loadPrivateKey(keyInputStream); + X509Certificate certificate = PemUtils.loadCertificate(certInputStream); + if(StringUtils.isBlank(serialNo)){ + this.certSerialNo = certificate.getSerialNumber().toString(16).toUpperCase(); + } AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier( new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey)), @@ -255,7 +260,7 @@ public CloseableHttpClient initApiV3HttpClient() throws WxPayException { CloseableHttpClient httpClient = WxPayV3HttpClientBuilder.create() .withMerchant(mchId, certSerialNo, merchantPrivateKey) - .withWechatpay(Collections.singletonList(PemUtils.loadCertificate(certInputStream))) + .withWechatpay(Collections.singletonList(certificate)) .withValidator(new WxPayValidator(verifier)) .build(); this.apiV3HttpClient = httpClient; From 5db9706ba0d669b89dee6293ff92a696ac317063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=BE=E6=B5=A9?= Date: Mon, 24 Aug 2020 20:21:07 +0800 Subject: [PATCH 4/5] =?UTF-8?q?new:jsapi=E5=90=88=E5=8D=95=E6=94=AF?= =?UTF-8?q?=E4=BB=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bean/ecommerce/ApplymentsStatusResult.java | 4 ++++ .../ecommerce/CombineTransactionsJsResult.java | 14 ++++++++++++++ .../wxpay/service/EcommerceService.java | 15 ++++++++++++--- .../wxpay/service/impl/EcommerceServiceImpl.java | 11 ++++++++--- 4 files changed, 38 insertions(+), 6 deletions(-) diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsStatusResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsStatusResult.java index 7defd21452..b3704d6526 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsStatusResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsStatusResult.java @@ -7,6 +7,10 @@ import java.io.Serializable; import java.util.List; +/** + * 二级商户进件 查询申请状态结果响应 + * + */ @Data @NoArgsConstructor public class ApplymentsStatusResult implements Serializable { diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsJsResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsJsResult.java index f8f826cbea..7a33ec8404 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsJsResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsJsResult.java @@ -6,10 +6,24 @@ import java.io.Serializable; +/** + * 合单支付 JSAPI支付结果响应 + */ @Data @NoArgsConstructor public class CombineTransactionsJsResult implements Serializable { + /** + *
+   * 字段名:预支付交易会话标识
+   * 变量名:prepay_id
+   * 是否必填:是
+   * 类型:string(64)
+   * 描述:
+   *  数字和字母。微信生成的预支付会话标识,用于后续接口调用使用。
+   *  示例值:wx201410272009395522657a690389285100
+   * 
+ */ @SerializedName("prepay_id") private String prepayId; diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java index 5c86306b9f..066fe372cb 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java @@ -1,8 +1,6 @@ package com.github.binarywang.wxpay.service; -import com.github.binarywang.wxpay.bean.ecommerce.ApplymentsRequest; -import com.github.binarywang.wxpay.bean.ecommerce.ApplymentsResult; -import com.github.binarywang.wxpay.bean.ecommerce.ApplymentsStatusResult; +import com.github.binarywang.wxpay.bean.ecommerce.*; import com.github.binarywang.wxpay.exception.WxPayException; /** @@ -55,4 +53,15 @@ public interface EcommerceService { */ ApplymentsStatusResult queryApplyStatusByOutRequestNo(String outRequestNo) throws WxPayException; + /** + *
+   * 合单下单-JS支付API.
+   * 请求URL:https://api.mch.weixin.qq.com/v3/combine-transactions/jsapi
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/combine/chapter3_2.shtml
+   * 
+ * + * @param request 请求对象 + * @return 预支付交易会话标识, 数字和字母。微信生成的预支付会话标识,用于后续接口调用使用。 + */ + CombineTransactionsJsResult combineTransactions(CombineTransactionsJsRequest request) throws WxPayException; } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java index 9631631272..fe916819e7 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java @@ -1,8 +1,6 @@ package com.github.binarywang.wxpay.service.impl; -import com.github.binarywang.wxpay.bean.ecommerce.ApplymentsRequest; -import com.github.binarywang.wxpay.bean.ecommerce.ApplymentsResult; -import com.github.binarywang.wxpay.bean.ecommerce.ApplymentsStatusResult; +import com.github.binarywang.wxpay.bean.ecommerce.*; import com.github.binarywang.wxpay.exception.WxPayException; import com.github.binarywang.wxpay.service.EcommerceService; import com.github.binarywang.wxpay.service.WxPayService; @@ -41,5 +39,12 @@ public ApplymentsStatusResult queryApplyStatusByOutRequestNo(String outRequestNo return GSON.fromJson(result, ApplymentsStatusResult.class); } + @Override + public CombineTransactionsJsResult combineTransactions(CombineTransactionsJsRequest request) throws WxPayException { + String url = String.format("%s/v3/combine-transactions/jsapi", this.payService.getPayBaseUrl()); + String response = this.payService.postV3(url, GSON.toJson(request)); + return GSON.fromJson(response, CombineTransactionsJsResult.class); + } + } From e0d87ebe49e34a36e0d95a804452d4a223586cd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=BE=E6=B5=A9?= Date: Wed, 26 Aug 2020 12:17:30 +0800 Subject: [PATCH 5/5] =?UTF-8?q?new:=E5=90=88=E5=8D=95=E6=94=AF=E4=BB=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CombineTransactionsJsResult.java | 30 ---- ...t.java => CombineTransactionsRequest.java} | 146 ++++++++++++------ .../ecommerce/CombineTransactionsResult.java | 119 ++++++++++++++ .../bean/ecommerce/enums/TradeTypeEnum.java | 27 ++++ .../wxpay/service/EcommerceService.java | 4 +- .../service/impl/EcommerceServiceImpl.java | 10 +- .../binarywang/wxpay/v3/util/SignUtils.java | 47 ++++++ 7 files changed, 300 insertions(+), 83 deletions(-) delete mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsJsResult.java rename weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/{CombineTransactionsJsRequest.java => CombineTransactionsRequest.java} (81%) create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsResult.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/enums/TradeTypeEnum.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/SignUtils.java diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsJsResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsJsResult.java deleted file mode 100644 index 7a33ec8404..0000000000 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsJsResult.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.github.binarywang.wxpay.bean.ecommerce; - -import com.google.gson.annotations.SerializedName; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; - -/** - * 合单支付 JSAPI支付结果响应 - */ -@Data -@NoArgsConstructor -public class CombineTransactionsJsResult implements Serializable { - - /** - *
-   * 字段名:预支付交易会话标识
-   * 变量名:prepay_id
-   * 是否必填:是
-   * 类型:string(64)
-   * 描述:
-   *  数字和字母。微信生成的预支付会话标识,用于后续接口调用使用。
-   *  示例值:wx201410272009395522657a690389285100
-   * 
- */ - @SerializedName("prepay_id") - private String prepayId; - -} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsJsRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsRequest.java similarity index 81% rename from weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsJsRequest.java rename to weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsRequest.java index fa39f82974..3b138b4882 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsJsRequest.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsRequest.java @@ -8,11 +8,11 @@ import java.util.List; /** - * 合单支付 JSAPI支付 + * 合单支付 对象 */ @Data @NoArgsConstructor -public class CombineTransactionsJsRequest implements Serializable { +public class CombineTransactionsRequest implements Serializable { /** *
    * 字段名:合单商户appid
@@ -85,7 +85,7 @@ public class CombineTransactionsJsRequest implements Serializable {
    * 
    * 字段名:+支付者
    * 变量名:combine_payer_info
-   * 是否必填:是
+   * 是否必填:否(JSAPI必填)
    * 类型:object
    * 描述:支付者信息
    * 
@@ -136,25 +136,10 @@ public class CombineTransactionsJsRequest implements Serializable { @SerializedName(value = "notify_url") private String notifyUrl; - /** - *
-   * 字段名:指定支付方式
-   * 变量名:limit_pay
-   * 是否必填:否
-   * 类型:array
-   * 描述:
-   *  指定支付方式
-   *  no_credit:指定不能使用信用卡支付
-   *  特殊规则:长度最大限制32个字节
-   *  示例值:no_credit
-   * 
- */ - @SerializedName(value = "limit_pay") - private List limitPay; @Data @NoArgsConstructor - public static class SceneInfo implements Serializable{ + public static class SceneInfo implements Serializable { /** *
      * 字段名:商户端设备号
@@ -185,11 +170,23 @@ public static class SceneInfo implements Serializable{
     @SerializedName(value = "payer_client_ip")
     private String payerClientIp;
 
+    /**
+     * 
+     * 字段名:H5场景信息
+     * 变量名:h5_info
+     * 是否必填:否(H5支付必填)
+     * 类型:object
+     * 描述:
+     *  H5场景信息
+     * 
+ */ + @SerializedName(value = "h5_info") + private H5Info h5Info; } @Data @NoArgsConstructor - public static class SubOrders implements Serializable{ + public static class SubOrders implements Serializable { /** *
      * 字段名:子单商户号
@@ -261,34 +258,6 @@ public static class SubOrders implements Serializable{
     @SerializedName(value = "sub_mchid")
     private String subMchid;
 
-    /**
-     * 
-     * 字段名:商品详情
-     * 变量名:detail
-     * 是否必填:否
-     * 类型:string(6000)
-     * 描述:商品详细描述(商品列表)
-     * 
- */ - @SerializedName(value = "detail") - private String detail; - - /** - *
-     * 字段名:是否指定分账
-     * 变量名:profit_sharing
-     * 是否必填:是
-     * 类型:bool
-     * 描述:
-     *  是否指定分账
-     *  true:是
-     *  false:否
-     *  示例值:true
-     * 
- */ - @SerializedName(value = "profit_sharing") - private Boolean profitSharing; - /** *
      * 字段名:商品描述
@@ -319,7 +288,7 @@ public static class SubOrders implements Serializable{
 
   @Data
   @NoArgsConstructor
-  public static class CombinePayerInfo implements Serializable{
+  public static class CombinePayerInfo implements Serializable {
     /**
      * 
      * 字段名:用户标识
@@ -404,4 +373,83 @@ public static class SettleInfo implements Serializable{
 
   }
 
+  @Data
+  @NoArgsConstructor
+  public static class H5Info implements Serializable {
+
+    /**
+     * 
+     * 字段名:场景类型
+     * 变量名:type
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  场景类型,枚举值:
+     *  iOS:IOS移动应用;
+     *  Android:安卓移动应用;
+     *  Wap:WAP网站应用;
+     *  示例值:iOS
+     * 
+ */ + @SerializedName(value = "type") + private String type; + + /** + *
+     * 字段名:应用名称
+     * 变量名:app_name
+     * 是否必填:否
+     * 类型:string(64)
+     * 描述:
+     *  应用名称
+     *  示例值:王者荣耀
+     * 
+ */ + @SerializedName(value = "app_name") + private String appName; + + /** + *
+     * 字段名:网站URL
+     * 变量名:app_url
+     * 是否必填:否
+     * 类型:string(128)
+     * 描述:
+     *  网站URL
+     *  示例值:https://pay.qq.com
+     * 
+ */ + @SerializedName(value = "app_url") + private String appUrl; + + /** + *
+     * 字段名:iOS平台BundleID
+     * 变量名:bundle_id
+     * 是否必填:否
+     * 类型:string(128)
+     * 描述:
+     *  iOS平台BundleID
+     *  示例值:com.tencent.wzryiOS
+     * 
+ */ + @SerializedName(value = "bundle_id") + private String bundleId; + + /** + *
+     * 字段名:Android平台PackageName
+     * 变量名:package_name
+     * 是否必填:否
+     * 类型:string(128)
+     * 描述:
+     *  Android平台PackageName
+     *  示例值:com.tencent.tmgp.sgame
+     * 
+ */ + @SerializedName(value = "package_name") + private String packageName; + + } + } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsResult.java new file mode 100644 index 0000000000..cd4edc9d57 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsResult.java @@ -0,0 +1,119 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.github.binarywang.wxpay.bean.ecommerce.enums.TradeTypeEnum; +import com.github.binarywang.wxpay.v3.util.AesUtils; +import com.github.binarywang.wxpay.v3.util.SignUtils; +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.security.PrivateKey; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.UUID; + +/** + * 合单支付 JSAPI支付结果响应 + */ +@Data +@NoArgsConstructor +public class CombineTransactionsResult implements Serializable { + + /** + *
+   * 字段名:预支付交易会话标识 (APP支付、JSAPI支付 会返回)
+   * 变量名:prepay_id
+   * 是否必填:是
+   * 类型:string(64)
+   * 描述:
+   *  数字和字母。微信生成的预支付会话标识,用于后续接口调用使用。
+   *  示例值:wx201410272009395522657a690389285100
+   * 
+ */ + @SerializedName("prepay_id") + private String prepayId; + + /** + *
+   * 字段名:支付跳转链接   (H5支付 会返回)
+   * 变量名:h5_url
+   * 是否必填:是
+   * 类型:string(512)
+   * 描述:
+   *  支付跳转链接
+   *  示例值:https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx2016121516420242444321ca0631331346&package=1405458241
+   * 
+ */ + @SerializedName("h5_url") + private String h5Url; + + /** + *
+   * 字段名:二维码链接  (NATIVE支付 会返回)
+   * 变量名:h5_url
+   * 是否必填:是
+   * 类型:string(512)
+   * 描述:
+   *  二维码链接
+   * 示例值:weixin://pay.weixin.qq.com/bizpayurl/up?pr=NwY5Mz9&groupid=00
+   * 
+ */ + @SerializedName("code_url") + private String codeUrl; + + @Data + @Accessors(chain = true) + public static class JsapiResult implements Serializable { + private String appId; + private String timeStamp; + private String nonceStr; + private String packageValue; + private String signType; + private String paySign; + + private String getSignStr(){ + return String.format("%s\n%s\n%s\n%s\n", appId, timeStamp, nonceStr, packageValue); + } + } + + @Data + @Accessors(chain = true) + public static class AppResult implements Serializable { + private String appid; + private String partnerid; + private String prepayid; + private String packageValue; + private String noncestr; + private String timestamp; + + } + + public T getPayInfo(TradeTypeEnum tradeType, String appId, String mchId, PrivateKey privateKey){ + String timestamp = String.valueOf(System.currentTimeMillis() / 1000); + String nonceStr = SignUtils.genRandomStr(); + switch (tradeType){ + case JSAPI: + JsapiResult jsapiResult = new JsapiResult(); + jsapiResult.setAppId(appId).setTimeStamp(timestamp) + .setPackageValue("prepay_id=" + this.prepayId).setNonceStr(nonceStr) + //签名类型,默认为RSA,仅支持RSA。 + .setSignType("RSA").setPaySign(SignUtils.sign(jsapiResult.getSignStr(), privateKey)); + return (T) jsapiResult; + case H5: + return (T) this.h5Url; + case APP: + AppResult appResult = new AppResult(); + appResult.setAppid(appId).setPrepayid(this.prepayId).setPartnerid(mchId) + .setNoncestr(nonceStr).setTimestamp(timestamp) + //暂填写固定值Sign=WXPay + .setPackageValue("Sign=WXPay"); + return (T) appResult; + case NATIVE: + return (T) this.codeUrl; + } + return null; + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/enums/TradeTypeEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/enums/TradeTypeEnum.java new file mode 100644 index 0000000000..c65600ecf1 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/enums/TradeTypeEnum.java @@ -0,0 +1,27 @@ +package com.github.binarywang.wxpay.bean.ecommerce.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 支付方式 + */ +@Getter +@AllArgsConstructor +public enum TradeTypeEnum { + + APP("/v3/combine-transactions/app","/v3/pay/partner/transactions/app"), + JSAPI("/v3/combine-transactions/jsapi","/v3/pay/partner/transactions/jsapi"), + NATIVE("/v3/combine-transactions/native","/v3/pay/partner/transactions/native"), + H5("/v3/combine-transactions/h5","/v3/pay/partner/transactions/h5") + ; + + /** + * 合单url + */ + private String combineUrl; + /** + * 单独下单url + */ + private String partnerUrl; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java index 066fe372cb..c9faaeddfd 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java @@ -1,6 +1,7 @@ package com.github.binarywang.wxpay.service; import com.github.binarywang.wxpay.bean.ecommerce.*; +import com.github.binarywang.wxpay.bean.ecommerce.enums.TradeTypeEnum; import com.github.binarywang.wxpay.exception.WxPayException; /** @@ -63,5 +64,6 @@ public interface EcommerceService { * @param request 请求对象 * @return 预支付交易会话标识, 数字和字母。微信生成的预支付会话标识,用于后续接口调用使用。 */ - CombineTransactionsJsResult combineTransactions(CombineTransactionsJsRequest request) throws WxPayException; + T combineTransactions(TradeTypeEnum tradeType, CombineTransactionsRequest request) throws WxPayException; + } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java index fe916819e7..18f51a45c4 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java @@ -1,6 +1,7 @@ package com.github.binarywang.wxpay.service.impl; import com.github.binarywang.wxpay.bean.ecommerce.*; +import com.github.binarywang.wxpay.bean.ecommerce.enums.TradeTypeEnum; import com.github.binarywang.wxpay.exception.WxPayException; import com.github.binarywang.wxpay.service.EcommerceService; import com.github.binarywang.wxpay.service.WxPayService; @@ -8,6 +9,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; import java.net.URI; @@ -40,10 +42,12 @@ public ApplymentsStatusResult queryApplyStatusByOutRequestNo(String outRequestNo } @Override - public CombineTransactionsJsResult combineTransactions(CombineTransactionsJsRequest request) throws WxPayException { - String url = String.format("%s/v3/combine-transactions/jsapi", this.payService.getPayBaseUrl()); + public T combineTransactions(TradeTypeEnum tradeType, CombineTransactionsRequest request) throws WxPayException { + String url = this.payService.getPayBaseUrl() + tradeType.getCombineUrl(); String response = this.payService.postV3(url, GSON.toJson(request)); - return GSON.fromJson(response, CombineTransactionsJsResult.class); + CombineTransactionsResult result = GSON.fromJson(response, CombineTransactionsResult.class); + return result.getPayInfo(tradeType, request.getCombineAppid(), + request.getCombineMchid(), payService.getConfig().getPrivateKey()); } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/SignUtils.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/SignUtils.java new file mode 100644 index 0000000000..275a8d51ba --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/SignUtils.java @@ -0,0 +1,47 @@ +package com.github.binarywang.wxpay.v3.util; + +import java.security.*; +import java.util.Base64; +import java.util.Random; + +public class SignUtils { + + public static String sign(String string, PrivateKey privateKey){ + try { + Signature sign = Signature.getInstance("SHA256withRSA"); + sign.initSign(privateKey); + sign.update(string.getBytes()); + + return Base64.getEncoder().encodeToString(sign.sign()); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("当前Java环境不支持SHA256withRSA", e); + } catch (SignatureException e) { + throw new RuntimeException("签名计算失败", e); + } catch (InvalidKeyException e) { + throw new RuntimeException("无效的私钥", e); + } + } + + /** + * 随机生成32位字符串. + */ + public static String genRandomStr(){ + return genRandomStr(32); + } + + /** + * 生成随机字符串 + * @param length 字符串长度 + * @return + */ + public static String genRandomStr(int length) { + String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + Random random = new Random(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + int number = random.nextInt(base.length()); + sb.append(base.charAt(number)); + } + return sb.toString(); + } +}