diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..770ee41650180f9d8334001367428850b9e5a21d --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +.idea +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +target/ +.idea/ +*.iml + diff --git a/README.md b/README.md index ce607a5e33cd79560c87ad62ad57f2860bca8e51..228b7c3ba552f24a65ab833a66613748cb9c517c 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,28 @@ +全能第三方支付对接Java开发工具包.优雅的轻量级支付模块集成支付对接支付整合(微信,支付宝,银联,友店,富友,跨境支付paypal,payoneer(P卡派安盈)易极付)app,扫码,网页支付刷卡付条码付刷脸付转账红包服务商模式,微信分账,合并支付、支持多种支付类型多支付账户,支付与业务完全剥离,简单几行代码即可实现支付,简单快速完成支付模块的开发,可轻松嵌入到任何系统里 目前仅是一个开发工具包(即SDK),只提供简单Web实现,建议使用maven或gradle引用本项目即可使用本SDK提供的各种支付相关的功能 -##整合支付模块 - -声明: 本项目最初想法自 https://github.com/chanjarster/weixin-java-tools, 15年1月左右关注chanjarster/weixin-java-tools,并将其回调处理修改并进行使用。 - - -##### 详细文档请看 [wiki](https://gitee.com/egzosn/pay-java-parent/wikis/Home)。 ### 特性 - - - 1. 不依赖任何 mvc 框架,依赖极少:httpclient,fastjson,log4j,com.google.zxing,项目精简,不用担心项目迁移问题 2. 也不依赖 servlet,仅仅作为工具使用,可轻松嵌入到任何系统里(项目例子利用spring mvc的 @PathVariable进行,推荐使用类似的框架) 3. 支付请求调用支持HTTP和异步、支持http代理,连接池 - 4. 控制层统一异常处理 - 5. LogBack日志记录 - 6. 简单快速完成支付模块的开发 - 7. 支持多种支付类型多支付账户扩展 + 4. 简单快速完成支付模块的开发 + 5. 支持多种支付类型多支付账户扩展 -### 本项目包含 3 个部分: +### 本项目包含 4 个部分: 1. pay-java-common 公共lib,支付核心与规范定义 + 2. pay-java-web-support web支持包,目前已实现回调相关 2. pay-java-demo 具体的支付demo 3. pay-java-* 具体的支付实现库 + ### Maven配置 -支付核心模块 -```xml - - - com.egzosn - pay-java-common - 2.12.5 - - -``` - 具体支付模块 "{module-name}" 为具体的支付渠道的模块名 pay-java-ali,pay-java-wx等 ```xml - com.egzosn {module-name} - 2.12.5 + 2.14.7 ``` @@ -50,14 +30,21 @@ * 码云:https://gitee.com/egzosn/pay-java-parent * GitHub:https://github.com/egzosn/pay-java-parent +#### 基于spring-boot实现自动化配置的支付对接,让你真正做到一行代码实现支付聚合,让你可以不用理解支付怎么对接,只需要专注你的业务 全能第三方支付对接spring-boot-starter-pay开发工具包 +* 码云:https://gitee.com/egzosn/pay-spring-boot-starter-parent +* GitHub:https://github.com/egzosn/pay-spring-boot-starter-parent -### 使用 -这里不多说直接上代码 +##### 开源中国项目地址 +如果你觉得项目对你有帮助,也点击下进入后点击收藏呗 +* 基础支付聚合组件[pay-java-parent](https://www.oschina.net/p/pay-java-parent) +* spring-boot-starter自动化配置支付聚合组件 [pay-spring-boot-starter](https://www.oschina.net/p/spring-boot-starter-pay) - -###### 单一支付教程 +###### 支付教程 * [基础模块支付宝微信讲解](https://gitee.com/egzosn/pay-java-parent/wikis/Home) + * [微信V3,查看demo/WxV3PayController](pay-java-demo?dir=1&filepath=pay-java-demo) + * [微信合并支付,查看demo/WxV3CombinePayController](pay-java-demo?dir=1&filepath=pay-java-demo) + * [微信分账,查看demo/WxV3ProfitSharingController](pay-java-demo?dir=1&filepath=pay-java-demo) * [银联](pay-java-union?dir=1&filepath=pay-java-union) * [payoneer](pay-java-payoneer?dir=1&filepath=pay-java-payoneer) * [paypal](pay-java-paypal?dir=1&filepath=pay-java-paypal) @@ -74,13 +61,24 @@ android 例子 [pay-java-android](https://gitee.com/egzosn/pay-java-android) ## 交流 很希望更多志同道合友友一起扩展新的的支付接口。 -这里感谢[ouyangxiangshao](https://github.com/ouyangxiangshao),[ZhuangXiong](https://github.com/ZhuangXiong) 与[Actinian](http://git.oschina.net/Actinia517) 所提交的安卓例子或者分支 +开发者 +[ouyangxiangshao](https://github.com/ouyangxiangshao)、[ZhuangXiong](https://github.com/ZhuangXiong) 、[Actinian](http://gitee.com/Actinia517) 、[Menjoe](https://gitee.com/menjoe-z) 也感谢各大友友同学帮忙进行接口测试 非常欢迎和感谢对本项目发起Pull Request的同学,不过本项目基于git flow开发流程,因此在发起Pull Request的时候请选择develop分支。 -E-Mail:egzosn@gmail.com +作者公众号(每周输出) +![公众号](https://egzosn.gitee.io/pay-java-parent/gzh.png "gzh.png") + +E-Mail:egan@egzosn.com + + **QQ群:** + + +1. pay-java(1群): 542193977(已满) +2. pay-java(2群):766275051 -QQ群:542193977 +微信群: +![微信群](https://egzosn.gitee.io/pay-java-parent/wx.jpg "wx.jpg") diff --git a/pay-java-ali/README.md b/pay-java-ali/README.md index 48bb371f4c81cc253aa9e8a7be9f7da5a4cf1fd8..d48313c4692b67efe3bcec6a4dac6accd36edfb7 100644 --- a/pay-java-ali/README.md +++ b/pay-java-ali/README.md @@ -3,15 +3,41 @@ ## 支付宝支付简单例子 #### 支付配置 - +##### 普通公钥 ```java - - + + + AliPayConfigStorage aliPayConfigStorage = new AliPayConfigStorage(); + aliPayConfigStorage.setPid("合作者id"); + aliPayConfigStorage.setAppId("应用id"); +// aliPayConfigStorage.setAppAuthToken("ISV代商户代用,指定appAuthToken"); + aliPayConfigStorage.setKeyPublic("支付宝公钥"); + aliPayConfigStorage.setKeyPrivate("应用私钥"); + aliPayConfigStorage.setNotifyUrl("异步回调地址"); + aliPayConfigStorage.setReturnUrl("同步回调地址"); + aliPayConfigStorage.setSignType("签名方式"); + aliPayConfigStorage.setSeller("收款账号"); + aliPayConfigStorage.setInputCharset("utf-8"); + //是否为测试账号,沙箱环境 + aliPayConfigStorage.setTest(true); + +``` +##### 证书公钥 +```java + + AliPayConfigStorage aliPayConfigStorage = new AliPayConfigStorage(); aliPayConfigStorage.setPid("合作者id"); aliPayConfigStorage.setAppId("应用id"); - aliPayConfigStorage.setAliPublicKey("支付宝公钥"); +// aliPayConfigStorage.setAppAuthToken("ISV代商户代用,指定appAuthToken"); aliPayConfigStorage.setKeyPrivate("应用私钥"); + //设置为证书方式 + aliPayConfigStorage.setCertSign(true); + //设置证书存储方式,这里为路径 + aliPayConfigStorage.setCertStoreType(CertStoreType.PATH); + aliPayConfigStorage.setMerchantCert("请填写您的应用公钥证书文件路径,例如:d:/appCertPublicKey_2019051064521003.crt"); + aliPayConfigStorage.setAliPayCert("请填写您的支付宝公钥证书文件路径,例如:d:/alipayCertPublicKey_RSA2.crt"); + aliPayConfigStorage.setAliPayRootCert("请填写您的支付宝根证书文件路径,例如:d:/alipayRootCert.crt"); aliPayConfigStorage.setNotifyUrl("异步回调地址"); aliPayConfigStorage.setReturnUrl("同步回调地址"); aliPayConfigStorage.setSignType("签名方式"); @@ -34,16 +60,19 @@ //代理端口 httpConfigStorage.setHttpProxyPort(3308); //代理用户名 - httpConfigStorage.setHttpProxyUsername("user"); + httpConfigStorage.setAuthUsername("user"); //代理密码 - httpConfigStorage.setHttpProxyPassword("password"); + httpConfigStorage.setAuthPassword("password"); /* /网路代理配置 根据需求进行设置**/ /* 网络请求ssl证书 根据需求进行设置**/ - //设置ssl证书路径 - httpConfigStorage.setKeystorePath("证书绝对路径"); + //设置ssl证书路径 跟着setCertStoreType 进行对应 + httpConfigStorage.setKeystore("证书文件流,证书字符串信息或证书绝对地址"); //设置ssl证书对应的密码 httpConfigStorage.setStorePassword("证书对应的密码"); + //设置ssl证书对应的存储方式 + httpConfigStorage.setCertStoreType(CertStoreType.PATH); + /* /网络请求ssl证书**/ /* /网络请求连接池**/ @@ -60,7 +89,7 @@ ```java //支付服务 - PayService service = new AliPayService(aliPayConfigStorage); + AliPayService service = new AliPayService(aliPayConfigStorage); //设置网络请求配置根据需求进行设置 //service.setRequestTemplateConfigStorage(httpConfigStorage) @@ -82,7 +111,7 @@ ```java //支付订单基础信息 - PayOrder payOrder = new PayOrder("订单title", "摘要", new BigDecimal(0.01) , UUID.randomUUID().toString().replace("-", "")); + PayOrder payOrder = new PayOrder("订单title", "摘要", BigDecimal.valueOf(0.01) , UUID.randomUUID().toString().replace("-", "")); ``` @@ -94,6 +123,7 @@ /*-----------扫码付-------------------*/ payOrder.setTransactionType(AliTransactionType.SWEEPPAY); //获取扫码付的二维码 +// String image = service.getQrPay(payOrder); BufferedImage image = service.genQrPay(payOrder); /*-----------/扫码付-------------------*/ @@ -109,6 +139,18 @@ Map appOrderInfo = service.orderInfo(payOrder); /*-----------/APP-------------------*/ +``` +#### 小程序支付 + +```java + + /*-----------APP-------------------*/ + payOrder.setTransactionType(AliTransactionType.MINAPP); + payOrder.setOpenid("支付宝小程序授权登录成功后获取到的支付宝 user_id") + //获取小程序支付所需的信息组,直接给小程序网页端就可使用 + Map appOrderInfo = service.orderInfo(payOrder); + /*-----------/APP-------------------*/ + ``` #### 即时到帐 WAP 网页支付 @@ -118,7 +160,7 @@ /*-----------即时到帐 WAP 网页支付-------------------*/ // payOrder.setTransactionType(AliTransactionType.WAP); //WAP支付 - payOrder.setTransactionType(AliTransactionType.DIRECT); // 即时到帐 PC网页支付 + payOrder.setTransactionType(AliTransactionType.PAGE); // 即时到帐 PC网页支付 //获取支付所需的信息 Map directOrderInfo = service.orderInfo(payOrder); //获取表单提交对应的字符串,将其序列化到页面即可, @@ -176,11 +218,24 @@ +#### 统一收单交易结算接口 + +```java + OrderSettle order = new OrderSettle(); + order.setTradeNo("支付宝单号"); + order.setOutRequestNo("商户单号"); + order.setAmount(new BigDecimal(100)); + order.setDesc("线下转账"); + Map result = service.settle(order); + +``` + + #### 支付订单查询 ```java - Map result = service..query("支付宝单号", "我方系统单号"); + Map result = service.query("支付宝单号", "我方系统单号"); ``` @@ -207,14 +262,21 @@ RefundOrder order = new RefundOrder("支付宝单号", "我方系统单号", "退款金额", "订单总金额"); //非必填, 根据业务需求而定,可用于多次退款 order.setRefundNo("退款单号") - Map result = service.refund(order); + AliRefundResult result = service.refund(order); ``` #### 查询退款 ```java - Map result = service.refundquery("支付宝单号", "我方系统单号"); + RefundOrder order = new RefundOrder(); + order.setOutTradeNo("我方系统商户单号"); + order.setTradeNo("支付宝单号"); + //退款金额 + order.setRefundAmount(new BigDecimal(1)); + order.setRefundNo("退款单号"); + order.setDescription(""); + Map result = service.refundquery(); ``` @@ -227,15 +289,17 @@ #### 转账 ```java - TransferOrder order = new TransferOrder(); - order.setOutNo("商户转账订单号"); - order.setPayeeAccount("收款方账户,支付宝登录号,支持邮箱和手机号格式"); - order.setAmount(new BigDecimal(10)); - order.setPayerName("付款方姓名, 非必填"); - order.setPayeeName("收款方真实姓名, 非必填"); + order.setOutBizNo("转账单号"); + order.setTransAmount(new BigDecimal(10)); + order.setOrderTitle("转账业务的标题"); + order.setIdentity("参与方的唯一标识"); + order.setIdentityType("参与方的标识类型,目前支持如下类型:"); + order.setName("参与方真实姓名"); order.setRemark("转账备注, 非必填"); - //收款方账户类型 ,默认值 ALIPAY_LOGONID:支付宝登录号,支持邮箱和手机号格式。 - order.setTransferType(AliTransferType.ALIPAY_LOGONID); + //单笔无密转账到支付宝账户 + order.setTransferType(AliTransferType.TRANS_ACCOUNT_NO_PWD); + //单笔无密转账到银行卡 +// order.setTransferType(AliTransferType.TRANS_BANKCARD_NO_PWD); Map result = service.transfer(order); ``` diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index 97206851383245d111d52b9670612c0650d9d061..24256302122e033d1447dbced2e4dca960243830 100644 --- a/pay-java-ali/pom.xml +++ b/pay-java-ali/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.12.6-SNAPSHOT + 2.14.7 4.0.0 pay-java-ali @@ -19,7 +19,10 @@ pay-java-common - + + org.bouncycastle + bcprov-jdk15on + diff --git a/pay-java-ali/src/main/java/com/egzosn/pay/ali/api/AliPayConfigStorage.java b/pay-java-ali/src/main/java/com/egzosn/pay/ali/api/AliPayConfigStorage.java index fad78e7b4d4f8ae4c7e1457c1057c48a1993439b..2acb5a656b038eff3adaf3145b512835cd9a10ee 100644 --- a/pay-java-ali/src/main/java/com/egzosn/pay/ali/api/AliPayConfigStorage.java +++ b/pay-java-ali/src/main/java/com/egzosn/pay/ali/api/AliPayConfigStorage.java @@ -1,41 +1,102 @@ package com.egzosn.pay.ali.api; +import java.io.IOException; +import java.io.InputStream; + +import com.egzosn.pay.ali.bean.CertEnvironment; import com.egzosn.pay.common.api.BasePayConfigStorage; +import com.egzosn.pay.common.api.CertStore; +import com.egzosn.pay.common.bean.result.PayException; +import com.egzosn.pay.common.exception.PayErrorException; /** * 支付配置存储 - * @author egan * + * @author egan + *

* email egzosn@gmail.com * date 2016-5-18 14:09:01 + *

+ * 以下证书签名相关触发前提是 {@link BasePayConfigStorage#isCertSign}等于true的情况。不然走的就是普通的方式 */ public class AliPayConfigStorage extends BasePayConfigStorage { /** - * 商户应用id + * ISV代商户代用,指定appAuthToken + */ + private String appAuthToken; + /** + * 商户应用id */ - private volatile String appId ; + private String appId; /** - * 商户签约拿到的pid,partner_id的简称,合作伙伴身份等同于 partner + * 商户签约拿到的pid,partner_id的简称,合作伙伴身份等同于 partner */ - private volatile String pid ; + private String pid; /** * 商户收款账号 */ - private volatile String seller; + private String seller; + /** + * 应用公钥证书 + */ + private Object merchantCert; - public void setAppId(String appId) { + /** + * 支付宝公钥证书 + */ + private Object aliPayCert; + /** + * 支付宝CA证书,根证书 + */ + private Object aliPayRootCert; + + /** + * 证书存储类型 + */ + private CertStore certStoreType; + + /** + * 证书信息 + */ + private CertEnvironment certEnvironment; + + + public String getAppAuthToken() { + return appAuthToken; + } + + public void setAppAuthToken(String appAuthToken) { + this.appAuthToken = appAuthToken; + } + + public void setAppid(String appId) { this.appId = appId; } @Override + @Deprecated public String getAppid() { return appId; } + /** + * 应用id + * 纠正名称 + * + * @return 应用id + */ + @Override + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } @Override public String getPid() { @@ -55,7 +116,62 @@ public class AliPayConfigStorage extends BasePayConfigStorage { this.seller = seller; } + public Object getMerchantCert() { + return merchantCert; + } + + public void setMerchantCert(Object merchantCert) { + this.merchantCert = merchantCert; + } + + public Object getAliPayCert() { + return aliPayCert; + } + + public void setAliPayCert(Object aliPayCert) { + this.aliPayCert = aliPayCert; + } + + public Object getAliPayRootCert() { + return aliPayRootCert; + } + + public void setAliPayRootCert(Object aliPayRootCert) { + this.aliPayRootCert = aliPayRootCert; + } + + public CertStore getCertStoreType() { + return certStoreType; + } + public void setCertStoreType(CertStore certStoreType) { + this.certStoreType = certStoreType; + } + + public CertEnvironment getCertEnvironment() { + return certEnvironment; + } + + public void setCertEnvironment(CertEnvironment certEnvironment) { + this.certEnvironment = certEnvironment; + } + + /** + * 初始化证书信息 + */ + public void loadCertEnvironment() { + if (!isCertSign() || null != this.certEnvironment) { + return; + } + try (InputStream merchantCertStream = certStoreType.getInputStream(merchantCert); + InputStream aliPayCertStream = certStoreType.getInputStream(aliPayCert); + InputStream aliPayRootCertStream = certStoreType.getInputStream(aliPayRootCert)) { + this.certEnvironment = new CertEnvironment(merchantCertStream, aliPayCertStream, aliPayRootCertStream); + } + catch (IOException e) { + throw new PayErrorException(new PayException("读取证书异常", e.getMessage())); + } + } } diff --git a/pay-java-ali/src/main/java/com/egzosn/pay/ali/api/AliPayService.java b/pay-java-ali/src/main/java/com/egzosn/pay/ali/api/AliPayService.java index 214cb33d5da0eaf3db975efef087eb3666666711..f55316a4345e51acb35b1e21c107c9c34e9b2a8e 100644 --- a/pay-java-ali/src/main/java/com/egzosn/pay/ali/api/AliPayService.java +++ b/pay-java-ali/src/main/java/com/egzosn/pay/ali/api/AliPayService.java @@ -1,64 +1,88 @@ package com.egzosn.pay.ali.api; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.TreeMap; + +import static com.egzosn.pay.ali.bean.AliPayConst.ALIPAY_CERT_SN_FIELD; +import static com.egzosn.pay.ali.bean.AliPayConst.APP_AUTH_TOKEN; +import static com.egzosn.pay.ali.bean.AliPayConst.BIZ_CONTENT; +import static com.egzosn.pay.ali.bean.AliPayConst.CODE; +import static com.egzosn.pay.ali.bean.AliPayConst.DBACK_AMOUNT; +import static com.egzosn.pay.ali.bean.AliPayConst.HTTPS_REQ_URL; +import static com.egzosn.pay.ali.bean.AliPayConst.NOTIFY_URL; +import static com.egzosn.pay.ali.bean.AliPayConst.PASSBACK_PARAMS; +import static com.egzosn.pay.ali.bean.AliPayConst.PAYEE_INFO; +import static com.egzosn.pay.ali.bean.AliPayConst.PRODUCT_CODE; +import static com.egzosn.pay.ali.bean.AliPayConst.RETURN_URL; +import static com.egzosn.pay.ali.bean.AliPayConst.SIGN; +import static com.egzosn.pay.ali.bean.AliPayConst.SUCCESS_CODE; + import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; +import com.egzosn.pay.ali.bean.AliPayBillType; +import com.egzosn.pay.ali.bean.AliPayConst; +import com.egzosn.pay.ali.bean.AliPayMessage; +import com.egzosn.pay.ali.bean.AliRefundResult; import com.egzosn.pay.ali.bean.AliTransactionType; +import com.egzosn.pay.ali.bean.AliTransferType; +import com.egzosn.pay.ali.bean.CertEnvironment; +import com.egzosn.pay.ali.bean.OrderSettle; import com.egzosn.pay.common.api.BasePayService; -import com.egzosn.pay.common.bean.*; +import com.egzosn.pay.common.api.TransferService; +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.common.bean.BillType; +import com.egzosn.pay.common.bean.MethodType; +import com.egzosn.pay.common.bean.NoticeParams; +import com.egzosn.pay.common.bean.Order; +import com.egzosn.pay.common.bean.OrderParaStructure; +import com.egzosn.pay.common.bean.PayMessage; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.PayOutMessage; +import com.egzosn.pay.common.bean.RefundOrder; +import com.egzosn.pay.common.bean.TransactionType; +import com.egzosn.pay.common.bean.TransferOrder; +import com.egzosn.pay.common.bean.TransferType; import com.egzosn.pay.common.bean.result.PayException; import com.egzosn.pay.common.exception.PayErrorException; import com.egzosn.pay.common.http.HttpConfigStorage; import com.egzosn.pay.common.http.UriVariables; import com.egzosn.pay.common.util.DateUtils; -import com.egzosn.pay.common.util.MatrixToImageWriter; import com.egzosn.pay.common.util.Util; +import com.egzosn.pay.common.util.sign.SignTextUtils; import com.egzosn.pay.common.util.sign.SignUtils; import com.egzosn.pay.common.util.str.StringUtils; -import java.awt.image.BufferedImage; -import java.math.BigDecimal; -import java.util.*; /** * 支付宝支付服务 * * @author egan - *

- * email egzosn@gmail.com - * date 2017-2-22 20:09 + *

+ * email egzosn@gmail.com + * date 2017-2-22 20:09 */ -public class AliPayService extends BasePayService { - - /** - * 正式测试环境 - */ - private static final String HTTPS_REQ_URL = "https://openapi.alipay.com/gateway.do"; - /** - * 沙箱测试环境账号 - */ - private static final String DEV_REQ_URL = "https://openapi.alipaydev.com/gateway.do"; - - public static final String SIGN = "sign"; - - public static final String SUCCESS_CODE = "10000"; - - public static final String CODE = "code"; - /** - * 附加参数 - */ - public static final String PASSBACK_PARAMS = "passback_params"; - /** - * 产品代码 - */ - public static final String PRODUCT_CODE = "product_code"; +public class AliPayService extends BasePayService implements TransferService, AliPayServiceInf { + + /** - * 返回地址 + * api服务地址,默认为国内 */ - public static final String RETURN_URL = "return_url"; + private String apiServerUrl; /** - * 请求内容 + * 获取对应的请求地址 + * + * @return 请求地址 */ - public static final String BIZ_CONTENT = "biz_content"; + @Override + public String getReqUrl(TransactionType transactionType) { + if (StringUtils.isNotEmpty(apiServerUrl)) { + return apiServerUrl; + } + return payConfigStorage.isTest() ? AliPayConst.DEV_REQ_URL : HTTPS_REQ_URL; + } /** * 获取对应的请求地址 @@ -66,41 +90,61 @@ public class AliPayService extends BasePayService { * @return 请求地址 */ public String getReqUrl() { - return payConfigStorage.isTest() ? DEV_REQ_URL : HTTPS_REQ_URL; + return getReqUrl(null); } + /** + * 设置支付配置 + * + * @param payConfigStorage 支付配置 + */ + @Override + public AliPayService setPayConfigStorage(AliPayConfigStorage payConfigStorage) { + payConfigStorage.loadCertEnvironment(); + super.setPayConfigStorage(payConfigStorage); + return this; + } + public AliPayService(AliPayConfigStorage payConfigStorage, HttpConfigStorage configStorage) { super(payConfigStorage, configStorage); } public AliPayService(AliPayConfigStorage payConfigStorage) { - super(payConfigStorage); + this(payConfigStorage, null); } - public String getHttpsVerifyUrl() { - return getReqUrl() + "?service=notify_verify"; - } - /** * 回调校验 * * @param params 回调回来的参数集 * @return 签名校验 true通过 */ + @Deprecated @Override public boolean verify(Map params) { + return verify(new NoticeParams(params)); + } + /** + * 回调校验 + * + * @param noticeParams 回调回来的参数集 + * @return 签名校验 true通过 + */ + @Override + public boolean verify(NoticeParams noticeParams) { + final Map params = noticeParams.getBody(); if (params.get(SIGN) == null) { - LOG.debug("支付宝支付异常:params:" + params); + LOG.debug("支付宝支付异常:params:{}", params); return false; } - return signVerify(params, (String) params.get(SIGN)) && verifySource((String) params.get("notify_id")); - + return signVerify(params, (String) params.get(SIGN)); } + /** * 根据反馈回来的信息,生成签名结果 * @@ -108,36 +152,45 @@ public class AliPayService extends BasePayService { * @param sign 比对的签名结果 * @return 生成的签名结果 */ - @Override public boolean signVerify(Map params, String sign) { if (params instanceof JSONObject) { for (Map.Entry entry : params.entrySet()) { - if (SIGN.equals(entry.getKey())) { + if (SIGN.equals(entry.getKey()) || ALIPAY_CERT_SN_FIELD.equals(entry.getKey())) { continue; } - TreeMap response = new TreeMap((Map )entry.getValue()); + TreeMap response = new TreeMap((Map) entry.getValue()); LinkedHashMap linkedHashMap = new LinkedHashMap<>(); linkedHashMap.put(CODE, response.remove(CODE)); linkedHashMap.put("msg", response.remove("msg")); linkedHashMap.putAll(response); - return SignUtils.valueOf(payConfigStorage.getSignType()).verify(JSON.toJSONString(linkedHashMap), sign, payConfigStorage.getKeyPublic(), payConfigStorage.getInputCharset()); + return SignUtils.valueOf(payConfigStorage.getSignType()).verify(JSON.toJSONString(linkedHashMap), sign, getKeyPublic(params), payConfigStorage.getInputCharset()); } } - - return SignUtils.valueOf(payConfigStorage.getSignType()).verify(params, sign, payConfigStorage.getKeyPublic(), payConfigStorage.getInputCharset()); + return SignUtils.valueOf(payConfigStorage.getSignType()).verify(params, sign, getKeyPublic(params), payConfigStorage.getInputCharset()); } + /** + * 获取公钥信息 + * + * @param params 响应参数 + * @return 公钥信息 + */ + protected String getKeyPublic(Map params) { + if (!payConfigStorage.isCertSign()) { + return payConfigStorage.getKeyPublic(); + } + return payConfigStorage.getCertEnvironment().getAliPayPublicKey(getAliPayCertSN(params)); + } /** - * 校验数据来源 + * 从响应Map中提取支付宝公钥证书序列号 * - * @param id 业务id, 数据的真实性. - * @return true通过 + * @param respMap 响应Map + * @return 支付宝公钥证书序列号 */ - @Override - public boolean verifySource(String id) { - return true; + public String getAliPayCertSN(java.util.Map respMap) { + return (String) respMap.get(ALIPAY_CERT_SN_FIELD); } @@ -147,9 +200,9 @@ public class AliPayService extends BasePayService { * @param parameters 请求参数 * @return 请求参数 */ - private Map setSign(Map parameters) { + protected Map setSign(Map parameters) { parameters.put("sign_type", payConfigStorage.getSignType()); - String sign = createSign(SignUtils.parameterText(parameters, "&", SIGN), payConfigStorage.getInputCharset()); + String sign = createSign(SignTextUtils.parameterText(parameters, "&", SIGN), payConfigStorage.getInputCharset()); parameters.put(SIGN, sign); return parameters; @@ -169,6 +222,17 @@ public class AliPayService extends BasePayService { return setSign(getOrder(order)); } + private void setNotifyUrl(Map orderInfo, AssistOrder order) { +// orderInfo.put(NOTIFY_URL, payConfigStorage.getNotifyUrl()); + OrderParaStructure.loadParameters(orderInfo, NOTIFY_URL, payConfigStorage.getNotifyUrl()); + OrderParaStructure.loadParameters(orderInfo, NOTIFY_URL, order.getNotifyUrl()); + OrderParaStructure.loadParameters(orderInfo, NOTIFY_URL, order); + } + + private void setReturnUrl(Map orderInfo, PayOrder order) { + orderInfo.put(RETURN_URL, payConfigStorage.getReturnUrl()); + OrderParaStructure.loadParameters(orderInfo, RETURN_URL, order); + } /** * 支付宝创建订单信息 @@ -178,51 +242,85 @@ public class AliPayService extends BasePayService { * @return 返回支付宝预下单信息 * @see PayOrder 支付订单信息 */ - private Map getOrder(PayOrder order) { + protected Map getOrder(PayOrder order) { Map orderInfo = getPublicParameters(order.getTransactionType()); - - orderInfo.put("notify_url", payConfigStorage.getNotifyUrl()); + setNotifyUrl(orderInfo, order); orderInfo.put("format", "json"); - + setAppAuthToken(orderInfo, order.getAttrs()); Map bizContent = new TreeMap<>(); bizContent.put("body", order.getBody()); - bizContent.put("seller_id", payConfigStorage.getSeller()); + OrderParaStructure.loadParameters(bizContent, "seller_id", payConfigStorage.getSeller()); bizContent.put("subject", order.getSubject()); bizContent.put("out_trade_no", order.getOutTradeNo()); bizContent.put("total_amount", Util.conversionAmount(order.getPrice()).toString()); switch ((AliTransactionType) order.getTransactionType()) { case PAGE: - case DIRECT: bizContent.put(PASSBACK_PARAMS, order.getAddition()); bizContent.put(PRODUCT_CODE, "FAST_INSTANT_TRADE_PAY"); - orderInfo.put(RETURN_URL, payConfigStorage.getReturnUrl()); + bizContent.put(AliPayConst.REQUEST_FROM_URL, payConfigStorage.getReturnUrl()); + OrderParaStructure.loadParameters(bizContent, AliPayConst.REQUEST_FROM_URL, order); + setReturnUrl(orderInfo, order); break; case WAP: bizContent.put(PASSBACK_PARAMS, order.getAddition()); + //产品码。 + //商家和支付宝签约的产品码。 枚举值(点击查看签约情况): + //QUICK_WAP_WAY:无线快捷支付产品。 + //默认值为QUICK_WAP_PAY。 bizContent.put(PRODUCT_CODE, "QUICK_WAP_PAY"); - orderInfo.put(RETURN_URL, payConfigStorage.getReturnUrl()); + OrderParaStructure.loadParameters(bizContent, PRODUCT_CODE, order); + + bizContent.put(AliPayConst.QUIT_URL, payConfigStorage.getReturnUrl()); + OrderParaStructure.loadParameters(bizContent, AliPayConst.QUIT_URL, order); + setReturnUrl(orderInfo, order); break; case APP: bizContent.put(PASSBACK_PARAMS, order.getAddition()); bizContent.put(PRODUCT_CODE, "QUICK_MSECURITY_PAY"); - orderInfo.put(RETURN_URL, payConfigStorage.getReturnUrl()); + break; + case MINAPP: + bizContent.put("extend_params", order.getAddition()); + bizContent.put("buyer_id", order.getOpenid()); + bizContent.put(PRODUCT_CODE, "FACE_TO_FACE_PAYMENT"); break; case BAR_CODE: case WAVE_CODE: + case SECURITY_CODE: bizContent.put("scene", order.getTransactionType().toString().toLowerCase()); bizContent.put(PRODUCT_CODE, "FACE_TO_FACE_PAYMENT"); bizContent.put("auth_code", order.getAuthCode()); break; } - if (null != order.getExpirationTime()) { - bizContent.put("timeout_express", DateUtils.minutesRemaining(order.getExpirationTime()) + "m"); - } + + + setExpirationTime(bizContent, order); + + bizContent.putAll(order.getAttrs()); orderInfo.put(BIZ_CONTENT, JSON.toJSONString(bizContent)); - return orderInfo; + return preOrderHandler(orderInfo, order); + } + + private Map setExpirationTime(Map bizContent, PayOrder order) { + if (null == order.getExpirationTime()) { + return bizContent; + } + bizContent.put("timeout_express", DateUtils.minutesRemaining(order.getExpirationTime()) + "m"); + switch ((AliTransactionType) order.getTransactionType()) { + case SWEEPPAY: + bizContent.put("qr_code_timeout_express", DateUtils.minutesRemaining(order.getExpirationTime()) + "m"); + break; + case PAGE: + case WAP: + case APP: + bizContent.put("time_expire", DateUtils.formatDate(order.getExpirationTime(), DateUtils.YYYY_MM_DD_HH_MM_SS)); + break; + default: + } + return bizContent; } /** @@ -231,16 +329,42 @@ public class AliPayService extends BasePayService { * @param transactionType 交易类型 * @return 放回公共请求参数 */ - private Map getPublicParameters(TransactionType transactionType) { + protected Map getPublicParameters(TransactionType transactionType) { + boolean depositBack = transactionType == AliTransactionType.REFUND_DEPOSITBACK_COMPLETED; Map orderInfo = new TreeMap<>(); - orderInfo.put("app_id", payConfigStorage.getAppid()); - orderInfo.put("method", transactionType.getMethod()); + orderInfo.put("app_id", payConfigStorage.getAppId()); orderInfo.put("charset", payConfigStorage.getInputCharset()); - orderInfo.put("timestamp", DateUtils.format(new Date())); - orderInfo.put("version", "1.0"); + String method = "method"; + String version = "1.0"; + if (depositBack) { + method = "msg_method"; + orderInfo.put("utc_timestamp", System.currentTimeMillis()); + version = "1.1"; + } + else { + orderInfo.put("timestamp", DateUtils.format(new Date())); + } + + orderInfo.put(method, transactionType.getMethod()); + orderInfo.put("version", version); + + loadCertSn(orderInfo); return orderInfo; } + /** + * 加载证书序列 + * + * @param orderInfo 订单信息 + */ + protected void loadCertSn(Map orderInfo) { + if (payConfigStorage.isCertSign()) { + final CertEnvironment certEnvironment = payConfigStorage.getCertEnvironment(); + OrderParaStructure.loadParameters(orderInfo, "app_cert_sn", certEnvironment.getMerchantCertSN()); + OrderParaStructure.loadParameters(orderInfo, "alipay_root_cert_sn", certEnvironment.getRootCertSN()); + } + } + /** * 获取输出消息,用户返回给支付端 @@ -266,6 +390,17 @@ public class AliPayService extends BasePayService { return PayOutMessage.TEXT().content("success").build(); } + @Override + public String toPay(PayOrder order) { + if (null == order.getTransactionType()) { + order.setTransactionType(AliTransactionType.PAGE); + } + else if (order.getTransactionType() != AliTransactionType.PAGE && order.getTransactionType() != AliTransactionType.WAP) { + throw new PayErrorException(new PayException("-1", "错误的交易类型:" + order.getTransactionType())); + } + return super.toPay(order); + } + /** * @param orderInfo 发起支付的订单信息 * @param method 请求方式 "post" "get", @@ -278,30 +413,29 @@ public class AliPayService extends BasePayService { String bizContent = (String) orderInfo.remove(BIZ_CONTENT); formHtml.append(getReqUrl()).append("?").append(UriVariables.getMapToParameters(orderInfo)) .append("\" method=\"").append(method.name().toLowerCase()).append("\">"); - formHtml.append(""); + formHtml.append(""); formHtml.append(""); formHtml.append(""); return formHtml.toString(); } + /** - * 生成二维码支付 + * 获取输出二维码信息, * * @param order 发起支付的订单信息 - * @return 返回图片信息,支付时需要的 + * @return 返回二维码信息,,支付时需要的 */ @Override - public BufferedImage genQrPay(PayOrder order) { - + public String getQrPay(PayOrder order) { + order.setTransactionType(AliTransactionType.SWEEPPAY); Map orderInfo = orderInfo(order); - - //预订单 JSONObject result = getHttpRequestTemplate().postForObject(getReqUrl() + "?" + UriVariables.getMapToParameters(orderInfo), null, JSONObject.class); JSONObject response = result.getJSONObject("alipay_trade_precreate_response"); if (SUCCESS_CODE.equals(response.getString(CODE))) { - return MatrixToImageWriter.writeInfoToJpgBuff(response.getString("qr_code")); + return response.getString("qr_code"); } throw new PayErrorException(new PayException(response.getString(CODE), response.getString("msg"), result.toJSONString())); @@ -315,6 +449,13 @@ public class AliPayService extends BasePayService { */ @Override public Map microPay(PayOrder order) { + if (null == order.getTransactionType()) { + order.setTransactionType(AliTransactionType.BAR_CODE); + } + else if (order.getTransactionType() != AliTransactionType.BAR_CODE && order.getTransactionType() != AliTransactionType.WAVE_CODE && order.getTransactionType() != AliTransactionType.SECURITY_CODE) { + throw new PayErrorException(new PayException("-1", "错误的交易类型:" + order.getTransactionType())); + } + Map orderInfo = orderInfo(order); //预订单 JSONObject result = getHttpRequestTemplate().postForObject(getReqUrl() + "?" + UriVariables.getMapToParameters(orderInfo), null, JSONObject.class); @@ -325,6 +466,25 @@ public class AliPayService extends BasePayService { return result; } + + /** + * 统一收单交易结算接口 + * + * @param order 交易结算信息 + * @return 结算结果 + */ + public Map settle(OrderSettle order) { + //获取公共参数 + Map parameters = getPublicParameters(AliTransactionType.SETTLE); + setAppAuthToken(parameters, order.getAttrs()); + final Map bizContent = order.toBizContent(); + bizContent.putAll(order.getAttrs()); + parameters.put(BIZ_CONTENT, JSON.toJSONString(bizContent)); + //设置签名 + setSign(parameters); + return getHttpRequestTemplate().postForObject(getReqUrl() + "?" + UriVariables.getMapToParameters(parameters), null, JSONObject.class); + } + /** * 交易查询接口 * @@ -338,6 +498,29 @@ public class AliPayService extends BasePayService { } + /** + * 交易查询接口 + * + * @param assistOrder 查询条件 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(AssistOrder assistOrder) { + if (null == assistOrder.getTransactionType()) { + assistOrder.setTransactionType(AliTransactionType.QUERY); + } + //获取公共参数 + Map parameters = getPublicParameters(assistOrder.getTransactionType()); + Map bizContent = new TreeMap<>(); + OrderParaStructure.loadParameters(bizContent, "query_options", assistOrder); + + //设置请求参数的集合 + parameters.put(BIZ_CONTENT, JSON.toJSONString(getBizContent(assistOrder.getTradeNo(), assistOrder.getOutTradeNo(), bizContent))); + //设置签名 + setSign(parameters); + return requestTemplate.getForObject(getReqUrl(assistOrder.getTransactionType()) + "?" + UriVariables.getMapToParameters(parameters), JSONObject.class); + } + /** * 交易关闭接口 @@ -351,6 +534,17 @@ public class AliPayService extends BasePayService { return secondaryInterface(tradeNo, outTradeNo, AliTransactionType.CLOSE); } + /** + * 交易关闭接口 + * + * @param assistOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 + */ + @Override + public Map close(AssistOrder assistOrder) { + return secondaryInterface(assistOrder.getTradeNo(), assistOrder.getOutTradeNo(), AliTransactionType.CLOSE); + } + /** * 支付交易返回失败或支付系统超时,调用该接口撤销交易。 * 如果此订单用户支付失败,支付宝系统会将此订单关闭;如果用户支付成功,支付宝系统会将此订单资金退还给用户。 @@ -367,58 +561,63 @@ public class AliPayService extends BasePayService { } /** - * 申请退款接口 - * 废弃 + * 设置支付宝授权Token * - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * @param refundAmount 退款金额 - * @param totalAmount 总金额 - * @return 返回支付方申请退款后的结果 - * @see #refund(RefundOrder, com.egzosn.pay.common.api.Callback) - * @deprecated 版本替代 {@link #refund(RefundOrder, com.egzosn.pay.common.api.Callback)} + * @param parameters 参数 + * @param attrs 订单属性 */ - @Deprecated - @Override - public Map refund(String tradeNo, String outTradeNo, BigDecimal refundAmount, BigDecimal totalAmount) { - return refund(new RefundOrder(tradeNo, outTradeNo, refundAmount, totalAmount)); + protected void setAppAuthToken(Map parameters, Map attrs) { + setAppAuthToken(parameters); + OrderParaStructure.loadParameters(parameters, APP_AUTH_TOKEN, (String) attrs.remove(APP_AUTH_TOKEN)); + } + + /** + * 设置支付宝授权Token + * + * @param parameters 参数 + */ + protected void setAppAuthToken(Map parameters) { + OrderParaStructure.loadParameters(parameters, APP_AUTH_TOKEN, payConfigStorage.getAppAuthToken()); } /** * 申请退款接口 + * 兼容 收单退款冲退完成通知 {@link #refundDepositBackCompleted(RefundOrder)} 与 {@link com.egzosn.pay.ali.bean.RefundDepositBackCompletedNotify} * * @param refundOrder 退款订单信息 * @return 返回支付方申请退款后的结果 */ @Override - public Map refund(RefundOrder refundOrder) { + public AliRefundResult refund(RefundOrder refundOrder) { + if (null != refundOrder.getTransactionType() && refundOrder.getTransactionType() == AliTransactionType.REFUND_DEPOSITBACK_COMPLETED) { + String status = refundDepositBackCompleted(refundOrder); + AliRefundResult result = new AliRefundResult(); + result.setCode(status); + return result; + } //获取公共参数 Map parameters = getPublicParameters(AliTransactionType.REFUND); + setAppAuthToken(parameters, refundOrder.getAttrs()); Map bizContent = getBizContent(refundOrder.getTradeNo(), refundOrder.getOutTradeNo(), null); - if (!StringUtils.isEmpty(refundOrder.getRefundNo())) { - bizContent.put("out_request_no", refundOrder.getRefundNo()); - } + OrderParaStructure.loadParameters(bizContent, AliPayConst.OUT_REQUEST_NO, refundOrder.getRefundNo()); bizContent.put("refund_amount", Util.conversionAmount(refundOrder.getRefundAmount())); + OrderParaStructure.loadParameters(bizContent, AliPayConst.REFUND_REASON, refundOrder.getDescription()); + OrderParaStructure.loadParameters(bizContent, AliPayConst.REFUND_REASON, refundOrder); + OrderParaStructure.loadParameters(bizContent, "refund_royalty_parameters", refundOrder); + OrderParaStructure.loadParameters(bizContent, AliPayConst.QUERY_OPTIONS, refundOrder); //设置请求参数的集合 parameters.put(BIZ_CONTENT, JSON.toJSONString(bizContent)); //设置签名 setSign(parameters); - return requestTemplate.getForObject(getReqUrl() + "?" + UriVariables.getMapToParameters(parameters), JSONObject.class); + JSONObject result = requestTemplate.getForObject(getReqUrl() + "?" + UriVariables.getMapToParameters(parameters), JSONObject.class); + JSONObject refundResponse = result.getJSONObject("alipay_trade_refund_response"); + AliRefundResult refundResult = AliRefundResult.create(refundResponse); + refundResult.setOutRequestNo(refundOrder.getRefundNo()); + return refundResult; } - /** - * 查询退款 - * - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * @return 返回支付方查询退款后的结果 - */ - @Override - public Map refundquery(String tradeNo, String outTradeNo) { - return secondaryInterface(tradeNo, outTradeNo, AliTransactionType.REFUNDQUERY); - } /** * 查询退款 @@ -428,17 +627,15 @@ public class AliPayService extends BasePayService { */ @Override public Map refundquery(RefundOrder refundOrder) { - //获取公共参数 Map parameters = getPublicParameters(AliTransactionType.REFUNDQUERY); - + setAppAuthToken(parameters, refundOrder.getAttrs()); Map bizContent = getBizContent(refundOrder.getTradeNo(), refundOrder.getOutTradeNo(), null); - if (!StringUtils.isEmpty(refundOrder.getRefundNo())) { - bizContent.put("out_request_no", refundOrder.getRefundNo()); - } + OrderParaStructure.loadParameters(bizContent, AliPayConst.OUT_REQUEST_NO, refundOrder.getRefundNo()); + OrderParaStructure.loadParameters(bizContent, AliPayConst.QUERY_OPTIONS, refundOrder); +// bizContent.putAll(refundOrder.getAttrs()); //设置请求参数的集合 parameters.put(BIZ_CONTENT, JSON.toJSONString(bizContent)); - //设置签名 setSign(parameters); return requestTemplate.getForObject(getReqUrl() + "?" + UriVariables.getMapToParameters(parameters), JSONObject.class); @@ -448,26 +645,42 @@ public class AliPayService extends BasePayService { /** * 目前只支持日账单 * - * @param billDate 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; - * @param billType 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 + * @param billDate 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 + * @param billType 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; * @return 返回支付方下载对账单的结果 */ @Override - public Map downloadbill(Date billDate, String billType) { + public Map downloadBill(Date billDate, String billType) { + + return this.downloadBill(billDate, "trade".equals(billType) ? AliPayBillType.TRADE_DAY : AliPayBillType.SIGNCUSTOMER_DAY); + } + + /** + * 下载对账单 + * + * @param billDate 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 + * @param billType 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; + * @return 返回支付方下载对账单的结果 + */ + @Override + public Map downloadBill(Date billDate, BillType billType) { //获取公共参数 Map parameters = getPublicParameters(AliTransactionType.DOWNLOADBILL); Map bizContent = new TreeMap<>(); - bizContent.put("bill_type", billType); + bizContent.put("bill_type", billType.getType()); //目前只支持日账单 - bizContent.put("bill_date", DateUtils.formatDay(billDate)); + bizContent.put("bill_date", DateUtils.formatDate(billDate, billType.getDatePattern())); //设置请求参数的集合 - parameters.put(BIZ_CONTENT, JSON.toJSONString(bizContent)); + final String bizContentStr = JSON.toJSONString(bizContent); + parameters.put(BIZ_CONTENT, bizContentStr); //设置签名 setSign(parameters); - return requestTemplate.getForObject(getReqUrl() + "?" + UriVariables.getMapToParameters(parameters), JSONObject.class); - } + Map bizContentMap = new HashMap(1); + parameters.put(BIZ_CONTENT, bizContentStr); + return requestTemplate.postForObject(getReqUrl() + "?" + UriVariables.getMapToParameters(parameters), bizContentMap, JSONObject.class); + } /** * @param tradeNoOrBillDate 支付平台订单号或者账单类型, 具体请 @@ -476,7 +689,6 @@ public class AliPayService extends BasePayService { * @param transactionType 交易类型 * @return 返回支付方对应接口的结果 */ - @Override public Map secondaryInterface(Object tradeNoOrBillDate, String outTradeNoBillType, TransactionType transactionType) { if (transactionType == AliTransactionType.REFUND) { @@ -485,43 +697,45 @@ public class AliPayService extends BasePayService { if (transactionType == AliTransactionType.DOWNLOADBILL) { if (tradeNoOrBillDate instanceof Date) { - return downloadbill((Date) tradeNoOrBillDate, outTradeNoBillType); + return downloadBill((Date) tradeNoOrBillDate, outTradeNoBillType); } throw new PayErrorException(new PayException("failure", "非法类型异常:" + tradeNoOrBillDate.getClass())); } //获取公共参数 Map parameters = getPublicParameters(transactionType); + //设置请求参数的集合 - parameters.put(BIZ_CONTENT, getContentToJson(tradeNoOrBillDate.toString(), outTradeNoBillType)); + parameters.put(BIZ_CONTENT, getContentToJson((String) tradeNoOrBillDate, outTradeNoBillType)); //设置签名 setSign(parameters); - return requestTemplate.getForObject(getReqUrl() + "?" + UriVariables.getMapToParameters(parameters), JSONObject.class); + + return requestTemplate.getForObject(getReqUrl(transactionType) + "?" + UriVariables.getMapToParameters(parameters), JSONObject.class); } /** - * 转账 + * 新版转账转账 * * @param order 转账订单 * @return 对应的转账结果 */ @Override public Map transfer(TransferOrder order) { + final TransferType transferType = order.getTransferType(); //获取公共参数 - Map parameters = getPublicParameters(AliTransactionType.TRANS); + Map parameters = getPublicParameters(transferType); + setAppAuthToken(parameters, order.getAttrs()); - Map bizContent = new TreeMap(); + Map bizContent = new LinkedHashMap(); bizContent.put("out_biz_no", order.getOutNo()); - //默认 支付宝登录号,支持邮箱和手机号格式。 - bizContent.put("payee_type", "ALIPAY_LOGONID"); - if (null != order.getTransferType()) { - bizContent.put("payee_type", order.getTransferType().getType()); - } - bizContent.put("payee_account", order.getPayeeAccount()); - bizContent.put("amount", Util.conversionAmount(order.getAmount())); - bizContent.put("payer_show_name", order.getPayerName()); - bizContent.put("payee_real_name", order.getPayeeName()); + bizContent.put("trans_amount", order.getAmount()); + transferType.setAttr(bizContent, order); + OrderParaStructure.loadParameters(bizContent, "order_title", order); + OrderParaStructure.loadParameters(bizContent, "original_order_id", order); + setPayeeInfo(bizContent, order); bizContent.put("remark", order.getRemark()); + OrderParaStructure.loadParameters(bizContent, "business_params", order); + //设置请求参数的集合 parameters.put(BIZ_CONTENT, JSON.toJSONString(bizContent)); //设置签名 @@ -532,26 +746,57 @@ public class AliPayService extends BasePayService { /** * 转账查询 * - * @param outNo 商户转账订单号 - * @param tradeNo 支付平台转账订单号 + * @param assistOrder 辅助交易订单 * @return 对应的转账订单 */ @Override - public Map transferQuery(String outNo, String tradeNo) { + public Map transferQuery(AssistOrder assistOrder) { //获取公共参数 - Map parameters = getPublicParameters(AliTransactionType.TRANS_QUERY); + Map parameters = getPublicParameters(AliTransferType.TRANS_QUERY); Map bizContent = new TreeMap(); - if (StringUtils.isEmpty(outNo)) { - bizContent.put("order_id", tradeNo); - } else { - bizContent.put("out_biz_no", outNo); + if (StringUtils.isEmpty(assistOrder.getOutTradeNo())) { + bizContent.put("order_id", assistOrder.getTradeNo()); + } + else { + bizContent.put("out_biz_no", assistOrder.getOutTradeNo()); } //设置请求参数的集合 parameters.put(BIZ_CONTENT, JSON.toJSONString(bizContent)); //设置签名 setSign(parameters); return getHttpRequestTemplate().postForObject(getReqUrl() + "?" + UriVariables.getMapToParameters(parameters), null, JSONObject.class); + + } + + private Map setPayeeInfo(Map bizContent, Order order) { + final Object attr = order.getAttr(PAYEE_INFO); + + if (attr instanceof String) { + bizContent.put(PAYEE_INFO, attr); + } + if (attr instanceof TreeMap) { + bizContent.put(PAYEE_INFO, attr); + } + if (attr instanceof Map) { + Map payeeInfo = new TreeMap((Map) attr); + bizContent.put(PAYEE_INFO, payeeInfo); + } + return bizContent; + } + + + /** + * 转账查询 + * + * @param outNo 商户转账订单号 + * @param tradeNo 支付平台转账订单号 + * @return 对应的转账订单 + */ + @Override + public Map transferQuery(String outNo, String tradeNo) { + + return transferQuery(new AssistOrder(tradeNo, outNo)); } @@ -588,4 +833,59 @@ public class AliPayService extends BasePayService { return JSON.toJSONString(getBizContent(tradeNo, outTradeNo, null)); } + /** + * 创建消息 + * + * @param message 支付平台返回的消息 + * @return 支付消息对象 + */ + @Override + public PayMessage createMessage(Map message) { + return AliPayMessage.create(message); + } + + /** + * 收单退款冲退完成通知 + * 退款存在退到银行卡场景下时,收单会根据银行回执消息发送退款完成信息 + * + * @param refundOrder 退款订单 + * @return fail 消息获取失败 是 success 消息获取成功 否 + */ + @Override + public String refundDepositBackCompleted(RefundOrder refundOrder) { + //获取公共参数 + Map parameters = getPublicParameters(refundOrder.getTransactionType()); + OrderParaStructure.loadParameters(parameters, "notify_id", refundOrder); + OrderParaStructure.loadParameters(parameters, "msg_type", refundOrder); + OrderParaStructure.loadParameters(parameters, "msg_uid", refundOrder); + OrderParaStructure.loadParameters(parameters, "msg_app_id", refundOrder); + + Map bizContent = getBizContent(refundOrder.getTradeNo(), refundOrder.getOutTradeNo(), null); + OrderParaStructure.loadParameters(bizContent, AliPayConst.OUT_REQUEST_NO, refundOrder.getRefundNo()); + OrderParaStructure.loadParameters(bizContent, "dback_status", refundOrder); + bizContent.put(DBACK_AMOUNT, refundOrder.getRefundAmount()); + OrderParaStructure.loadParameters(bizContent, DBACK_AMOUNT, refundOrder); + OrderParaStructure.loadParameters(bizContent, "bank_ack_time", refundOrder); + OrderParaStructure.loadParameters(bizContent, "est_bank_receipt_time", refundOrder); + //设置请求参数的集合 + parameters.put(BIZ_CONTENT, JSON.toJSONString(bizContent)); + //设置签名 + setSign(parameters); + + return null; + } + + /** + * 设置api服务器地址 + * + * @param apiServerUrl api服务器地址 + * @return 自身 + */ + @Override + public AliPayServiceInf setApiServerUrl(String apiServerUrl) { + this.apiServerUrl = apiServerUrl; + return this; + } + + } diff --git a/pay-java-ali/src/main/java/com/egzosn/pay/ali/api/AliPayServiceInf.java b/pay-java-ali/src/main/java/com/egzosn/pay/ali/api/AliPayServiceInf.java new file mode 100644 index 0000000000000000000000000000000000000000..699f2f88278554b00be1ea21c19faf0d2ca2e048 --- /dev/null +++ b/pay-java-ali/src/main/java/com/egzosn/pay/ali/api/AliPayServiceInf.java @@ -0,0 +1,31 @@ +package com.egzosn.pay.ali.api; + +import com.egzosn.pay.common.bean.RefundOrder; + +/** + * 支付宝定制化服务接口 + * @author Egan + *

+ * email egan@egzosn.com
+ * date 2021/12/4
+ * 
+ */ +public interface AliPayServiceInf { + + /** + * 收单退款冲退完成通知 + * 退款存在退到银行卡场景下时,收单会根据银行回执消息发送退款完成信息 + * @param refundOrder 退款订单 + * @return fail 消息获取失败 是 success 消息获取成功 否 + */ + String refundDepositBackCompleted(RefundOrder refundOrder); + + + /** + * 设置api服务器地址 + * + * @param apiServerUrl api服务器地址 + * @return 自身 + */ + AliPayServiceInf setApiServerUrl(String apiServerUrl); +} diff --git a/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliPayBillType.java b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliPayBillType.java new file mode 100644 index 0000000000000000000000000000000000000000..297398f39a37f94bf59fce81904ab8195e1dc63f --- /dev/null +++ b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliPayBillType.java @@ -0,0 +1,90 @@ +package com.egzosn.pay.ali.bean; + +import com.egzosn.pay.common.bean.BillType; +import com.egzosn.pay.common.util.DateUtils; + +/** + * 支付宝账单类型 + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2021/2/22
+ * 
+ */ +public enum AliPayBillType implements BillType { + /** + * 商户基于支付宝交易收单的业务账单;每日账单 + */ + TRADE_DAY("trade", DateUtils.YYYY_MM_DD), + /** + * 商户基于支付宝交易收单的业务账单;每月账单 + */ + TRADE_MONTH("trade", DateUtils.YYYY_MM), + /** + * 基于商户支付宝余额收入及支出等资金变动的帐务账单;每日账单 + */ + SIGNCUSTOMER_DAY("signcustomer", DateUtils.YYYY_MM_DD), + /** + * 基于商户支付宝余额收入及支出等资金变动的帐务账单;每月账单 + */ + SIGNCUSTOMER_MONTH("signcustomer", DateUtils.YYYY_MM), + + ; + + /** + * 账单类型 + */ + private String type; + /** + * 日期格式化表达式 + */ + private String datePattern; + + AliPayBillType(String type, String datePattern) { + this.type = type; + this.datePattern = datePattern; + } + + /** + * 获取类型名称 + * + * @return 类型 + */ + @Override + public String getType() { + return type; + } + + /** + * 获取类型对应的日期格式化表达式 + * + * @return 日期格式化表达式 + */ + @Override + public String getDatePattern() { + return datePattern; + } + + /** + * 获取文件类型 + * + * @return 文件类型 + */ + @Override + public String getFileType() { + return null; + } + + + /** + * 自定义属性 + * + * @return 自定义属性 + */ + @Override + public String getCustom() { + return null; + } + + +} diff --git a/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliPayConst.java b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliPayConst.java new file mode 100644 index 0000000000000000000000000000000000000000..7bce3cd117778d7b3c9476c0ff9cecb5593097b2 --- /dev/null +++ b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliPayConst.java @@ -0,0 +1,80 @@ +package com.egzosn.pay.ali.bean; + +/** + * 常量 + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2020/10/8
+ * 
+ */ +public final class AliPayConst { + private AliPayConst() { + } + + + /** + * 正式测试环境 + */ + public static final String HTTPS_REQ_URL = "https://openapi.alipay.com/gateway.do"; + /** + * 沙箱测试环境账号 + */ + public static final String DEV_REQ_URL = "https://openapi-sandbox.dl.alipaydev.com/gateway.do"; + + public static final String SIGN = "sign"; + + public static final String SUCCESS_CODE = "10000"; + + public static final String CODE = "code"; + /** + * 附加参数 + */ + public static final String PASSBACK_PARAMS = "passback_params"; + /** + * 产品代码 + */ + public static final String PRODUCT_CODE = "product_code"; + /** + * 返回地址 + */ + public static final String RETURN_URL = "return_url"; + /** + * 回调地址 + */ + public static final String NOTIFY_URL = "notify_url"; + + /** + * 请求内容 + */ + public static final String BIZ_CONTENT = "biz_content"; + /** + * 应用授权概述 + */ + public static final String APP_AUTH_TOKEN = "app_auth_token"; + /** + * 收款方信息 + */ + public static final String PAYEE_INFO = "payee_info"; + /** + * 收款方信息 + */ + public static final String ALIPAY_CERT_SN_FIELD = "alipay_cert_sn"; + /** + * 业务扩展参数 + */ + public static final String EXTEND_PARAMS = "extend_params"; + public static final String BIZ_TYPE = "biz_type"; + public static final String REFUND_REASON = "refund_reason"; + public static final String QUERY_OPTIONS = "query_options"; + public static final String OUT_REQUEST_NO = "out_request_no"; + /** + * 用户付款中途退出返回商户网站的地址 + */ + public static final String QUIT_URL = "quit_url"; + /** + * 请求来源地址。如果使用ALIAPP的集成方式,用户中途取消支付会返回该地址。 + */ + public static final String REQUEST_FROM_URL = "request_from_url"; + public static final String DBACK_AMOUNT = "dback_amount"; +} diff --git a/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliPayMessage.java b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliPayMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..82261e8b37bea78b064d89dfa4feccb91abab5cd --- /dev/null +++ b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliPayMessage.java @@ -0,0 +1,368 @@ +package com.egzosn.pay.ali.bean; + +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.annotation.JSONField; +import com.egzosn.pay.common.bean.PayMessage; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.Map; + +/** + * 支付宝回调信息 + * + * @author egan + * email egzosn@gmail.com + * date 2019/6/30.19:19 + */ + +public class AliPayMessage extends PayMessage { + //notify_time 通知时间 Date 是 通知的发送时间。格式为yyyy-MM-dd HH:mm:ss 2015-14-27 15:45:58 + @JSONField(name = "notify_time") + private Date notifyTime; + //notify_type 通知类型 String(64) 是 通知的类型 trade_status_sync + @JSONField(name = "notify_type") + private String notifyType; + // notify_id 通知校验ID String(128) 是 通知校验ID ac05099524730693a8b330c5ecf72da9786 + @JSONField(name = "notify_id") + private String notifyId; + // app_id 支付宝分配给开发者的应用Id String(32) 是 支付宝分配给开发者的应用Id 2014072300007148 + @JSONField(name = "app_id") + private String appId; + // charset 编码格式 String(10) 是 编码格式,如utf-8、gbk、gb2312等 utf-8 + private String charset; + // version 接口版本 String(3) 是 调用的接口版本,固定为:1.0 1.0 + private String version; + // sign_type 签名类型 String(10) 是 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2 RSA2 + @JSONField(name = "sign_type") + private String signType; + // sign 签名 String(256) 是 请参考文末 异步返回结果的验签 601510b7970e52cc63db0f44997cf70e + private String sign; + // trade_no 支付宝交易号 String(64) 是 支付宝交易凭证号 2013112011001004330000121536 + @JSONField(name = "trade_no") + private String tradeNo; + // out_trade_no 商户订单号 String(64) 是 原支付请求的商户订单号 6823789339978248 + @JSONField(name = "out_trade_no") + private String outTradeNo; + // out_biz_no 商户业务号 String(64) 否 商户业务ID,主要是退款通知中返回退款申请的流水号 HZRF001 + @JSONField(name = "out_biz_no") + private String outBizNo; + // buyer_id 买家支付宝用户号 String(16) 否 买家支付宝账号对应的支付宝唯一用户号。以2088开头的纯16位数字 2088102122524333 + @JSONField(name = "buyer_id") + private String buyerId; + // buyer_logon_id 买家支付宝账号 String(100) 否 买家支付宝账号 15901825620 + @JSONField(name = "buyer_logon_id") + private String buyerLogonId; + // seller_id 卖家支付宝用户号 String(30) 否 卖家支付宝用户号 2088101106499364 + @JSONField(name = "seller_id") + private String sellerId; + // seller_email 卖家支付宝账号 String(100) 否 卖家支付宝账号 zhuzhanghu@alitest.com + @JSONField(name = "seller_email") + private String sellerEmail; + // trade_status 交易状态 String(32) 否 交易目前所处的状态,见下张表 交易状态说明 TRADE_CLOSED + @JSONField(name = "trade_status") + private String tradeStatus; + // total_amount 订单金额 Number(9,2) 否 本次交易支付的订单金额,单位为人民币(元) 20 + @JSONField(name = "total_amount") + private BigDecimal totalAmount; + // receipt_amount 实收金额 Number(9,2) 否 商家在交易中实际收到的款项,单位为元 15 + @JSONField(name = "receipt_amount") + private BigDecimal receiptAmount; + // invoice_amount 开票金额 Number(9,2) 否 用户在交易中支付的可开发票的金额 10.00 + @JSONField(name = "invoice_amount") + private BigDecimal invoiceAmount; + // buyer_pay_amount 付款金额 Number(9,2) 否 用户在交易中支付的金额 13.88 + @JSONField(name = "buyer_pay_amount") + private BigDecimal buyerPayAmount; + // point_amount 集分宝金额 Number(9,2) 否 使用集分宝支付的金额 12.00 + @JSONField(name = "point_amount") + private BigDecimal pointAmount; + // refund_fee 总退款金额 Number(9,2) 否 退款通知中,返回总退款金额,单位为元,支持两位小数 2.58 + @JSONField(name = "refund_fee") + private BigDecimal refundFee; + // subject 订单标题 String(256) 否 商品的标题/交易标题/订单标题/订单关键字等,是请求时对应的参数,原样通知回来 当面付交易 + @JSONField(name = "subject") + private String subject; + // body 商品描述 String(400) 否 该订单的备注、描述、明细等。对应请求时的body参数,原样通知回来 当面付交易内容 + @JSONField(name = "body") + private String body; + // gmt_create 交易创建时间 Date 否 该笔交易创建的时间。格式为yyyy-MM-dd HH:mm:ss 2015-04-27 15:45:57 + @JSONField(name = "gmt_create") + private Date gmtCreate; + // gmt_payment 交易付款时间 Date 否 该笔交易的买家付款时间。格式为yyyy-MM-dd HH:mm:ss 2015-04-27 15:45:57 + @JSONField(name = "gmt_payment") + private Date gmtPayment; + // gmt_refund 交易退款时间 Date 否 该笔交易的退款时间。格式为yyyy-MM-dd HH:mm:ss.S 2015-04-28 15:45:57.320 + @JSONField(name = "gmt_refund") + private Date gmtRefund; + // gmt_close 交易结束时间 Date 否 该笔交易结束时间。格式为yyyy-MM-dd HH:mm:ss 2015-04-29 15:45:57 + @JSONField(name = "gmt_close") + private Date gmtClose; + // fund_bill_list 支付金额信息 String(512) 否 支付成功的各个渠道金额信息,详见下表 资金明细信息说明 [{“amount”:“15.00”,“fundChannel”:“ALIPAYACCOUNT”}] + @JSONField(name = "fund_bill_list") + private String fundBillList; + // passback_params 回传参数 String(512) 否 公共回传参数,如果请求时传递了该参数,则返回给商户时会在异步通知时将该参数原样返回。本参数必须进行UrlEncode之后才可以发送给支付宝 merchantBizType%3d3C%26merchantBizNo%3d2016010101111 + @JSONField(name = "passback_params") + private String passbackParams; + // voucher_detail_list 优惠券信息 String 否 本交易支付时所使用的所有优惠券信息,详见下表 优惠券信息说明 [{“amount”:“0.20”,“merchantContribute”:“0.00”,“name”:“一键创建券模板的券名称”,“otherContribute”:“0.20”,“type”:“ALIPAY_DISCOUNT_VOUCHER”,“memo”:“学生卡8折优惠”] + @JSONField(name = "voucher_detail_list") + private String voucherDetailList; + + public Date getNotifyTime() { + return notifyTime; + } + + public void setNotifyTime(Date notifyTime) { + this.notifyTime = notifyTime; + } + + public String getNotifyType() { + return notifyType; + } + + public void setNotifyType(String notifyType) { + this.notifyType = notifyType; + } + + public String getNotifyId() { + return notifyId; + } + + public void setNotifyId(String notifyId) { + this.notifyId = notifyId; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getCharset() { + return charset; + } + + public void setCharset(String charset) { + this.charset = charset; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getSignType() { + return signType; + } + + public void setSignType(String signType) { + this.signType = signType; + } + + @Override + public String getSign() { + return sign; + } + + public void setSign(String sign) { + this.sign = sign; + } + + public String getTradeNo() { + return tradeNo; + } + + public void setTradeNo(String tradeNo) { + this.tradeNo = tradeNo; + } + + @Override + public String getOutTradeNo() { + return outTradeNo; + } + + public void setOutTradeNo(String outTradeNo) { + this.outTradeNo = outTradeNo; + } + + public String getOutBizNo() { + return outBizNo; + } + + public void setOutBizNo(String outBizNo) { + this.outBizNo = outBizNo; + } + + public String getBuyerId() { + return buyerId; + } + + public void setBuyerId(String buyerId) { + this.buyerId = buyerId; + } + + public String getBuyerLogonId() { + return buyerLogonId; + } + + public void setBuyerLogonId(String buyerLogonId) { + this.buyerLogonId = buyerLogonId; + } + + public String getSellerId() { + return sellerId; + } + + public void setSellerId(String sellerId) { + this.sellerId = sellerId; + } + + public String getSellerEmail() { + return sellerEmail; + } + + public void setSellerEmail(String sellerEmail) { + this.sellerEmail = sellerEmail; + } + + public String getTradeStatus() { + return tradeStatus; + } + + public void setTradeStatus(String tradeStatus) { + this.tradeStatus = tradeStatus; + } + + public BigDecimal getTotalAmount() { + return totalAmount; + } + + public void setTotalAmount(BigDecimal totalAmount) { + this.totalAmount = totalAmount; + } + + public BigDecimal getReceiptAmount() { + return receiptAmount; + } + + public void setReceiptAmount(BigDecimal receiptAmount) { + this.receiptAmount = receiptAmount; + } + + public BigDecimal getInvoiceAmount() { + return invoiceAmount; + } + + public void setInvoiceAmount(BigDecimal invoiceAmount) { + this.invoiceAmount = invoiceAmount; + } + + public BigDecimal getBuyerPayAmount() { + return buyerPayAmount; + } + + public void setBuyerPayAmount(BigDecimal buyerPayAmount) { + this.buyerPayAmount = buyerPayAmount; + } + + public BigDecimal getPointAmount() { + return pointAmount; + } + + public void setPointAmount(BigDecimal pointAmount) { + this.pointAmount = pointAmount; + } + + public BigDecimal getRefundFee() { + return refundFee; + } + + public void setRefundFee(BigDecimal refundFee) { + this.refundFee = refundFee; + } + + @Override + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public Date getGmtCreate() { + return gmtCreate; + } + + public void setGmtCreate(Date gmtCreate) { + this.gmtCreate = gmtCreate; + } + + public Date getGmtPayment() { + return gmtPayment; + } + + public void setGmtPayment(Date gmtPayment) { + this.gmtPayment = gmtPayment; + } + + public Date getGmtRefund() { + return gmtRefund; + } + + public void setGmtRefund(Date gmtRefund) { + this.gmtRefund = gmtRefund; + } + + public Date getGmtClose() { + return gmtClose; + } + + public void setGmtClose(Date gmtClose) { + this.gmtClose = gmtClose; + } + + public String getFundBillList() { + return fundBillList; + } + + public void setFundBillList(String fundBillList) { + this.fundBillList = fundBillList; + } + + public String getPassbackParams() { + return passbackParams; + } + + public void setPassbackParams(String passbackParams) { + this.passbackParams = passbackParams; + } + + public String getVoucherDetailList() { + return voucherDetailList; + } + + public void setVoucherDetailList(String voucherDetailList) { + this.voucherDetailList = voucherDetailList; + } + + public static final AliPayMessage create(Map message){ + AliPayMessage payMessage = new JSONObject(message).toJavaObject(AliPayMessage.class); + payMessage.setPayMessage(message); + return payMessage; + } + +} diff --git a/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliRefundResult.java b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliRefundResult.java new file mode 100644 index 0000000000000000000000000000000000000000..65c6cbca82aba2d99137a7320d1978e7c39f4723 --- /dev/null +++ b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliRefundResult.java @@ -0,0 +1,378 @@ +package com.egzosn.pay.ali.bean; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.annotation.JSONField; +import com.egzosn.pay.common.bean.BaseRefundResult; +import com.egzosn.pay.common.bean.CurType; +import com.egzosn.pay.common.bean.DefaultCurType; +import com.egzosn.pay.common.bean.RefundOrder; + +/** + * 支付宝退款结果返回 + * + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2020/8/16 17:53
+ * 
+ */ +public class AliRefundResult extends BaseRefundResult { + + /** + * 网关返回码,详见文档 40004 + */ + private String code; + /** + * 网关返回码描述,详见文档 Business Failed + */ + private String msg; + /** + * 业务返回码,参见具体的API接口文档 ACQ.TRADE_HAS_SUCCESS + */ + @JSONField(name = "sub_code") + private String subCode; + + /** + * 业务返回码描述,参见具体的API接口文档 交易已被支付 + */ + @JSONField(name = "sub_msg") + private String subMsg; + /** + * 签名,详见文档 + */ + private String sign; + /** + * 支付宝交易号 + */ + @JSONField(name = "trade_no") + private String tradeNo; + /** + * 商户订单号 6823789339978248 + */ + @JSONField(name = "out_trade_no") + private String outTradeNo; + /** + * 标识一次退款请求,同一笔交易多次退款需要保证唯一 + *

+ * 因支付宝退款结果中没有返回此参数,该参数从{@link RefundOrder#getRefundNo()}获取 + */ + private String outRequestNo; + /** + * 用户的登录id 159****5620 + */ + @JSONField(name = "buyer_logon_id") + private String buyerLogonId; + /** + * 本次退款是否发生了资金变化 Y + */ + @JSONField(name = "fund_change") + private String fundChange; + /** + * 退款总金额 88.88 + */ + @JSONField(name = "refund_fee") + private BigDecimal refundFee; + /** + * 退款币种信息 USD + */ + @JSONField(name = "refund_currency") + private DefaultCurType refundCurrency; + /** + * 退款支付时间 2014-11-27 15:45:57 + */ + @JSONField(name = "gmt_refund_pay") + private Date gmtRefundPay; + + /** + * 退款使用的资金渠道。 + * 只有在签约中指定需要返回资金明细,或者入参的query_options中指定时才返回该字段信息。 + */ + @JSONField(name = "refund_detail_item_list") + private List refundDetailItemList; + /** + * 交易在支付时候的门店名称 + */ + @JSONField(name = "store_name") + private String storeName; + /** + * 买家在支付宝的用户id 2088101117955611 + */ + @JSONField(name = "buyer_user_id") + private String buyerUserId; + /** + * 退回的前置资产列表 + */ + @JSONField(name = "refund_preset_paytool_list") + private PresetPayToolInfo refundPresetPaytoolList; + /** + * 退款清算编号,用于清算对账使用; + * 只在银行间联交易场景下返回该信息; 2018101610032004620239146945 + */ + @JSONField(name = "refund_settlement_id") + private String refundSettlementId; + /** + * 本次退款金额中买家退款金额 88.88 + */ + @JSONField(name = "present_refund_buyer_amount") + private String presentRefundBuyerAmount; + /** + * 本次退款金额中平台优惠退款金额 88.88 + */ + @JSONField(name = "present_refund_discount_amount") + private String presentRefundDiscountAmount; + /** + * 本次退款金额中商家优惠退款金额 88.88 + */ + @JSONField(name = "present_refund_mdiscount_amount") + private String presentRefundMdiscountAmount; + + + + /** + * 获取退款请求结果状态码 + * + * @return 状态码 + */ + @Override + public String getCode() { + return code; + } + + /** + * 获取退款请求结果状态提示信息 + * + * @return 提示信息 + */ + @Override + public String getMsg() { + return msg; + } + + /** + * 返回业务结果状态码 + * + * @return 业务结果状态码 + */ + @Override + public String getResultCode() { + return subCode; + } + + /** + * 返回业务结果状态提示信息 + * + * @return 业务结果状态提示信息 + */ + @Override + public String getResultMsg() { + return subMsg; + } + + /** + * 退款金额 + * + * @return 退款金额 + */ + @Override + public BigDecimal getRefundFee() { + return refundFee; + } + + /** + * 退款币种信息 + * + * @return 币种信息 + */ + @Override + public CurType getRefundCurrency() { + return refundCurrency; + } + + /** + * 支付平台交易号 + * 发起支付时 支付平台(如支付宝)返回的交易订单号 + * + * @return 支付平台交易号 + */ + @Override + public String getTradeNo() { + return tradeNo; + } + + /** + * 支付订单号 + * 发起支付时,用户系统的订单号 + * + * @return 支付订单号 + */ + @Override + public String getOutTradeNo() { + return outTradeNo; + } + + /** + * 商户退款单号 + * + * @return 商户退款单号 + */ + @Override + public String getRefundNo() { + return outRequestNo; + } + + + + public void setCode(String code) { + this.code = code; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public String getSubCode() { + return subCode; + } + + public void setSubCode(String subCode) { + this.subCode = subCode; + } + + public String getSubMsg() { + return subMsg; + } + + public void setSubMsg(String subMsg) { + this.subMsg = subMsg; + } + + public String getSign() { + return sign; + } + + public void setSign(String sign) { + this.sign = sign; + } + + public void setTradeNo(String tradeNo) { + this.tradeNo = tradeNo; + } + + public void setOutTradeNo(String outTradeNo) { + this.outTradeNo = outTradeNo; + } + + public String getOutRequestNo() { + return outRequestNo; + } + + public void setOutRequestNo(String outRequestNo) { + this.outRequestNo = outRequestNo; + } + + public String getBuyerLogonId() { + return buyerLogonId; + } + + public void setBuyerLogonId(String buyerLogonId) { + this.buyerLogonId = buyerLogonId; + } + + public String getFundChange() { + return fundChange; + } + + public void setFundChange(String fundChange) { + this.fundChange = fundChange; + } + + public void setRefundFee(BigDecimal refundFee) { + this.refundFee = refundFee; + } + + public void setRefundCurrency(DefaultCurType refundCurrency) { + this.refundCurrency = refundCurrency; + } + + public Date getGmtRefundPay() { + return gmtRefundPay; + } + + public void setGmtRefundPay(Date gmtRefundPay) { + this.gmtRefundPay = gmtRefundPay; + } + + public List getRefundDetailItemList() { + return refundDetailItemList; + } + + public void setRefundDetailItemList(List refundDetailItemList) { + this.refundDetailItemList = refundDetailItemList; + } + + public String getStoreName() { + return storeName; + } + + public void setStoreName(String storeName) { + this.storeName = storeName; + } + + public String getBuyerUserId() { + return buyerUserId; + } + + public void setBuyerUserId(String buyerUserId) { + this.buyerUserId = buyerUserId; + } + + public PresetPayToolInfo getRefundPresetPaytoolList() { + return refundPresetPaytoolList; + } + + public void setRefundPresetPaytoolList(PresetPayToolInfo refundPresetPaytoolList) { + this.refundPresetPaytoolList = refundPresetPaytoolList; + } + + public String getRefundSettlementId() { + return refundSettlementId; + } + + public void setRefundSettlementId(String refundSettlementId) { + this.refundSettlementId = refundSettlementId; + } + + public String getPresentRefundBuyerAmount() { + return presentRefundBuyerAmount; + } + + public void setPresentRefundBuyerAmount(String presentRefundBuyerAmount) { + this.presentRefundBuyerAmount = presentRefundBuyerAmount; + } + + public String getPresentRefundDiscountAmount() { + return presentRefundDiscountAmount; + } + + public void setPresentRefundDiscountAmount(String presentRefundDiscountAmount) { + this.presentRefundDiscountAmount = presentRefundDiscountAmount; + } + + public String getPresentRefundMdiscountAmount() { + return presentRefundMdiscountAmount; + } + + public void setPresentRefundMdiscountAmount(String presentRefundMdiscountAmount) { + this.presentRefundMdiscountAmount = presentRefundMdiscountAmount; + } + public static final AliRefundResult create(Map result){ + AliRefundResult refundResult = new JSONObject(result).toJavaObject(AliRefundResult.class); + refundResult.setAttrs(result); + return refundResult; + } +} diff --git a/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliTransactionType.java b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliTransactionType.java index ff1a4fdad20c37f965ada30027ffabdf3d2c8834..10ae405f4c1c2f3cba47e21170f6c09668ac1d68 100644 --- a/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliTransactionType.java +++ b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliTransactionType.java @@ -18,12 +18,7 @@ import com.egzosn.pay.common.bean.TransactionType; * date 2016/10/19 22:58 */ public enum AliTransactionType implements TransactionType { - /** - * 即时到帐 - * 过时的名称,请换至 {@link #PAGE} - */ - @Deprecated - DIRECT("alipay.trade.page.pay"), + /** * 网页支付 */ @@ -49,8 +44,26 @@ public enum AliTransactionType implements TransactionType { * 声波付 */ WAVE_CODE("alipay.trade.pay"), + /** + * 小程序 + */ + MINAPP("alipay.trade.create"), + /** + * 刷脸付 + */ + SECURITY_CODE("alipay.trade.pay"), + /** + * 人脸初始化刷脸付 + * 暂时未接入 + * + */ + SMILEPAY("zoloz.authentication.customer.smilepay.initialize"), //交易辅助接口 + /** + * 统一收单交易结算接口 + */ + SETTLE("alipay.trade.order.settle"), /** * 交易订单查询 */ @@ -72,17 +85,19 @@ public enum AliTransactionType implements TransactionType { */ REFUNDQUERY("alipay.trade.fastpay.refund.query"), /** - * 下载对账单 + * 收单退款冲退完成通知 + * 退款存在退到银行卡场景下时,收单会根据银行回执消息发送退款完成信息 */ - DOWNLOADBILL("alipay.data.dataservice.bill.downloadurl.query"), + REFUND_DEPOSITBACK_COMPLETED ("alipay.trade.refund.depositback.completed"), /** - * 转账到支付宝 + * 下载对账单 */ - TRANS("alipay.fund.trans.toaccount.transfer"), + DOWNLOADBILL("alipay.data.dataservice.bill.downloadurl.query"), /** - * 转账查询 + * 查询刷脸结果信息 + * 暂时未接入 */ - TRANS_QUERY("alipay.fund.trans.order.query") + FTOKEN_QUERY("zoloz.authentication.customer.ftoken.query") ; diff --git a/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliTransferOrder.java b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliTransferOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..31c26e30fc5cce8752fdd2afe876948a113fe2aa --- /dev/null +++ b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliTransferOrder.java @@ -0,0 +1,153 @@ +package com.egzosn.pay.ali.bean; + +import com.egzosn.pay.common.bean.TransferOrder; + +import java.math.BigDecimal; +import java.util.TreeMap; + +/** + * 支付转账(红包)订单 + * + * @author egan + * date 2020/5/18 21:08 + * email egzosn@gmail.com + */ +public class AliTransferOrder extends TransferOrder { + + private String identity; + private String identityType; + + /** + * 商户端的唯一订单号,对于同一笔转账请求,商户需保证该订单号唯一。 + * + * @return 商户端的唯一订单号 + */ + public String getOutBizNo() { + return getOutNo(); + } + + public void setOutBizNo(String outBizNo) { + setOutNo(outBizNo); + } + + /** + * 订单总金额,单位为元,精确到小数点后两位,STD_RED_PACKET产品取值范围[0.01,100000000]; + * TRANS_ACCOUNT_NO_PWD产品取值范围[0.1,100000000] + * + * @return 订单总金额 + */ + public BigDecimal getTransAmount() { + return getAmount(); + } + + public void setTransAmount(BigDecimal transAmount) { + setAmount(transAmount); + } + + /** + * 转账业务的标题,用于在支付宝用户的账单里显示 + * + * @return 转账业务的标题 + */ + public String getOrderTitle() { + return (String) getAttr("order_title"); + } + + public void setOrderTitle(String orderTitle) { + addAttr("order_title", orderTitle); + } + + /** + * 描述特定的业务场景,可传的参数如下: + * DIRECT_TRANSFER:单笔无密转账到支付宝/银行卡, B2C现金红包; + * PERSONAL_COLLECTION:C2C现金红包-领红包 + * + * @return 描述特定的业务场景 + */ + public String getBizScene() { + return (String) getAttr("biz_scene"); + } + + public void setBizScene(String bizScene) { + addAttr("biz_scene", bizScene); + } + + /** + * 收款方信息 + * + * @return 收款方信息 + */ + private TreeMap getPayeeinfo() { + Object payeeInfo = getAttr("payee_info"); + if (null != payeeInfo && payeeInfo instanceof TreeMap){ + return (TreeMap) payeeInfo; + } + TreeMap payee = new TreeMap<>(); + addAttr("payee_info", payee); + return payee; + + } + + /** + * 参与方的唯一标识 + * + * @return 参与方的唯一标识 + */ + public String getIdentity() { + return identity; + } + + public void setIdentity(String identity) { + this.identity = identity; + getPayeeinfo().put("identity", identity); + } + + /** + * 参与方的标识类型,目前支持如下类型: + * 1、ALIPAY_USER_ID 支付宝的会员ID + * 2、ALIPAY_LOGON_ID:支付宝登录号,支持邮箱和手机号格式 + * + * @return 参与方的标识类型 + */ + public String getIdentityType() { + return identityType; + } + + public void setIdentityType(String identityType) { + this.identityType = identityType; + getPayeeinfo().put("identity_type", identityType); + } + + + /** + * 参与方真实姓名 + * + * @return 参与方真实姓名 + */ + public String getName() { + return getPayeeName(); + } + + public void setName(String name) { + setPayeeName(name); + getPayeeinfo().put("name", name); + } + + /** + * 转账业务请求的扩展参数,支持传入的扩展参数如下: + * 1、sub_biz_scene 子业务场景,红包业务必传,取值REDPACKET,C2C现金红包、B2C现金红包均需传入; + *

+ * 2、withdraw_timeliness为转账到银行卡的预期到账时间,可选(不传入则默认为T1),取值T0表示预期T+0到账,取值T1表示预期T+1到账,因到账时效受银行机构处理影响,支付宝无法保证一定是T0或者T1到账; + * + * @return 转账业务请求的扩展参数 + */ + public String getBusinessParams() { + return (String) getAttr("business_params"); + } + + public void setBusinessParams(String businessParams) { + addAttr("business_params", businessParams); + } + + +} diff --git a/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliTransferType.java b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliTransferType.java index 9f35aec86ac8b1565f527b7641f61b452ba48b98..df10e73daf98cb7af3d3e39e4acf018d3d2c7f53 100644 --- a/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliTransferType.java +++ b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliTransferType.java @@ -1,26 +1,69 @@ package com.egzosn.pay.ali.bean; +import com.egzosn.pay.common.bean.TransferOrder; import com.egzosn.pay.common.bean.TransferType; +import java.util.Map; + /** - * 收款方账户类型 + * 收款方账户类型 + * * @author egan - * email egzosn@gmail.com - * date 2018/9/28.20:32 + * email egzosn@gmail.com + * date 2018/9/28.20:32 */ public enum AliTransferType implements TransferType { /** - * 支付宝账号对应的支付宝唯一用户号。以2088开头的16位纯数字组成。 + * 单笔无密转账到支付宝账户固定为 + */ + TRANS_ACCOUNT_NO_PWD("alipay.fund.trans.uni.transfer", "DIRECT_TRANSFER"), + /** + * 单笔无密转账到银行卡固定为 */ - ALIPAY_USERID, + TRANS_BANKCARD_NO_PWD("alipay.fund.trans.uni.transfer", "DIRECT_TRANSFER"), /** - * 支付宝登录号,支持邮箱和手机号格式。 + * 收发现金红包固定为 */ - ALIPAY_LOGONID - ; + STD_RED_PACKET("alipay.fund.trans.uni.transfer", "DIRECT_TRANSFER"), + /** + * 现金红包无线支付接口 + */ + STD_RED_PACKET_APP("alipay.fund.trans.app.pay", "PERSONAL_PAY") { + /** + * 获取转账类型 + * + * @return 转账类型 + */ + @Override + public String getType() { + return STD_RED_PACKET.name(); + } + }, /** - * 获取转账类型 + * 转账查询 + */ + TRANS_QUERY("alipay.fund.trans.order.query"); + /** + * 接口名称 + */ + private String method; + /** + * 业务场景 + */ + private String bizScene; + + AliTransferType(String method) { + this.method = method; + } + + AliTransferType(String method, String bizScene) { + this.method = method; + this.bizScene = bizScene; + } + + /** + * 获取转账类型, product_code 业务产品码 * * @return 转账类型 */ @@ -29,6 +72,10 @@ public enum AliTransferType implements TransferType { return name(); } + public String getBizScene() { + return bizScene; + } + /** * 获取接口 * @@ -36,6 +83,20 @@ public enum AliTransferType implements TransferType { */ @Override public String getMethod() { - return name(); + return method; + } + + /** + * 设置属性 + * + * @param attr 已有属性对象 + * @param order 转账订单 + * @return 属性对象 + */ + @Override + public Map setAttr(Map attr, TransferOrder order) { + attr.put("product_code", getType()); + attr.put("biz_scene", getBizScene()); + return attr; } } diff --git a/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/CertEnvironment.java b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/CertEnvironment.java new file mode 100644 index 0000000000000000000000000000000000000000..79dcb82e957a29ba8c9ce37a02b70bda98313fd9 --- /dev/null +++ b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/CertEnvironment.java @@ -0,0 +1,93 @@ +/** + * Alipay.com Inc. Copyright (c) 2004-2020 All Rights Reserved. + */ +package com.egzosn.pay.ali.bean; + +import java.io.InputStream; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.egzosn.pay.ali.utils.AntCertificationUtil; +import com.egzosn.pay.common.bean.result.PayException; +import com.egzosn.pay.common.exception.PayErrorException; +import com.egzosn.pay.common.util.str.StringUtils; + +/** + * 证书模式运行时环境 + * + * @author zhongyu + * @version $Id: CertEnvironment.java, v 0.1 2020年01月02日 5:21 PM zhongyu Exp $ + * + * @author egan update 2020/10/12 + * + */ +public class CertEnvironment { + /** + * 支付宝根证书内容 + */ + private String rootCertContent; + + /** + * 支付宝根证书序列号 + */ + private String rootCertSN; + + /** + * 商户应用公钥证书序列号 + */ + private String merchantCertSN; + /** + * 默认的支付宝公钥证书序列号 + */ + private String aliPayPublicKeySN; + + /** + * 缓存的不同支付宝公钥证书序列号对应的支付宝公钥 + */ + private static final Map CACHED_ALI_PAY_PUBLIC_KEY = new ConcurrentHashMap(); + + /** + * 构造证书运行环境 + * + * @param merchantCert 商户公钥证书路径 + * @param aliPayCert 支付宝公钥证书路径 + * @param aliPayRootCert 支付宝根证书路径 + */ + public CertEnvironment(InputStream merchantCert, InputStream aliPayCert, InputStream aliPayRootCert) { + if (null == merchantCert || null == aliPayCert || null == aliPayRootCert) { + throw new PayErrorException(new PayException("", "证书参数merchantCert、aliPayCert或aliPayRootCert设置不完整。")); + } + + this.rootCertContent = AntCertificationUtil.readFromInputStream(aliPayRootCert); + this.rootCertSN = AntCertificationUtil.getRootCertSN(rootCertContent); + this.merchantCertSN = AntCertificationUtil.getCertSN(AntCertificationUtil.readFromInputStream((merchantCert))); + + String aliPayPublicCertContent = AntCertificationUtil.readFromInputStream(aliPayCert); + aliPayPublicKeySN = AntCertificationUtil.getCertSN(aliPayPublicCertContent); + CACHED_ALI_PAY_PUBLIC_KEY.put(aliPayPublicKeySN, + AntCertificationUtil.getCertPublicKey(aliPayPublicCertContent)); + } + + public String getRootCertSN() { + return rootCertSN; + } + + public String getMerchantCertSN() { + return merchantCertSN; + } + + public String getAliPayPublicKey(String sn) { + //如果没有指定sn,则默认取缓存中的第一个值 + if (StringUtils.isEmpty(sn)) { + return CACHED_ALI_PAY_PUBLIC_KEY.values().iterator().next(); + } + + if (CACHED_ALI_PAY_PUBLIC_KEY.containsKey(sn)) { + return CACHED_ALI_PAY_PUBLIC_KEY.get(sn); + } else { + //网关在支付宝公钥证书变更前,一定会确认通知到商户并在商户做出反馈后,才会更新该商户的支付宝公钥证书 + //TODO: 后续可以考虑加入自动升级支付宝公钥证书逻辑,注意并发更新冲突问题 + throw new PayErrorException(new PayException("", "支付宝公钥证书[" + sn + "]已过期,请重新下载最新支付宝公钥证书并替换原证书文件")); + } + } +} \ No newline at end of file diff --git a/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/OrderSettle.java b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/OrderSettle.java new file mode 100644 index 0000000000000000000000000000000000000000..e111bc16db3cd92d84546aa5f7e5e27807dc636c --- /dev/null +++ b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/OrderSettle.java @@ -0,0 +1,192 @@ +package com.egzosn.pay.ali.bean; + +import com.egzosn.pay.common.bean.Order; +import com.egzosn.pay.common.util.Util; +import com.egzosn.pay.common.util.str.StringUtils; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; + +/** + * 交易结算信息 + * @author egan + * email egzosn@gmail.com + * date 2019/4/28.20:29 + */ +public class OrderSettle implements Order { + + /** + * 结算请求流水号 开发者自行生成并保证唯一性 + */ + private String outRequestNo; + /** + * 支付宝订单号 + */ + private String tradeNo; + /** + * 分账支出方账户,类型为userId,本参数为要分账的支付宝账号对应的支付宝唯一用户号。以2088开头的纯16位数字。 + */ + private String transOut; + + /** + * 分账收入方账户,类型为userId,本参数为要分账的支付宝账号对应的支付宝唯一用户号。以2088开头的纯16位数字。 + */ + private String transIn; + + /** + * 分账的金额,单位为元 + */ + private BigDecimal amount; + /** + * 分账信息中分账百分比。取值范围为大于0,少于或等于100的整数。 + */ + private Integer amountPercentage; + + /** + * 分账描述 + */ + private String desc; + + /** + * 操作员id + */ + private String operatorId; + + /** + * 订单附加信息,可用于预设未提供的参数,这里会覆盖以上所有的订单信息, + */ + private Map attr; + + public String getOutRequestNo() { + return outRequestNo; + } + + public void setOutRequestNo(String outRequestNo) { + this.outRequestNo = outRequestNo; + } + + public String getTradeNo() { + return tradeNo; + } + + public void setTradeNo(String tradeNo) { + this.tradeNo = tradeNo; + } + + public String getTransOut() { + return transOut; + } + + public void setTransOut(String transOut) { + this.transOut = transOut; + } + + public String getTransIn() { + return transIn; + } + + public void setTransIn(String transIn) { + this.transIn = transIn; + } + + public BigDecimal getAmount() { + return amount; + } + + public void setAmount(BigDecimal amount) { + this.amount = amount; + } + + public Integer getAmountPercentage() { + return amountPercentage; + } + + public void setAmountPercentage(Integer amountPercentage) { + this.amountPercentage = amountPercentage; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public String getOperatorId() { + return operatorId; + } + + public void setOperatorId(String operatorId) { + this.operatorId = operatorId; + } + + /** + * 获取分账明细信息 + * @return 分账明细信息 + */ + public Map toRoyaltyParameters(){ + Map royalty = new TreeMap(); + + if (StringUtils.isNotEmpty(transOut)){ + royalty.put("trans_out", transOut); + } + + if (StringUtils.isNotEmpty( transIn)){ + royalty.put("trans_in", transIn); + } + if (null != amount){ + royalty.put("amount", Util.conversionAmount(amount)); + } + if (null != amountPercentage){ + royalty.put("amount_percentage", amountPercentage); + } + + if (StringUtils.isNotEmpty( desc)){ + royalty.put(" desc", desc); + } + return royalty; + } + + /** + * 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各产品快速接入文档 + * @return 请求参数的集合 + */ + public Map toBizContent(){ + Map bizContent = new TreeMap(); + bizContent.put("out_request_no", outRequestNo); + bizContent.put("trade_no", tradeNo); + bizContent.put("royalty_parameters", toRoyaltyParameters()); + if (StringUtils.isNotEmpty(operatorId)){ + bizContent.put("operator_id", operatorId); + } + return bizContent; + } + + @Override + public Map getAttrs() { + if (null == attr){ + attr = new HashMap<>(); + } + return attr; + } + + @Override + public Object getAttr(String key) { + return getAttrs().get(key); + } + + + /** + * 添加订单信息 + * @param key key + * @param value 值 + */ + @Override + public void addAttr(String key, Object value) { + getAttrs().put(key, value); + } + +} diff --git a/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/PresetPayToolInfo.java b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/PresetPayToolInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..d7617c4cc8feb37404559fb87f03637e1eff5b59 --- /dev/null +++ b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/PresetPayToolInfo.java @@ -0,0 +1,44 @@ +package com.egzosn.pay.ali.bean; + +import java.math.BigDecimal; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * 退回的前置资产列表 + * + * @author Egan + *

+ * email egzosn@gmail.com
+ * date 2020/8/16 18:58
+ * 
+ */ +public class PresetPayToolInfo { + + /** + * 必填 32前置资产金额 12.21 + */ + private BigDecimal[] amount; + + /** + * 前置资产类型编码,和收单支付传入的preset_pay_tool里面的类型编码保持一致。盒马礼品卡:HEMA;抓猫猫红包:T_CAT_COUPON + */ + @JSONField(name = "assert_type_code") + private String assertTypeCode; + + public BigDecimal[] getAmount() { + return amount; + } + + public void setAmount(BigDecimal[] amount) { + this.amount = amount; + } + + public String getAssertTypeCode() { + return assertTypeCode; + } + + public void setAssertTypeCode(String assertTypeCode) { + this.assertTypeCode = assertTypeCode; + } +} diff --git a/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/RefundDepositBackCompletedNotify.java b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/RefundDepositBackCompletedNotify.java new file mode 100644 index 0000000000000000000000000000000000000000..33f19785e366074010b2b36180074289bb29af9e --- /dev/null +++ b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/RefundDepositBackCompletedNotify.java @@ -0,0 +1,139 @@ +package com.egzosn.pay.ali.bean; + +import com.egzosn.pay.common.bean.RefundOrder; +import com.egzosn.pay.common.bean.SignType; + +/** + * 收单退款冲退完成通知 + * + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/12/4
+ * 
+ */ +public class RefundDepositBackCompletedNotify extends RefundOrder { + + /** + * 通知 + */ + private String notifyId; + /** + * 消息类型。目前支持类型:sys:系统消息;usr,用户消息;app,应用消息 + */ + private String msgType; + + /** + * 消息归属的商户支付宝uid。用户消息和应用消息时非空 + */ + private String msgUid; + + /** + * 消息归属方的应用id。应用消息时非空 + */ + private String msgAppId; + /** + * 加密算法 + */ + private SignType encryptType; + + + /** + * 银行卡冲退状态。S-成功,F-失败。银行卡冲退失败,资金自动转入用户支付宝余额。 + */ + private String dbackStatus; + /** + * 银行卡冲退金额 + */ + private String dbackAmount; + /** + * 银行响应时间,格式为yyyy-MM-dd HH:mm:ss + */ + private String bankAckTime; + /** + * 预估银行入账时间,格式为yyyy-MM-dd HH:mm:ss + */ + private String estBankReceiptTime; + + public String getNotifyId() { + return notifyId; + } + + public void setNotifyId(String notifyId) { + this.notifyId = notifyId; + addAttr("notify_id", notifyId); + } + + public String getMsgType() { + return msgType; + } + + public void setMsgType(String msgType) { + this.msgType = msgType; + addAttr("msg_type", msgType); + } + + public String getMsgUid() { + return msgUid; + } + + public void setMsgUid(String msgUid) { + this.msgUid = msgUid; + addAttr("msg_uid", msgUid); + } + + public String getMsgAppId() { + return msgAppId; + } + + public void setMsgAppId(String msgAppId) { + this.msgAppId = msgAppId; + addAttr("msg_app_id", msgAppId); + } + + public SignType getEncryptType() { + return encryptType; + } + + public void setEncryptType(SignType encryptType) { + this.encryptType = encryptType; + addAttr("encrypt_type", encryptType); + } + + + public String getDbackStatus() { + return dbackStatus; + } + + public void setDbackStatus(String dbackStatus) { + this.dbackStatus = dbackStatus; + addAttr("dback_status", dbackStatus); + } + + public String getDbackAmount() { + return dbackAmount; + } + + public void setDbackAmount(String dbackAmount) { + this.dbackAmount = dbackAmount; + addAttr("dback_amount", dbackAmount); + } + + public String getBankAckTime() { + return bankAckTime; + } + + public void setBankAckTime(String bankAckTime) { + this.bankAckTime = bankAckTime; + addAttr("bank_ack_time", bankAckTime); + } + + public String getEstBankReceiptTime() { + return estBankReceiptTime; + } + + public void setEstBankReceiptTime(String estBankReceiptTime) { + this.estBankReceiptTime = estBankReceiptTime; + addAttr("est_bank_receipt_time", estBankReceiptTime); + } +} diff --git a/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/TradeFundBill.java b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/TradeFundBill.java new file mode 100644 index 0000000000000000000000000000000000000000..f2862ff4581797ade917d660da5a28795a4949ac --- /dev/null +++ b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/TradeFundBill.java @@ -0,0 +1,82 @@ +package com.egzosn.pay.ali.bean; + +import java.math.BigDecimal; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * 退款使用的资金渠道。 + * 只有在签约中指定需要返回资金明细,或者入参的query_options中指定时才返回该字段信息。 + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2020/8/16 18:51
+ * 
+ */ +public class TradeFundBill { + /** + * 交易使用的资金渠道,详见 支付渠道列表 ALIPAYACCOUNT + */ + @JSONField(name = "fund_channel") + private String fundChannel; + /** + * 银行卡支付时的银行代码 CEB + */ + @JSONField(name = "bank_code") + private String bankCode; + /** + * 该支付工具类型所使用的金额 10 + */ + private BigDecimal amount; + + /** + * 渠道实际付款金额 11.21 + */ + @JSONField(name = "real_amount") + private BigDecimal realAmount; + /** + * 渠道所使用的资金类型,目前只在资金渠道(fund_channel)是银行卡渠道(BANKCARD)的情况下才返回该信息(DEBIT_CARD:借记卡,CREDIT_CARD:信用卡,MIXED_CARD:借贷合一卡) DEBIT_CARD + */ + @JSONField(name = "fund_type") + private String fundType; + + public String getFundChannel() { + return fundChannel; + } + + public void setFundChannel(String fundChannel) { + this.fundChannel = fundChannel; + } + + public String getBankCode() { + return bankCode; + } + + public void setBankCode(String bankCode) { + this.bankCode = bankCode; + } + + public BigDecimal getAmount() { + return amount; + } + + public void setAmount(BigDecimal amount) { + this.amount = amount; + } + + public BigDecimal getRealAmount() { + return realAmount; + } + + public void setRealAmount(BigDecimal realAmount) { + this.realAmount = realAmount; + } + + public String getFundType() { + return fundType; + } + + public void setFundType(String fundType) { + this.fundType = fundType; + } +} diff --git a/pay-java-ali/src/main/java/com/egzosn/pay/ali/before/api/AliPayService.java b/pay-java-ali/src/main/java/com/egzosn/pay/ali/before/api/AliPayService.java deleted file mode 100644 index dff7756452891197d10102035267a333ad09a5e3..0000000000000000000000000000000000000000 --- a/pay-java-ali/src/main/java/com/egzosn/pay/ali/before/api/AliPayService.java +++ /dev/null @@ -1,543 +0,0 @@ -package com.egzosn.pay.ali.before.api; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; -import com.egzosn.pay.ali.api.AliPayConfigStorage; -import com.egzosn.pay.ali.before.bean.AliTransactionType; -import com.egzosn.pay.common.api.BasePayService; -import com.egzosn.pay.common.api.Callback; -import com.egzosn.pay.common.bean.*; -import com.egzosn.pay.common.bean.result.PayException; -import com.egzosn.pay.common.exception.PayErrorException; -import com.egzosn.pay.common.http.HttpConfigStorage; -import com.egzosn.pay.common.http.UriVariables; -import com.egzosn.pay.common.util.DateUtils; -import com.egzosn.pay.common.util.Util; -import com.egzosn.pay.common.util.sign.SignUtils; -import com.egzosn.pay.common.util.str.StringUtils; -import java.awt.image.BufferedImage; -import java.io.UnsupportedEncodingException; -import java.math.BigDecimal; -import java.net.URLEncoder; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.*; - -import static com.egzosn.pay.ali.api.AliPayService.SIGN; - -/** - * 支付宝支付服务 - * @author egan - * - * email egzosn@gmail.com - * date 2016-5-18 14:09:01 - * 旧版本支付服务,2015年之前的支付方式,之后版本请看新类 - * @see com.egzosn.pay.ali.api.AliPayService - */ -@Deprecated -public class AliPayService extends BasePayService { - - - - private static final String HTTPS_REQ_URL = "https://mapi.alipay.com/gateway.do"; - private static final String QUERY_REQ_URL = "https://openapi.alipay.com/gateway.do"; - public static final String NOTIFY_ID = "notify_id"; - - public static final DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); - - static { - df.setTimeZone(TimeZone.getTimeZone("GMT+8")); - } - public AliPayService(AliPayConfigStorage payConfigStorage) { - super(payConfigStorage); - } - - public AliPayService(AliPayConfigStorage payConfigStorage, HttpConfigStorage configStorage) { - super(payConfigStorage, configStorage); - } - - - public String getHttpsVerifyUrl() { - return HTTPS_REQ_URL + "?service=notify_verify"; - } - - /** - * 回调校验 - * - * @param params 回调回来的参数集 - * @return 签名校验 true通过 - */ - @Override - public boolean verify(Map params) { - - if (params.get(SIGN) == null || params.get(NOTIFY_ID) == null) { - LOG.debug("支付宝支付异常:params:" + params); - return false; - } - - try { - return signVerify(params, (String) params.get(SIGN)) && verifySource((String) params.get(NOTIFY_ID)); - } catch (PayErrorException e) { - LOG.error(e); - } - - return false; - } - /** - * 校验数据来源 - * - * @param id 业务id, 数据的真实性. - * @return true通过 - */ - @Override - public boolean verifySource(String id) { - return "true".equals(requestTemplate.getForObject( getHttpsVerifyUrl() + "&partner=" + payConfigStorage.getPid() + "¬ify_id=" + id, String.class)); - } - - /** - * 根据反馈回来的信息,生成签名结果 - * @param params 通知返回来的参数数组 - * @param sign 比对的签名结果 - * @return 生成的签名结果 - */ - @Override - public boolean signVerify(Map params, String sign) { - - return SignUtils.valueOf(payConfigStorage.getSignType()).verify(params, sign, payConfigStorage.getKeyPublic(), payConfigStorage.getInputCharset()); - } - - - /** - * 生成并设置签名 - * @param parameters 请求参数 - * @return 订单信息 - */ - private Map setSign(Map parameters){ - parameters.put("sign_type", payConfigStorage.getSignType()); - String sign = createSign(SignUtils.parameterText(parameters, "&", SIGN, "appId"), payConfigStorage.getInputCharset()); - parameters.put(SIGN, sign); - - return parameters; - } - - /** - * 获取公共请求参数 - * @param transactionType 交易类型 - * @return 公共请求参数 - */ - private Map getPublicParameters(TransactionType transactionType ){ - Map orderInfo = new TreeMap<>(); - orderInfo.put("app_id", payConfigStorage.getAppid()); - orderInfo.put("method", transactionType.getMethod()); - orderInfo.put("format", "json"); - orderInfo.put("charset", payConfigStorage.getInputCharset()); - - DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - df.setTimeZone(TimeZone.getTimeZone("GMT+8")); -// DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - orderInfo.put("timestamp", df.format(new Date())); - orderInfo.put("version", "1.0"); - return orderInfo; - } - - - - - /** - * 返回创建的订单信息 - * - * @param order 支付订单 - * @return 订单信息 - * @see PayOrder 支付订单信息 - */ - @Override - public Map orderInfo(PayOrder order) { - - Map orderInfo = getOrder(order); - - String sign = null; - if (AliTransactionType.APP == order.getTransactionType() ){ - sign = createSign(getOrderInfo(order), payConfigStorage.getInputCharset()); - }else { - sign = createSign(orderInfo, payConfigStorage.getInputCharset()); - } - - try { - sign = URLEncoder.encode(sign, "UTF-8"); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - orderInfo.put(SIGN, sign); - orderInfo.put("sign_type", payConfigStorage.getSignType()); - return orderInfo; - } - - private String getOrderInfo(PayOrder order) { - String orderInfo = "partner=\"" + this.payConfigStorage.getPid() + "\""; - orderInfo = orderInfo + "&seller_id=\"" + this.payConfigStorage.getSeller() + "\""; - orderInfo = orderInfo + "&out_trade_no=\"" + order.getOutTradeNo() + "\""; - orderInfo = orderInfo + "&subject=\"" + order.getSubject() + "\""; - orderInfo = orderInfo + "&body=\"" + order.getBody() + "\""; - orderInfo = orderInfo + "&total_fee=\"" + Util.conversionAmount(order.getPrice()).toString() + "\""; - orderInfo = orderInfo + "¬ify_url=\"" + this.payConfigStorage.getNotifyUrl() + "\""; - orderInfo = orderInfo + "&service=\"mobile.securitypay.pay\""; - orderInfo = orderInfo + "&payment_type=\"1\""; - orderInfo = orderInfo + "&_input_charset=\""+ payConfigStorage.getInputCharset()+"\""; - orderInfo = orderInfo + "&it_b_pay=\"30m\""; - orderInfo = orderInfo + "&return_url=\""+payConfigStorage.getReturnUrl()+"\""; - return orderInfo; - } - - /** - * 支付宝创建订单信息 - * create the order info - * - * @param order 支付订单 - * @return 订单信息 - * @see PayOrder - */ - private Map getOrder(PayOrder order) { - Map orderInfo = new TreeMap<>(); - // 签约合作者身份ID - orderInfo.put("partner", payConfigStorage.getPid()); - // 签约卖家支付宝账号 - orderInfo.put("seller_id", payConfigStorage.getSeller()); - // 商户网站唯一订单号 - orderInfo.put("out_trade_no", order.getOutTradeNo()); - // 商品名称 - orderInfo.put("subject", order.getSubject()); - // 商品详情 - orderInfo.put("body", order.getBody()); - // 商品金额 - orderInfo.put("total_fee", Util.conversionAmount(order.getPrice()).toString() ); - // 服务器异步通知页面路径 - orderInfo.put("notify_url", payConfigStorage.getNotifyUrl() ); - // 服务接口名称, 固定值 - orderInfo.put("service", order.getTransactionType().getMethod() ); - // 支付类型, 固定值 - orderInfo.put("payment_type", "1" ); - // 参数编码, 固定值 - orderInfo.put("_input_charset", payConfigStorage.getInputCharset()); - // 设置未付款交易的超时时间 - // 默认30分钟,一旦超时,该笔交易就会自动被关闭。 - // 取值范围:1m~15d。 - // m-分钟,h-小时,d-天,1c-当天(无论交易何时创建,都在0点关闭)。 - // 该参数数值不接受小数点,如1.5h,可转换为90m。 - // TODO 2017/2/6 11:05 author: egan 目前写死,这一块建议配置 - - if (null != order.getExpirationTime()) { - orderInfo.put("timeout_express", DateUtils.minutesRemaining(order.getExpirationTime()) + "m"); - }else { - orderInfo.put("it_b_pay", "30m"); - } - // 支付宝处理完请求后,当前页面跳转到商户指定页面的路径,可空 - orderInfo.put("return_url", payConfigStorage.getReturnUrl()); - - return orderInfo; - } - - - - /** - * 获取输出消息,用户返回给支付端 - * - * @param code 状态 - * @param message 消息 - * @return 返回输出消息 - */ - @Override - public PayOutMessage getPayOutMessage(String code, String message) { - return PayOutMessage.TEXT().content(code.toLowerCase()).build(); - } - - /** - * 获取成功输出消息,用户返回给支付端 - * 主要用于拦截器中返回 - * @param payMessage 支付回调消息 - * @return 返回输出消息 - */ - @Override - public PayOutMessage successPayOutMessage(PayMessage payMessage) { - return PayOutMessage.TEXT().content("success").build(); - } - - - /** - * 获取输出消息,用户返回给支付端, 针对于web端 - * - * @param orderInfo 发起支付的订单信息 - * @param method 请求方式 "post" "get", - * @return 获取输出消息,用户返回给支付端, 针对于web端 - * @see MethodType 请求类型 - */ - @Override - public String buildRequest(Map orderInfo, MethodType method) { - - StringBuffer formHtml = new StringBuffer(); - - formHtml.append("
"); - - for (Map.Entry entry : orderInfo.entrySet()) { - Object o = entry.getValue(); - if (StringUtils.isEmpty((String)o) || "null".equals(o) ) { - continue; - } - formHtml.append(""); - } - - - //submit按钮控件请不要含有name属性 - formHtml.append(""); - formHtml.append(""); - - return formHtml.toString(); - } - - - - /** - * 生成二维码支付 - * 暂未实现或无此功能 - * @param orderInfo 发起支付的订单信息 - * @return 返回图片信息,支付时需要的 - */ - @Override - public BufferedImage genQrPay(PayOrder orderInfo) { - throw new UnsupportedOperationException(); - } - - /** - * 刷卡付,pos主动扫码付款(条码付) - * @param order 发起支付的订单信息 - * @return 支付结果 - */ - @Override - public Map microPay(PayOrder order) { - throw new UnsupportedOperationException(); - } - - - /** - * 交易查询接口 - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * @return 返回查询回来的结果集,支付方原值返回 - */ - @Override - public Map query(String tradeNo, String outTradeNo) { - - return secondaryInterface(tradeNo, outTradeNo, AliTransactionType.QUERY); - } - - - - /** - * 交易关闭接口 - * - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * @return 返回支付方交易关闭后的结果 - */ - @Override - public Map close(String tradeNo, String outTradeNo) { - - return secondaryInterface(tradeNo, outTradeNo, AliTransactionType.CLOSE); - } - - - /** - * 支付交易返回失败或支付系统超时,调用该接口撤销交易。 - * 如果此订单用户支付失败,支付宝系统会将此订单关闭;如果用户支付成功,支付宝系统会将此订单资金退还给用户。 - * 注意:只有发生支付系统超时或者支付结果未知时可调用撤销,其他正常支付的单如需实现相同功能请调用申请退款API。 - * 提交支付交易后调用【查询订单API】,没有明确的支付结果再调用【撤销订单API】。 - * - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * @return 返回支付方交易撤销后的结果 - */ - @Override - public Map cancel(String tradeNo, String outTradeNo) { - return secondaryInterface(tradeNo, outTradeNo, AliTransactionType.CANCEL); - } - - /** - * 申请退款接口 - * 废弃 - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * @param refundAmount 退款金额 - * @param totalAmount 总金额 - * @return 返回支付方申请退款后的结果 - * @see #refund(RefundOrder, Callback) - */ - @Deprecated - @Override - public Map refund(String tradeNo, String outTradeNo, BigDecimal refundAmount, BigDecimal totalAmount) { - - return refund(new RefundOrder(tradeNo, outTradeNo, refundAmount, totalAmount)); - } - - /** - * 申请退款接口 - * - * @param refundOrder 退款订单信息 - * @return 返回支付方申请退款后的结果 - */ - @Override - public Map refund(RefundOrder refundOrder) { - Map parameters = getPublicParameters(AliTransactionType.REFUND); - - Map bizContent = getBizContent(refundOrder.getTradeNo(), refundOrder.getOutTradeNo(), null); - if (!StringUtils.isEmpty(refundOrder.getRefundNo())){ - bizContent.put("out_request_no", refundOrder.getRefundNo()); - } - bizContent.put("refund_amount", refundOrder.getRefundAmount()); - //设置请求参数的集合 - parameters.put("biz_content", JSON.toJSONString(bizContent)); - //设置签名 - setSign(parameters); - return requestTemplate.getForObject(QUERY_REQ_URL + "?" + UriVariables.getMapToParameters(parameters), JSONObject.class); - } - - /** - * 查询退款 - * - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * @return 返回支付方查询退款后的结果 - */ - @Override - public Map refundquery(String tradeNo, String outTradeNo) { - return secondaryInterface(tradeNo, outTradeNo, AliTransactionType.REFUNDQUERY); - } - /** - * 查询退款 - * - * @param refundOrder 退款订单单号信息 - * @return 返回支付方查询退款后的结果 - */ - @Override - public Map refundquery(RefundOrder refundOrder){ - - //获取公共参数 - Map parameters = getPublicParameters(AliTransactionType.REFUNDQUERY); - - Map bizContent = getBizContent(refundOrder.getTradeNo(), refundOrder.getOutTradeNo(), null); - if (!StringUtils.isEmpty(refundOrder.getRefundNo())){ - bizContent.put("out_request_no", refundOrder.getRefundNo()); - } - //设置请求参数的集合 - parameters.put("biz_content", JSON.toJSONString(bizContent)); - - //设置签名 - setSign(parameters); - return requestTemplate.getForObject(QUERY_REQ_URL + "?" + UriVariables.getMapToParameters(parameters), JSONObject.class); - - } - - - /** - * 目前只支持日账单 - * @param billDate 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; - * @param billType 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 - * @return 返回支付方下载对账单的结果 - */ - @Override - public Map downloadbill(Date billDate, String billType) { - //获取公共参数 - Map parameters = getPublicParameters(AliTransactionType.DOWNLOADBILL); - - Map bizContent = new TreeMap<>(); - bizContent.put("bill_type", billType); - //目前只支持日账单 - - bizContent.put("bill_date", df.format(billDate)); - //设置请求参数的集合 - parameters.put("biz_content", JSON.toJSONString(bizContent)); - //设置签名 - setSign(parameters); - return requestTemplate.getForObject(QUERY_REQ_URL + "?" + UriVariables.getMapToParameters(parameters), JSONObject.class); - } - - - /** - * - * @param tradeNoOrBillDate 支付平台订单号或者账单类型, 具体请 - * 类型为{@link String }或者 {@link Date },类型须强制限制,类型不对应则抛出异常{@link PayErrorException} - * @param outTradeNoBillType 商户单号或者 账单类型 - * @param transactionType 交易类型 - * @return 返回支付方对应接口的结果 - */ - @Override - public Map secondaryInterface(Object tradeNoOrBillDate, String outTradeNoBillType, TransactionType transactionType) { - if (transactionType == AliTransactionType.DOWNLOADBILL){ - if (tradeNoOrBillDate instanceof Date){ - return downloadbill((Date) tradeNoOrBillDate, outTradeNoBillType); - } - throw new PayErrorException(new PayException("failure", "非法类型异常:" + tradeNoOrBillDate.getClass())); - } - - if (!(tradeNoOrBillDate instanceof String)){ - throw new PayErrorException(new PayException("failure", "非法类型异常:" + tradeNoOrBillDate.getClass())); - } - - //获取公共参数 - Map parameters = getPublicParameters(transactionType); - //设置请求参数的集合 - parameters.put("biz_content", getContentToJson(tradeNoOrBillDate.toString(), outTradeNoBillType)); - //设置签名 - setSign(parameters); - return requestTemplate.getForObject(QUERY_REQ_URL + "?" + UriVariables.getMapToParameters(parameters), JSONObject.class); - - } - - /** - * 转账 - * - * @param order 转账订单 - * - * @return 对应的转账结果 - */ - @Override - public Map transfer(TransferOrder order) { - return null; - } - - - /** - * 获取biz_content。请求参数的集合 不包含下载账单 - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * @param bizContent 请求参数的集合 - * @return 请求参数的集合 不包含下载账单 - */ - private Map getBizContent(String tradeNo, String outTradeNo, Map bizContent){ - if (null == bizContent){ - bizContent = new TreeMap<>(); - } - if (null != outTradeNo){ - bizContent.put("out_trade_no", outTradeNo); - } - if (null != tradeNo){ - bizContent.put("trade_no", tradeNo); - } - return bizContent; - } - - /** - * 获取biz_content。不包含下载账单 - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * @return 请求参数的集合 不包含下载账单 - */ - private String getContentToJson(String tradeNo, String outTradeNo){ - - return JSON.toJSONString(getBizContent(tradeNo, outTradeNo, null)); - } - -} diff --git a/pay-java-ali/src/main/java/com/egzosn/pay/ali/utils/AntCertificationUtil.java b/pay-java-ali/src/main/java/com/egzosn/pay/ali/utils/AntCertificationUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..e574d205f619d49501f40a643df6bbd66d44843a --- /dev/null +++ b/pay-java-ali/src/main/java/com/egzosn/pay/ali/utils/AntCertificationUtil.java @@ -0,0 +1,402 @@ +package com.egzosn.pay.ali.utils; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateFactory; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.egzosn.pay.common.bean.result.PayException; +import com.egzosn.pay.common.exception.PayErrorException; +import com.egzosn.pay.common.util.IOUtils; +import com.egzosn.pay.common.util.sign.SignUtils; +import com.egzosn.pay.common.util.sign.encrypt.Base64; +import com.egzosn.pay.common.util.str.StringUtils; + +/** + * 证书文件可信校验 + * + * @author junying.wjy + * @author egan update 2020/10/12 + * @version $Id: AntCertificationUtil.java, v 0.1 2019-07-29 下午04:46 junying.wjy Exp $ + */ +public class AntCertificationUtil { + private static final Logger LOGGER = LoggerFactory.getLogger(AntCertificationUtil.class); + + static { + SignUtils.initBc(); + } + + /** + * 验证证书是否可信 + * + * @param certContent 需要验证的目标证书或者证书链 + * @param rootCertContent 可信根证书列表 + * @return 是否校验成功 + */ + public static boolean isTrusted(String certContent, String rootCertContent) { + X509Certificate[] certificates; + try { + certificates = readPemCertChain(certContent); + } + catch (Exception e) { + LOGGER.error("读取证书失败", e); + throw new RuntimeException(e); + } + + List rootCerts = new ArrayList(); + try { + X509Certificate[] certs = readPemCertChain(rootCertContent); + rootCerts.addAll(Arrays.asList(certs)); + } + catch (Exception e) { + LOGGER.error("读取根证书失败", e); + throw new RuntimeException(e); + } + + return verifyCertChain(certificates, rootCerts.toArray(new X509Certificate[rootCerts.size()])); + } + + /** + * 验证证书是否是信任证书库中证书签发的 + * + * @param cert 目标验证证书 + * @param rootCerts 可信根证书列表 + * @return 验证结果 + */ + private static boolean verifyCert(X509Certificate cert, X509Certificate[] rootCerts) { + try { + cert.checkValidity(); + } + catch (CertificateExpiredException e) { + LOGGER.error("证书已经过期", e); + return false; + } + catch (CertificateNotYetValidException e) { + LOGGER.error("证书未激活", e); + return false; + } + + Map subjectMap = new HashMap(); + + for (X509Certificate root : rootCerts) { + subjectMap.put(root.getSubjectDN(), root); + } + + Principal issuerDN = cert.getIssuerDN(); + X509Certificate issuer = subjectMap.get(issuerDN); + if (issuer == null) { + LOGGER.error("证书链验证失败"); + return false; + } + try { + PublicKey publicKey = issuer.getPublicKey(); + verifySignature(publicKey, cert); + } + catch (PayErrorException e) { + LOGGER.error("证书链验证失败", e); + return false; + } + return true; + } + + /** + * 验证证书链是否是信任证书库中证书签发的 + * + * @param certs 目标验证证书列表 + * @param rootCerts 可信根证书列表 + * @return 验证结果 + */ + private static boolean verifyCertChain(X509Certificate[] certs, X509Certificate[] rootCerts) { + boolean sorted = sortByDn(certs); + if (!sorted) { + LOGGER.error("证书链验证失败:不是完整的证书链"); + return false; + } + + //先验证第一个证书是不是信任库中证书签发的 + X509Certificate prev = certs[0]; + boolean firstOK = verifyCert(prev, rootCerts); + if (!firstOK || certs.length == 1) { + return firstOK; + } + + //验证证书链 + for (int i = 1; i < certs.length; i++) { + X509Certificate cert = certs[i]; + if (!checkValidity(cert)) { + return false; + } + verifySignature(prev.getPublicKey(), cert); + prev = cert; + } + + return true; + } + + + /** + * 验证证书链是否是信任证书库中证书签发的 + * + * @param cert 目标验证证书 + * @return 验证结果 + */ + private static boolean checkValidity(X509Certificate cert) { + try { + cert.checkValidity(); + } + catch (CertificateExpiredException e) { + LOGGER.error("证书已经过期"); + return false; + } + catch (CertificateNotYetValidException e) { + LOGGER.error("证书未激活"); + return false; + } + return true; + } + + + private static void verifySignature(PublicKey publicKey, X509Certificate cert) { + try { + cert.verify(publicKey); + } + catch (GeneralSecurityException e) { + throw new PayErrorException(new PayException("证书校验失败", e.getMessage())); + } + } + + /** + * 将证书链按照完整的签发顺序进行排序,排序后证书链为:[issuerA, subjectA]-[issuerA, subjectB]-[issuerB, subjectC]-[issuerC, subjectD]... + * + * @param certs 证书链 + * @return true:排序成功,false:证书链不完整 + */ + private static boolean sortByDn(X509Certificate[] certs) { + //主题和证书的映射 + Map subjectMap = new HashMap(); + //签发者和证书的映射 + Map issuerMap = new HashMap(); + //是否包含自签名证书 + boolean hasSelfSignedCert = false; + + for (X509Certificate cert : certs) { + if (isSelfSigned(cert)) { + if (hasSelfSignedCert) { + return false; + } + hasSelfSignedCert = true; + } + + Principal subjectDN = cert.getSubjectDN(); + Principal issuerDN = cert.getIssuerDN(); + + subjectMap.put(subjectDN, cert); + issuerMap.put(issuerDN, cert); + } + + List certChain = new ArrayList(); + + X509Certificate current = certs[0]; + addressingUp(subjectMap, certChain, current); + addressingDown(issuerMap, certChain, current); + + //说明证书链不完整 + if (certs.length != certChain.size()) { + return false; + } + + //将证书链复制到原先的数据 + for (int i = 0; i < certChain.size(); i++) { + certs[i] = certChain.get(i); + } + return true; + } + + /** + * 验证证书是否是自签发的 + * + * @param cert 目标证书 + * @return true;自签发,false;不是自签发 + */ + private static boolean isSelfSigned(X509Certificate cert) { + return cert.getSubjectDN().equals(cert.getIssuerDN()); + } + + /** + * 向上构造证书链 + * + * @param subjectMap 主题和证书的映射 + * @param certChain 证书链 + * @param current 当前需要插入证书链的证书,include + */ + private static void addressingUp(final Map subjectMap, List certChain, + final X509Certificate current) { + certChain.add(0, current); + if (isSelfSigned(current)) { + return; + } + Principal issuerDN = current.getIssuerDN(); + X509Certificate issuer = subjectMap.get(issuerDN); + if (issuer == null) { + return; + } + addressingUp(subjectMap, certChain, issuer); + } + + /** + * 向下构造证书链 + * + * @param issuerMap 签发者和证书的映射 + * @param certChain 证书链 + * @param current 当前需要插入证书链的证书,exclude + */ + private static void addressingDown(final Map issuerMap, List certChain, + final X509Certificate current) { + Principal subjectDN = current.getSubjectDN(); + X509Certificate subject = issuerMap.get(subjectDN); + if (subject == null) { + return; + } + if (isSelfSigned(subject)) { + return; + } + certChain.add(subject); + addressingDown(issuerMap, certChain, subject); + } + + private static X509Certificate[] readPemCertChain(String cert) { + ByteArrayInputStream inputStream = new ByteArrayInputStream(cert.getBytes()); + try { + CertificateFactory factory = CertificateFactory.getInstance("X.509", "BC"); + ; + Collection certificates = factory.generateCertificates(inputStream); + return certificates.toArray(new X509Certificate[certificates.size()]); + } + catch (GeneralSecurityException e) { + LOGGER.error("提取根证书失败", e); + } + return null; + + } + + /** + * 获取支付宝根证书序列号 + * + * @param rootCertContent 支付宝根证书内容 + * @return 支付宝根证书序列号 + */ + public static String getRootCertSN(String rootCertContent) { + String rootCertSN = null; + try { + X509Certificate[] x509Certificates = readPemCertChain(rootCertContent); + if (null == x509Certificates) { + return null; + } + MessageDigest md = MessageDigest.getInstance("MD5"); + for (X509Certificate c : x509Certificates) { + if (c.getSigAlgOID().startsWith("1.2.840.113549.1.1")) { + md.update((c.getIssuerX500Principal().getName() + c.getSerialNumber()).getBytes()); + String certSN = new BigInteger(1, md.digest()).toString(16); + //BigInteger会把0省略掉,需补全至32位 + certSN = fillMD5(certSN); + if (StringUtils.isEmpty(rootCertSN)) { + rootCertSN = certSN; + } + else { + rootCertSN = rootCertSN + "_" + certSN; + } + } + + } + } + catch (NoSuchAlgorithmException e) { + LOGGER.error("提取根证书失败", e); + } + return rootCertSN; + } + + /** + * 获取公钥证书序列号 + * + * @param certContent 公钥证书内容 + * @return 公钥证书序列号 + */ + public static String getCertSN(String certContent) { + try { + InputStream inputStream = new ByteArrayInputStream(certContent.getBytes()); + CertificateFactory factory = CertificateFactory.getInstance("X.509", "BC"); + X509Certificate cert = (X509Certificate) factory.generateCertificate(inputStream); + return md5((cert.getIssuerX500Principal().getName() + cert.getSerialNumber()).getBytes()); + } + catch (GeneralSecurityException e) { + throw new PayErrorException(new PayException(" 获取公钥证书序列号异常", e.getMessage())); + } + } + + private static String md5(byte[] bytes) { + MessageDigest md = null; + try { + md = MessageDigest.getInstance("MD5"); + } + catch (NoSuchAlgorithmException e) { + throw new PayErrorException(new PayException("", e.getMessage())); + } + md.update(bytes); + String certSN = new BigInteger(1, md.digest()).toString(16); + //BigInteger会把0省略掉,需补全至32位 + certSN = fillMD5(certSN); + return certSN; + } + + private static String fillMD5(String md5) { + return md5.length() == 32 ? md5 : fillMD5("0" + md5); + } + + /** + * 提取公钥证书中的公钥 + * + * @param certContent 公钥证书内容 + * @return 公钥证书中的公钥 + */ + public static String getCertPublicKey(String certContent) { + try { + InputStream inputStream = new ByteArrayInputStream(certContent.getBytes()); + CertificateFactory factory = CertificateFactory.getInstance("X.509", "BC"); + X509Certificate cert = (X509Certificate) factory.generateCertificate(inputStream); + return Base64.encode(cert.getPublicKey().getEncoded()); + } + catch (GeneralSecurityException e) { + throw new PayErrorException(new PayException(" 提取公钥证书中的公钥异常", e.getMessage())); + } + } + + + public static String readFromInputStream(InputStream cert) { + try { + return new String(IOUtils.toByteArray(cert), StandardCharsets.UTF_8); + } + catch (IOException e) { + throw new PayErrorException(new PayException("读取证书异常", e.getMessage())); + } + } +} diff --git a/pay-java-ali/src/test/java/PayTest.java b/pay-java-ali/src/test/java/PayTest.java index 639cdbf1dddc2fa3bc7d1d9917a69b575bd57b50..416ec15281e40b4bb8379d62f2645ce2baad33e8 100644 --- a/pay-java-ali/src/test/java/PayTest.java +++ b/pay-java-ali/src/test/java/PayTest.java @@ -2,6 +2,7 @@ import com.egzosn.pay.ali.api.AliPayConfigStorage; import com.egzosn.pay.ali.api.AliPayService; import com.egzosn.pay.ali.bean.AliTransactionType; import com.egzosn.pay.common.api.PayService; +import com.egzosn.pay.common.bean.CertStoreType; import com.egzosn.pay.common.bean.MethodType; import com.egzosn.pay.common.bean.PayOrder; @@ -15,17 +16,44 @@ import java.util.UUID; * * 支付宝测试 * @author egan - * @email egzosn@gmail.com - * @date 2017/8/18 + * email egzosn@gmail.com + * date 2017/8/18 */ public class PayTest { + /** + * 设置普通公钥的方式 + * 普通公钥方式与证书公钥方式为两者取其一的方式 + * @param aliPayConfigStorage 支付宝配置信息 + * + */ + private static void keyPublic(AliPayConfigStorage aliPayConfigStorage){ + aliPayConfigStorage.setKeyPublic("支付宝公钥"); + } + + /** + * 设置证书公钥信息 + * 普通公钥方式与证书公钥方式为两者取其一的方式 + * @param aliPayConfigStorage 支付宝配置信息 + */ + private static void certKeyPublic(AliPayConfigStorage aliPayConfigStorage){ + //设置为证书方式 + aliPayConfigStorage.setCertSign(true); + //设置证书存储方式,这里为路径 + aliPayConfigStorage.setCertStoreType(CertStoreType.PATH); + aliPayConfigStorage.setMerchantCert("请填写您的应用公钥证书文件路径,例如:d:/appCertPublicKey_2019051064521003.crt"); + aliPayConfigStorage.setAliPayCert("请填写您的支付宝公钥证书文件路径,例如:d:/alipayCertPublicKey_RSA2.crt"); + aliPayConfigStorage.setAliPayRootCert("请填写您的支付宝根证书文件路径,例如:d:/alipayRootCert.crt"); + } + public static void main(String[] args) { AliPayConfigStorage aliPayConfigStorage = new AliPayConfigStorage(); aliPayConfigStorage.setPid("合作者id"); aliPayConfigStorage.setAppId("应用id"); - aliPayConfigStorage.setKeyPublic("支付宝公钥"); + //普通公钥方式与证书公钥方式为两者取其一的方式 + keyPublic(aliPayConfigStorage); +// certKeyPublic(aliPayConfigStorage); aliPayConfigStorage.setKeyPrivate("应用私钥"); aliPayConfigStorage.setNotifyUrl("异步回调地址"); aliPayConfigStorage.setReturnUrl("同步回调地址"); @@ -37,7 +65,7 @@ public class PayTest { //支付服务 PayService service = new AliPayService(aliPayConfigStorage); //支付订单基础信息 - PayOrder payOrder = new PayOrder("订单title", "摘要", new BigDecimal(0.01) , UUID.randomUUID().toString().replace("-", "")); + PayOrder payOrder = new PayOrder("订单title", "摘要", BigDecimal.valueOf(0.01) , UUID.randomUUID().toString().replace("-", "")); /*-----------扫码付-------------------*/ payOrder.setTransactionType(AliTransactionType.SWEEPPAY); //获取扫码付的二维码 @@ -53,7 +81,7 @@ public class PayTest { /*-----------即时到帐 WAP 网页支付-------------------*/ // payOrder.setTransactionType(AliTransactionType.WAP); //WAP支付 - payOrder.setTransactionType(AliTransactionType.DIRECT); // 即时到帐 PC网页支付 + payOrder.setTransactionType(AliTransactionType.PAGE); // 即时到帐 PC网页支付 //获取支付所需的信息 Map directOrderInfo = service.orderInfo(payOrder); //获取表单提交对应的字符串,将其序列化到页面即可, diff --git a/pay-java-baidu/pom.xml b/pay-java-baidu/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..4d2ed8f325f41c34652782e28e887e9b6de0994d --- /dev/null +++ b/pay-java-baidu/pom.xml @@ -0,0 +1,24 @@ + + + + pay-java-parent + com.egzosn + 2.14.7 + + 4.0.0 + pay-java-baidu + + + + + com.egzosn + pay-java-common + + + + + + + \ No newline at end of file diff --git a/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/api/BaiduPayConfigStorage.java b/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/api/BaiduPayConfigStorage.java new file mode 100644 index 0000000000000000000000000000000000000000..61ee1cdba16620e4f62a0fc2ac4f044c7a238f0d --- /dev/null +++ b/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/api/BaiduPayConfigStorage.java @@ -0,0 +1,66 @@ +package com.egzosn.pay.baidu.api; + +import com.egzosn.pay.common.api.BasePayConfigStorage; + +public class BaiduPayConfigStorage extends BasePayConfigStorage { + + private String appid; + + private String dealId; + /** + * 支付平台公钥(签名校验使用) + */ + private String keyPublic; + + @Override + public String getAppid() { + return this.appid; + } + + @Override + public String getAppId() { + return this.appid; + } + + @Override + public String getPid() { + return getDealId(); + } + + //使用json序列化的时候会报错,所以不要直接抛出异常 + @Override + public String getSeller() { + return getDealId(); + } + + public String getDealId() { + return dealId; + } + + public void setDealId(String dealId) { + this.dealId = dealId; + } + + public String getAppKey() { + return this.appid; + } + + public void setAppKey(String appKey) { + this.setAppid(appKey); + } + + @Override + public String getKeyPublic() { + return keyPublic; + } + + @Override + public void setKeyPublic(String keyPublic) { + this.keyPublic = keyPublic; + } + + public void setAppid(String appid) { + this.appid = appid; + } + +} diff --git a/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/api/BaiduPayService.java b/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/api/BaiduPayService.java new file mode 100644 index 0000000000000000000000000000000000000000..42c064cb565e71b89d5a3b33e24b530e8878f3b3 --- /dev/null +++ b/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/api/BaiduPayService.java @@ -0,0 +1,637 @@ +package com.egzosn.pay.baidu.api; + +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.net.URLEncoder; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.egzosn.pay.baidu.bean.BaiduBillType; +import com.egzosn.pay.baidu.bean.BaiduPayOrder; +import com.egzosn.pay.baidu.bean.BaiduTransactionType; +import com.egzosn.pay.baidu.bean.type.AuditStatus; +import com.egzosn.pay.baidu.util.Asserts; +import com.egzosn.pay.common.api.BasePayService; +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.common.bean.BaseRefundResult; +import com.egzosn.pay.common.bean.BillType; +import com.egzosn.pay.common.bean.CurType; +import com.egzosn.pay.common.bean.MethodType; +import com.egzosn.pay.common.bean.NoticeParams; +import com.egzosn.pay.common.bean.PayMessage; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.PayOutMessage; +import com.egzosn.pay.common.bean.RefundOrder; +import com.egzosn.pay.common.bean.TransactionType; +import com.egzosn.pay.common.http.HttpConfigStorage; +import com.egzosn.pay.common.http.UriVariables; +import com.egzosn.pay.common.util.DateUtils; +import com.egzosn.pay.common.util.Util; +import com.egzosn.pay.common.util.sign.SignTextUtils; +import com.egzosn.pay.common.util.sign.SignUtils; +import com.egzosn.pay.common.util.sign.encrypt.Base64; +import com.egzosn.pay.common.util.str.StringUtils; + + +public class BaiduPayService extends BasePayService { + public static final String APP_KEY = "appKey"; + public static final String APP_ID = "appId"; + public static final String DEAL_ID = "dealId"; + public static final String TP_ORDER_ID = "tpOrderId"; + public static final String DEAL_TITLE = "dealTitle"; + public static final String TOTAL_AMOUNT = "totalAmount"; + public static final String SIGN_FIELDS_RANGE = "signFieldsRange"; + public static final String BIZ_INFO = "bizInfo"; + public static final String RSA_SIGN = "rsaSign"; + public static final String ORDER_ID = "orderId"; + public static final String USER_ID = "userId"; + public static final String SITE_ID = "siteId"; + public static final String SIGN = "sign"; + public static final String METHOD = "method"; + public static final String TYPE = "type"; + + public static final Integer RESPONSE_SUCCESS = 2; + public static final String RESPONSE_STATUS = "status"; + + + private static final String CHARSET = "UTF-8"; + private static final String SIGN_ALGORITHMS = "SHA1WithRSA"; + private static final String SIGN_TYPE_RSA = "RSA"; + private static final String SIGN_KEY = "rsaSign"; + + + public BaiduPayService(BaiduPayConfigStorage payConfigStorage) { + super(payConfigStorage); + } + + public BaiduPayService(BaiduPayConfigStorage payConfigStorage, + HttpConfigStorage configStorage) { + super(payConfigStorage, configStorage); + } + + /** + * 验证响应 + * + * @param params 回调回来的参数集 + * @return 结果 + */ + @Deprecated + @Override + public boolean verify(Map params) { + + return verify(new NoticeParams(params)); + } + + /** + * 回调校验 + * + * @param noticeParams 回调回来的参数集 + * @return 签名校验 true通过 + */ + @Override + public boolean verify(NoticeParams noticeParams) { + final Map params = noticeParams.getBody(); + if (!RESPONSE_SUCCESS.equals(params.get(RESPONSE_STATUS)) && !RESPONSE_SUCCESS.toString().equals(params.get(RESPONSE_STATUS))) { + return false; + } + LOG.info("开始验证回调签名参数:" + params); + try { + return this.checkReturnSign(params, payConfigStorage.getKeyPublic(), (String) params.get(RSA_SIGN)); + } + catch (Exception e) { + LOG.info("验签失败", e); + } + return false; + } + public boolean checkReturnSign(Map params, String publicKey, String rsaSign) { + try { + String content = signContent(params); + Signature signature = Signature.getInstance(SIGN_ALGORITHMS); + signature.initVerify(this.getPublicKeyX509(publicKey)); + signature.update(content.getBytes(CHARSET)); + boolean verify = signature.verify(Base64.decode(rsaSign)); + LOG.info("使用公钥进行验签: " + verify); + return verify; + } + catch (Exception e) { + LOG.info("使用公钥进行验签出错, 返回false", e); + } + return false; + } + + + /** + * 将公钥字符串进行Base64 decode之后,生成X509标准公钥 + * + * @param publicKey 公钥原始字符串 + * @return X509标准公钥 + * @throws InvalidKeySpecException InvalidKeySpecException + * @throws NoSuchAlgorithmException NoSuchAlgorithmException + */ + private static PublicKey getPublicKeyX509(String publicKey) throws InvalidKeySpecException, NoSuchAlgorithmException { + if (StringUtils.isEmpty(publicKey)) { + return null; + } + KeyFactory keyFactory = KeyFactory.getInstance(SIGN_TYPE_RSA); + byte[] decodedKey = Base64.decode(publicKey); + return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey)); + } + + /** + * 对输入参数进行key过滤排序和字符串拼接 + * + * @param params 待签名参数集合 + * @return 待签名内容 + * @throws UnsupportedEncodingException UnsupportedEncodingException + */ + private String signContent(Map params) throws UnsupportedEncodingException { + Map sortedParams = new TreeMap<>(new Comparator() { + @Override + public int compare(String o1, String o2) { + return o1.compareTo(o2); + } + }); + for (Map.Entry entry : params.entrySet()) { + String key = entry.getKey(); + if (legalKey(key)) { + String value = + entry.getValue() == null ? null : URLEncoder.encode(entry.getValue().toString(), CHARSET); + sortedParams.put(key, value); + } + } + + StringBuilder builder = new StringBuilder(); + if (sortedParams != null && sortedParams.size() > 1) { + for (Map.Entry entry : sortedParams.entrySet()) { + if (StringUtils.equals(entry.getKey(), RSA_SIGN)) continue; + builder.append(entry.getKey()); + builder.append("="); + builder.append(entry.getValue()); + builder.append("&"); + } + builder.deleteCharAt(builder.length() - 1); + } + LOG.info("验签字符串:\n" + builder); + return builder.toString(); + } + + /** + * 有效的待签名参数key值 + * 非空、且非签名字段 + * + * @param key 待签名参数key值 + * @return true | false + */ + private static boolean legalKey(String key) { + return StringUtils.isNotBlank(key) && !SIGN_KEY.equalsIgnoreCase(key); + } + + /** + * 返回创建的订单信息 + * + * @param order 支付订单 + * @return 结果 + */ + @Override + public Map orderInfo(PayOrder order) { + LOG.info("百度支付配置:" + JSON.toJSONString(payConfigStorage)); + Map params = this.getUseOrderInfoParams(order); + String rsaSign = this.getRsaSign(params, RSA_SIGN); + params.put(RSA_SIGN, rsaSign); + return params; + } + + /** + * 获取"查询支付状态"所需参数 + * + * @return 结果 + */ + public Map getUseQueryPay() { + String appKey = payConfigStorage.getAppKey(); + Map result = new HashMap<>(); + result.put(APP_KEY, appKey); + result.put(APP_ID, payConfigStorage.getAppId()); + return result; + } + + /** + * 获取"创建订单"所需参数 + * + * @param order 订单信息 + * @return 结果 + */ + private Map getUseOrderInfoParams(PayOrder order) { + BaiduPayOrder payOrder = (BaiduPayOrder) order; + Map result = new HashMap<>(); + String appKey = payConfigStorage.getAppKey(); + String dealId = payConfigStorage.getDealId(); + result.put(APP_KEY, appKey); + result.put(DEAL_ID, dealId); + result.put(TOTAL_AMOUNT, String.valueOf(Util.conversionCentAmount(order.getPrice()))); + result.put(TP_ORDER_ID, payOrder.getOutTradeNo()); + + result.put(DEAL_TITLE, payOrder.getSubject()); + result.put(SIGN_FIELDS_RANGE, payOrder.getSignFieldsRange()); + result.put(BIZ_INFO, JSON.toJSONString(payOrder.getBizInfo())); + + LOG.info("百度支付 getUseOrderInfoParams:" + JSON.toJSONString(result)); + + return result; + } + + /** + * 获取输出消息,用户返回给支付端 + * + * @param code 状态 + * @param message 消息 + * @return 结果 + */ + @Override + @Deprecated + public PayOutMessage getPayOutMessage(String code, String message) { + throw new UnsupportedOperationException("请使用 " + getClass().getName() + "#getPayOutMessageUseBaidu"); + } + + /** + * 请求业务方退款审核/响应处理 + * http://smartprogram.baidu.com/docs/develop/function/tune_up_examine/ + * + * @param errno 错误代码 + * @param message 消息 + * @param auditStatus 状态 + * @param refundPayMoney 退款金额 + * @return 结果 + */ + public PayOutMessage getApplyRefundOutMessageUseBaidu(Integer errno, + String message, + AuditStatus auditStatus, + BigDecimal refundPayMoney) { + JSONObject data = new JSONObject(); + data.put("auditStatus", auditStatus.getCode()); + JSONObject calculateRes = new JSONObject(); + calculateRes.put("refundPayMoney", refundPayMoney); + data.put("calculateRes", calculateRes); + return PayOutMessage.JSON() + .content("errno", errno) + .content("message", message) + .content("data", data) + .build(); + + } + + /** + * 通知退款状态/响应处理 + * http://smartprogram.baidu.com/docs/develop/function/tune_up_drawback/ + * + * @param errno 错误代码 + * @param message 消息 + * @return 结果 + */ + public PayOutMessage getRefundOutMessageUseBaidu(Integer errno, + String message) { + return PayOutMessage.JSON() + .content("errno", errno) + .content("message", message) + .content("data", "{}") + .build(); + + } + + /** + * 支付通知/响应处理 + * + * @param errno 错误代码 + * @param message 消息 + * @param isConsumed 是否消费 + * @param isErrorOrder 错误订单 + * @return 结果 + */ + public PayOutMessage getPayOutMessageUseBaidu(Integer errno, + String message, + Integer isConsumed, + Integer isErrorOrder) { + Asserts.isNoNull(errno, "errno 是必填的"); + Asserts.isNoNull(message, "message 是必填的"); + Asserts.isNoNull(isConsumed, "isConsumed 是必填的"); + JSONObject data = new JSONObject(); + data.put("isConsumed", isConsumed); + if (isErrorOrder != null) { + data.put("isErrorOrder", isErrorOrder); + } + return PayOutMessage.JSON() + .content("errno", errno) + .content("message", message) + .content("data", data) + .build(); + } + + /** + * 支付通知/响应处理 + * http://smartprogram.baidu.com/docs/develop/function/tune_up_notice/ + * + * @param code 状态码 + * @param message 消息 + * @param isConsumed 是否消费 + * @return 结果 + */ + public PayOutMessage getPayOutMessageUseBaidu(Integer code, + String message, + Integer isConsumed) { + return getPayOutMessageUseBaidu(code, message, isConsumed, null); + } + + /** + * 支付通知/响应处理 + * http://smartprogram.baidu.com/docs/develop/function/tune_up_notice/ + * + * @param payMessage 支付回调消息 + * @return 结果 + */ + @Override + public PayOutMessage successPayOutMessage(PayMessage payMessage) { + return getPayOutMessageUseBaidu(0, "success", 2); + } + + /** + * 获取输出消息,用户返回给支付端, 针对于web端 + * + * @param orderInfo 发起支付的订单信息 + * @param method 请求方式 "post" "get", + * @return 结果 + */ + @Override + public String buildRequest(Map orderInfo, + MethodType method) { + throw new UnsupportedOperationException("百度不支持PC支付"); + } + + + /** + * 百度不支持扫码付 + * + * @param order 发起支付的订单信息 + * @return 结果 + */ + @Override + public String getQrPay(PayOrder order) { + throw new UnsupportedOperationException("百度不支持扫码付"); + } + + /** + * 百度不支持刷卡付 + * + * @param order 发起支付的订单信息 + * @return 结果 + */ + @Override + public Map microPay(PayOrder order) { + throw new UnsupportedOperationException("百度不支持刷卡付"); + } + + /** + * 查询订单 + * + * @param tradeNo 支付平台订单号 + * @param outTradeNo 商户单号 + * @return 结果 + */ + @Override + public Map query(String tradeNo, String outTradeNo) { + return secondaryInterface(tradeNo, outTradeNo, BaiduTransactionType.PAY_QUERY); + } + + /** + * 交易查询接口 + * + * @param assistOrder 查询条件 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(AssistOrder assistOrder) { + return secondaryInterface(assistOrder.getTradeNo(), assistOrder.getOutTradeNo(), BaiduTransactionType.PAY_QUERY); + } + + /** + * 百度不支持该操作 + * + * @param tradeNo 支付平台订单号 + * @param outTradeNo 商户单号 + * @return 结果 + */ + @Override + public Map close(String tradeNo, String outTradeNo) { + throw new UnsupportedOperationException("不支持该操作"); + } + /** + * 交易关闭接口 + * + * @param assistOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 + */ + @Override + public Map close(AssistOrder assistOrder){ + throw new UnsupportedOperationException("不支持该操作"); + } + + /** + * 退款 + * + * @param refundOrder 退款订单信息 + * @return 退款结果 + */ + @Override + public BaseRefundResult refund(RefundOrder refundOrder) { + Map parameters = getUseQueryPay(); + BaiduTransactionType transactionType = BaiduTransactionType.APPLY_REFUND; + parameters.put(METHOD, transactionType.getMethod()); + parameters.put(ORDER_ID, refundOrder.getOutTradeNo()); + parameters.put(USER_ID, refundOrder.getUserId()); + setParameters(parameters, "refundType", refundOrder); + parameters.put("refundReason", refundOrder.getDescription()); + parameters.put(TP_ORDER_ID, refundOrder.getTradeNo()); + parameters.put("applyRefundMoney", refundOrder.getRefundAmount()); + parameters.put("bizRefundBatchId", refundOrder.getRefundNo()); + parameters.put(APP_KEY, payConfigStorage.getAppKey()); + parameters.put(RSA_SIGN, getRsaSign(parameters, RSA_SIGN)); + final JSONObject result = requestTemplate.getForObject(String.format("%s?%s", getReqUrl(transactionType), UriVariables.getMapToParameters(parameters)), JSONObject.class); + return new BaseRefundResult(result) { + @Override + public String getCode() { + return getAttrString(RESPONSE_STATUS); + } + + @Override + public String getMsg() { + return null; + } + + @Override + public String getResultCode() { + return null; + } + + @Override + public String getResultMsg() { + return null; + } + + @Override + public BigDecimal getRefundFee() { + return null; + } + + @Override + public CurType getRefundCurrency() { + return null; + } + + @Override + public String getTradeNo() { + return null; + } + + @Override + public String getOutTradeNo() { + return null; + } + + @Override + public String getRefundNo() { + return null; + } + }; + + } + + + /** + * 退费查询 + * + * @param refundOrder 退款订单单号信息 + * @return 退款查询结果 + */ + @Override + public Map refundquery(RefundOrder refundOrder) { + + Map parameters = getUseQueryPay(); + BaiduTransactionType transactionType = BaiduTransactionType.REFUND_QUERY; + parameters.put(METHOD, transactionType.getMethod()); + parameters.put(TYPE, 3); + parameters.put(ORDER_ID, refundOrder.getTradeNo()); + parameters.put(USER_ID, refundOrder.getUserId()); + parameters.put(APP_KEY, payConfigStorage.getAppKey()); + parameters.put(RSA_SIGN, getRsaSign(parameters, RSA_SIGN)); + return requestTemplate.getForObject(String.format("%s?%s", getReqUrl(transactionType), UriVariables.getMapToParameters(parameters)), JSONObject.class); + } + + /** + * 下载订单对账单 + * + * @param billDate 账单时间:日账单格式为yyyy-MM-dd + * @param accessToken 用户token + * @return 对账单 + */ + @Override + public Map downloadBill(Date billDate, String accessToken) { + return downloadBill(billDate, new BaiduBillType(accessToken, BaiduTransactionType.DOWNLOAD_ORDER_BILL.name())); + } + + /** + * 下载对账单 + * + * @param billDate 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 + * @param billType 账单类型 {@link BaiduBillType} + * @return 返回支付方下载对账单的结果 + */ + @Override + public Map downloadBill(Date billDate, BillType billType) { + Map parameters = new HashMap<>(); + parameters.put("access_token", billType.getCustom()); + parameters.put("billTime", DateUtils.formatDate(billDate, billType.getDatePattern())); + final String type = billType.getType(); + BaiduTransactionType transactionType = BaiduTransactionType.DOWNLOAD_ORDER_BILL; + if (BaiduTransactionType.DOWNLOAD_BILL.name().equals(type)) { + transactionType = BaiduTransactionType.DOWNLOAD_BILL; + } + return requestTemplate.getForObject(String.format("%s?%s", getReqUrl(transactionType), + UriVariables.getMapToParameters(parameters)), JSONObject.class); + } + + /** + * 下载资金账单 + * + * @param billDate 账单时间:日账单格式为yyyy-MM-dd + * @param accessToken 用户token + * @return 账单结果 + */ + @Deprecated + public Map downloadMoneyBill(Date billDate, String accessToken) { + return downloadBill(billDate, new BaiduBillType(accessToken, BaiduTransactionType.DOWNLOAD_BILL.name())); + } + + /** + * 通用查询接口 + * + * @param orderId 订单id + * @param siteId 用户id + * @param transactionType 交易类型 + * @return 结果 + */ + public Map secondaryInterface(Object orderId, + String siteId, + TransactionType transactionType) { + if (!BaiduTransactionType.PAY_QUERY.equals(transactionType)) { + throw new UnsupportedOperationException("不支持该操作"); + } + + Map parameters = getUseQueryPay(); + parameters.put(ORDER_ID, orderId); + parameters.put(SITE_ID, siteId); + parameters.put(SIGN, getRsaSign(parameters, SIGN)); + return requestTemplate.getForObject(String.format("%s?%s", getReqUrl(transactionType), UriVariables.getMapToParameters(parameters)), JSONObject.class); + } + + /** + * 获取支付请求地址 + * + * @param transactionType 交易类型 + * @return 请求URL + */ + @Override + public String getReqUrl(TransactionType transactionType) { + return ((BaiduTransactionType) transactionType).getUrl(); + } + + /** + * 签名 + * + * @param params 参数 + * @param ignoreKeys 忽略字段 + * @return 签名结果 + */ + private String getRsaSign(Map params, String... ignoreKeys) { + Map result = new HashMap<>(); + String appKey = payConfigStorage.getAppKey(); + String dealId = payConfigStorage.getDealId(); + result.put(APP_KEY, appKey); + result.put(DEAL_ID, dealId); + result.put(TOTAL_AMOUNT, params.get(TOTAL_AMOUNT)); + result.put(TP_ORDER_ID, params.get(TP_ORDER_ID)); + + LOG.info("百度支付签名参数:" + JSON.toJSONString(result)); + + String waitSignVal = SignTextUtils.parameterText(result, "&", false, ignoreKeys); + return SignUtils.RSA.createSign(waitSignVal, payConfigStorage.getKeyPrivate(), payConfigStorage.getInputCharset()); + } +} diff --git a/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/bean/BaiduBillType.java b/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/bean/BaiduBillType.java new file mode 100644 index 0000000000000000000000000000000000000000..ded156bbd4d8fc0a7627a78bcc31bf841aa0ab9c --- /dev/null +++ b/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/bean/BaiduBillType.java @@ -0,0 +1,102 @@ +package com.egzosn.pay.baidu.bean; + +import com.egzosn.pay.common.bean.BillType; +import com.egzosn.pay.common.util.DateUtils; +import com.egzosn.pay.common.util.str.StringUtils; + +/** + * 百度 + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2021/2/22
+ * 
+ */ +public class BaiduBillType implements BillType { + /** + * 用户accessToken + */ + private String accessToken; + /** + + * 值为DOWNLOAD_ORDER_BILL与DOWNLOAD_BILL + * com.egzosn.pay.baidu.bean.BaiduTransactionType#DOWNLOAD_ORDER_BILL + * com.egzosn.pay.baidu.bean.BaiduTransactionType#DOWNLOAD_BILL + */ + private String type; + + private String datePattern; + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public void setType(String type) { + this.type = type; + } + + /** + * 获取类型名称 + * + * @return 类型 + */ + @Override + public String getType() { + return type; + } + + /** + * 获取类型对应的日期格式化表达式 + * + * @return 日期格式化表达式 + */ + @Override + public String getDatePattern() { + if (StringUtils.isEmpty(datePattern)){ + datePattern = DateUtils.YYYY_MM_DD; + } + return datePattern; + } + + /** + * 获取文件类型 + * + * @return 文件类型 + */ + @Override + public String getFileType() { + return null; + } + + public void setDatePattern(String datePattern) { + this.datePattern = datePattern; + } + + + + /** + * 自定义属性 + * + * @return 自定义属性 + */ + @Override + public String getCustom() { + return accessToken; + } + + public BaiduBillType() { + } + + public BaiduBillType(String accessToken) { + this.accessToken = accessToken; + } + + public BaiduBillType(String accessToken, String type) { + this.accessToken = accessToken; + this.type = type; + } +} diff --git a/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/bean/BaiduPayOrder.java b/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/bean/BaiduPayOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..2f82452a5809f51c4a465f1728bbda735c6bcaa5 --- /dev/null +++ b/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/bean/BaiduPayOrder.java @@ -0,0 +1,76 @@ +package com.egzosn.pay.baidu.bean; + +import com.alibaba.fastjson.JSONObject; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.TransactionType; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.List; + +import static com.egzosn.pay.baidu.api.BaiduPayService.BIZ_INFO; +import static com.egzosn.pay.baidu.api.BaiduPayService.SIGN_FIELDS_RANGE; + +public class BaiduPayOrder extends PayOrder { + + /** + * 需要隐藏的支付方式 + */ + private List bannedChannels = Collections.emptyList(); + + /** + * 固定值 + */ + private String signFieldsRange; + + /** + * 附加信息 + */ + private JSONObject bizInfo = new JSONObject(); + + public BaiduPayOrder(String dealTitle, + BigDecimal totalAmount, + String tpOrderId, + String signFieldsRange) { + this(dealTitle, totalAmount, tpOrderId, signFieldsRange, Collections.emptyList()); + } + + public BaiduPayOrder(String dealTitle, + BigDecimal totalAmount, + String tpOrderId, + String signFieldsRange, + List bannedChannels) { + setPrice(totalAmount); + setOutTradeNo(tpOrderId); + setSubject(dealTitle); + setSignFieldsRange(signFieldsRange); + setBannedChannels(bannedChannels); + } + + public JSONObject getBizInfo() { + return bizInfo; + } + + public void setBizInfo(JSONObject bizInfo) { + this.bizInfo = bizInfo; + addAttr(BIZ_INFO, bizInfo); + } + + public List getBannedChannels() { + return bannedChannels; + } + + public void setBannedChannels(List bannedChannels) { + this.bannedChannels = bannedChannels; + } + + public String getSignFieldsRange() { + return signFieldsRange; + } + + public void setSignFieldsRange(String signFieldsRange) { + this.signFieldsRange = signFieldsRange; + addAttr(SIGN_FIELDS_RANGE, signFieldsRange); + } + +} diff --git a/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/bean/BaiduRefundOrder.java b/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/bean/BaiduRefundOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..50974eb0fee555dd83f8c8ce115facef2b596031 --- /dev/null +++ b/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/bean/BaiduRefundOrder.java @@ -0,0 +1,72 @@ +package com.egzosn.pay.baidu.bean; + +import com.egzosn.pay.common.bean.RefundOrder; + +import java.math.BigDecimal; + +public class BaiduRefundOrder extends RefundOrder { + + /** + * 退款类型 + */ + private Integer refundType; + + + public BaiduRefundOrder(Long orderId, + String userId, + Integer refundType, + String refundReason, + String tpOrderId) { + super(); + setOutTradeNo(String.valueOf(orderId)); + setUserId(userId); + setRefundType(refundType); + setDescription(refundReason); + setTradeNo(tpOrderId); + } + + /** + * 退款金额,单位:分,发起部分退款时必传 + * + * @return 退款金额 + */ + public BigDecimal getApplyRefundMoney() { + return getRefundAmount(); + } + + /** + * 退款金额,单位:分,发起部分退款时必传 + * + * @param applyRefundMoney 退款金额 + */ + public void setApplyRefundMoney(BigDecimal applyRefundMoney) { + setRefundAmount(applyRefundMoney); + } + + /** + * 业务方退款批次id,退款业务流水唯一编号,发起部分退款时必传 + * + * @return 退款业务流水 + */ + public String getBizRefundBatchId() { + return getRefundNo(); + } + + /** + * 业务方退款批次id,退款业务流水唯一编号,发起部分退款时必传 + * @param bizRefundBatchId 业务方退款批次id + */ + public void setBizRefundBatchId(String bizRefundBatchId) { + setRefundNo(bizRefundBatchId); + } + + public void setRefundType(Integer refundType) { + this.refundType = refundType; + addAttr("refundType", refundType); + } + + public Integer getRefundType() { + return refundType; + } + +} diff --git a/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/bean/BaiduTransactionType.java b/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/bean/BaiduTransactionType.java new file mode 100644 index 0000000000000000000000000000000000000000..7a0cefe2b1c9ff9f82c69a3cd98e1cfa548e32a0 --- /dev/null +++ b/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/bean/BaiduTransactionType.java @@ -0,0 +1,47 @@ +package com.egzosn.pay.baidu.bean; + +import com.egzosn.pay.common.bean.TransactionType; + +public enum BaiduTransactionType implements TransactionType { + /** + * 查询支付状态 + */ + PAY_QUERY("https://dianshang.baidu.com/platform/entity/openapi/queryorderdetail", null), + /** + * 取消核销 + */ + REFUND_QUERY("https://nop.nuomi.com/nop/server/rest", "nuomi.cashier.syncorderstatus"), + /** + * 下载资金账单 + */ + DOWNLOAD_BILL("https://openapi.baidu.com/rest/2.0/smartapp/pay/paymentservice/capitaBill", null), + /** + * 下载订单对账单 + */ + DOWNLOAD_ORDER_BILL("https://openapi.baidu.com/rest/2.0/smartapp/pay/paymentservice/orderBill", null), + /** + * 申请退款 + */ + APPLY_REFUND("https://nop.nuomi.com/nop/server/rest", "nuomi.cashier.applyorderrefund"); + private final String method; + private final String url; + + BaiduTransactionType( String url, String method) { + this.url = url; + this.method = method; + } + + @Override + public String getType() { + return this.name(); + } + + @Override + public String getMethod() { + return this.method; + } + + public String getUrl() { + return url; + } +} diff --git a/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/bean/type/AuditStatus.java b/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/bean/type/AuditStatus.java new file mode 100644 index 0000000000000000000000000000000000000000..22960bfe1f25b375c6317194e462b66cae6fab54 --- /dev/null +++ b/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/bean/type/AuditStatus.java @@ -0,0 +1,22 @@ +package com.egzosn.pay.baidu.bean.type; + +public enum AuditStatus { + SUCCESS(1, "审核通过可退款"), + FAIL(2, "审核不通过,不能退款"), + UNKNOWN(3, "审核结果不确定,待重试"); + private final int code; + private final String desc; + + AuditStatus(int code, String desc) { + this.code = code; + this.desc = desc; + } + + public int getCode() { + return code; + } + + public String getDesc() { + return desc; + } +} diff --git a/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/util/Asserts.java b/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/util/Asserts.java new file mode 100644 index 0000000000000000000000000000000000000000..7c949968c907ec507e3a734ff0380c39f1c45ee8 --- /dev/null +++ b/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/util/Asserts.java @@ -0,0 +1,16 @@ +package com.egzosn.pay.baidu.util; + +public class Asserts { + + public static void isNoNull(Object object, String message) { + if (object == null) { + throw new IllegalArgumentException(message); + } + } + + public static void isTrue(boolean bool, String message) { + if (!bool) { + throw new IllegalArgumentException(message); + } + } +} diff --git a/pay-java-baidu/src/test/java/com/egzosn/pay/baidu/api/BaiduPayServiceTest.java b/pay-java-baidu/src/test/java/com/egzosn/pay/baidu/api/BaiduPayServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..43f31a129c4ee4ccb59afdede357743da743e212 --- /dev/null +++ b/pay-java-baidu/src/test/java/com/egzosn/pay/baidu/api/BaiduPayServiceTest.java @@ -0,0 +1,22 @@ +package com.egzosn.pay.baidu.api; + +/** + * Created by hocgin on 2019/11/24. + * email: hocgin@gmail.com + * + * @author hocgin + */ +public class BaiduPayServiceTest { + + + public static void main(String[] args) { + BaiduPayConfigStorage configStorage = new BaiduPayConfigStorage(); + configStorage.setAppid("APP ID"); + configStorage.setAppKey("APP KEY"); + configStorage.setDealId("DEAL ID"); + configStorage.setKeyPublic("KEY PUBLIC"); + + BaiduPayService payService = new BaiduPayService(configStorage); + } + +} diff --git a/pay-java-common/pom.xml b/pay-java-common/pom.xml index 3f34fa4a2fe34652e5b3d868352fd68e78afd576..217b19268484867a3918053b66c811dc6a8876a0 100644 --- a/pay-java-common/pom.xml +++ b/pay-java-common/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.12.6-SNAPSHOT + 2.14.7 4.0.0 jar @@ -28,8 +28,8 @@ - log4j - log4j + org.slf4j + slf4j-api @@ -38,6 +38,10 @@ com.google.zxing core + + org.bouncycastle + bcprov-jdk15on + diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/api/BasePayConfigStorage.java b/pay-java-common/src/main/java/com/egzosn/pay/common/api/BasePayConfigStorage.java index 397cd733b5384432fb81487bbfa9ba471a9c4aaa..f813936396ccd546d78c24fe53ab7587bbb0fd3f 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/api/BasePayConfigStorage.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/api/BasePayConfigStorage.java @@ -1,17 +1,13 @@ package com.egzosn.pay.common.api; -import com.egzosn.pay.common.bean.MsgType; -import com.egzosn.pay.common.bean.result.PayException; -import com.egzosn.pay.common.exception.PayErrorException; -import com.egzosn.pay.common.util.sign.CertDescriptor; - +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; /** * 支付基础配置存储 * - * @author: egan + * @author egan *
  *     email egzosn@gmail.com
  *     date 2017/3/5 20:33
@@ -19,51 +15,39 @@ import java.util.concurrent.locks.ReentrantLock;
  */
 public abstract class BasePayConfigStorage implements PayConfigStorage {
 
-    private volatile Object attach;
-    /**
-     * 证书管理器
-     */
-    private volatile CertDescriptor certDescriptor;
+    private Object attach;
 
     /**
      * 应用私钥,rsa_private pkcs8格式 生成签名时使用
      */
-    private volatile String keyPrivate;
-    /**
-     * 应用私钥,rsa_private pkcs8格式 生成签名时使用
-     */
-    private volatile String keyPrivateCertPwd;
+    private String keyPrivate;
+
     /**
      * 支付平台公钥(签名校验使用)
      */
-    private volatile String keyPublic;
+    private String keyPublic;
     /**
      * 异步回调地址
      */
-    private volatile String notifyUrl;
+    private String notifyUrl;
     /**
      * 同步回调地址,支付完成后展示的页面
      */
-    private volatile String returnUrl;
+    private String returnUrl;
     /**
      * 签名加密类型
      */
-    private volatile String signType;
+    private String signType;
     /**
      * 字符类型
      */
-    private volatile String inputCharset;
+    private String inputCharset;
 
 
     /**
      * 支付类型 aliPay 支付宝, wxPay微信..等等,扩展支付模块定义唯一。
      */
-    private volatile String payType;
-
-    /**
-     * 消息来源类型
-     */
-    private volatile MsgType msgType;
+    private String payType;
 
 
     /**
@@ -77,7 +61,7 @@ public abstract class BasePayConfigStorage implements PayConfigStorage {
     /**
      * 授权码锁
      */
-    private Lock accessTokenLock = new ReentrantLock();
+    private Lock accessTokenLock;
     /**
      * 是否为沙箱环境,默认为正式环境
      */
@@ -86,12 +70,12 @@ public abstract class BasePayConfigStorage implements PayConfigStorage {
     /**
      * 是否为证书签名
      */
-    private boolean isCertSign = false;
+    private boolean certSign = false;
 
     /**
-     * 支付回调消息
+     * 配置附加信息,可用于预设未提供的参数,这里会覆盖以上所有的配置信息,
      */
-    protected volatile PayMessageHandler handler;
+    private volatile Map attr;
 
     @Override
     public Object getAttach() {
@@ -102,17 +86,6 @@ public abstract class BasePayConfigStorage implements PayConfigStorage {
         this.attach = attach;
     }
 
-    @Override
-    public CertDescriptor getCertDescriptor() {
-        if (!isCertSign) {
-            throw new PayErrorException(new PayException("certDescriptor fail", "isCertSign is false"));
-        }
-        if (null == certDescriptor) {
-            certDescriptor = new CertDescriptor();
-        }
-        return certDescriptor;
-    }
-
     @Override
     public String getKeyPrivate() {
         return keyPrivate;
@@ -121,14 +94,7 @@ public abstract class BasePayConfigStorage implements PayConfigStorage {
     public void setKeyPrivate(String keyPrivate) {
         this.keyPrivate = keyPrivate;
     }
-    @Override
-    public String getKeyPrivateCertPwd() {
-        return keyPrivateCertPwd;
-    }
 
-    public void setKeyPrivateCertPwd(String keyPrivateCertPwd) {
-        this.keyPrivateCertPwd = keyPrivateCertPwd;
-    }
 
     @Override
     public String getKeyPublic() {
@@ -148,7 +114,6 @@ public abstract class BasePayConfigStorage implements PayConfigStorage {
         this.notifyUrl = notifyUrl;
     }
 
-
     @Override
     public String getReturnUrl() {
         return returnUrl;
@@ -185,31 +150,38 @@ public abstract class BasePayConfigStorage implements PayConfigStorage {
         this.payType = payType;
     }
 
-    @Override
-    public MsgType getMsgType() {
-        return msgType;
-    }
-
-    public void setMsgType(MsgType msgType) {
-        this.msgType = msgType;
-    }
-
-    @Override
+    /**
+     * 获取访问令牌
+     *
+     * @return 访问令牌
+     */
     public String getAccessToken() {
         return this.accessToken;
     }
 
-    @Override
+    /**
+     * 获取access token锁
+     *
+     * @return access token锁
+     */
     public Lock getAccessTokenLock() {
         return this.accessTokenLock;
     }
 
-    @Override
+    /**
+     * 强制将access token过期掉
+     *
+     * @return 过期时间
+     */
     public long getExpiresTime() {
         return expiresTime;
     }
 
-    @Override
+    /**
+     * 访问令牌是否过期
+     *
+     * @return true过期
+     */
     public boolean isAccessTokenExpired() {
         return System.currentTimeMillis() > this.expiresTime;
     }
@@ -217,8 +189,7 @@ public abstract class BasePayConfigStorage implements PayConfigStorage {
 
     @Override
     public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) {
-        this.accessToken = accessToken;
-        this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 600) * 1000L;
+        updateAccessToken(accessToken, System.currentTimeMillis() + (expiresInSeconds - 600) * 1000L);
     }
 
     @Override
@@ -227,7 +198,10 @@ public abstract class BasePayConfigStorage implements PayConfigStorage {
         this.expiresTime = expiresTime;
     }
 
-    @Override
+
+    /**
+     * 强制将access token过期掉
+     */
     public void expireAccessToken() {
         this.expiresTime = 0;
     }
@@ -259,15 +233,34 @@ public abstract class BasePayConfigStorage implements PayConfigStorage {
     }
 
     public boolean isCertSign() {
-        return isCertSign;
+        return certSign;
     }
 
     public void setCertSign(boolean certSign) {
-        isCertSign = certSign;
-        if (certSign) {
-            certDescriptor = new CertDescriptor();
+        this.certSign = certSign;
+    }
+
+    @Override
+    public Map getAttrs() {
+        if (null == attr) {
+            attr = new HashMap<>();
         }
+        return attr;
+    }
+
+    @Override
+    public Object getAttr(String key) {
+        return getAttrs().get(key);
     }
 
 
+    /**
+     * 添加配置信息
+     *
+     * @param key   key
+     * @param value 值
+     */
+    public void addAttr(String key, Object value) {
+        getAttrs().put(key, value);
+    }
 }
diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/api/BasePayService.java b/pay-java-common/src/main/java/com/egzosn/pay/common/api/BasePayService.java
index a455d4c51b7f8dbabfe8c6ef00a31cb517372fab..1b54d95a8363d3e098432a4b52bdce491a24b676 100644
--- a/pay-java-common/src/main/java/com/egzosn/pay/common/api/BasePayService.java
+++ b/pay-java-common/src/main/java/com/egzosn/pay/common/api/BasePayService.java
@@ -1,30 +1,51 @@
 package com.egzosn.pay.common.api;
 
+import java.awt.image.BufferedImage;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.http.Consts;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import com.alibaba.fastjson.JSON;
-import com.egzosn.pay.common.bean.*;
-import com.egzosn.pay.common.exception.PayErrorException;
+import com.egzosn.pay.common.bean.BillType;
+import com.egzosn.pay.common.bean.DefaultNoticeRequest;
+import com.egzosn.pay.common.bean.MethodType;
+import com.egzosn.pay.common.bean.NoticeParams;
+import com.egzosn.pay.common.bean.NoticeRequest;
+import com.egzosn.pay.common.bean.Order;
+import com.egzosn.pay.common.bean.OrderParaStructure;
+import com.egzosn.pay.common.bean.PayMessage;
+import com.egzosn.pay.common.bean.PayOrder;
+import com.egzosn.pay.common.bean.PayOutMessage;
+import com.egzosn.pay.common.bean.RefundOrder;
+import com.egzosn.pay.common.bean.TransferOrder;
 import com.egzosn.pay.common.http.HttpConfigStorage;
 import com.egzosn.pay.common.http.HttpRequestTemplate;
+import com.egzosn.pay.common.util.MatrixToImageWriter;
 import com.egzosn.pay.common.util.sign.SignUtils;
 import com.egzosn.pay.common.util.str.StringUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.math.BigDecimal;
-import java.util.*;
 
 /**
  * 支付基础服务
- * @author: egan
- *  
+ *
+ * @author egan
+ * 
  *      email egzosn@gmail.com
  *      date 2017/3/5 20:36
  *   
*/ -public abstract class BasePayService implements PayService { - protected final Log LOG = LogFactory.getLog(getClass()); +public abstract class BasePayService implements PayService { + protected final Logger LOG = LoggerFactory.getLogger(getClass()); protected PC payConfigStorage; protected HttpRequestTemplate requestTemplate; @@ -38,15 +59,22 @@ public abstract class BasePayService implements Pay /** * 支付消息拦截器 */ - protected List interceptors = new ArrayList();; + protected List> interceptors = new ArrayList>(); + + private Charset inputCharset = Consts.UTF_8; /** * 设置支付配置 + * * @param payConfigStorage 支付配置 */ @Override public BasePayService setPayConfigStorage(PC payConfigStorage) { this.payConfigStorage = payConfigStorage; + + if (StringUtils.isNotEmpty(payConfigStorage.getInputCharset())) { + this.inputCharset = Charset.forName(payConfigStorage.getInputCharset()); + } return this; } @@ -54,6 +82,7 @@ public abstract class BasePayService implements Pay public PC getPayConfigStorage() { return payConfigStorage; } + @Override public HttpRequestTemplate getHttpRequestTemplate() { return requestTemplate; @@ -61,6 +90,7 @@ public abstract class BasePayService implements Pay /** * 设置并创建请求模版, 代理请求配置这里是否合理??, + * * @param configStorage http请求配置 * @return 支付服务 */ @@ -78,26 +108,30 @@ public abstract class BasePayService implements Pay public BasePayService(PC payConfigStorage, HttpConfigStorage configStorage) { setPayConfigStorage(payConfigStorage); setRequestTemplateConfigStorage(configStorage); + } /** - * Generate a Base64 encoded String from user , password - * @param user 用户名 + * Generate a Base64 encoded String from user , password + * + * @param user 用户名 * @param password 密码 * @return authorizationString */ protected String authorizationString(String user, String password) { String base64ClientID = null; try { - base64ClientID = com.egzosn.pay.common.util.sign.encrypt.Base64.encode(String.format("%s:%s", user , password).getBytes("UTF-8")); - } catch (UnsupportedEncodingException e) { - LOG.error(e); + base64ClientID = com.egzosn.pay.common.util.sign.encrypt.Base64.encode(String.format("%s:%s", user, password).getBytes("UTF-8")); + } + catch (UnsupportedEncodingException e) { + LOG.error("", e); } return base64ClientID; } + /** * 创建签名 * @@ -108,8 +142,9 @@ public abstract class BasePayService implements Pay @Override public String createSign(String content, String characterEncoding) { - return SignUtils.valueOf(payConfigStorage.getSignType()).createSign(content, payConfigStorage.getKeyPrivate(),characterEncoding); + return SignUtils.valueOf(payConfigStorage.getSignType()).createSign(content, payConfigStorage.getKeyPrivate(), characterEncoding); } + /** * 创建签名 * @@ -117,9 +152,49 @@ public abstract class BasePayService implements Pay * @param characterEncoding 字符编码 * @return 签名 */ - @Override public String createSign(Map content, String characterEncoding) { - return SignUtils.valueOf(payConfigStorage.getSignType()).sign(content, payConfigStorage.getKeyPrivate(),characterEncoding); + return SignUtils.valueOf(payConfigStorage.getSignType()).sign(content, payConfigStorage.getKeyPrivate(), characterEncoding); + } + + /** + * 页面转跳支付, 返回对应页面重定向信息 + * + * @param order 订单信息 + * @return 对应页面重定向信息 + */ + @Override + public String toPay(O order) { + if (StringUtils.isNotEmpty(order.getSubject()) && order.getSubject().contains("'")) { + order.setSubject(order.getSubject().replace("'", "")); + } + if (StringUtils.isNotEmpty(order.getBody()) && order.getBody().contains("'")) { + order.setBody(order.getBody().replace("'", "")); + } + Map orderInfo = orderInfo(order); + return buildRequest(orderInfo, MethodType.POST); + } + + /** + * app支付 + * + * @param order 订单信息 + * @param 预订单类型 + * @return 对应app所需参数信息 + */ + @Override + public Map app(O order) { + return orderInfo(order); + } + + /** + * 生成二维码支付 + * + * @param order 发起支付的订单信息 + * @return 返回图片信息,支付时需要的 + */ + @Override + public BufferedImage genQrPay(O order) { + return MatrixToImageWriter.writeInfoToJpgBuff(getQrPay(order)); } /** @@ -130,38 +205,47 @@ public abstract class BasePayService implements Pay * @return 获得回调的请求参数 */ @Override - public Map getParameter2Map (Map parameterMap, InputStream is) { + public Map getParameter2Map(Map parameterMap, InputStream is) { + return getNoticeParams(new DefaultNoticeRequest(parameterMap, is)).getBody(); + } + + /** + * 将请求参数或者请求流转化为 Map + * + * @param request 通知请求 + * @return 获得回调的请求参数 + */ + @Override + public NoticeParams getNoticeParams(NoticeRequest request) { + final Map parameterMap = request.getParameterMap(); - Map params = new TreeMap(); - for (Map.Entry entry : parameterMap.entrySet()) { + Map params = new TreeMap<>(); + for (Map.Entry entry : parameterMap.entrySet()) { String name = entry.getKey(); String[] values = entry.getValue(); - String valueStr = ""; - for (int i = 0,len = values.length; i < len; i++) { - valueStr += (i == len - 1) ? values[i] : values[i] + ","; + StringBuilder sb = new StringBuilder(); + for (int i = 0, len = values.length; i < len; i++) { + sb.append(values[i]).append((i == len - 1) ? "" : ','); } - if (StringUtils.isNotEmpty(payConfigStorage.getInputCharset()) && !valueStr.matches("\\w+")){ - try { - if(valueStr.equals(new String(valueStr.getBytes("iso8859-1"), "iso8859-1"))){ - valueStr=new String(valueStr.getBytes("iso8859-1"), payConfigStorage.getInputCharset()); - } - } catch (UnsupportedEncodingException e) { - LOG.error(e); + String valueStr = sb.toString(); + if (StringUtils.isNotEmpty(payConfigStorage.getInputCharset()) && !valueStr.matches("\\w+")) { + if (valueStr.equals(new String(valueStr.getBytes(Consts.ISO_8859_1), Consts.ISO_8859_1))) { + valueStr = new String(valueStr.getBytes(Consts.ISO_8859_1), inputCharset); } } params.put(name, valueStr); } - return params; + return new NoticeParams(params); } - /** * 交易查询接口,带处理器 + * * @param tradeNo 支付平台订单号 * @param outTradeNo 商户单号 - * @param callback 处理器 - * @param 返回类型 - * @return 返回查询回来的结果集 + * @param callback 处理器 + * @param 返回类型 + * @return 返回查询回来的结果集 */ @Override public T query(String tradeNo, String outTradeNo, Callback callback) { @@ -174,26 +258,28 @@ public abstract class BasePayService implements Pay * * @param tradeNo 支付平台订单号 * @param outTradeNo 商户单号 - * @param callback 处理器 - * @param 返回类型 + * @param callback 处理器 + * @param 返回类型 * @return 返回支付方交易关闭后的结果 */ + @Deprecated @Override public T close(String tradeNo, String outTradeNo, Callback callback) { - return callback.perform(close(tradeNo, outTradeNo)); + return callback.perform(close(tradeNo, outTradeNo)); } + /** * 交易撤销 * * @param tradeNo 支付平台订单号 * @param outTradeNo 商户单号 - * @param callback 处理器 - * @param 返回类型 + * @param callback 处理器 + * @param 返回类型 * @return 返回支付方交易撤销后的结果 */ @Override public T cancel(String tradeNo, String outTradeNo, Callback callback) { - return callback.perform(close(tradeNo, outTradeNo)); + return callback.perform(cancel(tradeNo, outTradeNo)); } /** @@ -205,99 +291,48 @@ public abstract class BasePayService implements Pay */ @Override public Map cancel(String tradeNo, String outTradeNo) { - return Collections.EMPTY_MAP; + return Collections.emptyMap(); } - /** - * 退款 - * - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * @param refundAmount 退款金额 - * @param totalAmount 总金额 - * @param callback 处理器 - * @param 返回类型 - * - * @return 处理过后的类型对象, 返回支付方申请退款后的结果 - * @see #refund(RefundOrder, Callback) - */ - @Deprecated - @Override - public T refund(String tradeNo, String outTradeNo, BigDecimal refundAmount, BigDecimal totalAmount, Callback callback) { - - return callback.perform(refund(new RefundOrder(tradeNo, outTradeNo, refundAmount, totalAmount))); - } /** * 申请退款接口 * - * @param refundOrder 退款订单信息 - * @return 返回支付方申请退款后的结果 - * @param callback 处理器 - * @param 返回类型 + * @param refundOrder 退款订单信息 + * @param callback 处理器 + * @param 返回类型 * @return 返回支付方申请退款后的结果 */ @Override public T refund(RefundOrder refundOrder, Callback callback) { - return callback.perform(refund(refundOrder)); + return callback.perform(refund(refundOrder).getAttrs()); } /** * 查询退款 * - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * @param callback 处理器 - * @param 返回类型 - * - * @return 处理过后的类型对象,返回支付方查询退款后的结果 - */ - @Override - public T refundquery(String tradeNo, String outTradeNo, Callback callback) { - return callback.perform(refundquery(tradeNo, outTradeNo)); - } - - /** - * 查询退款 - * - * @param refundOrder 退款订单信息 - * @param callback 处理器 - * @param 返回类型 + * @param refundOrder 退款订单信息 + * @param callback 处理器 + * @param 返回类型 * @return 返回支付方查询退款后的结果 */ @Override - public T refundquery(RefundOrder refundOrder, Callback callback){ + public T refundquery(RefundOrder refundOrder, Callback callback) { return callback.perform(refundquery(refundOrder)); } /** - * 目前只支持日账单 - * - * @param billDate 账单时间:具体请查看对应支付平台 - * @param billType 账单类型,具体请查看对应支付平台 - * @param callback 处理器 - * @param 返回类型 + * 下载对账单 * + * @param billDate 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 + * @param billType 账单类型 内部自动转化 {@link BillType} * @return 返回支付方下载对账单的结果 */ @Override - public T downloadbill(Date billDate, String billType, Callback callback) { - return callback.perform(downloadbill(billDate, billType)); - } - - /** - * @param tradeNoOrBillDate 支付平台订单号或者账单类型, 具体请 类型为{@link String }或者 {@link Date },类型须强制限制,类型不对应则抛出异常{@link PayErrorException} - * @param outTradeNoBillType 商户单号或者 账单类型 - * @param transactionType 交易类型 - * @param callback 处理器 - * @param 返回类型 - * @return 返回支付方对应接口的结果 - */ - @Override - public T secondaryInterface(Object tradeNoOrBillDate, String outTradeNoBillType, TransactionType transactionType, Callback callback){ - return callback.perform(secondaryInterface(tradeNoOrBillDate, outTradeNoBillType, transactionType)); + public Map downloadBill(Date billDate, String billType) { + return Collections.emptyMap(); } /** @@ -305,7 +340,6 @@ public abstract class BasePayService implements Pay * * @param order 转账订单 * @param callback 处理器 - * * @return 对应的转账结果 */ @Override @@ -318,38 +352,36 @@ public abstract class BasePayService implements Pay * 转账 * * @param order 转账订单 - * * @return 对应的转账结果 */ @Override public Map transfer(TransferOrder order) { - return new HashMap<>(0); + return Collections.emptyMap(); } /** * 转账查询 * - * @param outNo 商户转账订单号 + * @param outNo 商户转账订单号 * @param tradeNo 支付平台转账订单号 - * * @return 对应的转账订单 */ @Override - public Map transferQuery(String outNo, String tradeNo){ - return new HashMap<>(0); + public Map transferQuery(String outNo, String tradeNo) { + return Collections.emptyMap(); } /** * 转账查询 * - * @param outNo 商户转账订单号 - * @param tradeNo 支付平台转账订单号 + * @param outNo 商户转账订单号 + * @param tradeNo 支付平台转账订单号 * @param callback 处理器 - * @param 返回类型 + * @param 返回类型 * @return 对应的转账订单 */ @Override - public T transferQuery(String outNo, String tradeNo, Callback callback){ + public T transferQuery(String outNo, String tradeNo, Callback callback) { return callback.perform(transferQuery(outNo, tradeNo)); } @@ -370,11 +402,11 @@ public abstract class BasePayService implements Pay * 获取支付消息处理器,这里用于处理具体的支付业务 * 配合{@link PayService#payBack(Map, InputStream)}进行使用 *

- * @return 默认使用{@link DefaultPayMessageHandler }进行实现 * + * @return 默认使用{@link DefaultPayMessageHandler }进行实现 */ public PayMessageHandler getPayMessageHandler() { - if (null == handler){ + if (null == handler) { setPayMessageHandler(new DefaultPayMessageHandler()); } return handler; @@ -391,6 +423,7 @@ public abstract class BasePayService implements Pay interceptors.add(interceptor); } + /** * 将请求参数或者请求流转化为 Map * @@ -398,22 +431,87 @@ public abstract class BasePayService implements Pay * @param is 请求流 * @return 获得回调响应信息 */ + @Deprecated @Override public PayOutMessage payBack(Map parameterMap, InputStream is) { - Map data = getParameter2Map(parameterMap, is); + return payBack(new DefaultNoticeRequest(parameterMap, is)); + } + + /** + * 回调处理 + * + * @param request 请求参数 + * @return 获得回调响应信息 + */ + @Override + public PayOutMessage payBack(NoticeRequest request) { + final NoticeParams noticeParams = getNoticeParams(request); if (LOG.isDebugEnabled()) { - LOG.debug("回调响应:" + JSON.toJSONString(data)); + LOG.debug("回调响应:{}", JSON.toJSONString(noticeParams)); } - if (!verify(data)){ + if (!verify(noticeParams)) { return getPayOutMessage("fail", "失败"); } - PayMessage payMessage = new PayMessage(data); - Map context = new HashMap(); - for (PayMessageInterceptor interceptor : interceptors){ - if (!interceptor.intercept(payMessage, context, this)){ + PayMessage payMessage = this.createMessage(noticeParams.getBody()); + Map context = new HashMap<>(); + for (PayMessageInterceptor interceptor : interceptors) { + if (!interceptor.intercept(payMessage, context, this)) { return successPayOutMessage(payMessage); } } return getPayMessageHandler().handle(payMessage, context, this); } + + /** + * 创建消息 + * + * @param message 支付平台返回的消息 + * @return 支付消息对象 + */ + @Override + public PayMessage createMessage(Map message) { + return new PayMessage(message); + } + + /** + * 预订单回调处理器,用于订单信息的扩展 + * 签名之前使用 + * 如果需要进行扩展请重写该方法即可 + * + * @param orderInfo 预订单信息 + * @param orderInfo 订单信息 + * @return 处理后订单信息 + */ + @Override + public Map preOrderHandler(Map orderInfo, O payOrder) { + return orderInfo; + } + + /** + * 过时 + * + * @param parameters 参数map + * @param key key + * @param value 值 + * @return 返回订单参数 + */ + @Deprecated + protected Map setParameters(Map parameters, String key, String value) { + return OrderParaStructure.loadParameters(parameters, key, value); + } + + /** + * 过时 + * + * @param parameters 参数map + * @param key key + * @param order 订单对象 + * @return 返回订单参数 + */ + @Deprecated + protected Map setParameters(Map parameters, String key, Order order) { + return OrderParaStructure.loadParameters(parameters, key, order); + } + + } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/api/Callback.java b/pay-java-common/src/main/java/com/egzosn/pay/common/api/Callback.java index fb519aede1e556926110e9f3a3650a310b32a178..43289420cc7139a8bce9314afe692054a6719c46 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/api/Callback.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/api/Callback.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original huodull or egan. + * Copyright 2017 the original egan * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import java.util.Map; /** * 回调,可用于类型转换 - * @author: egan + * @author egan *

  *     email egzosn@gmail.com
  *     date 2017/3/7 18:55
diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/api/CertStore.java b/pay-java-common/src/main/java/com/egzosn/pay/common/api/CertStore.java
new file mode 100644
index 0000000000000000000000000000000000000000..dde73fb1880b24d699cda8cc91f150e48aade87e
--- /dev/null
+++ b/pay-java-common/src/main/java/com/egzosn/pay/common/api/CertStore.java
@@ -0,0 +1,23 @@
+package com.egzosn.pay.common.api;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * 证书存储方式
+ *
+ * @author egan
+ * email egzosn@gmail.com
+ * date 2019/10/13.23:09
+ */
+public interface CertStore {
+
+    /**
+     * 证书信息转化为对应的输入流
+     *
+     * @param cert 证书信息
+     * @return 输入流
+     * @throws IOException 找不到文件异常
+     */
+    InputStream getInputStream(Object cert) throws IOException;
+}
diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/api/DefaultPayMessageHandler.java b/pay-java-common/src/main/java/com/egzosn/pay/common/api/DefaultPayMessageHandler.java
index a9637204690ee0b7341e5ae1d19c97487e48ee7d..0f136f5d1313be29f11c956155071be28b97fb2a 100644
--- a/pay-java-common/src/main/java/com/egzosn/pay/common/api/DefaultPayMessageHandler.java
+++ b/pay-java-common/src/main/java/com/egzosn/pay/common/api/DefaultPayMessageHandler.java
@@ -1,13 +1,14 @@
 package com.egzosn.pay.common.api;
 
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import com.alibaba.fastjson.JSON;
 import com.egzosn.pay.common.bean.PayMessage;
 import com.egzosn.pay.common.bean.PayOutMessage;
 import com.egzosn.pay.common.exception.PayErrorException;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-import java.util.Map;
 
 /**
  * 默认处理支付回调消息的处理器接口
@@ -19,9 +20,9 @@ import java.util.Map;
  *     date 2018-10-29 17:31:05
  * 
*/ -public class DefaultPayMessageHandler implements PayMessageHandler { +public class DefaultPayMessageHandler implements PayMessageHandler { - protected final Log LOG = LogFactory.getLog(DefaultPayMessageHandler.class); + protected final Logger LOG = LoggerFactory.getLogger(DefaultPayMessageHandler.class); /** * @param payMessage 支付消息 * @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayConfigStorage.java b/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayConfigStorage.java index 30d93295189458e6db5ac89ff7a981e8ea6b0504..b6bc6000251bb466d1612b779622ded767d5efe6 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayConfigStorage.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayConfigStorage.java @@ -1,147 +1,126 @@ package com.egzosn.pay.common.api; -import com.egzosn.pay.common.bean.MsgType; -import com.egzosn.pay.common.util.sign.CertDescriptor; - -import java.util.concurrent.locks.Lock; +import com.egzosn.pay.common.bean.Attrs; /** * 支付客户端配置存储 - * @author egan + * + * @author egan *
  *     email egzosn@gmail.com
  *     date 2016-5-18 14:09:01
  * 
*/ - public interface PayConfigStorage { +public interface PayConfigStorage extends Attrs { /** * 附加支付配置 + * * @return 附加信息 */ - Object getAttach(); - /** - * 获取证书解释器 - * @return 证书解释器 - */ - CertDescriptor getCertDescriptor(); + Object getAttach(); /** - * 获取私钥证书密码 - * @return 私钥证书密码 + * 应用id + * 纠正名称 + * + * @return 应用id + * @see #getAppId() */ - String getKeyPrivateCertPwd(); + @Deprecated + String getAppid(); + /** - * 应用id - * @return 应用id + * 应用id + * 纠正名称 + * + * @return 应用id */ - String getAppid(); + String getAppId(); /** * 合作商唯一标识 - * @return 合作商唯一标识 + * + * @return 合作商唯一标识 */ - String getPid(); + String getPid(); /** * 获取收款账号 - * @return 收款账号 + * + * @return 收款账号 */ - String getSeller(); + String getSeller(); /** * 授权令牌 - * @return 授权令牌 + * + * @return 授权令牌 */ - String getToken(); + String getToken(); /** * 服务端异步回调Url - * @return 异步回调Url + * + * @return 异步回调Url */ - String getNotifyUrl(); + String getNotifyUrl(); + /** * 服务端同步回调Url - * @return 同步回调Url + * + * @return 同步回调Url */ - String getReturnUrl(); + String getReturnUrl(); + /** - * 签名方式 - * @return 签名方式 + * 签名方式 + * + * @return 签名方式 */ - String getSignType(); + String getSignType(); /** - * 字符编码格式 + * 字符编码格式 + * * @return 字符编码 */ - String getInputCharset(); + String getInputCharset(); /** * 支付平台公钥(签名校验使用) + * * @return 公钥 */ - String getKeyPublic(); + String getKeyPublic(); /** - * 应用私钥(生成签名时使用) + * 应用私钥(生成签名时使用) + * * @return 私钥 */ - String getKeyPrivate(); + String getKeyPrivate(); /** * 支付类型 自定义 * 这里暂定 aliPay 支付宝, wxPay微信支付 + * * @return 支付类型 */ - String getPayType(); - - /** - * 消息类型 - * @see #getMsgType - * @see MsgType - * @return "text" 或者 "xml",json - */ - MsgType getMsgType(); - - - /** - * 获取访问令牌 - * @return 访问令牌 - */ - String getAccessToken(); - - /** - * 访问令牌是否过期 - * @return true过期 - */ - boolean isAccessTokenExpired(); - /** - * 获取access token锁 - * @return access token锁 - */ - Lock getAccessTokenLock(); - - /** - * 强制将access token过期掉 - */ - void expireAccessToken(); - /** - * 强制将access token过期掉 - * @return 过期时间 - */ - long getExpiresTime(); + String getPayType(); /** * 应该是线程安全的 - * @param accessToken 新的accessToken值 + * + * @param accessToken 新的accessToken值 * @param expiresInSeconds 过期时间,以秒为单位 多少秒 */ void updateAccessToken(String accessToken, int expiresInSeconds); /** * 应该是线程安全的 + * * @param accessToken 新的accessToken值 * @param expiresTime 过期时间,时间戳 */ @@ -149,11 +128,10 @@ import java.util.concurrent.locks.Lock; /** * 是否为测试环境, true测试环境 + * * @return true测试环境 */ boolean isTest(); - - } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayMessageHandler.java b/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayMessageHandler.java index 5485d84f9f365f166dcda62410125740e69a379a..53485a72e3fe629a03e79e04a892a6483d14dda1 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayMessageHandler.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayMessageHandler.java @@ -19,7 +19,7 @@ import java.util.Map; * source Daniel Qian *
*/ -public interface PayMessageHandler { +public interface PayMessageHandler { /** * 处理支付回调消息的处理器接口 @@ -29,9 +29,9 @@ public interface PayMessageHandler { * @return xml,text格式的消息,如果在异步规则里处理的话,可以返回null * @throws PayErrorException 支付错误异常 */ - PayOutMessage handle(PayMessage payMessage, + PayOutMessage handle(M payMessage, Map context, - PayService payService + S payService ) throws PayErrorException; } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayMessageInterceptor.java b/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayMessageInterceptor.java index bcec73159a983af291c026abddd34333d5330701..69b7f4de1e8972f11b3bb6196ae19bf4b0b3c4f3 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayMessageInterceptor.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayMessageInterceptor.java @@ -18,7 +18,7 @@ import java.util.Map; * source Daniel Qian *
*/ -public interface PayMessageInterceptor { +public interface PayMessageInterceptor { /** * 拦截微信消息 @@ -28,9 +28,9 @@ public interface PayMessageInterceptor { * @param payService 支付服务 * @return true代表OK,false代表不OK */ - boolean intercept(PayMessage payMessage, + boolean intercept(M payMessage, Map context, - PayService payService + S payService ) throws PayErrorException; } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayMessageRouter.java b/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayMessageRouter.java index 4675b7159ebfc7a15ddce3ed08fc51b7264a0097..22817b77ba5087ec1c04a902be7ebdbb0c476ed7 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayMessageRouter.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayMessageRouter.java @@ -1,23 +1,24 @@ package com.egzosn.pay.common.api; -import com.egzosn.pay.common.bean.PayMessage; -import com.egzosn.pay.common.bean.PayOutMessage; - -import com.egzosn.pay.common.util.LogExceptionHandler; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.egzosn.pay.common.bean.PayMessage; +import com.egzosn.pay.common.bean.PayOutMessage; +import com.egzosn.pay.common.util.LogExceptionHandler; + /** *
  * 支付消息路由器,通过代码化的配置,把来自支付的消息交给handler处理
- * 
+ *
  * 说明:
  * 1. 配置路由规则时要按照从细到粗的原则,否则可能消息可能会被提前处理
  * 2. 默认情况下消息只会被处理一次,除非使用 {@link PayMessageRouterRule#next()}
@@ -39,147 +40,169 @@ import java.util.concurrent.Future;
  * router.route(message);
  *  source chanjarster/weixin-java-tools  Daniel Qian
  * 
- * @author egan * + * @author egan */ public class PayMessageRouter { + protected final Logger LOG = LoggerFactory.getLogger(PayMessageRouter.class); + + /** + * 异步线程大小 + */ + private static final int DEFAULT_THREAD_POOL_SIZE = 100; + /** + * 规则集 + */ + private final List rules = new ArrayList(); + /** + * 支付服务 + */ + private final PayService payService; + /** + * 异步线程处理器 + */ + private ExecutorService executorService; + /** + * 支付异常处理器 + */ + private PayErrorExceptionHandler exceptionHandler; + + /** + * 根据支付服务创建路由 + * + * @param payService 支付服务 + */ + public PayMessageRouter(PayService payService) { + this.payService = payService; + this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); + this.exceptionHandler = new LogExceptionHandler(); + } - protected final Log LOG = LogFactory.getLog(PayMessageRouter.class); - /** - * 异步线程大小 - */ - private static final int DEFAULT_THREAD_POOL_SIZE = 100; - /** - * 规则集 - */ - private final List rules = new ArrayList(); - /** - * 支付服务 - */ - private final PayService payService; - /** - * 异步线程处理器 - */ - private ExecutorService executorService; - /** - * 支付异常处理器 - */ - private PayErrorExceptionHandler exceptionHandler; - - /** - * 根据支付服务创建路由 - * @param payService 支付服务 - */ - public PayMessageRouter(PayService payService) { - this.payService = payService; - this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); - this.exceptionHandler = new LogExceptionHandler(); - } - - /** - *
-   * 设置自定义的 {@link ExecutorService}
-   * 如果不调用用该方法,默认使 Executors.newFixedThreadPool(100)
-   * 
- * @param executorService 异步线程处理器 - */ - public void setExecutorService(ExecutorService executorService) { - this.executorService = executorService; - } - - - - /** - *
-   * 设置自定义的{@link PayErrorExceptionHandler}
-   * 如果不调用该方法,默认使用 {@link LogExceptionHandler}
-   * 
- * @param exceptionHandler 异常处理器 - */ - public void setExceptionHandler(PayErrorExceptionHandler exceptionHandler) { - this.exceptionHandler = exceptionHandler; - } - - /** - * 获取所有的规则 - * @return 规则 - */ - List getRules() { - return this.rules; - } - - /** - * 开始一个新的Route规则 - * @return 新的Route规则 - */ - public PayMessageRouterRule rule() { - return new PayMessageRouterRule(this); - } - - /** - * 处理支付消息 - * @param payMessage 支付消息 - * @return 支付输出结果 - */ - public PayOutMessage route(final PayMessage payMessage) { - - final List matchRules = new ArrayList(); - // 收集匹配的规则 - for (final PayMessageRouterRule rule : rules) { - if (rule.test(payMessage)) { - matchRules.add(rule); - if(!rule.isReEnter()) { - break; - } - } + /** + *
+     * 设置自定义的 {@link ExecutorService}
+     * 如果不调用用该方法,默认使 Executors.newFixedThreadPool(100)
+     * 
+ * + * @param executorService 异步线程处理器 + */ + public void setExecutorService(ExecutorService executorService) { + this.executorService = executorService; } - if (matchRules.isEmpty()) { - return null; + + /** + *
+     * 设置自定义的{@link PayErrorExceptionHandler}
+     * 如果不调用该方法,默认使用 {@link LogExceptionHandler}
+     * 
+ * + * @param exceptionHandler 异常处理器 + */ + public void setExceptionHandler(PayErrorExceptionHandler exceptionHandler) { + this.exceptionHandler = exceptionHandler; } - PayOutMessage res = null; - final List futures = new ArrayList(); - for (final PayMessageRouterRule rule : matchRules) { - // 返回最后一个非异步的rule的执行结果 - if(rule.isAsync()) { - futures.add( - executorService.submit(new Runnable() { - @Override - public void run() { - rule.service(payMessage, payService, exceptionHandler); - } - }) - ); - } else { - res = rule.service(payMessage, payService, exceptionHandler); - // 在同步操作结束,session访问结束 - if (LOG.isDebugEnabled()) { - LOG.debug("End session access: async=false, fromPay=" + payMessage.getFromPay()); - } - } + /** + * 获取所有的规则 + * + * @return 规则 + */ + List getRules() { + return this.rules; + } + + /** + * 开始一个新的Route规则 + * + * @return 新的Route规则 + */ + public PayMessageRouterRule rule() { + return new PayMessageRouterRule(this); } - if (futures.size() > 0) { - executorService.submit(new Runnable() { - @Override - public void run() { - for (Future future : futures) { - try { - future.get(); - LOG.debug("End session access: async=true, fromPay=" + payMessage.getFromPay()); - - } catch (InterruptedException e) { - LOG.error("Error happened when wait task finish", e); - } catch (ExecutionException e) { - LOG.error("Error happened when wait task finish", e); + /** + * 处理支付消息 + * + * @param payMessage 支付消息 + * @param storage 支付配置 + * @return 支付输出结果 + */ + public PayOutMessage route(Map payMessage, PayConfigStorage storage) { + PayMessage message = payService.createMessage(payMessage); + message.setPayType(storage.getPayType()); + + return route(message); + } + + /** + * 处理支付消息 + * + * @param payMessage 支付消息 + * @return 支付输出结果 + */ + public PayOutMessage route(final PayMessage payMessage) { + + final List matchRules = new ArrayList(); + // 收集匹配的规则 + for (final PayMessageRouterRule rule : rules) { + if (rule.test(payMessage)) { + matchRules.add(rule); + if (!rule.isReEnter()) { + break; + } + } + } + + if (matchRules.isEmpty()) { + return null; + } + + PayOutMessage res = null; + final List futures = new ArrayList(); + for (final PayMessageRouterRule rule : matchRules) { + // 返回最后一个非异步的rule的执行结果 + if (rule.isAsync()) { + futures.add( + executorService.submit(new Runnable() { + @Override + public void run() { + rule.service(payMessage, payService, exceptionHandler); + } + }) + ); + } + else { + res = rule.service(payMessage, payService, exceptionHandler); + // 在同步操作结束,session访问结束 + if (LOG.isDebugEnabled()) { + LOG.debug("End session access: async=false, fromPay=" + payMessage.getFromPay()); + } } - } } - }); + + if (futures.size() > 0) { + executorService.submit(new Runnable() { + @Override + public void run() { + for (Future future : futures) { + try { + future.get(); + LOG.debug("End session access: async=true, fromPay=" + payMessage.getFromPay()); + + } + catch (InterruptedException e) { + LOG.error("Error happened when wait task finish", e); + } + catch (ExecutionException e) { + LOG.error("Error happened when wait task finish", e); + } + } + } + }); + } + return res; } - return res; - } } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayMessageRouterRule.java b/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayMessageRouterRule.java index 97bd21002c9f768bc93e1fbabceffb93011a8e9e..08477f58c567079b20d029a733834f491c8c5171 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayMessageRouterRule.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayMessageRouterRule.java @@ -1,20 +1,21 @@ package com.egzosn.pay.common.api; -import com.egzosn.pay.common.bean.PayMessage; -import com.egzosn.pay.common.bean.PayOutMessage; -import com.egzosn.pay.common.exception.PayErrorException; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; +import com.egzosn.pay.common.bean.PayMessage; +import com.egzosn.pay.common.bean.PayOutMessage; +import com.egzosn.pay.common.exception.PayErrorException; + /** * Route规则 路由 - * @author egan + * + * @author egan *
  *  email egzosn@gmail.com
  *  date 2016-6-1 11:28:01
@@ -32,10 +33,6 @@ public class PayMessageRouterRule {
      */
     private boolean async = false;
 
-    /**
-     * 消息类型
-     */
-    private String msgType;
     /**
      * 支付类型
      */
@@ -74,7 +71,7 @@ public class PayMessageRouterRule {
     /**
      * 设置是否异步执行,默认是true
      *
-     * @param async  是否异步执行,默认是true
+     * @param async 是否异步执行,默认是true
      * @return Route规则
      */
     public PayMessageRouterRule async(boolean async) {
@@ -82,21 +79,11 @@ public class PayMessageRouterRule {
         return this;
     }
 
-    /**
-     * 如果msgType等于某值
-     *
-     * @param msgType 消息类型
-     * @return Route规则
-     */
-    public PayMessageRouterRule msgType(String msgType) {
-        this.msgType = msgType;
-        return this;
-    }
 
     /**
      * 如果payType等于某值
      *
-     * @param payType  支付类型
+     * @param payType 支付类型
      * @return Route规则
      */
     public PayMessageRouterRule payType(String payType) {
@@ -110,13 +97,12 @@ public class PayMessageRouterRule {
      * @param transactionType 交易类型
      * @return Route规则
      */
-    public PayMessageRouterRule transactionType(String ... transactionType) {
+    public PayMessageRouterRule transactionType(String... transactionType) {
         this.transactionType = transactionType;
         return this;
     }
 
 
-
     /**
      * 如果subject等于某值
      *
@@ -138,10 +124,11 @@ public class PayMessageRouterRule {
         this.rSubject = regex;
         return this;
     }
+
     /**
      * 如果subject匹配该正则表达式
      *
-     * @param key 需要匹配支付消息内键的名字
+     * @param key   需要匹配支付消息内键的名字
      * @param regex key值对应的正则
      * @return Route规则
      */
@@ -165,7 +152,7 @@ public class PayMessageRouterRule {
     /**
      * 设置消息拦截器
      *
-     * @param interceptor 消息拦截器
+     * @param interceptor       消息拦截器
      * @param otherInterceptors 其他消息拦截器
      * @return Route规则
      */
@@ -192,7 +179,7 @@ public class PayMessageRouterRule {
     /**
      * 设置消息处理器
      *
-     * @param handler 消息处理器
+     * @param handler       消息处理器
      * @param otherHandlers 其他消息处理器
      * @return Route规则
      */
@@ -229,41 +216,41 @@ public class PayMessageRouterRule {
     /**
      * 将支付事件修正为不区分大小写,
      * 比如框架定义的事件常量为
+     *
      * @param payMessage 支付消息
      * @return 是否匹配通过
      */
     protected boolean test(PayMessage payMessage) {
         return (
-                       (this.msgType == null || this.msgType.toLowerCase().equals((payMessage.getMsgType() ==null?null:payMessage.getMsgType().toLowerCase())))
-                        &&
-                        (this.payType == null || this.payType.equals((payMessage.getPayType() == null ? null : payMessage.getPayType())))
+                (this.payType == null || this.payType.equals((payMessage.getPayType() == null ? null : payMessage.getPayType())))
                         &&
                         (this.transactionType == null || equalsTransactionType(payMessage.getTransactionType()))
                         &&
-                        (this.key == null ||this.rValue == null || Pattern
+                        (this.key == null || this.rValue == null || Pattern
                                 .matches(this.rValue, payMessage.getPayMessage().get(key) == null ? "" : payMessage.getPayMessage().get(key).toString().trim()))
-                         &&
+                        &&
                         (this.subject == null || this.subject
                                 .equals(payMessage.getSubject() == null ? null : payMessage.getSubject().trim()))
                         &&
                         (this.rSubject == null || Pattern
                                 .matches(this.rSubject, payMessage.getSubject() == null ? "" : payMessage.getSubject().trim()))
-                )
+        )
                 ;
     }
 
     /**
      * 匹配交易类型
+     *
      * @param transactionType 交易类型
      * @return 匹配交易类型
      */
     public boolean equalsTransactionType(String transactionType) {
-        if (null == transactionType){
+        if (null == transactionType) {
             return false;
         }
 
-        for (String type :this.getTransactionType()){
-            if (type.toLowerCase().equals((transactionType.toLowerCase()))){
+        for (String type : this.getTransactionType()) {
+            if (type.toLowerCase().equals((transactionType.toLowerCase()))) {
                 return true;
             }
         }
@@ -273,15 +260,16 @@ public class PayMessageRouterRule {
 
 
     /**
-     *  返回支付响应消息
-     * @param payMessage 支付消息
-     * @param payService 支付服务
+     * 返回支付响应消息
+     *
+     * @param payMessage       支付消息
+     * @param payService       支付服务
      * @param exceptionHandler 异常处理器
      * @return 支付响应消息
      */
     protected PayOutMessage service(PayMessage payMessage,
-                                        PayService payService,
-                                        PayErrorExceptionHandler exceptionHandler) {
+                                    PayService payService,
+                                    PayErrorExceptionHandler exceptionHandler) {
 
         try {
 
@@ -301,7 +289,8 @@ public class PayMessageRouterRule {
                 res = handler.handle(payMessage, context, payService);
             }
             return res;
-        } catch (PayErrorException e) {
+        }
+        catch (PayErrorException e) {
             exceptionHandler.handle(e);
         }
         return null;
@@ -320,15 +309,6 @@ public class PayMessageRouterRule {
         this.async = async;
     }
 
-
-    public String getMsgType() {
-        return msgType;
-    }
-
-    public void setMsgType(String msgType) {
-        this.msgType = msgType;
-    }
-
     public String getPayType() {
         return payType;
     }
diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayService.java b/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayService.java
index 19363640707cea1708889171c400d65d186af797..28f41066afc0180c8b7f3fa413410d92a24d3cb2 100644
--- a/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayService.java
+++ b/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayService.java
@@ -1,31 +1,40 @@
 package com.egzosn.pay.common.api;
 
-import com.egzosn.pay.common.bean.*;
-import com.egzosn.pay.common.exception.PayErrorException;
-import com.egzosn.pay.common.http.HttpConfigStorage;
-import com.egzosn.pay.common.http.HttpRequestTemplate;
-
 import java.awt.image.BufferedImage;
 import java.io.InputStream;
-import java.math.BigDecimal;
 import java.util.Date;
 import java.util.Map;
 
+import com.egzosn.pay.common.bean.AssistOrder;
+import com.egzosn.pay.common.bean.BillType;
+import com.egzosn.pay.common.bean.MethodType;
+import com.egzosn.pay.common.bean.NoticeParams;
+import com.egzosn.pay.common.bean.NoticeRequest;
+import com.egzosn.pay.common.bean.PayMessage;
+import com.egzosn.pay.common.bean.PayOrder;
+import com.egzosn.pay.common.bean.PayOutMessage;
+import com.egzosn.pay.common.bean.RefundOrder;
+import com.egzosn.pay.common.bean.RefundResult;
+import com.egzosn.pay.common.bean.TransactionType;
+import com.egzosn.pay.common.bean.TransferOrder;
+import com.egzosn.pay.common.http.HttpConfigStorage;
+import com.egzosn.pay.common.http.HttpRequestTemplate;
+
 /**
  * 支付服务
  *
  * @author egan
  * 
- * email egzosn@gmail.com
- * date 2016-5-18 14:09:01
- *
+ * email egzosn@gmail.com + * date 2016-5-18 14:09:01 + *
*/ public interface PayService { - /** * 设置支付配置 + * * @param payConfigStorage 支付配置 * @return 支付服务 */ @@ -37,6 +46,7 @@ public interface PayService { * @return 支付配置 */ PC getPayConfigStorage(); + /** * 获取http请求工具 * @@ -46,55 +56,58 @@ public interface PayService { /** * 设置 请求工具配置 设置并创建请求模版, 代理请求配置这里是否合理??, + * * @param configStorage http请求配置 - * @return 支付服务 + * @return 支付服务 */ PayService setRequestTemplateConfigStorage(HttpConfigStorage configStorage); /** * 回调校验 - * + * 已过时方法,详情{@link #verify(NoticeParams)} * @param params 回调回来的参数集 * @return 签名校验 true通过 + * @see #verify(NoticeParams) */ + @Deprecated boolean verify(Map params); /** - * 签名校验 + * 回调校验 * - * @param params 参数集 - * @param sign 签名原文 + * @param params 回调回来的参数集 * @return 签名校验 true通过 */ - boolean signVerify(Map params, String sign); - - - /** - * 支付宝需要,微信是否也需要再次校验来源,进行订单查询 - * 校验数据来源 - * @param id 业务id, 数据的真实性. - * @return true通过 - */ - boolean verifySource(String id); + boolean verify(NoticeParams params); /** * 返回创建的订单信息 * * @param order 支付订单 + * @param 预订单类型 * @return 订单信息 * @see PayOrder 支付订单信息 */ - Map orderInfo(PayOrder order); + Map orderInfo(O order); /** - * 创建签名 + * 页面转跳支付, 返回对应页面重定向信息 * - * @param content 需要签名的内容 - * @param characterEncoding 字符编码 - * @return 签名 + * @param order 订单信息 + * @param 预订单类型 + * @return 对应页面重定向信息 */ - String createSign(String content, String characterEncoding); + String toPay(O order); + + /** + * app支付 + * + * @param order 订单信息 + * @param 预订单类型 + * @return 对应app所需参数信息 + */ + Map app(O order); /** * 创建签名 @@ -103,7 +116,8 @@ public interface PayService { * @param characterEncoding 字符编码 * @return 签名 */ - String createSign(Map content, String characterEncoding); + String createSign(String content, String characterEncoding); + /** * 将请求参数或者请求流转化为 Map @@ -111,13 +125,23 @@ public interface PayService { * @param parameterMap 请求参数 * @param is 请求流 * @return 获得回调的请求参数 + * @see #getNoticeParams(NoticeRequest) */ + @Deprecated Map getParameter2Map(Map parameterMap, InputStream is); + /** + * 将请求参数或者请求流转化为 Map + * + * @param request 通知请求 + * @return 获得回调的请求参数 + */ + NoticeParams getNoticeParams(NoticeRequest request); + /** * 获取输出消息,用户返回给支付端 * - * @param code 状态 + * @param code 状态 * @param message 消息 * @return 返回输出消息 */ @@ -126,6 +150,7 @@ public interface PayService { /** * 获取成功输出消息,用户返回给支付端 * 主要用于拦截器中返回 + * * @param payMessage 支付回调消息 * @return 返回输出消息 */ @@ -141,21 +166,34 @@ public interface PayService { */ String buildRequest(Map orderInfo, MethodType method); + /** * 获取输出二维码,用户返回给支付端, * * @param order 发起支付的订单信息 + * @param 预订单类型 * @return 返回图片信息,支付时需要的 */ - BufferedImage genQrPay(PayOrder order); + BufferedImage genQrPay(O order); + + /** + * 获取输出二维码信息, + * + * @param order 发起支付的订单信息 + * @param 预订单类型 + * @return 返回二维码信息,,支付时需要的 + */ + String getQrPay(O order); /** * 刷卡付,pos主动扫码付款(条码付) + * 刷脸付 * * @param order 发起支付的订单信息 + * @param 预订单类型 * @return 返回支付结果 */ - Map microPay(PayOrder order); + Map microPay(O order); /** * 交易查询接口 @@ -163,18 +201,32 @@ public interface PayService { * @param tradeNo 支付平台订单号 * @param outTradeNo 商户单号 * @return 返回查询回来的结果集,支付方原值返回 + * @see #query(AssistOrder) */ + @Deprecated Map query(String tradeNo, String outTradeNo); /** * 交易查询接口,带处理器 + * * @param tradeNo 支付平台订单号 * @param outTradeNo 商户单号 - * @param callback 处理器 - * @param 返回类型 - * @return 返回查询回来的结果集 + * @param callback 处理器 + * @param 返回类型 + * @return 返回查询回来的结果集 + */ + @Deprecated + T query(String tradeNo, String outTradeNo, Callback callback); + + + /** + * 交易查询接口 + * + * @param assistOrder 查询条件 + * @return 返回查询回来的结果集,支付方原值返回 */ - T query(String tradeNo, String outTradeNo, Callback callback); + Map query(AssistOrder assistOrder); + /** * 交易关闭接口 @@ -182,20 +234,32 @@ public interface PayService { * @param tradeNo 支付平台订单号 * @param outTradeNo 商户单号 * @return 返回支付方交易关闭后的结果 + * @see #close(AssistOrder) */ + @Deprecated Map close(String tradeNo, String outTradeNo); + /** + * 交易关闭接口 + * + * @param assistOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 + */ + Map close(AssistOrder assistOrder); + /** * 交易关闭接口 * * @param tradeNo 支付平台订单号 * @param outTradeNo 商户单号 - * @param callback 处理器 - * @param 返回类型 + * @param callback 处理器 + * @param 返回类型 * @return 返回支付方交易关闭后的结果 */ - T close(String tradeNo, String outTradeNo, Callback callback); + @Deprecated + T close(String tradeNo, String outTradeNo, Callback callback); + /** * 交易交易撤销 @@ -211,203 +275,182 @@ public interface PayService { * * @param tradeNo 支付平台订单号 * @param outTradeNo 商户单号 - * @param callback 处理器 - * @param 返回类型 + * @param callback 处理器 + * @param 返回类型 * @return 返回支付方交易撤销后的结果 */ - T cancel(String tradeNo, String outTradeNo, Callback callback); - - /** - * 申请退款接口 - * 废弃 - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * @param refundAmount 退款金额 - * @param totalAmount 总金额 - * @return 返回支付方申请退款后的结果 - * @see #refund(RefundOrder) - */ - @Deprecated - Map refund(String tradeNo, String outTradeNo, BigDecimal refundAmount, BigDecimal totalAmount); - /** - * 申请退款接口 - * 废弃 - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * @param refundAmount 退款金额 - * @param totalAmount 总金额 - * @param callback 处理器 - * @param 返回类型 - * @return 返回支付方申请退款后的结果 - * @see #refund(RefundOrder, Callback) - */ @Deprecated - T refund(String tradeNo, String outTradeNo, BigDecimal refundAmount, BigDecimal totalAmount, Callback callback); + T cancel(String tradeNo, String outTradeNo, Callback callback); + /** * 申请退款接口 * - * @param refundOrder 退款订单信息 + * @param refundOrder 退款订单信息 * @return 返回支付方申请退款后的结果 */ - Map refund(RefundOrder refundOrder); + RefundResult refund(RefundOrder refundOrder); + /** * 申请退款接口 * - * @param refundOrder 退款订单信息 - * @param callback 处理器 - * @param 返回类型 + * @param refundOrder 退款订单信息 + * @param callback 处理器 + * @param 返回类型 * @return 返回支付方申请退款后的结果 */ - T refund(RefundOrder refundOrder, Callback callback); - - /** - * 查询退款 - * - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * @return 返回支付方查询退款后的结果 - */ - @Deprecated - Map refundquery(String tradeNo, String outTradeNo); - /** - * 查询退款 - * - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * @param callback 处理器 - * @param 返回类型 - * @return 返回支付方查询退款后的结果 - */ @Deprecated - T refundquery(String tradeNo, String outTradeNo, Callback callback); + T refund(RefundOrder refundOrder, Callback callback); + + /** * 查询退款 * - * @param refundOrder 退款订单单号信息 + * @param refundOrder 退款订单单号信息 * @return 返回支付方查询退款后的结果 */ Map refundquery(RefundOrder refundOrder); + /** * 查询退款 * - * @param refundOrder 退款订单信息 - * @param callback 处理器 - * @param 返回类型 + * @param refundOrder 退款订单信息 + * @param callback 处理器 + * @param 返回类型 * @return 返回支付方查询退款后的结果 */ - T refundquery(RefundOrder refundOrder, Callback callback); + @Deprecated + T refundquery(RefundOrder refundOrder, Callback callback); /** * 下载对账单 * * @param billDate 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 - * @param billType 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; + * @param billType 账单类型 内部自动转化 {@link BillType} * @return 返回支付方下载对账单的结果 */ - Map downloadbill(Date billDate, String billType); + Map downloadBill(Date billDate, String billType); /** * 下载对账单 * - * @param billDate 账单时间:具体请查看对应支付平台 - * @param billType 账单类型,具体请查看对应支付平台 - * @param callback 处理器 - * @param 返回类型 + * @param billDate 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 + * @param billType 账单类型 * @return 返回支付方下载对账单的结果 */ - T downloadbill(Date billDate, String billType, Callback callback); - - - /** - * 通用查询接口 - * @param tradeNoOrBillDate 支付平台订单号或者账单类型, 具体请 - * 类型为{@link String }或者 {@link Date },类型须强制限制,类型不对应则抛出异常{@link PayErrorException} - * @param outTradeNoBillType 商户单号或者 账单类型 - * @param transactionType 交易类型 - * @return 返回支付方对应接口的结果 - */ - Map secondaryInterface(Object tradeNoOrBillDate, String outTradeNoBillType, TransactionType transactionType); - - /** - * 通用查询接口 - * - * @param tradeNoOrBillDate 支付平台订单号或者账单日期, 具体请 类型为{@link String }或者 {@link Date },类型须强制限制,类型不对应则抛出异常{@link PayErrorException} - * - * @param outTradeNoBillType 商户单号或者 账单类型 - * @param transactionType 交易类型 - * @param callback 处理器 - * @param 返回类型 - * @return 返回支付方对应接口的结果 - */ - T secondaryInterface(Object tradeNoOrBillDate, String outTradeNoBillType, TransactionType transactionType, Callback callback); + Map downloadBill(Date billDate, BillType billType); /** * 转账 + * * @param order 转账订单 * @return 对应的转账结果 */ Map transfer(TransferOrder order); + /** * 转账 - * @param order 转账订单 + * + * @param order 转账订单 * @param callback 处理器 - * @param 返回类型 + * @param 返回类型 * @return 对应的转账结果 */ - T transfer(TransferOrder order, Callback callback); - + @Deprecated + T transfer(TransferOrder order, Callback callback); /** * 转账查询 * - * @param outNo 商户转账订单号 + * @param outNo 商户转账订单号 * @param tradeNo 支付平台转账订单号 - * * @return 对应的转账订单 + * @deprecated 替代{@link TransferService#transferQuery(com.egzosn.pay.common.bean.AssistOrder)} */ - Map transferQuery(String outNo, String tradeNo); + @Deprecated + Map transferQuery(String outNo, String tradeNo); /** * 转账查询 * - * @param outNo 商户转账订单号 - * @param tradeNo 支付平台转账订单号 + * @param outNo 商户转账订单号 + * @param tradeNo 支付平台转账订单号 * @param callback 处理器 - * @param 返回类型 + * @param 返回类型 * @return 对应的转账订单 */ - T transferQuery(String outNo, String tradeNo, Callback callback); + @Deprecated + T transferQuery(String outNo, String tradeNo, Callback callback); - /** - * 将请求参数或者请求流转化为 Map + /** + * 回调处理 * * @param parameterMap 请求参数 * @param is 请求流 * @return 获得回调响应信息 + * 过时方法,详情查看 {@link #payBack(NoticeRequest)} */ + @Deprecated PayOutMessage payBack(Map parameterMap, InputStream is); /** - * 设置支付消息处理器,这里用于处理具体的支付业务 - * @param handler 消息处理器 - * 配合{@link com.egzosn.pay.common.api.PayService#payBack(java.util.Map, java.io.InputStream)}进行使用 + * 回调处理 * - * 默认使用{@link com.egzosn.pay.common.api.DefaultPayMessageHandler }进行实现 + * @param request 请求参数 + * @return 获得回调响应信息 + */ + PayOutMessage payBack(NoticeRequest request); + + + /** + * 设置支付消息处理器,这里用于处理具体的支付业务 * + * @param handler 消息处理器 + * 配合{@link com.egzosn.pay.common.api.PayService#payBack(NoticeRequest)}进行使用 + *

+ * 默认使用{@link com.egzosn.pay.common.api.DefaultPayMessageHandler }进行实现 */ void setPayMessageHandler(PayMessageHandler handler); /** * 设置支付消息处理器,这里用于处理具体的支付业务 + * * @param interceptor 消息拦截器 - * 配合{@link com.egzosn.pay.common.api.PayService#payBack(java.util.Map, java.io.InputStream)}进行使用 + * 配合{@link com.egzosn.pay.common.api.PayService#payBack(NoticeRequest)}进行使用 + *

+ * 默认使用{@link com.egzosn.pay.common.api.DefaultPayMessageHandler }进行实现 + */ + void addPayMessageInterceptor(PayMessageInterceptor interceptor); + + /** + * 获取支付请求地址 * - * 默认使用{@link com.egzosn.pay.common.api.DefaultPayMessageHandler }进行实现 + * @param transactionType 交易类型 + * @return 请求地址 + */ + String getReqUrl(TransactionType transactionType); + + /** + * 创建消息 * + * @param message 支付平台返回的消息 + * @return 支付消息对象 */ - void addPayMessageInterceptor(PayMessageInterceptor interceptor); + PayMessage createMessage(Map message); + + /** + * 预订单回调处理器,用于订单信息的扩展 + * 签名之前使用 + * 如果需要进行扩展请重写该方法即可 + * + * @param orderInfo 商户平台预订单信息 + * @param payOrder 订单信息 + * @param 预订单类型 + * @return 处理后订单信息 + */ + @Deprecated + Map preOrderHandler(Map orderInfo, O payOrder); } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/api/TransferService.java b/pay-java-common/src/main/java/com/egzosn/pay/common/api/TransferService.java new file mode 100644 index 0000000000000000000000000000000000000000..69d51e2fdd14f53236cf9399c1a8e6ab6933c171 --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/api/TransferService.java @@ -0,0 +1,35 @@ +package com.egzosn.pay.common.api; + +import java.util.Map; + +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.common.bean.TransferOrder; + +/** + * 转账服务 + * + * @author Egan + *

+ *  email egan@egzosn.com
+ *  date 2023/1/8
+ *  
+ */ +public interface TransferService { + + /** + * 转账 + * + * @param transferOrder 转账订单 + * @return 结果 + */ + Map transfer(TransferOrder transferOrder); + + /** + * 转账查询 + * + * @param assistOrder 辅助交易订单 + * @return 对应的转账订单 + */ + Map transferQuery(AssistOrder assistOrder); + +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/AssistOrder.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/AssistOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..09dbd1844b2accdd60df1f3e5a4a92bff9af97ed --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/AssistOrder.java @@ -0,0 +1,138 @@ +package com.egzosn.pay.common.bean; + +import java.util.HashMap; +import java.util.Map; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * 辅助订单实体 + * + * @author egan + *
+ * email egan@egzosn.com
+ * date 2021/10/6
+ * 
+ */ +public class AssistOrder implements Order { + + + /** + * 支付平台订单号,交易号, 平台批次单号 + */ + private String tradeNo; + /** + * 商户订单号,商家批次单号 + */ + private String outTradeNo; + /** + * 交易类型 + */ + private TransactionType transactionType; + + /** + * 异步回调通知 + */ + private String notifyUrl; + + /** + * 订单附加信息,可用于预设未提供的参数,这里会覆盖以上所有的订单信息, + */ + @JSONField(serialize = false) + private volatile Map attr; + + public AssistOrder() { + } + + public AssistOrder(String outTradeNo) { + this.outTradeNo = outTradeNo; + } + + public AssistOrder(String tradeNo, String outTradeNo) { + this.tradeNo = tradeNo; + this.outTradeNo = outTradeNo; + } + + public AssistOrder(String tradeNo, TransactionType transactionType) { + this.tradeNo = tradeNo; + this.transactionType = transactionType; + } + + /** + * 支付平台订单号,交易号 + * + * @return 支付平台订单号, 交易号 + */ + public String getTradeNo() { + return tradeNo; + } + + /** + * 支付平台订单号,交易号 + * + * @param tradeNo 支付平台订单号,交易号 + */ + public void setTradeNo(String tradeNo) { + this.tradeNo = tradeNo; + } + + /** + * 获取商户订单号,商家批次单号 + * + * @return 商户订单号, 商家批次单号 + */ + public String getOutTradeNo() { + return outTradeNo; + } + + /** + * 设置商户订单号,商家批次单号 + * + * @param outTradeNo 商户订单号,商家批次单号 + */ + public void setOutTradeNo(String outTradeNo) { + this.outTradeNo = outTradeNo; + } + + public TransactionType getTransactionType() { + return transactionType; + } + + public void setTransactionType(TransactionType transactionType) { + this.transactionType = transactionType; + } + + + @Override + public Map getAttrs() { + if (null == attr) { + attr = new HashMap<>(); + } + return attr; + } + + @Override + public Object getAttr(String key) { + return getAttrs().get(key); + } + + + /** + * 添加订单信息 + * + * @param key key + * @param value 值 + */ + @Override + public void addAttr(String key, Object value) { + getAttrs().put(key, value); + } + + public String getNotifyUrl() { + return notifyUrl; + } + + public void setNotifyUrl(String notifyUrl) { + this.notifyUrl = notifyUrl; + } +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/Attrs.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/Attrs.java new file mode 100644 index 0000000000000000000000000000000000000000..48dcf887bb1b1ed089de90bfb8e9385e55839fb8 --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/Attrs.java @@ -0,0 +1,121 @@ +package com.egzosn.pay.common.bean; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; +import java.util.Map; + +/** + * 属性信息 + * + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2020/10/8
+ * 
+ */ +public interface Attrs extends Serializable { + + /** + * 获取属性 这里可用做覆盖已设置的信息属性,订单信息在签名前进行覆盖。 + * + * @return 属性 + */ + Map getAttrs(); + + /** + * 获取属性 这里可用做覆盖已设置的订单信息属性,订单信息在签名前进行覆盖。 + * + * @param key 属性名 + * @return 属性 + */ + default Object getAttr(String key) { + return getAttrs().get(key); + } + + + /** + * 获取属性 这里可用做覆盖已设置的属性信息属性。 + * + * @param key 属性名 + * @return 属性 + */ + default Number getAttrForNumber(String key) { + final Object attr = getAttr(key); + if (null == attr || "".equals(attr)) { + return null; + } + if (attr instanceof Number) { + return (Number) attr; + } + + return new BigDecimal(attr.toString()); + } + + /** + * 获取属性 这里可用做覆盖已设置的属性信息属性。 + * + * @param key 属性名 + * @return 属性 + */ + default Integer getAttrForInt(String key) { + Number attr = getAttrForNumber(key); + if (null == attr) { + return null; + } + if (attr instanceof Integer) { + return (Integer) attr; + } + return attr.intValue(); + } + + /** + * 获取属性 这里可用做覆盖已设置的属性信息属性。 + * + * @param key 属性名 + * @param defaultValue 默认值 + * @return 属性 + */ + default Integer getAttrForInt(String key, Integer defaultValue) { + Integer value = getAttrForInt(key); + return null == value ? defaultValue : value; + } + + /** + * 获取属性 这里可用做覆盖已设置的属性信息属性。 + * + * @param key 属性名 + * @return 属性 + */ + default Long getAttrForLong(String key) { + Number attr = getAttrForNumber(key); + if (null == attr) { + return null; + } + if (attr instanceof Long) { + return (Long) attr; + } + + return attr.longValue(); + } + + /** + * 获取属性 这里可用做覆盖已设置的属性信息属性。 + * + * @param key 属性名 + * @return 属性 + */ + default String getAttrForString(String key) { + return (String) getAttr(key); + } + + /** + * 获取属性 这里可用做覆盖已设置的属性信息属性。 + * + * @param key 属性名 + * @return 属性 + */ + default Date getAttrForDate(String key) { + return (Date) getAttr(key); + } +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/BaseRefundResult.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/BaseRefundResult.java new file mode 100644 index 0000000000000000000000000000000000000000..100a7b3e0b88f1b77b63000ec29e63db4e44a0f3 --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/BaseRefundResult.java @@ -0,0 +1,78 @@ +package com.egzosn.pay.common.bean; + +import java.math.BigDecimal; +import java.util.Map; + +/** + * 基础的退款结果对象 + * + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2020/8/16 19:29
+ * 
+ */ +public abstract class BaseRefundResult implements RefundResult { + + /** + * 属性集,支付宝退款结果 + */ + private Map attrs; + + + public BaseRefundResult() { + } + + public BaseRefundResult(Map attrs) { + this.attrs = attrs; + + } + + /** + * 获取退款结果原信息集 + * + * @return 属性 + */ + @Override + public Map getAttrs() { + return attrs; + } + + public void setAttrs(Map attrs) { + this.attrs = attrs; + } + + /** + * 获取退款结果属性值 + * + * @param key 属性名 + * @return 属性 + */ + @Override + public Object getAttr(String key) { + return attrs.get(key); + } + + /** + * 获取退款结果属性值 + * + * @param key 属性名 + * @return 属性值 + */ + @Override + public String getAttrString(String key) { + return attrs.get(key).toString(); + } + + /** + * 获取退款结果属性值 + * + * @param key 属性名 + * @return 属性值 + */ + @Override + public BigDecimal getAttrDecimal(String key) { + return new BigDecimal(getAttrString(key)); + } + +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/BillType.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/BillType.java new file mode 100644 index 0000000000000000000000000000000000000000..a85bb111c8fefd82ac7fd9b2f5de3b709578d338 --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/BillType.java @@ -0,0 +1,35 @@ +package com.egzosn.pay.common.bean; + +/** + * 账单类型 + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2021/2/22
+ * 
+ */ +public interface BillType { + /** + * 获取类型名称 + * @return 类型 + */ + String getType(); + + /** + * 获取类型对应的日期格式化表达式 + * @return 日期格式化表达式 + */ + String getDatePattern(); + + /** + * 获取文件类型 + * @return 文件类型 + */ + String getFileType(); + + /** + * 自定义属性 + * @return 自定义属性 + */ + String getCustom(); +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/CertStoreType.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/CertStoreType.java new file mode 100644 index 0000000000000000000000000000000000000000..4517b83207189e060c82ef0f1d71855ffea7c76d --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/CertStoreType.java @@ -0,0 +1,148 @@ +package com.egzosn.pay.common.bean; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import com.egzosn.pay.common.api.CertStore; +import com.egzosn.pay.common.bean.result.PayException; +import com.egzosn.pay.common.exception.PayErrorException; +import com.egzosn.pay.common.http.HttpRequestTemplate; + +/** + * 证书存储类型 + * + * @author egan + * email egzosn@gmail.com + * date 2019/4/14.23:04 + */ +public enum CertStoreType implements CertStore { + + /** + * 无存储类型,表示无需要转换为输入流 + */ + NONE{ + /** + * 证书信息转化为对应的输入流 + * + * @param cert 证书信息 + * @return 输入流 + * @throws IOException 找不到文件异常 + */ + @Override + public InputStream getInputStream(Object cert) throws IOException { + return null; + } + }, + /** + * 文件路径,建议绝对路径 + */ + PATH { + /** + * 证书信息转化为对应的输入流 + * + * @param cert 证书信息 + * @return 输入流 + * @throws IOException 找不到文件异常 + */ + @Override + public InputStream getInputStream(Object cert) throws IOException { + return new FileInputStream(new File((String) cert)); + } + }, + /** + * class路径 + */ + CLASS_PATH { + /** + * 证书信息转化为对应的输入流 + * + * @param cert 证书信息 + * @return 输入流 + * @throws IOException 找不到文件异常 + */ + @Override + public InputStream getInputStream(Object cert) throws IOException { + return Thread.currentThread().getContextClassLoader().getResourceAsStream((String) cert); + } + }, + /** + * 文件流转化成字符串存储至文件或者数据库中 + */ + STR { + /** + * 证书信息转化为对应的输入流 + * + * @param cert 证书信息 + * @return 输入流 + * @throws IOException 找不到文件异常 + */ + @Override + public InputStream getInputStream(Object cert) throws IOException { + return new ByteArrayInputStream(((String) cert).getBytes("ISO-8859-1")); + } + }, + + /** + * 文件流 + */ + INPUT_STREAM { + /** + * 证书信息转化为对应的输入流 + * + * @param cert 证书信息 + * @return 输入流 + * @throws IOException 找不到文件异常 + */ + @Override + public InputStream getInputStream(Object cert) throws IOException { + return (InputStream) cert; + } + }, + + /** + * URL获取的方式 + */ + URL { + /** + * 证书信息转化为对应的输入流 + * + * @param url 获取证书信息的URL + * @return 输入流 + * @throws IOException 找不到文件异常 + */ + @Override + public InputStream getInputStream(Object url) throws IOException { + return new HttpRequestTemplate().getForObject((String) url, InputStream.class); + } + }, + + /** + * URL获取的方式 + */ + BEAN { + /** + * 证书信息转化为对应的输入流 + * + * @param beanClazz 获取证书信息的类路径(字符串),该类必须实现{@link CertStore} + * @return 输入流 + * @throws IOException 找不到文件异常 + */ + @Override + public InputStream getInputStream(Object beanClazz) throws IOException { + try { + Class clazz = Class.forName((String) beanClazz); + CertStore certStore = (CertStore)clazz.newInstance(); + return certStore.getInputStream(beanClazz); + } catch (ReflectiveOperationException e) { + throw new PayErrorException(new PayException("证书获取异常", e.getMessage())); + } + + } + }; + + + +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/CloseOrder.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/CloseOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..9ea3277bda44afd6553b3343cd792428def2e016 --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/CloseOrder.java @@ -0,0 +1,14 @@ +package com.egzosn.pay.common.bean; + +/** + * 关闭订单 + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/10/6
+ * 
+ */ +@Deprecated +public class CloseOrder extends AssistOrder { + +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/CountryCode.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/CountryCode.java new file mode 100644 index 0000000000000000000000000000000000000000..6806e7a56d031cf372208d5e111d1d72ba354acd --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/CountryCode.java @@ -0,0 +1,22 @@ +package com.egzosn.pay.common.bean; + +/** + * 国家代码 + * @author egan + * email egzosn@gmail.com + * date 2019/4/16.22:43 + */ +public interface CountryCode { + + /** + * 获取国家代码 + * @return 国家代码 + */ + String getCode(); + + /** + * 获取国家名称 + * @return 国家名称 + */ + String getName(); +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/CurType.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/CurType.java index 4917abe7b0669774e49a10fcf9572cdedb0a3363..7ec8e34e40a22dfecc03cbb6ff2369235da396cd 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/CurType.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/CurType.java @@ -1,39 +1,22 @@ package com.egzosn.pay.common.bean; /** - * 货币类型 - * @author Actinia - *
- * email hayesfu@qq.com
- * create 2017 2017/1/16
- * 
+ * 基础货币类型 + * @author egan + * email egzosn@gmail.com + * date 2019/4/16.20:55 */ -public enum CurType { - - CNY("人民币"), - USD("美元"), - HKD("港币"), - MOP("澳门元"), - EUR("欧元"), - TWD("新台币"), - KRW("韩元"), - JPY("日元"), - SGD("新加坡元"), - AUD("澳大利亚元"); +public interface CurType { /** - * 币种名称 + * 获取货币类型 + * @return 货币类型 */ - private String name; - //索引 - private int index; + String getType(); /** - * 构造函数 - * @param name + * 货币名称 + * @return 货币名称 */ - CurType(String name) { - this.name = name; - } - + String getName(); } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/DefaultCountryCode.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/DefaultCountryCode.java new file mode 100644 index 0000000000000000000000000000000000000000..63abfc2801e88f6e3782e6755151a82d753957ee --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/DefaultCountryCode.java @@ -0,0 +1,47 @@ +package com.egzosn.pay.common.bean; + +/** + * 默认的国家地区代码 + * @author egan + * email egzosn@gmail.com + * date 2019/4/16.22:43 + */ +public enum DefaultCountryCode implements CountryCode{ + + CHN("中国"), + USA("美国"), + JPN("日本"), + HKG("香港"), + GBR("英国"), + MAC("澳门"), + TWN("中国台湾"), + ; + /** + * 国家名称 + */ + private String name; + + DefaultCountryCode(String name) { + this.name = name; + } + + /** + * 获取国家代码 + * + * @return 国家代码 + */ + @Override + public String getCode() { + return this.name(); + } + + /** + * 获取国家名称 + * + * @return 国家名称 + */ + @Override + public String getName() { + return name; + } +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/DefaultCurType.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/DefaultCurType.java new file mode 100644 index 0000000000000000000000000000000000000000..51f509e6ff28d16965b1bae073bbc664b5d39c4a --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/DefaultCurType.java @@ -0,0 +1,54 @@ +package com.egzosn.pay.common.bean; + +/** + * 基础货币类型 + * @author Actinia + *
+ * email hayesfu@qq.com
+ * create 2017 2017/1/16
+ * 
+ */ +public enum DefaultCurType implements CurType{ + + CNY("人民币"), + USD("美元"), + HKD("港币"), + MOP("澳门元"), + EUR("欧元"), + TWD("新台币"), + KRW("韩元"), + JPY("日元"), + SGD("新加坡元"), + AUD("澳大利亚元"); + /** + * 币种名称 + */ + private String name; + + /** + * 构造函数 + * @param name + */ + DefaultCurType(String name) { + this.name = name; + } + + /** + * 获取货币类型 + * + * @return 货币类型 + */ + @Override + public String getType() { + return this.name(); + } + /** + * 货币名称 + * + * @return 货币名称 + */ + @Override + public String getName() { + return name; + } +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/DefaultNoticeRequest.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/DefaultNoticeRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..b5f781944663e64c0a01dd782b824845f9e16190 --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/DefaultNoticeRequest.java @@ -0,0 +1,95 @@ +package com.egzosn.pay.common.bean; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; + +/**` + * + * 默认的通知请求 + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2021/8/18
+ * 
+ */ +public class DefaultNoticeRequest implements NoticeRequest { + + private Map parameterMap; + private InputStream inputStream; + + private Map> headers; + + public DefaultNoticeRequest(Map parameterMap, InputStream inputStream) { + this.parameterMap = parameterMap; + this.inputStream = inputStream; + } + + public DefaultNoticeRequest(Map parameterMap, InputStream inputStream, Map> headers) { + this.parameterMap = parameterMap; + this.inputStream = inputStream; + this.headers = headers; + } + + public DefaultNoticeRequest(InputStream inputStream, Map> headers) { + this.inputStream = inputStream; + this.headers = headers; + } + + /** + * 根据请求头名称获取请求头信息 + * + * @param name 名称 + * @return 请求头值 + */ + @Override + public String getHeader(String name) { + List value = this.headers.get(name); + return (null == value || value.isEmpty()) ? null : value.get(0); + } + + /** + * 根据请求头名称获取请求头信息 + * + * @param name 名称 + * @return 请求头值 + */ + @Override + public Enumeration getHeaders(String name) { + return Collections.enumeration(this.headers.get(name)); + } + + /** + * 获取所有的请求头名称 + * + * @return 请求头名称 + */ + @Override + public Enumeration getHeaderNames() { + return Collections.enumeration(this.headers.keySet()); + } + + /** + * 输入流 + * + * @return 输入流 + * @throws IOException IOException + */ + @Override + public InputStream getInputStream() throws IOException { + return inputStream; + } + + /** + * 获取所有的请求参数 + * + * @return 请求参数 + */ + @Override + public Map getParameterMap() { + return parameterMap; + } +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/MethodType.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/MethodType.java index 36b02f534dc833fecb94f6cf3c17cef3501afb01..72d9c9be6731576d6df5b8e6f46a53094387a108 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/MethodType.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/MethodType.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original egan. + * Copyright 2017 the original egan. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ package com.egzosn.pay.common.bean; /** - * @author: egan + * @author egan *
  *     email egzosn@gmail.com
  *     date 2017/2/7 9:52
diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/MsgType.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/MsgType.java
deleted file mode 100644
index c00304099541950b8191e4bc923749f91abae99f..0000000000000000000000000000000000000000
--- a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/MsgType.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2002-2017 the original huodull or egan.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package com.egzosn.pay.common.bean;
-
-/**
- * 消息类型
- * @author: egan
- * 
- *     email egzosn@gmail.com
- *     date 2016/11/18 0:59
- *  
- */ -public enum MsgType { - text, xml,json -} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/NoticeParams.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/NoticeParams.java new file mode 100644 index 0000000000000000000000000000000000000000..752e7fb799988d18da6f74aa5c6629de2e05b1ff --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/NoticeParams.java @@ -0,0 +1,141 @@ +/* + * Copyright 2017-2023 the original Egan. + * email egzosn@gmail.com + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.egzosn.pay.common.bean; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; + +/** + * 通知参数 + * + * @author Egan + * email egzosn@gmail.com + * date 2021/8/8 + */ +public class NoticeParams implements Attrs { + + /** + * body原始字符串 + */ + private String bodyStr; + + /** + * 为了获取request里面传过来的动态参数 + */ + private Map body; + + /** + * 存放请求头信息 + */ + private Map> headers; + + /** + * 附加属性 + */ + private Map attr; + + public NoticeParams() { + } + + public NoticeParams(Map body) { + this.body = body; + } + + public NoticeParams(Map body, Map> headers) { + this.body = body; + this.headers = headers; + } + + public String getBodyStr() { + return bodyStr; + } + + public void setBodyStr(String bodyStr) { + this.bodyStr = bodyStr; + } + + private T getValueMatchingKey(Map values, String key) { + T value = values.get(key); + if (null != value) { + return value; + } + + for (Map.Entry entry : values.entrySet()) { + if (entry.getKey().equalsIgnoreCase(key)) { + return entry.getValue(); + } + } + return null; + } + + + public String getHeader(String name) { + List value = getValueMatchingKey(headers, name); + return (null == value || value.isEmpty()) ? null : value.get(0); + } + + public Enumeration getHeaders(String name) { + List value = getValueMatchingKey(headers, name); + return (Collections.enumeration(value != null ? value : Collections.emptySet())); + } + + public Enumeration getHeaderNames() { + if (null == headers) { + return Collections.enumeration(Collections.emptySet()); + } + return Collections.enumeration(this.headers.keySet()); + } + + + public Map getBody() { + return body; + } + + public void setBody(Map body) { + this.body = body; + } + + public Map> getHeaders() { + return headers; + } + + public void setHeaders(Map> headers) { + this.headers = headers; + } + + public Map getAttr() { + return attr; + } + + public void setAttr(Map attr) { + this.attr = attr; + } + + + /** + * 获取属性 这里可用做覆盖已设置的信息属性,订单信息在签名前进行覆盖。 + * + * @return 属性 + */ + @Override + public Map getAttrs() { + return attr; + } +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/NoticeRequest.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/NoticeRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..3ec9af76a6ab18ef83007e6ae095745eca99b72b --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/NoticeRequest.java @@ -0,0 +1,47 @@ +package com.egzosn.pay.common.bean; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Map; + +/** + * 通知请求 + * @author Egan + * email egzosn@gmail.com + * date 2021/8/8 + */ +public interface NoticeRequest { + + /** + * 根据请求头名称获取请求头信息 + * @param name 名称 + * @return 请求头值 + */ + String getHeader(String name); + /** + * 根据请求头名称获取请求头信息 + * @param name 名称 + * @return 请求头值 + */ + Enumeration getHeaders(String name); + + /** + * 获取所有的请求头名称 + * @return 请求头名称 + */ + Enumeration getHeaderNames(); + + /** + * 输入流 + * @return 输入流 + * @throws IOException IOException + */ + InputStream getInputStream() throws IOException; + + /** + * 获取所有的请求参数 + * @return 请求参数 + */ + Map getParameterMap(); +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/Order.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/Order.java new file mode 100644 index 0000000000000000000000000000000000000000..0c06f367ab48c99a37c408b4fb709de398fd1a19 --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/Order.java @@ -0,0 +1,23 @@ +package com.egzosn.pay.common.bean; + +/** + * 支付订单信息 + * + * @author egan + *
+ *      email egzosn@gmail.com
+ *      date 2020/01/05 13:34
+ *  
+ */ +public interface Order extends Attrs { + + + /** + * 添加订单信息 + * + * @param key key + * @param value 值 + */ + void addAttr(String key, Object value); + +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/OrderParaStructure.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/OrderParaStructure.java new file mode 100644 index 0000000000000000000000000000000000000000..04533062b95c05b730e00fce917aa1d0c0a2e25f --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/OrderParaStructure.java @@ -0,0 +1,43 @@ +package com.egzosn.pay.common.bean; + +import java.util.Date; +import java.util.Map; + +import com.egzosn.pay.common.util.DateUtils; +import com.egzosn.pay.common.util.str.StringUtils; + +/** + * 订单参数构造器 + * + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2021/8/16
+ * 
+ */ +public final class OrderParaStructure { + private OrderParaStructure() { + } + + public static Map loadParameters(Map parameters, String key, String value) { + if (StringUtils.isNotEmpty(value)) { + parameters.put(key, value); + } + return parameters; + } + + public static Map loadParameters(Map parameters, String key, Order order) { + Object attr = order.getAttr(key); + if (null != attr && !"".equals(attr)) { + order.getAttrs().remove(key); + parameters.put(key, attr); + } + return parameters; + } + + public static Map loadDateParameters(Map parameters, String key, Order order, String datePattern) { + return OrderParaStructure.loadParameters(parameters, key, DateUtils.formatDate((Date) order.getAttr(key), datePattern)); + } + + +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/PayMessage.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/PayMessage.java index cff3dc2a30f7380e2273682fc0ded668874b2e92..888b78702e6b8b4f54dcef81bf43125111691c45 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/PayMessage.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/PayMessage.java @@ -2,14 +2,12 @@ package com.egzosn.pay.common.bean; import java.io.Serializable; import java.math.BigDecimal; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; import java.util.Map; /** * 支付回调消息 * 基础实现,具体可根据具体支付回调的消息去实现 + * * @author egan *
  *     email egzosn@gmail.com
@@ -18,36 +16,33 @@ import java.util.Map;
  */
 public class PayMessage implements Serializable {
     private Map payMessage = null;
-    private String msgType;
     private String payType;
     private String transactionType;
     private String fromPay;
     private String describe;
 
+
+    public PayMessage() {
+    }
+
     public PayMessage(Map payMessage) {
         this.payMessage = payMessage;
     }
 
-    public PayMessage(Map payMessage, String payType, String msgType) {
-        this(payMessage);
+    public PayMessage(Map payMessage, String payType) {
+        this.payMessage = payMessage;
         this.payType = payType;
-        this.msgType = msgType;
     }
 
 
-    public PayMessage(Map payMessage, String msgType, String payType, String transactionType) {
+    public PayMessage(Map payMessage, String payType, String transactionType) {
         this.payMessage = payMessage;
-        this.msgType = msgType;
         this.payType = payType;
         this.transactionType = transactionType;
     }
 
-    public String getMsgType() {
-        return msgType;
-    }
-
-    public void setMsgType(String msgType) {
-        this.msgType = msgType;
+    protected void setPayMessage(Map payMessage) {
+        this.payMessage = payMessage;
     }
 
 
@@ -64,7 +59,7 @@ public class PayMessage implements Serializable {
     }
 
     public void setTransactionType(String transactionType) {
-            this.transactionType = transactionType;
+        this.transactionType = transactionType;
     }
 
     public String getFromPay() {
@@ -82,28 +77,31 @@ public class PayMessage implements Serializable {
     public void setDescribe(String describe) {
         this.describe = describe;
     }
-    public String getDiscount(){
+
+    public String getDiscount() {
         return (String) payMessage.get("discount");
     }
-    public String getSubject(){
+
+    public String getSubject() {
         return (String) payMessage.get("subject");
     }
 
 
-
     /////////微信与支付宝共用
-    public String getOutTradeNo(){
+    public String getOutTradeNo() {
         return (String) payMessage.get("out_trade_no");
     }
 
-    public String getSign(){
+    public String getSign() {
         return (String) payMessage.get("sign");
     }
 
-    public Number getTotalFee(){
+    public Number getTotalFee() {
         String totalFee = (String) payMessage.get("total_fee");
-        if (null == totalFee || "".equals(totalFee)){    return 0;      }
-        if (isNumber(totalFee)){
+        if (null == totalFee || "".equals(totalFee)) {
+            return 0;
+        }
+        if (isNumber(totalFee)) {
             return new BigDecimal(totalFee);
         }
         return 0;
@@ -112,13 +110,11 @@ public class PayMessage implements Serializable {
     /////////微信与支付宝共用
 
 
-
-    public boolean isNumber(String str){
+    public boolean isNumber(String str) {
         return str.matches("^(-?[1-9]\\d*\\.?\\d*)|(-?0\\.\\d*[1-9])|(-?[0])|(-?[0]\\.\\d*)$");
     }
 
 
-
     @Override
     public String toString() {
         return payMessage.toString();
@@ -129,5 +125,4 @@ public class PayMessage implements Serializable {
     }
 
 
-
 }
diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/PayOrder.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/PayOrder.java
index 753054da24919f4b7e6f6117c96e11c3694f8131..1a88e0fd4939e8d76db75ff22ab7a4a2e90e9140 100644
--- a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/PayOrder.java
+++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/PayOrder.java
@@ -3,16 +3,18 @@ package com.egzosn.pay.common.bean;
 import java.math.BigDecimal;
 import java.util.Date;
 
+import com.egzosn.pay.common.util.str.StringUtils;
+
 /**
  * 支付订单信息
  *
  * @author egan
- *  
+ * 
  *      email egzosn@gmail.com
  *      date 2016/10/19 22:34
  *  
*/ -public class PayOrder { +public class PayOrder extends AssistOrder { /** * 商品名称 */ @@ -29,10 +31,7 @@ public class PayOrder { * 价格 */ private BigDecimal price; - /** - * 商户订单号 - */ - private String outTradeNo; + /** * 银行卡类型 */ @@ -40,35 +39,36 @@ public class PayOrder { /** * 设备信息 */ + @Deprecated private String deviceInfo; /** * 支付创建ip */ + @Deprecated private String spbillCreateIp; /** - * 付款条码串 与设备号类似??? + * 付款条码串,人脸凭证,有关支付代码相关的, */ private String authCode; /** * 微信专用,,,, * WAP支付链接 */ + @Deprecated private String wapUrl; /** * 微信专用,,,, * WAP支付网页名称 */ - + @Deprecated private String wapName; /** * 用户唯一标识 - * 微信含 sub_openid 字段 + * 微信含 sub_openid 字段 + * 支付宝 buyer_id */ private String openid; - /** - * 交易类型 - */ - private TransactionType transactionType; + /** * 支付币种 */ @@ -79,6 +79,21 @@ public class PayOrder { private Date expirationTime; + public PayOrder() { + } + + + public PayOrder(String subject, String body, BigDecimal price, String outTradeNo) { + this(subject, body, price, outTradeNo, null); + } + + public PayOrder(String subject, String body, BigDecimal price, String outTradeNo, TransactionType transactionType) { + this.subject = StringUtils.tryTrim(subject); + this.body = StringUtils.tryTrim(body); + this.price = price; + setOutTradeNo(StringUtils.tryTrim(outTradeNo)); + setTransactionType(transactionType); + } public CurType getCurType() { @@ -121,50 +136,6 @@ public class PayOrder { this.price = price; } - /** - * 获取商户订单号 - * @return 商户订单号 - * @see #getOutTradeNo() - */ - @Deprecated - public String getTradeNo() { - return outTradeNo; - } - - - /** - * - * @param tradeNo 商户订单号 - * @see #setOutTradeNo(String) - */ - @Deprecated - public void setTradeNo(String tradeNo) { - this.outTradeNo = tradeNo; - } - - /** - * 获取商户订单号 - * @return 商户订单号 - */ - public String getOutTradeNo() { - return outTradeNo; - } - - /** - * 设置商户订单号 - * @param outTradeNo 商户订单号 - */ - public void setOutTradeNo(String outTradeNo) { - this.outTradeNo = outTradeNo; - } - - public TransactionType getTransactionType() { - return transactionType; - } - - public void setTransactionType(TransactionType transactionType) { - this.transactionType = transactionType; - } public String getBankType() { return bankType; @@ -198,24 +169,6 @@ public class PayOrder { this.deviceInfo = deviceInfo; } - public PayOrder() { - } - - - public PayOrder(String subject, String body, BigDecimal price, String outTradeNo, TransactionType transactionType) { - this.subject = subject; - this.body = body; - this.price = price; - this.outTradeNo = outTradeNo; - this.transactionType = transactionType; - } - public PayOrder(String subject, String body, BigDecimal price, String outTradeNo) { - this.subject = subject; - this.body = body; - this.price = price; - this.outTradeNo = outTradeNo; - } - public String getWapUrl() { return wapUrl; } @@ -248,22 +201,4 @@ public class PayOrder { this.expirationTime = expirationTime; } - @Override - public String toString() { - return "PayOrder{" + - "subject='" + subject + '\'' + - ", body='" + body + '\'' + - ", price=" + price + - ", outTradeNo='" + outTradeNo + '\'' + - ", bankType='" + bankType + '\'' + - ", deviceInfo='" + deviceInfo + '\'' + - ", spbillCreateIp='" + spbillCreateIp + '\'' + - ", authCode='" + authCode + '\'' + - ", wapUrl='" + wapUrl + '\'' + - ", wapName='" + wapName + '\'' + - ", openid='" + openid + '\'' + - ", transactionType=" + transactionType + - ", curType=" + curType + - '}'; - } } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/PayOutMessage.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/PayOutMessage.java index 97f686fe6512272373a5e6888e2c5994b16c9eb3..2f50c10ced199099822b2403b372ad7eaee7fc26 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/PayOutMessage.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/PayOutMessage.java @@ -1,15 +1,16 @@ package com.egzosn.pay.common.bean; +import java.io.Serializable; + import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.common.bean.outbuilder.JsonBuilder; import com.egzosn.pay.common.bean.outbuilder.TextBuilder; import com.egzosn.pay.common.bean.outbuilder.XmlBuilder; -import java.io.Serializable; - /** - * 支付回调通知返回消息 - * @author egan + * 支付回调通知返回消息 + * + * @author egan *
  *     email egzosn@gmail.com
  *     date 2016-6-1 11:40:30
@@ -17,7 +18,6 @@ import java.io.Serializable;
  */
 public abstract class PayOutMessage implements Serializable {
     protected String content;
-    protected String msgType;
 
 
     public String getContent() {
@@ -28,34 +28,32 @@ public abstract class PayOutMessage implements Serializable {
         this.content = content;
     }
 
-    public String getMsgType() {
-        return msgType;
-    }
-
-    public void setMsgType(String msgType) {
-        this.msgType = msgType;
-    }
-
     /**
      * 获得文本消息builder
+     *
      * @return 文本消息builder
      */
     public static TextBuilder TEXT() {
         return new TextBuilder();
     }
+
     /**
      * 获得XML消息builder
+     *
      * @return XML消息builder
      */
     public static XmlBuilder XML() {
         return new XmlBuilder();
     }
+
     /**
      * 获得Json消息builder
+     *
      * @return Json消息builder
      */
     public static JsonBuilder JSON() {
         return new JsonBuilder(new JSONObject());
     }
+
     public abstract String toMessage();
 }
diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/RefundOrder.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/RefundOrder.java
index be5061a27410580b098a3468c9d3b4a3ad170daf..4bc0fa9ee28011a59f6556dadc6201e87d4a2af1 100644
--- a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/RefundOrder.java
+++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/RefundOrder.java
@@ -5,25 +5,18 @@ import java.util.Date;
 
 /**
  * 退款订单信息
- * @author: egan
- *  
+ *
+ * @author egan
+ * 
  *      email egzosn@gmail.com
  *      date 2018/1/15 21:40
  *   
*/ -public class RefundOrder { +public class RefundOrder extends AssistOrder { /** * 退款单号,每次进行退款的单号,此处唯一 */ private String refundNo; - /** - * 支付平台订单号,交易号 - */ - private String tradeNo; - /** - * 商户单号 - */ - private String outTradeNo; /** * 退款金额 */ @@ -46,6 +39,15 @@ public class RefundOrder { * 退款说明 */ private String description; + /** + * 退款用户 + */ + private String userId; + + /** + * 退款URL + */ + private String refundUrl; public String getRefundNo() { return refundNo; @@ -55,22 +57,6 @@ public class RefundOrder { this.refundNo = refundNo; } - public String getTradeNo() { - return tradeNo; - } - - public void setTradeNo(String tradeNo) { - this.tradeNo = tradeNo; - } - - public String getOutTradeNo() { - return outTradeNo; - } - - public void setOutTradeNo(String outTradeNo) { - this.outTradeNo = outTradeNo; - } - public BigDecimal getRefundAmount() { return refundAmount; } @@ -111,29 +97,43 @@ public class RefundOrder { this.description = description; } + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + public RefundOrder() { } public RefundOrder(String refundNo, String tradeNo, BigDecimal refundAmount) { this.refundNo = refundNo; - this.tradeNo = tradeNo; + setTradeNo(tradeNo); this.refundAmount = refundAmount; } public RefundOrder(String tradeNo, String outTradeNo, BigDecimal refundAmount, BigDecimal totalAmount) { - this.tradeNo = tradeNo; - this.outTradeNo = outTradeNo; + setTradeNo(tradeNo); + setOutTradeNo(outTradeNo); this.refundAmount = refundAmount; this.totalAmount = totalAmount; } public RefundOrder(String refundNo, String tradeNo, String outTradeNo, BigDecimal refundAmount, BigDecimal totalAmount) { this.refundNo = refundNo; - this.tradeNo = tradeNo; - this.outTradeNo = outTradeNo; + setTradeNo(tradeNo); + setOutTradeNo(outTradeNo); this.refundAmount = refundAmount; this.totalAmount = totalAmount; } + public String getRefundUrl() { + return refundUrl; + } + public void setRefundUrl(String refundUrl) { + this.refundUrl = refundUrl; + } } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/RefundResult.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/RefundResult.java new file mode 100644 index 0000000000000000000000000000000000000000..db31a45edf706fefb3d02c77d0d55ff0db192dcb --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/RefundResult.java @@ -0,0 +1,114 @@ +package com.egzosn.pay.common.bean; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Map; + +/** + * 退款结果 + *

+ * + * @author Egan + *

+ * email egzosn@gmail.com
+ * date 2020/8/16 9:55
+ * 
+ */ +public interface RefundResult extends Serializable { + /** + * 获取退款结果原信息集 + * + * @return 属性 + */ + Map getAttrs(); + + /** + * 获取退款结果属性值 + * + * @param key 属性名 + * @return 属性值 + */ + Object getAttr(String key); + + /** + * 获取退款结果属性值 + * + * @param key 属性名 + * @return 属性值 + */ + String getAttrString(String key); + + /** + * 获取退款结果属性值 + * + * @param key 属性名 + * @return 属性值 + */ + BigDecimal getAttrDecimal(String key); + + + /** + * 获取退款请求结果状态码 + * + * @return 状态码 + */ + String getCode(); + + /** + * 获取退款请求结果状态提示信息 + * + * @return 提示信息 + */ + String getMsg(); + + /** + * 返回业务结果状态码 + * + * @return 业务结果状态码 + */ + String getResultCode(); + + /** + * 返回业务结果状态提示信息 + * + * @return 业务结果状态提示信息 + */ + String getResultMsg(); + + /** + * 退款金额 + * + * @return 退款金额 + */ + BigDecimal getRefundFee(); + + /** + * 退款币种信息 + * + * @return 币种信息 + */ + CurType getRefundCurrency(); + + /** + * 支付平台交易号 + * 发起支付时 支付平台(如支付宝)返回的交易订单号 + * + * @return 支付平台交易号 + */ + String getTradeNo(); + + /** + * 支付订单号 + * 发起支付时,用户系统的订单号 + * + * @return 支付订单号 + */ + String getOutTradeNo(); + + /** + * 商户退款单号 + * + * @return 商户退款单号 + */ + String getRefundNo(); +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/SignType.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/SignType.java new file mode 100644 index 0000000000000000000000000000000000000000..4f9c159e549e767a1823077d0744444b5082bd4e --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/SignType.java @@ -0,0 +1,78 @@ +package com.egzosn.pay.common.bean; + + +import java.util.*; + +/** + * 签名类型 + * + * @author egan + *
+ * email egzosn@gmail.com
+ * date 2019/12/08 13:30
+ * 
+ */ +public interface SignType { + + + /** + * 获取签名类型名称 + * @return 类型名称 + */ + String getName(); + + /** + * 签名 + * + * @param parameters 需要进行排序签名的参数 + * @param key 密钥 + * @param characterEncoding 编码格式 + * @return 签名值 + */ + String sign(Map parameters, String key, String characterEncoding); + /** + * 签名 + * @param parameters 需要进行排序签名的参数 + * @param key 密钥 + * @param separator 分隔符 默认 & + * @param characterEncoding 编码格式 + * @return 签名值 + */ + String sign(Map parameters, String key, String separator, String characterEncoding); + + /** + * 签名 + * + * @param content 需要签名的内容 + * @param key 密钥 + * @param characterEncoding 字符编码 + * @return 签名值 + */ + String createSign(String content, String key, String characterEncoding); + + /** + * 签名字符串 + * + * @param params 需要签名的字符串 + * @param sign 签名结果 + * @param key 密钥 + * @param characterEncoding 编码格式 + * @return 签名结果 + */ + boolean verify(Map params, String sign, String key, String characterEncoding); + + + /** + * 签名字符串 + * + * @param text 需要签名的字符串 + * @param sign 签名结果 + * @param key 密钥 + * @param characterEncoding 编码格式 + * @return 签名结果 + */ + boolean verify(String text, String sign, String key, String characterEncoding); + + + +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/TransferOrder.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/TransferOrder.java index 16ee000d6059b7fd12ec93abddbdaba1f86c1943..73a11627a948936c976148dd5a79fba83c4a7c28 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/TransferOrder.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/TransferOrder.java @@ -1,16 +1,24 @@ package com.egzosn.pay.common.bean; import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; /** * 转账订单 + * * @author egan *
  * email egzosn@gmail.com
  * date 2018/1/31
  * 
*/ -public class TransferOrder { +public class TransferOrder implements Order { + + /** + * 转账批次订单单号 + */ + private String batchNo; /** * 转账订单单号 @@ -18,14 +26,14 @@ public class TransferOrder { private String outNo; /** - * 收款方账户, 用户openid + * 收款方账户, 用户openid,卡号等等 */ - private String payeeAccount ; + private String payeeAccount; /** * 转账金额 */ - private BigDecimal amount ; + private BigDecimal amount; /** * 付款人名称 @@ -36,6 +44,11 @@ public class TransferOrder { * 收款人名称 */ private String payeeName; + /** + * 收款人地址 + */ + private String payeeAddress; + /** * 备注 */ @@ -43,18 +56,46 @@ public class TransferOrder { /** * 收款开户行 - */ + */ private Bank bank; + /** + * 收款开户行地址 + */ + private String payeeBankAddress; + /** * 币种 */ private CurType curType; + /** + * 国家代码 + */ + private CountryCode countryCode; /** * 转账类型,收款方账户类型,比如支付宝账户或者银行卡 */ private TransferType transferType; + /** + * 操作者ip,根据支付平台所需进行设置 + */ + private String ip; + + + /** + * 订单附加信息,可用于预设未提供的参数,这里会覆盖以上所有的订单信息, + */ + private Map attr; + + public String getBatchNo() { + return batchNo; + } + + public void setBatchNo(String batchNo) { + this.batchNo = batchNo; + } + public String getOutNo() { return outNo; } @@ -95,6 +136,14 @@ public class TransferOrder { this.payeeName = payeeName; } + public String getPayeeAddress() { + return payeeAddress; + } + + public void setPayeeAddress(String payeeAddress) { + this.payeeAddress = payeeAddress; + } + public String getRemark() { return remark; } @@ -111,6 +160,22 @@ public class TransferOrder { this.bank = bank; } + public String getPayeeBankAddress() { + return payeeBankAddress; + } + + public void setPayeeBankAddress(String payeeBankAddress) { + this.payeeBankAddress = payeeBankAddress; + } + + public CountryCode getCountryCode() { + return countryCode; + } + + public void setCountryCode(CountryCode countryCode) { + this.countryCode = countryCode; + } + public CurType getCurType() { return curType; } @@ -126,4 +191,39 @@ public class TransferOrder { public void setTransferType(TransferType transferType) { this.transferType = transferType; } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + @Override + public Map getAttrs() { + if (null == attr) { + attr = new HashMap<>(); + } + return attr; + } + + @Override + public Object getAttr(String key) { + return getAttrs().get(key); + } + + + /** + * 添加订单信息 + * + * @param key key + * @param value 值 + */ + @Override + public void addAttr(String key, Object value) { + getAttrs().put(key, value); + } + + } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/TransferType.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/TransferType.java index 867acf908385c546b60a09b6a4b3f03788e6f732..24b34d5fb36d3fa4c1b090b83ee77191ba8bdb97 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/TransferType.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/TransferType.java @@ -1,5 +1,7 @@ package com.egzosn.pay.common.bean; +import java.util.Map; + /** * 转账类型 * @author egan @@ -7,6 +9,12 @@ package com.egzosn.pay.common.bean; * date 2018/9/28.19:45 */ public interface TransferType extends TransactionType{ - - + /** + * 设置属性 + * + * @param attr 已有属性对象 + * @param order 转账订单 + * @return 属性对象 + */ + Map setAttr(Map attr, TransferOrder order); } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/outbuilder/JsonBuilder.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/outbuilder/JsonBuilder.java index 726a2ed359423348028464bbd0c5f5382563ba91..179865ee5d0b41f110c5e65586b0703d4cf75ca6 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/outbuilder/JsonBuilder.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/outbuilder/JsonBuilder.java @@ -5,7 +5,7 @@ import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.common.bean.PayOutMessage; /** - * @author: egan + * @author egan *
  *      email egzosn@gmail.com
  *      date 2017/1/13 14:30
diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/outbuilder/PayJsonOutMessage.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/outbuilder/PayJsonOutMessage.java
index 576134b2ff45d5a1fa3fd13930231b5c910d639a..bf5e49bc5fc2283ccc0ecfb397b113b63f8d7636 100644
--- a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/outbuilder/PayJsonOutMessage.java
+++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/outbuilder/PayJsonOutMessage.java
@@ -1,19 +1,18 @@
 package com.egzosn.pay.common.bean.outbuilder;
 
-import com.egzosn.pay.common.bean.MsgType;
 import com.egzosn.pay.common.bean.PayOutMessage;
 
 /**
  * @author egan
- *  
+ * 
  *      email egzosn@gmail.com
  *      date 2016-6-1 11:40:30
  *   
*/ -public class PayJsonOutMessage extends PayOutMessage{ +public class PayJsonOutMessage extends PayOutMessage { public PayJsonOutMessage() { - this.msgType = MsgType.json.name(); + } @Override diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/outbuilder/PayTextOutMessage.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/outbuilder/PayTextOutMessage.java index 7d311962b65c4dd5f891ca67b4ab6645e9ab35f7..dc992679d9a660b127f94dc2c0aedcaa554fd699 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/outbuilder/PayTextOutMessage.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/outbuilder/PayTextOutMessage.java @@ -1,6 +1,5 @@ package com.egzosn.pay.common.bean.outbuilder; -import com.egzosn.pay.common.bean.MsgType; import com.egzosn.pay.common.bean.PayOutMessage; /** @@ -13,7 +12,6 @@ import com.egzosn.pay.common.bean.PayOutMessage; public class PayTextOutMessage extends PayOutMessage{ public PayTextOutMessage() { - this.msgType = MsgType.text.name(); } @Override diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/outbuilder/PayXmlOutMessage.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/outbuilder/PayXmlOutMessage.java index c4b96bda0a455a56c9a64660ba046d717e28d37a..576d1ba6889e47b12e5598e6dbff0f4f5f45d419 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/outbuilder/PayXmlOutMessage.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/outbuilder/PayXmlOutMessage.java @@ -1,6 +1,5 @@ package com.egzosn.pay.common.bean.outbuilder; -import com.egzosn.pay.common.bean.MsgType; import com.egzosn.pay.common.bean.PayOutMessage; /** @@ -15,7 +14,6 @@ public class PayXmlOutMessage extends PayOutMessage{ private String code; public PayXmlOutMessage() { - this.msgType = MsgType.xml.name(); } public String getCode() { diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/result/PayException.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/result/PayException.java index db26fba26207617eae18220d9036efa500fda6fe..2e738ac3da82bb3a4724750932b1c447f2c72e76 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/bean/result/PayException.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/result/PayException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original huodull or egan. + * Copyright 2002-2017 the original egan or egan. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ package com.egzosn.pay.common.bean.result; /** * 支付异常 - * @author: egan + * @author egan *
  *      email egzosn@gmail.com
  *      date 2017/3/7 12:32
diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/exception/PayErrorException.java b/pay-java-common/src/main/java/com/egzosn/pay/common/exception/PayErrorException.java
index 3b077b4b703c19964bb349631064409d25cbd613..6362978b219c9b372657eb99989ce05c3cf392d6 100644
--- a/pay-java-common/src/main/java/com/egzosn/pay/common/exception/PayErrorException.java
+++ b/pay-java-common/src/main/java/com/egzosn/pay/common/exception/PayErrorException.java
@@ -18,6 +18,11 @@ public class PayErrorException extends RuntimeException  {
         this.error = error;
     }
 
+    public PayErrorException(PayError error, Throwable throwable) {
+        super(error.getString(), throwable);
+        this.error = error;
+    }
+
 
     public PayError getPayError() {
         return error;
diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/http/ClientHttpRequest.java b/pay-java-common/src/main/java/com/egzosn/pay/common/http/ClientHttpRequest.java
index db0dccff0c0f2684f6bdf73fc9c544a9c92358c5..060a6f616583b7f7d170489df871ccd61129b14b 100644
--- a/pay-java-common/src/main/java/com/egzosn/pay/common/http/ClientHttpRequest.java
+++ b/pay-java-common/src/main/java/com/egzosn/pay/common/http/ClientHttpRequest.java
@@ -1,15 +1,22 @@
 package com.egzosn.pay.common.http;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONException;
-import com.egzosn.pay.common.bean.MethodType;
-import com.egzosn.pay.common.bean.result.PayException;
-import com.egzosn.pay.common.exception.PayErrorException;
-import com.egzosn.pay.common.util.XML;
-import com.egzosn.pay.common.util.str.StringUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.http.*;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.http.Consts;
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
 import org.apache.http.client.ClientProtocolException;
 import org.apache.http.client.HttpResponseException;
 import org.apache.http.client.config.RequestConfig;
@@ -18,26 +25,30 @@ import org.apache.http.entity.ContentType;
 import org.apache.http.entity.StringEntity;
 import org.apache.http.util.EntityUtils;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URI;
-import java.nio.charset.Charset;
-import java.util.Map;
-
 import static com.egzosn.pay.common.http.UriVariables.getMapToParameters;
 
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONException;
+import com.alibaba.fastjson.JSONObject;
+import com.egzosn.pay.common.bean.MethodType;
+import com.egzosn.pay.common.bean.result.PayException;
+import com.egzosn.pay.common.exception.PayErrorException;
+import com.egzosn.pay.common.util.XML;
+import com.egzosn.pay.common.util.str.StringUtils;
+
 /**
  * 一个HTTP请求的客户端
- * @author: egan
- *  
+ *
+ * @author egan
+ * 
  * email egzosn@gmail.com
  * date 2017/3/4 17:56
  *  
*/ -public class ClientHttpRequest extends HttpEntityEnclosingRequestBase implements org.apache.http.client.ResponseHandler{ - protected static final Log LOG = LogFactory.getLog(ClientHttpRequest.class); - public static final ContentType APPLICATION_FORM_URLENCODED_UTF_8 = ContentType.create("application/x-www-form-urlencoded", Consts.UTF_8);; +public class ClientHttpRequest extends HttpEntityEnclosingRequestBase implements org.apache.http.client.ResponseHandler { + protected static final Logger LOG = LoggerFactory.getLogger(ClientHttpRequest.class); + public static final ContentType APPLICATION_FORM_URLENCODED_UTF_8 = ContentType.create("application/x-www-form-urlencoded", Consts.UTF_8); + public static final ContentType APPLICATION_XML_UTF_8 = ContentType.create("application/xml", Consts.UTF_8); /** @@ -49,7 +60,7 @@ public class ClientHttpRequest extends HttpEntityEnclosingRequestBase impleme */ private Charset defaultCharset; /** - * 响应类型 + * 响应类型 */ private Class responseType; @@ -66,25 +77,28 @@ public class ClientHttpRequest extends HttpEntityEnclosingRequestBase impleme } /** - * 根据请求地址 请求方法,请求内容对象 - * @param uri 请求地址 - * @param method 请求方法 - * @param request 请求内容 + * 根据请求地址 请求方法,请求内容对象 + * + * @param uri 请求地址 + * @param method 请求方法 + * @param request 请求内容 * @param defaultCharset 默认使用的响应编码 */ public ClientHttpRequest(URI uri, MethodType method, Object request, String defaultCharset) { - this(uri, method); + this(uri, method); setParameters(request); - if (StringUtils.isNotEmpty(defaultCharset)){ - setDefaultCharset( Charset.forName(defaultCharset)); + if (StringUtils.isNotEmpty(defaultCharset)) { + setDefaultCharset(Charset.forName(defaultCharset)); } } + /** - * 根据请求地址 请求方法,请求内容对象 - * @param uri 请求地址 - * @param method 请求方法 - * @param request 请求内容 + * 根据请求地址 请求方法,请求内容对象 + * + * @param uri 请求地址 + * @param method 请求方法 + * @param request 请求内容 * @param defaultCharset 默认使用的响应编码 */ public ClientHttpRequest(URI uri, MethodType method, Object request, Charset defaultCharset) { @@ -92,20 +106,24 @@ public class ClientHttpRequest extends HttpEntityEnclosingRequestBase impleme setParameters(request); setDefaultCharset(defaultCharset); } + /** - * 根据请求地址 请求方法,请求内容对象 - * @param uri 请求地址 + * 根据请求地址 请求方法,请求内容对象 + * + * @param uri 请求地址 * @param method 请求方法 * @param request 请求内容 */ public ClientHttpRequest(URI uri, MethodType method, Object request) { - this(uri, method); + this(uri, method); setParameters(request); } + /** * 根据请求地址 请求方法 - * @param uri 请求地址 - * @param method 请求方法 + * + * @param uri 请求地址 + * @param method 请求方法 */ public ClientHttpRequest(URI uri, MethodType method) { this.setURI(uri); @@ -114,30 +132,37 @@ public class ClientHttpRequest extends HttpEntityEnclosingRequestBase impleme /** * 根据请求地址 - * @param uri 请求地址 + * + * @param uri 请求地址 */ public ClientHttpRequest(URI uri) { this.setURI(uri); } + /** * 根据请求地址 - * @param uri 请求地址 + * + * @param uri 请求地址 */ public ClientHttpRequest(String uri) { this.setURI(URI.create(uri)); } + /** * 根据请求地址 请求方法 - * @param uri 请求地址 - * @param method 请求方法 + * + * @param uri 请求地址 + * @param method 请求方法 */ public ClientHttpRequest(String uri, MethodType method) { this.setURI(URI.create(uri)); this.method = method; } + /** - * 根据请求地址 请求方法,请求内容对象 - * @param uri 请求地址 + * 根据请求地址 请求方法,请求内容对象 + * + * @param uri 请求地址 * @param method 请求方法 * @param request 请求内容 */ @@ -150,7 +175,7 @@ public class ClientHttpRequest extends HttpEntityEnclosingRequestBase impleme * 设置请求方式 * * @param method 请求方式 - * {@link com.egzosn.pay.common.bean.MethodType} 请求方式 + * {@link com.egzosn.pay.common.bean.MethodType} 请求方式 */ public void setMethod(MethodType method) { this.method = method; @@ -158,6 +183,7 @@ public class ClientHttpRequest extends HttpEntityEnclosingRequestBase impleme /** * 获取请求方式 + * * @return 请求方式 */ @Override @@ -178,10 +204,11 @@ public class ClientHttpRequest extends HttpEntityEnclosingRequestBase impleme /** * 设置代理 + * * @param httpProxy http代理配置信息 * @return 当前HTTP请求的客户端 */ - public ClientHttpRequest setProxy(HttpHost httpProxy){ + public ClientHttpRequest setProxy(HttpHost httpProxy) { if (httpProxy != null) { RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); setConfig(config); @@ -197,34 +224,34 @@ public class ClientHttpRequest extends HttpEntityEnclosingRequestBase impleme * @return 当前HTTP请求的客户端 */ public ClientHttpRequest setParameters(Object request) { - if (null == request){ + if (null == request) { return this; } - if (request instanceof HttpHeader){ - HttpHeader entity = (HttpHeader)request; - if (null != entity.getHeaders() ){ + if (request instanceof HttpHeader) { + HttpHeader entity = (HttpHeader) request; + if (null != entity.getHeaders()) { if (LOG.isDebugEnabled()) { LOG.debug("header : " + JSON.toJSONString(entity.getHeaders())); } - for (Header header : entity.getHeaders()){ + for (Header header : entity.getHeaders()) { addHeader(header); } } - }else if (request instanceof HttpStringEntity){ - HttpStringEntity entity = (HttpStringEntity)request; - if (!entity.isEmpty()){ + } else if (request instanceof HttpStringEntity) { + HttpStringEntity entity = (HttpStringEntity) request; + if (!entity.isEmpty()) { setEntity(entity); } - if (null != entity.getHeaders() ){ + if (null != entity.getHeaders()) { if (LOG.isDebugEnabled()) { LOG.debug("header : " + JSON.toJSONString(entity.getHeaders())); } - for (Header header : entity.getHeaders()){ + for (Header header : entity.getHeaders()) { addHeader(header); } } - } else if (request instanceof HttpEntity){ - setEntity((HttpEntity)request); + } else if (request instanceof HttpEntity) { + setEntity((HttpEntity) request); } else if (request instanceof Map) { String parameters = getMapToParameters((Map) request); if (LOG.isDebugEnabled()) { @@ -236,7 +263,7 @@ public class ClientHttpRequest extends HttpEntityEnclosingRequestBase impleme if (LOG.isDebugEnabled()) { LOG.debug("Parameter : " + request); } - StringEntity entity = new StringEntity((String) request, APPLICATION_FORM_URLENCODED_UTF_8); + StringEntity entity = new StringEntity((String) request, APPLICATION_FORM_URLENCODED_UTF_8); setEntity(entity); } else { String body = JSON.toJSONString(request); @@ -257,22 +284,25 @@ public class ClientHttpRequest extends HttpEntityEnclosingRequestBase impleme final StatusLine statusLine = response.getStatusLine(); final HttpEntity entity = response.getEntity(); + if (null == entity){ + return null; + } String[] value = null; - if (null == entity.getContentType()){ + if (null == entity.getContentType()) { value = new String[]{"application/x-www-form-urlencoded"}; - }else { + } else { value = entity.getContentType().getValue().split(";"); } //这里进行特殊处理,如果状态码非正常状态,但内容类型匹配至对应的结果也进行对应的响应类型转换 if (statusLine.getStatusCode() >= 300 && statusLine.getStatusCode() != 304) { - if (isJson(value[0], "") || isXml(value[0], "") ){ + if (isJson(value[0], "") || isXml(value[0], "")) { return toBean(entity, value); } EntityUtils.consume(entity); throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase()); } - if (null == responseType){ + if (null == responseType) { responseType = (Class) String.class; } @@ -283,12 +313,45 @@ public class ClientHttpRequest extends HttpEntityEnclosingRequestBase impleme /** * 对请求进行转化至对应的可转化类型 - * @param entity 响应实体 + * + * @param entity 响应实体 * @param contentType 内容类型编码数组,第一个值为内容类型,第二个值为编码类型 * @return 对应的响应对象 * @throws IOException 响应类型文本转换时抛出异常 */ private T toBean(HttpEntity entity, String[] contentType) throws IOException { + + + //是否为 输入流 + if (InputStream.class.isAssignableFrom(responseType)) { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + entity.writeTo(os); + return (T) new ByteArrayInputStream(os.toByteArray()); + } + //是否为 字节数数组 + if (byte[].class.isAssignableFrom(responseType)) { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + entity.writeTo(os); + return (T) os.toByteArray(); + } + //输出流 + if (OutputStream.class.isAssignableFrom(responseType)) { + try { + OutputStream t; + if (responseType == OutputStream.class){ + t= new ByteArrayOutputStream(); + }else { + t = (OutputStream) responseType.newInstance(); + } + entity.writeTo( t); + return (T) t; + } catch (InstantiationException e) { + throw new PayErrorException(new PayException("InstantiationException", e.getMessage())); + } catch (IllegalAccessException e) { + throw new PayErrorException(new PayException("IllegalAccessException", e.getMessage())); + } + } + //判断内容类型是否为文本类型 if (isText(contentType[0])) { /* String charset = "UTF-8"; @@ -298,7 +361,7 @@ public class ClientHttpRequest extends HttpEntityEnclosingRequestBase impleme //获取响应的文本内容 String result = EntityUtils.toString(entity, getDefaultCharset()); - if (LOG.isDebugEnabled()){ + if (LOG.isDebugEnabled()) { LOG.debug("请求响应内容:\r\n" + result); } if (responseType.isAssignableFrom(String.class)) { @@ -309,6 +372,9 @@ public class ClientHttpRequest extends HttpEntityEnclosingRequestBase impleme //json类型 if (isJson(contentType[0], first)) { try { + if (responseType.isAssignableFrom(JSONObject.class)) { + return (T)JSON.parseObject(result); + } return JSON.parseObject(result, responseType); } catch (JSONException e) { throw new PayErrorException(new PayException("failure", String.format("类型转化异常,contentType: %s\n%s", entity.getContentType().getValue(), e.getMessage()), result)); @@ -317,59 +383,50 @@ public class ClientHttpRequest extends HttpEntityEnclosingRequestBase impleme //xml类型 if (isXml(contentType[0], first)) { try { + if (responseType.isAssignableFrom(JSONObject.class)) { + return (T) XML.toJSONObject(result, getDefaultCharset()); + } return XML.toJSONObject(result, getDefaultCharset()).toJavaObject(responseType); - }catch (Exception e){ + } catch (Exception e) { ; } } throw new PayErrorException(new PayException("failure", "类型转化异常,contentType:" + entity.getContentType().getValue(), result)); } - //是否为 输入流 - if (InputStream.class.isAssignableFrom(responseType)) { - return (T) entity.getContent(); - } - //输出流 - if (OutputStream.class.isAssignableFrom(responseType)) { - try { - T t = responseType.newInstance(); - entity.writeTo((OutputStream) t); - return t; - } catch (InstantiationException e) { - throw new PayErrorException(new PayException("InstantiationException", e.getMessage())); - } catch (IllegalAccessException e) { - throw new PayErrorException(new PayException("IllegalAccessException", e.getMessage())); - } - } throw new PayErrorException(new PayException("failure", "类型转化异常,contentType:" + entity.getContentType().getValue())); } /** * 检测响应类型是否为json + * * @param contentType 内容类型 - * @param textFirst 文本第一个字符 + * @param textFirst 文本第一个字符 * @return 布尔型, true为json内容类型 */ - private boolean isJson(String contentType, String textFirst){ - return( ContentType.APPLICATION_JSON.getMimeType().equals(contentType) || "{[".indexOf(textFirst) >= 0 ); + private boolean isJson(String contentType, String textFirst) { + return (ContentType.APPLICATION_JSON.getMimeType().equals(contentType) || "{[".indexOf(textFirst) >= 0); } + /** * 检测响应类型是否为文本类型 + * * @param contentType 内容类型 * @return 布尔型, true为文本内容类型 */ - private boolean isText(String contentType){ - return contentType.contains("xml") || contentType.contains("json") || contentType.contains("text") || contentType.contains("form-data")|| contentType.contains("x-www-form-urlencoded"); + private boolean isText(String contentType) { + return contentType.contains("xml") || contentType.contains("json") || contentType.contains("text") || contentType.contains("form-data") || contentType.contains("x-www-form-urlencoded"); } /** * 检测响应类型是否为xml + * * @param contentType 内容类型 - * @param textFirst 文本第一个字符 + * @param textFirst 文本第一个字符 * @return 布尔型, true为xml内容类型 */ - private boolean isXml(String contentType, String textFirst){ - return( ContentType.APPLICATION_XML.getMimeType().equals(contentType) || "<".indexOf(textFirst) >= 0 ); + private boolean isXml(String contentType, String textFirst) { + return (ContentType.APPLICATION_XML.getMimeType().equals(contentType) || "<".indexOf(textFirst) >= 0); } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/http/HttpConfigStorage.java b/pay-java-common/src/main/java/com/egzosn/pay/common/http/HttpConfigStorage.java index f9380357a05907635c6bb90c0b998eda8c47c82a..f5cfc702b94f4220b793462dbb0d1b23041880a7 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/http/HttpConfigStorage.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/http/HttpConfigStorage.java @@ -1,11 +1,13 @@ package com.egzosn.pay.common.http; +import com.egzosn.pay.common.bean.CertStoreType; + import java.io.*; /** * HTTP 配置 - * @author: egan + * @author egan *
  * email egzosn@gmail.com
  * date 2017/3/3 20:48
@@ -29,10 +31,12 @@ public class HttpConfigStorage {
      */
     private String authPassword;
 
+
     /**
-     * @see #keystore 是否为https请求所需的证书(PKCS12)的地址,默认为地址,否则为证书信息串
+     * 证书存储类型
+     * @see #keystore 是否为https请求所需的证书(PKCS12)的地址,默认为地址,否则为证书信息串,文件流
      */
-    private boolean isPath = true;
+    private CertStoreType certStoreType = CertStoreType.PATH;
 
     /**
      * https请求所需的证书(PKCS12)
@@ -56,6 +60,10 @@ public class HttpConfigStorage {
      */
     private String charset;
 
+    private int socketTimeout = -1;
+
+    private int connectTimeout = -1;
+
     /**
      * http代理地址
      * @return http代理地址
@@ -104,109 +112,36 @@ public class HttpConfigStorage {
         this.authPassword = authPassword;
     }
 
-    /**
-     * 代理用户名
-     * @return 代理用户名
-     * @see #getAuthUsername()
-     */
-    @Deprecated
-    public String getHttpProxyUsername() {
-        return authUsername;
-    }
 
-    /**
-     * 设置代理用户名
-     * @param httpProxyUsername 代理用户名
-     *  @see #setAuthUsername(String)
-     */
-    @Deprecated
-    public void setHttpProxyUsername(String httpProxyUsername) {
-        this.authUsername = httpProxyUsername;
+    public CertStoreType getCertStoreType() {
+        return certStoreType;
     }
 
-    /**
-     *  代理密码
-     * @return 代理密码
-     * @see #getAuthPassword()
-     */
-    @Deprecated
-    public String getHttpProxyPassword() {
-        return authPassword;
-    }
-
-    /**
-     * 设置代理密码
-     * @param httpProxyPassword 代理密码
-     * @see #setAuthPassword(String)
-     */
-    @Deprecated
-    public void setHttpProxyPassword(String httpProxyPassword) {
-        this.authPassword = httpProxyPassword;
-    }
-
-    /**
-     * https请求所需的证书(PKCS12)地址,请使用绝对路径
-     * @return 证书(PKCS12)地址
-     * @see #getKeystore()
-     */
-    @Deprecated
-    public String getKeystorePath() {
-        return (String) keystore;
-    }
-
-    /**
-     * 设置https请求所需的证书(PKCS12)地址,请使用绝对路径
-     * @param keystorePath 证书(PKCS12)地址
-     * @see #getKeystore()
-     */
-    @Deprecated
-    public void setKeystorePath(String keystorePath) {
-        this.keystore = keystorePath;
-    }
-
-
-    /**
-     * 获取是否为证书地址
-     * @return  是否为证书地址,配合 {@link #getKeystore()}使用
-     */
-    public boolean isPath() {
-        return isPath;
-    }
-
-    /**
-     * 设置是否为证书地址
-     * @param path 是否为证书地址
-     */
-    public void setPath(boolean path) {
-        isPath = path;
+    public void setCertStoreType(CertStoreType certStoreType) {
+        this.certStoreType = certStoreType;
     }
 
     /**
      * 获取证书信息
-     * @return 证书信息 根据 {@link #isPath()}进行区别地址与信息串
+     * @return 证书信息 根据 {@link #getCertStoreType()}进行区别地址与信息串
+     * @throws IOException 找不到文件异常
      */
-    public InputStream getKeystoreInputStream() throws FileNotFoundException, UnsupportedEncodingException {
-        if (null == keystore){
+    public InputStream getKeystoreInputStream() throws IOException {
+        if (null == keystore) {
             return null;
         }
-        if(isPath()){
-            return new FileInputStream(new File(getKeystoreStr()));
-        }
-        if(this.keystore instanceof String){
-            return new ByteArrayInputStream(getKeystoreStr().getBytes("ISO-8859-1"));
-        }
-        return  (InputStream) keystore;
+        return certStoreType.getInputStream(keystore);
     }
     /**
      * 获取证书信息
-     * @return 证书信息 根据 {@link #isPath()}进行区别地址与信息串
+     * @return 证书信息 根据 {@link #getCertStoreType()}进行区别地址与信息串
      */
     public Object getKeystore() {
         return  keystore;
     }
     /**
      * 获取证书信息 证书地址
-     * @return 证书信息 根据 {@link #isPath()}进行区别地址与信息串
+     * @return 证书信息 根据 {@link #getCertStoreType()}进行区别地址与信息串
      */
     public String getKeystoreStr() {
         return (String) keystore;
@@ -262,4 +197,20 @@ public class HttpConfigStorage {
     public void setCharset(String charset) {
         this.charset = charset;
     }
+
+    public int getSocketTimeout() {
+        return socketTimeout;
+    }
+
+    public void setSocketTimeout(int socketTimeout) {
+        this.socketTimeout = socketTimeout;
+    }
+
+    public int getConnectTimeout() {
+        return connectTimeout;
+    }
+
+    public void setConnectTimeout(int connectTimeout) {
+        this.connectTimeout = connectTimeout;
+    }
 }
diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/http/HttpRequestTemplate.java b/pay-java-common/src/main/java/com/egzosn/pay/common/http/HttpRequestTemplate.java
index 2c5b071f880d15cbe9560a9ae2bcfd373b22a69c..ae0b000b1ca048736ff12e2404fa23e6afee3b4f 100644
--- a/pay-java-common/src/main/java/com/egzosn/pay/common/http/HttpRequestTemplate.java
+++ b/pay-java-common/src/main/java/com/egzosn/pay/common/http/HttpRequestTemplate.java
@@ -1,15 +1,22 @@
 package com.egzosn.pay.common.http;
 
-import com.egzosn.pay.common.bean.MethodType;
-import com.egzosn.pay.common.bean.result.PayException;
-import com.egzosn.pay.common.exception.PayErrorException;
-import com.egzosn.pay.common.util.str.StringUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.util.Map;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+
+import org.apache.http.Header;
 import org.apache.http.HttpHost;
 import org.apache.http.auth.AuthScope;
 import org.apache.http.auth.UsernamePasswordCredentials;
 import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.config.RequestConfig;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.config.Registry;
 import org.apache.http.config.RegistryBuilder;
@@ -22,28 +29,26 @@ import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
 import org.apache.http.ssl.SSLContexts;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.security.GeneralSecurityException;
-import java.security.KeyStore;
-import java.security.NoSuchAlgorithmException;
-import java.util.Map;
+import com.egzosn.pay.common.bean.MethodType;
+import com.egzosn.pay.common.bean.result.PayException;
+import com.egzosn.pay.common.exception.PayErrorException;
+import com.egzosn.pay.common.util.str.StringUtils;
 
 /**
  * http请求工具
- * @author: egan
- *  
+ *
+ * @author egan
+ * 
  * email egzosn@gmail.com 
* date 2017/3/3 21:33 - *
+ *
*/ public class HttpRequestTemplate { - protected final Log LOG = LogFactory.getLog(HttpRequestTemplate.class); + protected static final Logger LOG = LoggerFactory.getLogger(HttpRequestTemplate.class); protected CloseableHttpClient httpClient; @@ -51,9 +56,13 @@ public class HttpRequestTemplate { protected HttpHost httpProxy; - HttpConfigStorage configStorage; + protected HttpConfigStorage configStorage; + + private SSLConnectionSocketFactory sslsf; + /** - * 获取代理带代理地址的 HttpHost + * 获取代理带代理地址的 HttpHost + * * @return 获取代理带代理地址的 HttpHost */ public HttpHost getHttpProxy() { @@ -72,9 +81,10 @@ public class HttpRequestTemplate { .custom() //网络提供者 .setDefaultCredentialsProvider(createCredentialsProvider(configStorage)) + .setConnectionManager(connectionManager(configStorage)) //设置httpclient的SSLSocketFactory .setSSLSocketFactory(createSSL(configStorage)) - .setConnectionManager(connectionManager(configStorage)) + .setDefaultRequestConfig(createRequestConfig(configStorage)) .build(); if (null == connectionManager) { return this.httpClient = httpClient; @@ -84,8 +94,18 @@ public class HttpRequestTemplate { } + private RequestConfig createRequestConfig(HttpConfigStorage configStorage) { + RequestConfig requestConfig = RequestConfig.custom() + .setSocketTimeout(configStorage.getSocketTimeout()) + .setConnectTimeout(configStorage.getConnectTimeout()) +// .setConnectionRequestTimeout(1000) + .build(); + return requestConfig; + } + /** - * 初始化 + * 初始化 + * * @param configStorage 请求配置 */ public HttpRequestTemplate(HttpConfigStorage configStorage) { @@ -98,45 +118,51 @@ public class HttpRequestTemplate { /** - * 创建ssl配置 + * 创建ssl配置 + * * @param configStorage 请求配置 * @return SSLConnectionSocketFactory Layered socket factory for TLS/SSL connections. */ - public SSLConnectionSocketFactory createSSL( HttpConfigStorage configStorage){ - - if (null == configStorage.getKeystore()){ + public SSLConnectionSocketFactory createSSL(HttpConfigStorage configStorage) { + if (null != sslsf) { + return sslsf; + } + if (null == configStorage.getKeystore()) { try { - return new SSLConnectionSocketFactory(SSLContext.getDefault()); - } catch (NoSuchAlgorithmException e) { - LOG.error(e); + return sslsf = new SSLConnectionSocketFactory(SSLContext.getDefault()); + } + catch (NoSuchAlgorithmException e) { + LOG.error("", e); } } - //读取本机存放的PKCS12证书文件 - try(InputStream instream = configStorage.getKeystoreInputStream()){ - //指定读取证书格式为PKCS12 - KeyStore keyStore = KeyStore.getInstance("PKCS12"); - - char[] password = configStorage.getStorePassword().toCharArray(); - //指定PKCS12的密码 - keyStore.load(instream, password); - // 实例化密钥库 & 初始化密钥工厂 - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(keyStore, password); - // 创建 SSLContext - SSLContext sslcontext = SSLContexts.custom() - .loadKeyMaterial(keyStore, password).build(); - - //指定TLS版本 - SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( - sslcontext, new String[]{"TLSv1"}, null, - new DefaultHostnameVerifier()); - - return sslsf; - } catch (IOException e) { - LOG.error(e); - } catch (GeneralSecurityException e) { - LOG.error(e); + //读取本机存放的PKCS12证书文件 + try (InputStream instream = configStorage.getKeystoreInputStream()) { + //指定读取证书格式为PKCS12 + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + + char[] password = configStorage.getStorePassword().toCharArray(); + //指定PKCS12的密码 + keyStore.load(instream, password); + // 实例化密钥库 & 初始化密钥工厂 + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(keyStore, password); + // 创建 SSLContext + SSLContext sslcontext = SSLContexts.custom() + .loadKeyMaterial(keyStore, password).build(); + + //指定TLS版本 + sslsf = new SSLConnectionSocketFactory( + sslcontext, new String[]{"TLSv1", "TLSv1.2"}, null, + new DefaultHostnameVerifier()); + + return sslsf; + } + catch (IOException e) { + LOG.error("", e); + } + catch (GeneralSecurityException e) { + LOG.error("", e); } return null; @@ -144,10 +170,11 @@ public class HttpRequestTemplate { /** * 创建凭据提供程序 + * * @param configStorage 请求配置 * @return 凭据提供程序 */ - public CredentialsProvider createCredentialsProvider(HttpConfigStorage configStorage){ + public CredentialsProvider createCredentialsProvider(HttpConfigStorage configStorage) { if (StringUtils.isBlank(configStorage.getAuthUsername())) { @@ -157,7 +184,7 @@ public class HttpRequestTemplate { // 需要用户认证的代理服务器 CredentialsProvider credsProvider = new BasicCredentialsProvider(); credsProvider.setCredentials( - AuthScope.ANY, + AuthScope.ANY, new UsernamePasswordCredentials(configStorage.getAuthUsername(), configStorage.getAuthPassword())); @@ -166,20 +193,21 @@ public class HttpRequestTemplate { /** * 初始化连接池 + * * @param configStorage 配置 * @return 连接池对象 */ - public PoolingHttpClientConnectionManager connectionManager(HttpConfigStorage configStorage){ - if (null != connectionManager){ + public PoolingHttpClientConnectionManager connectionManager(HttpConfigStorage configStorage) { + if (null != connectionManager) { return connectionManager; } - if (0 == configStorage.getMaxTotal() || 0 == configStorage.getDefaultMaxPerRoute()){ + if (0 == configStorage.getMaxTotal() || 0 == configStorage.getDefaultMaxPerRoute()) { return null; } if (LOG.isInfoEnabled()) { LOG.info(String.format("Initialize the PoolingHttpClientConnectionManager -- maxTotal:%s, defaultMaxPerRoute:%s", configStorage.getMaxTotal(), configStorage.getDefaultMaxPerRoute())); } - Registry socketFactoryRegistry = RegistryBuilder. create() + Registry socketFactoryRegistry = RegistryBuilder.create() .register("https", createSSL(configStorage)) .register("http", new PlainConnectionSocketFactory()) .build(); @@ -201,26 +229,25 @@ public class HttpRequestTemplate { if (null != configStorage && StringUtils.isNotBlank(configStorage.getHttpProxyHost())) { //http代理地址设置 - httpProxy = new HttpHost(configStorage.getHttpProxyHost(),configStorage.getHttpProxyPort());; + httpProxy = new HttpHost(configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort()); + ; } return this; } - - /** - * * post - * @param uri 请求地址 - * @param request 请求参数 + * + * @param uri 请求地址 + * @param request 请求参数 * @param responseType 为响应类(需要自己依据响应格式来确定) * @param uriVariables 地址通配符对应的值 - * @param 响应类型 + * @param 响应类型 * @return 类型对象 */ - public T postForObject(String uri, Object request, Class responseType, Object... uriVariables){ + public T postForObject(String uri, Object request, Class responseType, Object... uriVariables) { return doExecute(URI.create(UriVariables.getUri(uri, uriVariables)), request, responseType, MethodType.POST); } @@ -228,7 +255,7 @@ public class HttpRequestTemplate { return doExecute(URI.create(UriVariables.getUri(uri, uriVariables)), request, responseType, MethodType.POST); } - public T postForObject(URI uri, Object request, Class responseType){ + public T postForObject(URI uri, Object request, Class responseType) { return doExecute(uri, request, responseType, MethodType.POST); } @@ -240,14 +267,13 @@ public class HttpRequestTemplate { * @param responseType 响应类型 * @param uriVariables 用于匹配表达式 * @param 响应类型 - * * @return 类型对象 - *

+ * * * getForObject("http://egan.in/pay/{id}/f/{type}", String.class, "1", "APP") * */ - public T getForObject(String uri, Class responseType, Object... uriVariables){ + public T getForObject(String uri, Class responseType, Object... uriVariables) { return doExecute(URI.create(UriVariables.getUri(uri, uriVariables)), null, responseType, MethodType.GET); } @@ -270,37 +296,75 @@ public class HttpRequestTemplate { * getForObject("http://egan.in/pay/{id}/f/{type}", String.class, uriVariables)
* */ - public T getForObject(String uri, Class responseType, Map uriVariables){ + public T getForObject(String uri, Class responseType, Map uriVariables) { return doExecute(URI.create(UriVariables.getUri(uri, uriVariables)), null, responseType, MethodType.GET); } /** * get 请求 - * @param uri 请求地址 - * @param header 请求头 + * + * @param uri 请求地址 + * @param header 请求头 + * @param responseType 响应类型 + * @param uriVariables 用于匹配表达式 + * @param 响应类型 + * @return 类型对象 + * + * + * getForObject("http://egan.in/pay/{id}/f/{type}", String.class, "1", "APP") + * + */ + public T getForObject(String uri, HttpHeader header, Class responseType, Object... uriVariables) { + + return getForObjectEntity(uri, header, responseType, uriVariables).getBody(); + } + + /** + * get 请求 + * + * @param uri 请求地址 + * @param header 请求头 + * @param responseType 响应类型 + * @param uriVariables 用于匹配表达式 + * @param 响应类型 + * @return 类型对象 + * + * + * getForObject("http://egan.in/pay/{id}/f/{type}", String.class, "1", "APP") + * + */ + public ResponseEntity getForObjectEntity(String uri, HttpHeader header, Class responseType, Object... uriVariables) { + + return doExecuteEntity(URI.create(UriVariables.getUri(uri, uriVariables)), header, responseType, MethodType.GET); + } + + /** + * get 请求 + * + * @param uri 请求地址 * @param responseType 响应类型 * @param uriVariables 用于匹配表达式 - * @param 响应类型 - * @return 类型对象 + * @param 响应类型 + * @return 类型对象 * * - * getForObject("http://egan.in/pay/{id}/f/{type}", String.class, "1", "APP") + * getForObject("http://egan.in/pay/{id}/f/{type}", String.class, "1", "APP") * */ - public T getForObject(String uri, HttpHeader header, Class responseType, Object... uriVariables){ + public ResponseEntity getForObjectEntity(String uri, Class responseType, Object... uriVariables) { - return doExecute(URI.create(UriVariables.getUri(uri, uriVariables)), header, responseType, MethodType.GET); + return doExecuteEntity(URI.create(UriVariables.getUri(uri, uriVariables)), null, responseType, MethodType.GET); } /** * get 请求 * * @param uri 请求地址 - * @param header 请求头 + * @param header 请求头 * @param responseType 响应类型 * @param uriVariables 用于匹配表达式 - * @param 响应类型 + * @param 响应类型 * @return 类型对象 * * Map<String, String> uriVariables = new HashMap<String, String>();
@@ -312,51 +376,110 @@ public class HttpRequestTemplate { * getForObject("http://egan.in/pay/{id}/f/{type}", String.class, uriVariables)
*
*/ - public T getForObject(String uri, HttpHeader header, Class responseType, Map uriVariables){ - return doExecute(URI.create(UriVariables.getUri(uri, uriVariables)), header, responseType, MethodType.GET); + public T getForObject(String uri, HttpHeader header, Class responseType, Map uriVariables) { + return getForObjectEntity(uri, header, responseType, uriVariables).getBody(); } + /** + * get 请求 + * + * @param uri 请求地址 + * @param header 请求头 + * @param responseType 响应类型 + * @param uriVariables 用于匹配表达式 + * @param 响应类型 + * @return 类型对象 + * + * Map<String, String> uriVariables = new HashMap<String, String>();
+ * + * uriVariables.put("id", "1");
+ * + * uriVariables.put("type", "APP");
+ * + * getForObject("http://egan.in/pay/{id}/f/{type}", String.class, uriVariables)
+ *
+ */ + public ResponseEntity getForObjectEntity(String uri, HttpHeader header, Class responseType, Map uriVariables) { + return doExecuteEntity(URI.create(UriVariables.getUri(uri, uriVariables)), header, responseType, MethodType.GET); + } + + + /** + * http 请求执行 + * + * @param uri 地址 + * @param request 请求数据 + * @param responseType 响应类型 + * @param method 请求方法 + * @param 响应类型 + * @return 类型对象 + */ + public T doExecute(URI uri, Object request, Class responseType, MethodType method) { + return doExecuteEntity(uri, request, responseType, method).getBody(); + + } /** * http 请求执行 - * @param uri 地址 - * @param request 请求数据 + * + * @param uri 地址 + * @param request 请求数据 * @param responseType 响应类型 - * @param method 请求方法 - * @param 响应类型 + * @param method 请求方法 + * @param 响应类型 * @return 类型对象 */ - public T doExecute(URI uri, Object request, Class responseType, MethodType method){ + public ResponseEntity doExecuteEntity(URI uri, Object request, Class responseType, MethodType method) { + if (LOG.isDebugEnabled()) { LOG.debug(String.format("uri:%s, httpMethod:%s ", uri, method.name())); } - ClientHttpRequest httpRequest = new ClientHttpRequest(uri ,method, request, null == configStorage ? null : configStorage.getCharset()); + ClientHttpRequest httpRequest = new ClientHttpRequest(uri, method, request, null == configStorage ? null : configStorage.getCharset()); //判断是否有代理设置 - if (null == httpProxy){ + if (null != httpProxy) { httpRequest.setProxy(httpProxy); } httpRequest.setResponseType(responseType); try (CloseableHttpResponse response = getHttpClient().execute(httpRequest)) { - return httpRequest.handleResponse(response); - }catch (IOException e){ - throw new PayErrorException(new PayException("IOException", e.getLocalizedMessage())); - }finally { + int statusCode = response.getStatusLine().getStatusCode(); + Header[] allHeaders = response.getAllHeaders(); + T body = httpRequest.handleResponse(response); + return new ResponseEntity<>(statusCode, allHeaders, body); + } + catch (IOException e) { + throw new PayErrorException(new PayException("IOException", e.getLocalizedMessage()), e); + } + finally { httpRequest.releaseConnection(); } - } /** * http 请求执行 - * @param uri 地址 - * @param request 请求数据 + * + * @param uri 地址 + * @param request 请求数据 * @param responseType 响应类型 - * @param method 请求方法 - * @param 响应类型 + * @param method 请求方法 + * @param 响应类型 + * @return 类型对象 + */ + public T doExecute(String uri, Object request, Class responseType, MethodType method) { + return doExecute(URI.create(uri), request, responseType, method); + } + + /** + * http 请求执行 + * + * @param uri 地址 + * @param request 请求数据 + * @param responseType 响应类型 + * @param method 请求方法 + * @param 响应类型 * @return 类型对象 */ - public T doExecute(String uri, Object request, Class responseType, MethodType method){ - return doExecute(URI.create(uri), request, responseType, method); + public ResponseEntity doExecuteEntity(String uri, Object request, Class responseType, MethodType method) { + return doExecuteEntity(URI.create(uri), request, responseType, method); } } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/http/HttpStringEntity.java b/pay-java-common/src/main/java/com/egzosn/pay/common/http/HttpStringEntity.java index 828c9bedfd784b5602c8f6a5c43252321c9dc8af..53e5062a415e2833ec349a1b76f4e1381a85bc45 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/http/HttpStringEntity.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/http/HttpStringEntity.java @@ -1,25 +1,30 @@ package com.egzosn.pay.common.http; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.nio.charset.UnsupportedCharsetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + import org.apache.http.Header; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.message.BasicHeader; -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; -import java.nio.charset.UnsupportedCharsetException; -import java.util.*; - import static com.egzosn.pay.common.http.UriVariables.getMapToParameters; +import com.egzosn.pay.common.util.str.StringUtils; + /** * 请求实体,包含请求头,内容类型,编码类型等 * * @author egan - *

-*               email egzosn@gmail.com
-*               date 2017/12/20
-*           
+ *
+ *               email egzosn@gmail.com
+ *               date 2017/12/20
+ *           
*/ public class HttpStringEntity extends StringEntity { /** @@ -42,12 +47,13 @@ public class HttpStringEntity extends StringEntity { public void requestIsEmpty(Map request) { - if (null == request || request.isEmpty()){ + if (null == request || request.isEmpty()) { this.isEmpty = true; } } + public void requestIsEmpty(String request) { - if (null == request || request.isEmpty()){ + if (StringUtils.isEmpty(request)) { this.isEmpty = true; } @@ -59,7 +65,6 @@ public class HttpStringEntity extends StringEntity { * * @param request 请求体 * @param headers 请求头 - * * @throws UnsupportedEncodingException 不支持默认的HTTP字符集 */ public HttpStringEntity(Map request, Header... headers) throws UnsupportedEncodingException { @@ -73,7 +78,6 @@ public class HttpStringEntity extends StringEntity { * * @param request 请求体 * @param headers 请求头 - * * @throws UnsupportedEncodingException 不支持默认的HTTP字符集 */ public HttpStringEntity(Map request, Map headers) throws UnsupportedEncodingException { @@ -119,7 +123,6 @@ public class HttpStringEntity extends StringEntity { * 构造器 * * @param request 请求体 - * * @throws UnsupportedEncodingException 不支持默认的HTTP字符集 */ public HttpStringEntity(Map request) throws UnsupportedEncodingException { @@ -132,7 +135,6 @@ public class HttpStringEntity extends StringEntity { * * @param request 请求体 * @param contentType 内容类型 - * * @throws UnsupportedCharsetException 不支持默认的HTTP字符集 */ public HttpStringEntity(String request, ContentType contentType) throws UnsupportedCharsetException { @@ -145,7 +147,6 @@ public class HttpStringEntity extends StringEntity { * * @param request 请求体 * @param charset 字符类型 - * * @throws UnsupportedCharsetException 不支持默认的HTTP字符集 */ public HttpStringEntity(String request, String charset) throws UnsupportedCharsetException { @@ -169,7 +170,6 @@ public class HttpStringEntity extends StringEntity { * * @param request 请求体 * @param headers 请求头 - * * @throws UnsupportedEncodingException 不支持默认的HTTP字符集 */ public HttpStringEntity(String request, Header... headers) throws UnsupportedEncodingException { @@ -185,7 +185,6 @@ public class HttpStringEntity extends StringEntity { * * @param request 请求体 * @param headers 请求头 - * * @throws UnsupportedEncodingException 不支持默认的HTTP字符集 */ public HttpStringEntity(String request, Map headers) throws UnsupportedEncodingException { @@ -237,6 +236,7 @@ public class HttpStringEntity extends StringEntity { addHeader(new BasicHeader(entry.getKey(), entry.getValue())); } } + /** * 设置请求头 * diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/http/ResponseEntity.java b/pay-java-common/src/main/java/com/egzosn/pay/common/http/ResponseEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..995bc677d23a18d8687b7c6c02dc1f9761355ced --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/http/ResponseEntity.java @@ -0,0 +1,33 @@ +package com.egzosn.pay.common.http; + +import org.apache.http.Header; + +/** + * 响应实体 + * @author Egan + * email egzosn@gmail.com + * date 2021/8/1 + */ +public class ResponseEntity { + private final int statusCode; + private final Header[] headers; + private final T body; + + public ResponseEntity(int statusCode, Header[] headers, T body) { + this.statusCode = statusCode; + this.headers = headers; + this.body = body; + } + + public int getStatusCode() { + return statusCode; + } + + public Header[] getHeaders() { + return headers; + } + + public T getBody() { + return body; + } +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/http/UriVariables.java b/pay-java-common/src/main/java/com/egzosn/pay/common/http/UriVariables.java index f183b68599d72a3f5cca1bc99eb3428bc5fa7972..5da5719e870476a8802dcd0799be77ed36352fe5 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/http/UriVariables.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/http/UriVariables.java @@ -1,44 +1,54 @@ package com.egzosn.pay.common.http; -import com.alibaba.fastjson.JSONObject; -import com.egzosn.pay.common.bean.result.PayException; -import com.egzosn.pay.common.exception.PayErrorException; - import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URLEncoder; import java.util.List; import java.util.Map; -import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.fastjson.JSONObject; +import com.egzosn.pay.common.bean.result.PayException; +import com.egzosn.pay.common.exception.PayErrorException; +import com.egzosn.pay.common.util.str.StringUtils; /** * URL表达式处理器 * - * @author: egan + * @author egan *
  * email egzosn@gmail.com
  * date 2017/3/5 10:07
  * 
*/ -public class UriVariables { +public final class UriVariables { + private static final Logger LOG = LoggerFactory.getLogger(UriVariables.class); + public static final String QUESTION = "?"; + + private UriVariables() { + } /** * 依次匹配 - * @param uri 匹配的uri,带代表式 + * + * @param uri 匹配的uri,带代表式 * @param uriVariables 匹配表达式的值 * @return 匹配完的url * - * System.out.println(getUri("http://egan.in/{a}/ba/{a1}?{bb}={a1}", "no1", "no2", "no3", "no4")); - * 结果 http://egan.in/no1/ba/no2?no3=no4 + * System.out.println(getUri("http://egan.in/{a}/ba/{a1}?{bb}={a1}", "no1", "no2", "no3", "no4")); + * 结果 http://egan.in/no1/ba/no2?no3=no4 * - * */ public static String getUri(String uri, Object... uriVariables) { - if (null == uriVariables){ + if (null == uriVariables) { return uri; } - for (Object variable : uriVariables){ - if (null == variable){ + for (Object variable : uriVariables) { + if (null == variable) { continue; } uri = uri.replaceFirst("\\{\\w+\\}", variable.toString()); @@ -47,29 +57,29 @@ public class UriVariables { } - /** * 匹配Map.key - * @param uri 匹配的uri,带代表式 + * + * @param uri 匹配的uri,带代表式 * @param uriVariables 匹配表达式的值 * @return 匹配完的url * - * Map<String, Object> uriVariable = new HashMap<String, Object>(); - * uriVariable.put("a", "no1"); - * uriVariable.put("a1", "no2"); - * uriVariable.put("bb", "no3"); - * System.out.println(getUri("http://egan.in/{a}/ba/{a1}?{bb}={a1}", uriVariable)); - * 结果 http://egan.in/no1/ba/no2?no3=no2 + * Map<String, Object> uriVariable = new HashMap<String, Object>(); + * uriVariable.put("a", "no1"); + * uriVariable.put("a1", "no2"); + * uriVariable.put("bb", "no3"); + * System.out.println(getUri("http://egan.in/{a}/ba/{a1}?{bb}={a1}", uriVariable)); + * 结果 http://egan.in/no1/ba/no2?no3=no2 * */ public static String getUri(String uri, Map uriVariables) { - if (null == uriVariables){ + if (null == uriVariables) { return uri; } for (Map.Entry entry : uriVariables.entrySet()) { Object uriVariable = entry.getValue(); - if (null == uriVariable){ + if (null == uriVariable) { continue; } @@ -79,15 +89,15 @@ public class UriVariables { } - /** * Map转化为对应得参数字符串 + * * @param pe 参数 * @return 参数字符串 */ - public static String getMapToParameters(Map pe){ + public static String getMapToParameters(Map pe) { StringBuilder builder = new StringBuilder(); - for (Map.Entry entry : (Set)pe.entrySet()) { + for (Map.Entry entry : pe.entrySet()) { Object o = entry.getValue(); if (null == o) { @@ -97,25 +107,21 @@ public class UriVariables { if (o instanceof List) { o = ((List) o).toArray(); } - try { - if (o instanceof Object[]) { - Object[] os = (Object[]) o; - String valueStr = ""; - for (int i = 0, len = os.length; i < len; i++) { - if (null == os[i]) { - continue; - } - String value = os[i].toString().trim(); - valueStr += (i == len - 1) ? value : value + ","; + if (o instanceof Object[]) { + Object[] os = (Object[]) o; + String valueStr = ""; + for (int i = 0, len = os.length; i < len; i++) { + if (null == os[i]) { + continue; } - builder.append(entry.getKey()).append("=").append(URLEncoder.encode(valueStr, "utf-8")).append("&"); - - continue; + String value = os[i].toString().trim(); + valueStr += (i == len - 1) ? value : value + ","; } - builder.append(entry.getKey()).append("=").append(URLEncoder.encode( entry.getValue().toString(), "utf-8")).append("&"); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); + builder.append(entry.getKey()).append("=").append(urlEncoder(valueStr)).append("&"); + continue; } + builder.append(entry.getKey()).append("=").append(urlEncoder(entry.getValue().toString())).append("&"); + } if (builder.length() > 1) { builder.deleteCharAt(builder.length() - 1); @@ -129,7 +135,7 @@ public class UriVariables { * @param str 需要解析的字符串 * @return 解析的结果map */ - public static JSONObject getParametersToMap (String str) { + public static JSONObject getParametersToMap(String str) { JSONObject map = new JSONObject(); int len = str.length(); @@ -140,7 +146,8 @@ public class UriVariables { boolean isOpen = false;//值里有嵌套 char openName = 0; if (len > 0) { - for (int i = 0; i < len; i++) {// 遍历整个带解析的字符串 + // 遍历整个带解析的字符串 + for (int i = 0; i < len; i++) { curChar = str.charAt(i);// 取当前字符 if (isKey) {// 如果当前生成的是key @@ -148,16 +155,19 @@ public class UriVariables { key = temp.toString(); temp.setLength(0); isKey = false; - } else { + } + else { temp.append(curChar); } - } else {// 如果当前生成的是value + } + else {// 如果当前生成的是value if (isOpen) { if (curChar == openName) { isOpen = false; } - } else {//如果没开启嵌套 + } + else {//如果没开启嵌套 if (curChar == '{') {//如果碰到,就开启嵌套 isOpen = true; openName = '}'; @@ -168,10 +178,11 @@ public class UriVariables { } } if (curChar == '&' && !isOpen) {// 如果读取到&分割符,同时这个分割符不是值域,这时将map里添加 - putKeyValueToMap(temp, isKey, key, map); + putKeyValueToMap(temp, false, key, map); temp.setLength(0); isKey = true; - } else { + } + else { temp.append(curChar); } } @@ -182,14 +193,15 @@ public class UriVariables { return map; } - private static void putKeyValueToMap (StringBuilder temp, boolean isKey, String key, Map map) { + private static void putKeyValueToMap(StringBuilder temp, boolean isKey, String key, Map map) { if (isKey) { key = temp.toString(); if (key.length() == 0) { throw new PayErrorException(new PayException("QString format illegal", "内容格式有误")); } map.put(key, ""); - } else { + } + else { if (key.length() == 0) { throw new PayErrorException(new PayException("QString format illegal", "内容格式有误")); } @@ -197,5 +209,42 @@ public class UriVariables { } } + public static String urlEncoder(String str) { + return urlEncoder(str, "utf-8"); + } + + public static String urlEncoder(String str, String enc) { + try { + return URLEncoder.encode(str, enc); + } + catch (UnsupportedEncodingException e) { + LOG.error("", e); + } + return str; + } + + /** + * 去除域名的标准url + * + * @param url url + * @return 去除域名的标准url + */ + public static String getCanonicalUrl(String url) { + if (StringUtils.isEmpty(url)) { + return url; + } + try { + URI uri = new URI(url); + String path = uri.getPath(); + String encodedQuery = uri.getQuery(); + if (StringUtils.isNotEmpty(encodedQuery)) { + path += QUESTION.concat(encodedQuery); + } + return path; + } + catch (URISyntaxException e) { + throw new PayErrorException(new PayException("failure", "去除域名的标准url失败"), e); + } + } } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/util/DateUtils.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/DateUtils.java index 530741e697be0727428cdb89e0e96cd1f2399bdf..cddec8bd7ca2cde7e82a3a10ab7a6d6a1608c604 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/util/DateUtils.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/util/DateUtils.java @@ -1,41 +1,52 @@ package com.egzosn.pay.common.util; -import org.apache.http.util.Args; - import java.lang.ref.SoftReference; +import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.*; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.TimeZone; + +import org.apache.http.util.Args; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * 日期转换运算工具 - * @author egan + * + * @author egan *
- * email egzosn@gmail.com
- * date 2018-11-21 16:43:20
- * 
+ * email egzosn@gmail.com + * date 2018-11-21 16:43:20 + *
*/ public final class DateUtils { + private static final Logger LOG = LoggerFactory.getLogger(DateUtils.class); + + private DateUtils() { + } static final class DateFormatHolder { - private static final ThreadLocal>> THREADLOCAL_FORMATS = new ThreadLocal(); + private static final ThreadLocal>> THREADLOCAL_FORMATS = new ThreadLocal>>(); DateFormatHolder() { } public static SimpleDateFormat formatFor(String pattern) { - SoftReference ref = (SoftReference)THREADLOCAL_FORMATS.get(); - Object formats = ref == null?null:(Map)ref.get(); - if(formats == null) { - formats = new HashMap(); + SoftReference> ref = THREADLOCAL_FORMATS.get(); + Map formats = ref == null ? null : ref.get(); + if (formats == null) { + formats = new HashMap(); THREADLOCAL_FORMATS.set(new SoftReference(formats)); } - SimpleDateFormat format = (SimpleDateFormat)((Map)formats).get(pattern); + SimpleDateFormat format = formats.get(pattern); - if(format == null) { + if (format == null) { format = new SimpleDateFormat(pattern); format.setTimeZone(TimeZone.getTimeZone("GMT+8")); - ((Map)formats).put(pattern, format); + ((Map) formats).put(pattern, format); } return format; @@ -46,51 +57,91 @@ public final class DateUtils { } } - public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; - public static final String YYYY_MM_DD = "yyyy-MM-dd"; + public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; + public static final String YYYY_MM_DD_T_HH_MM_SS_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"; + public static final String YYYY_MM_DD_T_HH_MM_SS_XX = "yyyy-MM-dd'T'HH:mm:ssXXX"; + public static final String YYYY_MM_DD = "yyyy-MM-dd"; public static final String YYYYMMDD = "yyyyMMdd"; public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; public static final String MMDD = "MMdd"; + public static final String YYYYMM = "yyyyMM"; + public static final String YYYY_MM = "yyyy-MM"; public static String formatDate(Date date, String pattern) { Args.notNull(date, "Date"); Args.notNull(pattern, "Pattern"); - SimpleDateFormat formatFor = DateFormatHolder.formatFor(pattern); + SimpleDateFormat formatFor = DateFormatHolder.formatFor(pattern); return formatFor.format(date); } - public static final String format(Date date){ + + public static Date parseDate(String date, String pattern) { + Args.notNull(date, "Date"); + Args.notNull(pattern, "Pattern"); + SimpleDateFormat formatFor = DateFormatHolder.formatFor(pattern); + try { + return formatFor.parse(date); + } + catch (ParseException e) { + LOG.error("", e); + } + return null; + } + + public static Date parse(String date) { + return parseDate(date, YYYY_MM_DD_HH_MM_SS); + } + + public static String format(Date date) { return formatDate(date, YYYY_MM_DD_HH_MM_SS); } - public static final String formatDay(Date date){ - return formatDate(date, YYYY_MM_DD); + public static Date parseDay(String date) { + return parseDate(date, YYYY_MM_DD); + } + + public static String formatDay(Date date) { + return formatDate(date, YYYY_MM_DD); } /** * 剩余分钟数 + * * @param date 结束点日期 * @return 分钟数 */ - public static final long minutesRemaining(Date date){ - return (date.getTime() - System.currentTimeMillis()) / 1000 / 60 ; + public static long minutesRemaining(Date date) { + return (date.getTime() / 1000 / 60 - DateUtils.toEpochSecond() / 60); } /** * 剩余小时 + * * @param date 结束点日期 * @return 小时数 */ - public static final long remainingHours(Date date){ - return minutesRemaining(date) / 60 ; + public static long remainingHours(Date date) { + return minutesRemaining(date) / 60; } + /** * 剩余天数 + * * @param date 结束点日期 * @return 天数 */ - public static final long remainingDays(Date date){ - return remainingHours(date) / 24 ; + public static long remainingDays(Date date) { + return remainingHours(date) / 24; } + /** + * 将此日期时间转换为从epoch开始的秒数 + * + * @return epoch开始的秒数 + */ + public static long toEpochSecond() { + return System.currentTimeMillis() / 1000; + } + + } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/util/IOUtils.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/IOUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..9b15a569b3f3f1a5668effab68926e76a30fa91b --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/util/IOUtils.java @@ -0,0 +1,2385 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.egzosn.pay.common.util; + +import org.apache.commons.codec.Charsets; + +import java.io.*; +import java.net.*; +import java.nio.channels.Selector; +import java.nio.charset.Charset; +import java.nio.charset.UnsupportedCharsetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * General IO stream manipulation utilities. + *

+ * This class provides static utility methods for input/output operations. + *

    + *
  • closeQuietly - these methods close a stream ignoring nulls and exceptions + *
  • toXxx/read - these methods read data from a stream + *
  • write - these methods write data to a stream + *
  • copy - these methods copy all the data from one stream to another + *
  • contentEquals - these methods compare the content of two streams + *
+ *

+ * The byte-to-char methods and char-to-byte methods involve a conversion step. + * Two methods are provided in each case, one that uses the platform default + * encoding and the other which allows you to specify an encoding. You are + * encouraged to always specify an encoding because relying on the platform + * default can lead to unexpected results, for example when moving from + * development to production. + *

+ * All the methods in this class that read a stream are buffered internally. + * This means that there is no cause to use a BufferedInputStream + * or BufferedReader. The default buffer size of 4K has been shown + * to be efficient in tests. + *

+ * Wherever possible, the methods in this class do not flush or close + * the stream. This is to avoid making non-portable assumptions about the + * streams' origin and further use. Thus the caller is still responsible for + * closing streams after use. + *

+ * Origin of code: Excalibur. + * + * @version $Id: IOUtils.java 1326636 2012-04-16 14:54:53Z ggregory $ + */ +public class IOUtils { + // NOTE: This class is focussed on InputStream, OutputStream, Reader and + // Writer. Each method should take at least one of these as a parameter, + // or return one of them. + + private static final int EOF = -1; + /** + * The system line separator string. + */ + public static final String LINE_SEPARATOR; + + static { + // avoid security issues + StringWriter buf = new StringWriter(4); + PrintWriter out = new PrintWriter(buf); + out.println(); + LINE_SEPARATOR = buf.toString(); + out.close(); + } + + /** + * The default buffer size ({@value}) to use for + * {@link #copyLarge(InputStream, OutputStream)} + * and + * {@link #copyLarge(Reader, Writer)} + */ + private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; + + /** + * The default buffer size to use for the skip() methods. + */ + private static final int SKIP_BUFFER_SIZE = 2048; + + // Allocated in the relevant skip method if necessary. + /* + * N.B. no need to synchronize these because: + * - we don't care if the buffer is created multiple times (the data is ignored) + * - we always use the same size buffer, so if it it is recreated it will still be OK + * (if the buffer size were variable, we would need to synch. to ensure some other thread + * did not create a smaller one) + */ + private static char[] SKIP_CHAR_BUFFER; + private static byte[] SKIP_BYTE_BUFFER; + + /** + * Instances should NOT be constructed in standard programming. + */ + public IOUtils() { + super(); + } + + //----------------------------------------------------------------------- + + /** + * Closes a URLConnection. + * + * @param conn the connection to close. + * @since 2.4 + */ + public static void close(URLConnection conn) { + if (conn instanceof HttpURLConnection) { + ((HttpURLConnection) conn).disconnect(); + } + } + + /** + * Unconditionally close an Reader. + *

+ * Equivalent to {@link Reader#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

+ * Example code: + *

+     *   char[] data = new char[1024];
+     *   Reader in = null;
+     *   try {
+     *       in = new FileReader("foo.txt");
+     *       in.read(data);
+     *       in.close(); //close errors are handled
+     *   } catch (Exception e) {
+     *       // error handling
+     *   } finally {
+     *       IOUtils.closeQuietly(in);
+     *   }
+     * 
+ * + * @param input the Reader to close, may be null or already closed + */ + public static void closeQuietly(Reader input) { + closeQuietly((Closeable) input); + } + + /** + * Unconditionally close a Writer. + *

+ * Equivalent to {@link Writer#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

+ * Example code: + *

+     *   Writer out = null;
+     *   try {
+     *       out = new StringWriter();
+     *       out.write("Hello World");
+     *       out.close(); //close errors are handled
+     *   } catch (Exception e) {
+     *       // error handling
+     *   } finally {
+     *       IOUtils.closeQuietly(out);
+     *   }
+     * 
+ * + * @param output the Writer to close, may be null or already closed + */ + public static void closeQuietly(Writer output) { + closeQuietly((Closeable) output); + } + + /** + * Unconditionally close an InputStream. + *

+ * Equivalent to {@link InputStream#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

+ * Example code: + *

+     *   byte[] data = new byte[1024];
+     *   InputStream in = null;
+     *   try {
+     *       in = new FileInputStream("foo.txt");
+     *       in.read(data);
+     *       in.close(); //close errors are handled
+     *   } catch (Exception e) {
+     *       // error handling
+     *   } finally {
+     *       IOUtils.closeQuietly(in);
+     *   }
+     * 
+ * + * @param input the InputStream to close, may be null or already closed + */ + public static void closeQuietly(InputStream input) { + closeQuietly((Closeable) input); + } + + /** + * Unconditionally close an OutputStream. + *

+ * Equivalent to {@link OutputStream#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

+ * Example code: + *

+     * byte[] data = "Hello, World".getBytes();
+     *
+     * OutputStream out = null;
+     * try {
+     *     out = new FileOutputStream("foo.txt");
+     *     out.write(data);
+     *     out.close(); //close errors are handled
+     * } catch (IOException e) {
+     *     // error handling
+     * } finally {
+     *     IOUtils.closeQuietly(out);
+     * }
+     * 
+ * + * @param output the OutputStream to close, may be null or already closed + */ + public static void closeQuietly(OutputStream output) { + closeQuietly((Closeable) output); + } + + /** + * Unconditionally close a Closeable. + *

+ * Equivalent to {@link Closeable#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

+ * Example code: + *

+     *   Closeable closeable = null;
+     *   try {
+     *       closeable = new FileReader("foo.txt");
+     *       // process closeable
+     *       closeable.close();
+     *   } catch (Exception e) {
+     *       // error handling
+     *   } finally {
+     *       IOUtils.closeQuietly(closeable);
+     *   }
+     * 
+ * + * @param closeable the object to close, may be null or already closed + * @since 2.0 + */ + public static void closeQuietly(Closeable closeable) { + try { + if (closeable != null) { + closeable.close(); + } + } catch (IOException ioe) { + // ignore + } + } + + /** + * Unconditionally close a Socket. + *

+ * Equivalent to {@link Socket#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

+ * Example code: + *

+     *   Socket socket = null;
+     *   try {
+     *       socket = new Socket("http://www.foo.com/", 80);
+     *       // process socket
+     *       socket.close();
+     *   } catch (Exception e) {
+     *       // error handling
+     *   } finally {
+     *       IOUtils.closeQuietly(socket);
+     *   }
+     * 
+ * + * @param sock the Socket to close, may be null or already closed + * @since 2.0 + */ + public static void closeQuietly(Socket sock) { + if (sock != null) { + try { + sock.close(); + } catch (IOException ioe) { + // ignored + } + } + } + + /** + * Unconditionally close a Selector. + *

+ * Equivalent to {@link Selector#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

+ * Example code: + *

+     *   Selector selector = null;
+     *   try {
+     *       selector = Selector.open();
+     *       // process socket
+     *
+     *   } catch (Exception e) {
+     *       // error handling
+     *   } finally {
+     *       IOUtils.closeQuietly(selector);
+     *   }
+     * 
+ * + * @param selector the Selector to close, may be null or already closed + * @since 2.2 + */ + public static void closeQuietly(Selector selector) { + if (selector != null) { + try { + selector.close(); + } catch (IOException ioe) { + // ignored + } + } + } + + /** + * Unconditionally close a ServerSocket. + *

+ * Equivalent to {@link ServerSocket#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

+ * Example code: + *

+     *   ServerSocket socket = null;
+     *   try {
+     *       socket = new ServerSocket();
+     *       // process socket
+     *       socket.close();
+     *   } catch (Exception e) {
+     *       // error handling
+     *   } finally {
+     *       IOUtils.closeQuietly(socket);
+     *   }
+     * 
+ * + * @param sock the ServerSocket to close, may be null or already closed + * @since 2.2 + */ + public static void closeQuietly(ServerSocket sock) { + if (sock != null) { + try { + sock.close(); + } catch (IOException ioe) { + // ignored + } + } + } + + /** + * Fetches entire contents of an InputStream and represent + * same data as result InputStream. + *

+ * This method is useful where, + *

    + *
  • Source InputStream is slow.
  • + *
  • It has network resources associated, so we cannot keep it open for + * long time.
  • + *
  • It has network timeout associated.
  • + *
+ * It can be used in favor of {@link #toByteArray(InputStream)}, since it + * avoids unnecessary allocation and copy of byte[].
+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input Stream to be fully buffered. + * @return A fully buffered stream. + * @since 2.0 + */ + public static InputStream toBufferedInputStream(InputStream input) { + return new BufferedInputStream(input); + } + + /** + * Returns the given reader if it is a {@link BufferedReader}, otherwise creates a toBufferedReader for the given + * reader. + * + * @param reader the reader to wrap or return + * @return the given reader or a new {@link BufferedReader} for the given reader + * @since 2.2 + */ + public static BufferedReader toBufferedReader(Reader reader) { + return reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader); + } + + // read toByteArray + //----------------------------------------------------------------------- + + /** + * Get the contents of an InputStream as a byte[]. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + */ + public static byte[] toByteArray(InputStream input) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + copy(input, output); + return output.toByteArray(); + } + + /** + * Get contents of an InputStream as a byte[]. + * Use this method instead of toByteArray(InputStream) + * when InputStream size is known. + * NOTE: the method checks that the length can safely be cast to an int without truncation + * before using {@link IOUtils#toByteArray(java.io.InputStream, int)} to read into the byte array. + * (Arrays can have no more than Integer.MAX_VALUE entries anyway) + * + * @param input the InputStream to read from + * @param size the size of InputStream + * @return the requested byte array + * @throws IOException if an I/O error occurs or InputStream size differ from parameter size + * @throws IllegalArgumentException if size is less than zero or size is greater than Integer.MAX_VALUE + * @see IOUtils#toByteArray(java.io.InputStream, int) + * @since 2.1 + */ + public static byte[] toByteArray(InputStream input, long size) throws IOException { + + if (size > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Size cannot be greater than Integer max value: " + size); + } + + return toByteArray(input, (int) size); + } + + /** + * Get the contents of an InputStream as a byte[]. + * Use this method instead of toByteArray(InputStream) + * when InputStream size is known + * + * @param input the InputStream to read from + * @param size the size of InputStream + * @return the requested byte array + * @throws IOException if an I/O error occurs or InputStream size differ from parameter size + * @throws IllegalArgumentException if size is less than zero + * @since 2.1 + */ + public static byte[] toByteArray(InputStream input, int size) throws IOException { + + if (size < 0) { + throw new IllegalArgumentException("Size must be equal or greater than zero: " + size); + } + + if (size == 0) { + return new byte[0]; + } + + byte[] data = new byte[size]; + int offset = 0; + int readed; + + while (offset < size && (readed = input.read(data, offset, size - offset)) != EOF) { + offset += readed; + } + + if (offset != size) { + throw new IOException("Unexpected readed size. current: " + offset + ", excepted: " + size); + } + + return data; + } + + /** + * Get the contents of a Reader as a byte[] + * using the default character encoding of the platform. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + * + * @param input the Reader to read from + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + */ + public static byte[] toByteArray(Reader input) throws IOException { + return toByteArray(input, Charset.defaultCharset()); + } + + /** + * Get the contents of a Reader as a byte[] + * using the specified character encoding. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + * + * @param input the Reader to read from + * @param encoding the encoding to use, null means platform default + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + * @since 2.3 + */ + public static byte[] toByteArray(Reader input, Charset encoding) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + copy(input, output, encoding); + return output.toByteArray(); + } + + /** + * Get the contents of a Reader as a byte[] + * using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + * + * @param input the Reader to read from + * @param encoding the encoding to use, null means platform default + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + * @throws UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static byte[] toByteArray(Reader input, String encoding) throws IOException { + return toByteArray(input, Charsets.toCharset(encoding)); + } + + /** + * Get the contents of a String as a byte[] + * using the default character encoding of the platform. + *

+ * This is the same as {@link String#getBytes()}. + * + * @param input the String to convert + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs (never occurs) + * @deprecated Use {@link String#getBytes()} + */ + @Deprecated + public static byte[] toByteArray(String input) throws IOException { + return input.getBytes(); + } + + /** + * Get the contents of a URI as a byte[]. + * + * @param uri the URI to read + * @return the requested byte array + * @throws NullPointerException if the uri is null + * @throws IOException if an I/O exception occurs + * @since 2.4 + */ + public static byte[] toByteArray(URI uri) throws IOException { + return IOUtils.toByteArray(uri.toURL()); + } + + /** + * Get the contents of a URL as a byte[]. + * + * @param url the URL to read + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O exception occurs + * @since 2.4 + */ + public static byte[] toByteArray(URL url) throws IOException { + URLConnection conn = url.openConnection(); + try { + return IOUtils.toByteArray(conn); + } finally { + close(conn); + } + } + + /** + * Get the contents of a URLConnection as a byte[]. + * + * @param urlConn the URLConnection to read + * @return the requested byte array + * @throws NullPointerException if the urlConn is null + * @throws IOException if an I/O exception occurs + * @since 2.4 + */ + public static byte[] toByteArray(URLConnection urlConn) throws IOException { + InputStream inputStream = urlConn.getInputStream(); + try { + return IOUtils.toByteArray(inputStream); + } finally { + inputStream.close(); + } + } + + // read char[] + //----------------------------------------------------------------------- + + /** + * Get the contents of an InputStream as a character array + * using the default character encoding of the platform. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param is the InputStream to read from + * @return the requested character array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + * @since 1.1 + */ + public static char[] toCharArray(InputStream is) throws IOException { + return toCharArray(is, Charset.defaultCharset()); + } + + /** + * Get the contents of an InputStream as a character array + * using the specified character encoding. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param is the InputStream to read from + * @param encoding the encoding to use, null means platform default + * @return the requested character array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + * @since 2.3 + */ + public static char[] toCharArray(InputStream is, Charset encoding) + throws IOException { + CharArrayWriter output = new CharArrayWriter(); + copy(is, output, encoding); + return output.toCharArray(); + } + + /** + * Get the contents of an InputStream as a character array + * using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param is the InputStream to read from + * @param encoding the encoding to use, null means platform default + * @return the requested character array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + * @throws UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static char[] toCharArray(InputStream is, String encoding) throws IOException { + return toCharArray(is, Charsets.toCharset(encoding)); + } + + /** + * Get the contents of a Reader as a character array. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + * + * @param input the Reader to read from + * @return the requested character array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + * @since 1.1 + */ + public static char[] toCharArray(Reader input) throws IOException { + CharArrayWriter sw = new CharArrayWriter(); + copy(input, sw); + return sw.toCharArray(); + } + + // read toString + //----------------------------------------------------------------------- + + /** + * Get the contents of an InputStream as a String + * using the default character encoding of the platform. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from + * @return the requested String + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + */ + public static String toString(InputStream input) throws IOException { + return toString(input, Charset.defaultCharset()); + } + + /** + * Get the contents of an InputStream as a String + * using the specified character encoding. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

+ * + * @param input the InputStream to read from + * @param encoding the encoding to use, null means platform default + * @return the requested String + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + * @since 2.3 + */ + public static String toString(InputStream input, Charset encoding) throws IOException { + StringWriter sw = new StringWriter(); + copy(input, sw, encoding); + return sw.toString(); + } + + /** + * Get the contents of an InputStream as a String + * using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from + * @param encoding the encoding to use, null means platform default + * @return the requested String + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + * @throws UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + */ + public static String toString(InputStream input, String encoding) + throws IOException { + return toString(input, Charsets.toCharset(encoding)); + } + + /** + * Get the contents of a Reader as a String. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + * + * @param input the Reader to read from + * @return the requested String + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + */ + public static String toString(Reader input) throws IOException { + StringWriter sw = new StringWriter(); + copy(input, sw); + return sw.toString(); + } + + /** + * Gets the contents at the given URI. + * + * @param uri The URI source. + * @return The contents of the URL as a String. + * @throws IOException if an I/O exception occurs. + * @since 2.1 + */ + public static String toString(URI uri) throws IOException { + return toString(uri, Charset.defaultCharset()); + } + + /** + * Gets the contents at the given URI. + * + * @param uri The URI source. + * @param encoding The encoding name for the URL contents. + * @return The contents of the URL as a String. + * @throws IOException if an I/O exception occurs. + * @since 2.3. + */ + public static String toString(URI uri, Charset encoding) throws IOException { + return toString(uri.toURL(), Charsets.toCharset(encoding)); + } + + /** + * Gets the contents at the given URI. + * + * @param uri The URI source. + * @param encoding The encoding name for the URL contents. + * @return The contents of the URL as a String. + * @throws IOException if an I/O exception occurs. + * @throws UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 2.1 + */ + public static String toString(URI uri, String encoding) throws IOException { + return toString(uri, Charsets.toCharset(encoding)); + } + + /** + * Gets the contents at the given URL. + * + * @param url The URL source. + * @return The contents of the URL as a String. + * @throws IOException if an I/O exception occurs. + * @since 2.1 + */ + public static String toString(URL url) throws IOException { + return toString(url, Charset.defaultCharset()); + } + + /** + * Gets the contents at the given URL. + * + * @param url The URL source. + * @param encoding The encoding name for the URL contents. + * @return The contents of the URL as a String. + * @throws IOException if an I/O exception occurs. + * @since 2.3 + */ + public static String toString(URL url, Charset encoding) throws IOException { + InputStream inputStream = url.openStream(); + try { + return toString(inputStream, encoding); + } finally { + inputStream.close(); + } + } + + /** + * Gets the contents at the given URL. + * + * @param url The URL source. + * @param encoding The encoding name for the URL contents. + * @return The contents of the URL as a String. + * @throws IOException if an I/O exception occurs. + * @throws UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 2.1 + */ + public static String toString(URL url, String encoding) throws IOException { + return toString(url, Charsets.toCharset(encoding)); + } + + /** + * Get the contents of a byte[] as a String + * using the default character encoding of the platform. + * + * @param input the byte array to read from + * @return the requested String + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs (never occurs) + * @deprecated Use {@link String#String(byte[])} + */ + @Deprecated + public static String toString(byte[] input) throws IOException { + return new String(input); + } + + /** + * Get the contents of a byte[] as a String + * using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + * + * @param input the byte array to read from + * @param encoding the encoding to use, null means platform default + * @return the requested String + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs (never occurs) + */ + public static String toString(byte[] input, String encoding) throws IOException { + return new String(input, Charsets.toCharset(encoding)); + } + + // readLines + //----------------------------------------------------------------------- + + /** + * Get the contents of an InputStream as a list of Strings, + * one entry per line, using the default character encoding of the platform. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from, not null + * @return the list of Strings, never null + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + * @since 1.1 + */ + public static List readLines(InputStream input) throws IOException { + return readLines(input, Charset.defaultCharset()); + } + + /** + * Get the contents of an InputStream as a list of Strings, + * one entry per line, using the specified character encoding. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from, not null + * @param encoding the encoding to use, null means platform default + * @return the list of Strings, never null + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + * @since 2.3 + */ + public static List readLines(InputStream input, Charset encoding) throws IOException { + InputStreamReader reader = new InputStreamReader(input, Charsets.toCharset(encoding)); + return readLines(reader); + } + + /** + * Get the contents of an InputStream as a list of Strings, + * one entry per line, using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from, not null + * @param encoding the encoding to use, null means platform default + * @return the list of Strings, never null + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + * @throws UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static List readLines(InputStream input, String encoding) throws IOException { + return readLines(input, Charsets.toCharset(encoding)); + } + + /** + * Get the contents of a Reader as a list of Strings, + * one entry per line. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + * + * @param input the Reader to read from, not null + * @return the list of Strings, never null + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + * @since 1.1 + */ + public static List readLines(Reader input) throws IOException { + BufferedReader reader = toBufferedReader(input); + List list = new ArrayList(); + String line = reader.readLine(); + while (line != null) { + list.add(line); + line = reader.readLine(); + } + return list; + } + + + //----------------------------------------------------------------------- + + /** + * Convert the specified CharSequence to an input stream, encoded as bytes + * using the default character encoding of the platform. + * + * @param input the CharSequence to convert + * @return an input stream + * @since 2.0 + */ + public static InputStream toInputStream(CharSequence input) { + return toInputStream(input, Charset.defaultCharset()); + } + + /** + * Convert the specified CharSequence to an input stream, encoded as bytes + * using the specified character encoding. + * + * @param input the CharSequence to convert + * @param encoding the encoding to use, null means platform default + * @return an input stream + * @since 2.3 + */ + public static InputStream toInputStream(CharSequence input, Charset encoding) { + return toInputStream(input.toString(), encoding); + } + + /** + * Convert the specified CharSequence to an input stream, encoded as bytes + * using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + * + * @param input the CharSequence to convert + * @param encoding the encoding to use, null means platform default + * @return an input stream + * @throws IOException if the encoding is invalid + * @throws UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 2.0 + */ + public static InputStream toInputStream(CharSequence input, String encoding) throws IOException { + return toInputStream(input, Charsets.toCharset(encoding)); + } + + //----------------------------------------------------------------------- + + /** + * Convert the specified string to an input stream, encoded as bytes + * using the default character encoding of the platform. + * + * @param input the string to convert + * @return an input stream + * @since 1.1 + */ + public static InputStream toInputStream(String input) { + return toInputStream(input, Charset.defaultCharset()); + } + + /** + * Convert the specified string to an input stream, encoded as bytes + * using the specified character encoding. + * + * @param input the string to convert + * @param encoding the encoding to use, null means platform default + * @return an input stream + * @since 2.3 + */ + public static InputStream toInputStream(String input, Charset encoding) { + return new ByteArrayInputStream(input.getBytes(Charsets.toCharset(encoding))); + } + + /** + * Convert the specified string to an input stream, encoded as bytes + * using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + * + * @param input the string to convert + * @param encoding the encoding to use, null means platform default + * @return an input stream + * @throws IOException if the encoding is invalid + * @throws UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static InputStream toInputStream(String input, String encoding) throws IOException { + byte[] bytes = input.getBytes(Charsets.toCharset(encoding)); + return new ByteArrayInputStream(bytes); + } + + // write byte[] + //----------------------------------------------------------------------- + + /** + * Writes bytes from a byte[] to an OutputStream. + * + * @param data the byte array to write, do not modify during output, + * null ignored + * @param output the OutputStream to write to + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since 1.1 + */ + public static void write(byte[] data, OutputStream output) + throws IOException { + if (data != null) { + output.write(data); + } + } + + /** + * Writes bytes from a byte[] to chars on a Writer + * using the default character encoding of the platform. + *

+ * This method uses {@link String#String(byte[])}. + * + * @param data the byte array to write, do not modify during output, + * null ignored + * @param output the Writer to write to + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since 1.1 + */ + public static void write(byte[] data, Writer output) throws IOException { + write(data, output, Charset.defaultCharset()); + } + + /** + * Writes bytes from a byte[] to chars on a Writer + * using the specified character encoding. + *

+ * This method uses {@link String#String(byte[], String)}. + * + * @param data the byte array to write, do not modify during output, + * null ignored + * @param output the Writer to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since 2.3 + */ + public static void write(byte[] data, Writer output, Charset encoding) throws IOException { + if (data != null) { + output.write(new String(data, Charsets.toCharset(encoding))); + } + } + + /** + * Writes bytes from a byte[] to chars on a Writer + * using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + *

+ * This method uses {@link String#String(byte[], String)}. + * + * @param data the byte array to write, do not modify during output, + * null ignored + * @param output the Writer to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @throws UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static void write(byte[] data, Writer output, String encoding) throws IOException { + write(data, output, Charsets.toCharset(encoding)); + } + + // write char[] + //----------------------------------------------------------------------- + + /** + * Writes chars from a char[] to a Writer + * using the default character encoding of the platform. + * + * @param data the char array to write, do not modify during output, + * null ignored + * @param output the Writer to write to + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since 1.1 + */ + public static void write(char[] data, Writer output) throws IOException { + if (data != null) { + output.write(data); + } + } + + /** + * Writes chars from a char[] to bytes on an + * OutputStream. + *

+ * This method uses {@link String#String(char[])} and + * {@link String#getBytes()}. + * + * @param data the char array to write, do not modify during output, + * null ignored + * @param output the OutputStream to write to + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since 1.1 + */ + public static void write(char[] data, OutputStream output) + throws IOException { + write(data, output, Charset.defaultCharset()); + } + + /** + * Writes chars from a char[] to bytes on an + * OutputStream using the specified character encoding. + *

+ * This method uses {@link String#String(char[])} and + * {@link String#getBytes(String)}. + * + * @param data the char array to write, do not modify during output, + * null ignored + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since 2.3 + */ + public static void write(char[] data, OutputStream output, Charset encoding) throws IOException { + if (data != null) { + output.write(new String(data).getBytes(Charsets.toCharset(encoding))); + } + } + + /** + * Writes chars from a char[] to bytes on an + * OutputStream using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + *

+ * This method uses {@link String#String(char[])} and + * {@link String#getBytes(String)}. + * + * @param data the char array to write, do not modify during output, + * null ignored + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @throws UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static void write(char[] data, OutputStream output, String encoding) + throws IOException { + write(data, output, Charsets.toCharset(encoding)); + } + + // write CharSequence + //----------------------------------------------------------------------- + + /** + * Writes chars from a CharSequence to a Writer. + * + * @param data the CharSequence to write, null ignored + * @param output the Writer to write to + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since 2.0 + */ + public static void write(CharSequence data, Writer output) throws IOException { + if (data != null) { + write(data.toString(), output); + } + } + + /** + * Writes chars from a CharSequence to bytes on an + * OutputStream using the default character encoding of the + * platform. + *

+ * This method uses {@link String#getBytes()}. + * + * @param data the CharSequence to write, null ignored + * @param output the OutputStream to write to + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since 2.0 + */ + public static void write(CharSequence data, OutputStream output) + throws IOException { + write(data, output, Charset.defaultCharset()); + } + + /** + * Writes chars from a CharSequence to bytes on an + * OutputStream using the specified character encoding. + *

+ * This method uses {@link String#getBytes(String)}. + * + * @param data the CharSequence to write, null ignored + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since 2.3 + */ + public static void write(CharSequence data, OutputStream output, Charset encoding) throws IOException { + if (data != null) { + write(data.toString(), output, encoding); + } + } + + /** + * Writes chars from a CharSequence to bytes on an + * OutputStream using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + *

+ * This method uses {@link String#getBytes(String)}. + * + * @param data the CharSequence to write, null ignored + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @throws UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 2.0 + */ + public static void write(CharSequence data, OutputStream output, String encoding) throws IOException { + write(data, output, Charsets.toCharset(encoding)); + } + + // write String + //----------------------------------------------------------------------- + + /** + * Writes chars from a String to a Writer. + * + * @param data the String to write, null ignored + * @param output the Writer to write to + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since 1.1 + */ + public static void write(String data, Writer output) throws IOException { + if (data != null) { + output.write(data); + } + } + + /** + * Writes chars from a String to bytes on an + * OutputStream using the default character encoding of the + * platform. + *

+ * This method uses {@link String#getBytes()}. + * + * @param data the String to write, null ignored + * @param output the OutputStream to write to + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since 1.1 + */ + public static void write(String data, OutputStream output) + throws IOException { + write(data, output, Charset.defaultCharset()); + } + + /** + * Writes chars from a String to bytes on an + * OutputStream using the specified character encoding. + *

+ * This method uses {@link String#getBytes(String)}. + * + * @param data the String to write, null ignored + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since 2.3 + */ + public static void write(String data, OutputStream output, Charset encoding) throws IOException { + if (data != null) { + output.write(data.getBytes(Charsets.toCharset(encoding))); + } + } + + /** + * Writes chars from a String to bytes on an + * OutputStream using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + *

+ * This method uses {@link String#getBytes(String)}. + * + * @param data the String to write, null ignored + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @throws UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static void write(String data, OutputStream output, String encoding) + throws IOException { + write(data, output, Charsets.toCharset(encoding)); + } + + // write StringBuffer + //----------------------------------------------------------------------- + + /** + * Writes chars from a StringBuffer to a Writer. + * + * @param data the StringBuffer to write, null ignored + * @param output the Writer to write to + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since 1.1 + * @deprecated replaced by write(CharSequence, Writer) + */ + @Deprecated + public static void write(StringBuffer data, Writer output) + throws IOException { + if (data != null) { + output.write(data.toString()); + } + } + + /** + * Writes chars from a StringBuffer to bytes on an + * OutputStream using the default character encoding of the + * platform. + *

+ * This method uses {@link String#getBytes()}. + * + * @param data the StringBuffer to write, null ignored + * @param output the OutputStream to write to + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @since 1.1 + * @deprecated replaced by write(CharSequence, OutputStream) + */ + @Deprecated + public static void write(StringBuffer data, OutputStream output) + throws IOException { + write(data, output, (String) null); + } + + /** + * Writes chars from a StringBuffer to bytes on an + * OutputStream using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + *

+ * This method uses {@link String#getBytes(String)}. + * + * @param data the StringBuffer to write, null ignored + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws IOException if an I/O error occurs + * @throws UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + * @deprecated replaced by write(CharSequence, OutputStream, String) + */ + @Deprecated + public static void write(StringBuffer data, OutputStream output, String encoding) throws IOException { + if (data != null) { + output.write(data.toString().getBytes(Charsets.toCharset(encoding))); + } + } + + // writeLines + //----------------------------------------------------------------------- + + /** + * Writes the toString() value of each item in a collection to + * an OutputStream line by line, using the default character + * encoding of the platform and the specified line ending. + * + * @param lines the lines to write, null entries produce blank lines + * @param lineEnding the line separator to use, null is system default + * @param output the OutputStream to write to, not null, not closed + * @throws NullPointerException if the output is null + * @throws IOException if an I/O error occurs + * @since 1.1 + */ + public static void writeLines(Collection lines, String lineEnding, + OutputStream output) throws IOException { + writeLines(lines, lineEnding, output, Charset.defaultCharset()); + } + + /** + * Writes the toString() value of each item in a collection to + * an OutputStream line by line, using the specified character + * encoding and the specified line ending. + * + * @param lines the lines to write, null entries produce blank lines + * @param lineEnding the line separator to use, null is system default + * @param output the OutputStream to write to, not null, not closed + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if the output is null + * @throws IOException if an I/O error occurs + * @since 2.3 + */ + public static void writeLines(Collection lines, String lineEnding, OutputStream output, Charset encoding) + throws IOException { + if (lines == null) { + return; + } + if (lineEnding == null) { + lineEnding = LINE_SEPARATOR; + } + Charset cs = Charsets.toCharset(encoding); + for (Object line : lines) { + if (line != null) { + output.write(line.toString().getBytes(cs)); + } + output.write(lineEnding.getBytes(cs)); + } + } + + /** + * Writes the toString() value of each item in a collection to + * an OutputStream line by line, using the specified character + * encoding and the specified line ending. + *

+ * Character encoding names can be found at + * IANA. + * + * @param lines the lines to write, null entries produce blank lines + * @param lineEnding the line separator to use, null is system default + * @param output the OutputStream to write to, not null, not closed + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if the output is null + * @throws IOException if an I/O error occurs + * @throws UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static void writeLines(Collection lines, String lineEnding, + OutputStream output, String encoding) throws IOException { + writeLines(lines, lineEnding, output, Charsets.toCharset(encoding)); + } + + /** + * Writes the toString() value of each item in a collection to + * a Writer line by line, using the specified line ending. + * + * @param lines the lines to write, null entries produce blank lines + * @param lineEnding the line separator to use, null is system default + * @param writer the Writer to write to, not null, not closed + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + * @since 1.1 + */ + public static void writeLines(Collection lines, String lineEnding, + Writer writer) throws IOException { + if (lines == null) { + return; + } + if (lineEnding == null) { + lineEnding = LINE_SEPARATOR; + } + for (Object line : lines) { + if (line != null) { + writer.write(line.toString()); + } + writer.write(lineEnding); + } + } + + // copy from InputStream + //----------------------------------------------------------------------- + + /** + * Copy bytes from an InputStream to an + * OutputStream. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

+ * Large streams (over 2GB) will return a bytes copied value of + * -1 after the copy has completed since the correct + * number of bytes cannot be returned as an int. For large streams + * use the copyLarge(InputStream, OutputStream) method. + * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @return the number of bytes copied, or -1 if > Integer.MAX_VALUE + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since 1.1 + */ + public static int copy(InputStream input, OutputStream output) throws IOException { + long count = copyLarge(input, output); + if (count > Integer.MAX_VALUE) { + return -1; + } + return (int) count; + } + + /** + * Copy bytes from a large (over 2GB) InputStream to an + * OutputStream. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

+ * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. + * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @return the number of bytes copied + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since 1.3 + */ + public static long copyLarge(InputStream input, OutputStream output) + throws IOException { + return copyLarge(input, output, new byte[DEFAULT_BUFFER_SIZE]); + } + + /** + * Copy bytes from a large (over 2GB) InputStream to an + * OutputStream. + *

+ * This method uses the provided buffer, so there is no need to use a + * BufferedInputStream. + *

+ * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @param buffer the buffer to use for the copy + * @return the number of bytes copied + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since 2.2 + */ + public static long copyLarge(InputStream input, OutputStream output, byte[] buffer) + throws IOException { + long count = 0; + int n = 0; + while (EOF != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + /** + * Copy some or all bytes from a large (over 2GB) InputStream to an + * OutputStream, optionally skipping input bytes. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

+ * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. + * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @param inputOffset : number of bytes to skip from input before copying + * -ve values are ignored + * @param length : number of bytes to copy. -ve means all + * @return the number of bytes copied + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since 2.2 + */ + public static long copyLarge(InputStream input, OutputStream output, long inputOffset, long length) + throws IOException { + return copyLarge(input, output, inputOffset, length, new byte[DEFAULT_BUFFER_SIZE]); + } + + /** + * Copy some or all bytes from a large (over 2GB) InputStream to an + * OutputStream, optionally skipping input bytes. + *

+ * This method uses the provided buffer, so there is no need to use a + * BufferedInputStream. + *

+ * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @param inputOffset : number of bytes to skip from input before copying + * -ve values are ignored + * @param length : number of bytes to copy. -ve means all + * @param buffer the buffer to use for the copy + * @return the number of bytes copied + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since 2.2 + */ + public static long copyLarge(InputStream input, OutputStream output, + final long inputOffset, final long length, byte[] buffer) throws IOException { + if (inputOffset > 0) { + skipFully(input, inputOffset); + } + if (length == 0) { + return 0; + } + final int bufferLength = buffer.length; + int bytesToRead = bufferLength; + if (length > 0 && length < bufferLength) { + bytesToRead = (int) length; + } + int read; + long totalRead = 0; + while (bytesToRead > 0 && EOF != (read = input.read(buffer, 0, bytesToRead))) { + output.write(buffer, 0, read); + totalRead += read; + if (length > 0) { // only adjust length if not reading to the end + // Note the cast must work because buffer.length is an integer + bytesToRead = (int) Math.min(length - totalRead, bufferLength); + } + } + return totalRead; + } + + /** + * Copy bytes from an InputStream to chars on a + * Writer using the default character encoding of the platform. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

+ * This method uses {@link InputStreamReader}. + * + * @param input the InputStream to read from + * @param output the Writer to write to + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since 1.1 + */ + public static void copy(InputStream input, Writer output) + throws IOException { + copy(input, output, Charset.defaultCharset()); + } + + /** + * Copy bytes from an InputStream to chars on a + * Writer using the specified character encoding. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

+ * This method uses {@link InputStreamReader}. + * + * @param input the InputStream to read from + * @param output the Writer to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since 2.3 + */ + public static void copy(InputStream input, Writer output, Charset encoding) throws IOException { + InputStreamReader in = new InputStreamReader(input, Charsets.toCharset(encoding)); + copy(in, output); + } + + /** + * Copy bytes from an InputStream to chars on a + * Writer using the specified character encoding. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

+ * Character encoding names can be found at + * IANA. + *

+ * This method uses {@link InputStreamReader}. + * + * @param input the InputStream to read from + * @param output the Writer to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @throws UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static void copy(InputStream input, Writer output, String encoding) throws IOException { + copy(input, output, Charsets.toCharset(encoding)); + } + + // copy from Reader + //----------------------------------------------------------------------- + + /** + * Copy chars from a Reader to a Writer. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + *

+ * Large streams (over 2GB) will return a chars copied value of + * -1 after the copy has completed since the correct + * number of chars cannot be returned as an int. For large streams + * use the copyLarge(Reader, Writer) method. + * + * @param input the Reader to read from + * @param output the Writer to write to + * @return the number of characters copied, or -1 if > Integer.MAX_VALUE + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since 1.1 + */ + public static int copy(Reader input, Writer output) throws IOException { + long count = copyLarge(input, output); + if (count > Integer.MAX_VALUE) { + return -1; + } + return (int) count; + } + + /** + * Copy chars from a large (over 2GB) Reader to a Writer. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + *

+ * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. + * + * @param input the Reader to read from + * @param output the Writer to write to + * @return the number of characters copied + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since 1.3 + */ + public static long copyLarge(Reader input, Writer output) throws IOException { + return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]); + } + + /** + * Copy chars from a large (over 2GB) Reader to a Writer. + *

+ * This method uses the provided buffer, so there is no need to use a + * BufferedReader. + *

+ * + * @param input the Reader to read from + * @param output the Writer to write to + * @param buffer the buffer to be used for the copy + * @return the number of characters copied + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since 2.2 + */ + public static long copyLarge(Reader input, Writer output, char[] buffer) throws IOException { + long count = 0; + int n = 0; + while (EOF != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + /** + * Copy some or all chars from a large (over 2GB) InputStream to an + * OutputStream, optionally skipping input chars. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + *

+ * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. + * + * @param input the Reader to read from + * @param output the Writer to write to + * @param inputOffset : number of chars to skip from input before copying + * -ve values are ignored + * @param length : number of chars to copy. -ve means all + * @return the number of chars copied + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since 2.2 + */ + public static long copyLarge(Reader input, Writer output, final long inputOffset, final long length) + throws IOException { + return copyLarge(input, output, inputOffset, length, new char[DEFAULT_BUFFER_SIZE]); + } + + /** + * Copy some or all chars from a large (over 2GB) InputStream to an + * OutputStream, optionally skipping input chars. + *

+ * This method uses the provided buffer, so there is no need to use a + * BufferedReader. + *

+ * + * @param input the Reader to read from + * @param output the Writer to write to + * @param inputOffset : number of chars to skip from input before copying + * -ve values are ignored + * @param length : number of chars to copy. -ve means all + * @param buffer the buffer to be used for the copy + * @return the number of chars copied + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since 2.2 + */ + public static long copyLarge(Reader input, Writer output, final long inputOffset, final long length, char[] buffer) + throws IOException { + if (inputOffset > 0) { + skipFully(input, inputOffset); + } + if (length == 0) { + return 0; + } + int bytesToRead = buffer.length; + if (length > 0 && length < buffer.length) { + bytesToRead = (int) length; + } + int read; + long totalRead = 0; + while (bytesToRead > 0 && EOF != (read = input.read(buffer, 0, bytesToRead))) { + output.write(buffer, 0, read); + totalRead += read; + if (length > 0) { // only adjust length if not reading to the end + // Note the cast must work because buffer.length is an integer + bytesToRead = (int) Math.min(length - totalRead, buffer.length); + } + } + return totalRead; + } + + /** + * Copy chars from a Reader to bytes on an + * OutputStream using the default character encoding of the + * platform, and calling flush. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + *

+ * Due to the implementation of OutputStreamWriter, this method performs a + * flush. + *

+ * This method uses {@link OutputStreamWriter}. + * + * @param input the Reader to read from + * @param output the OutputStream to write to + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since 1.1 + */ + public static void copy(Reader input, OutputStream output) + throws IOException { + copy(input, output, Charset.defaultCharset()); + } + + /** + * Copy chars from a Reader to bytes on an + * OutputStream using the specified character encoding, and + * calling flush. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + *

+ *

+ * Due to the implementation of OutputStreamWriter, this method performs a + * flush. + *

+ *

+ * This method uses {@link OutputStreamWriter}. + *

+ * + * @param input the Reader to read from + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @since 2.3 + */ + public static void copy(Reader input, OutputStream output, Charset encoding) throws IOException { + OutputStreamWriter out = new OutputStreamWriter(output, Charsets.toCharset(encoding)); + copy(input, out); + // XXX Unless anyone is planning on rewriting OutputStreamWriter, + // we have to flush here. + out.flush(); + } + + /** + * Copy chars from a Reader to bytes on an + * OutputStream using the specified character encoding, and + * calling flush. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + *

+ * Character encoding names can be found at + * IANA. + *

+ * Due to the implementation of OutputStreamWriter, this method performs a + * flush. + *

+ * This method uses {@link OutputStreamWriter}. + * + * @param input the Reader to read from + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if the input or output is null + * @throws IOException if an I/O error occurs + * @throws UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static void copy(Reader input, OutputStream output, String encoding) throws IOException { + copy(input, output, Charsets.toCharset(encoding)); + } + + // content equals + //----------------------------------------------------------------------- + + /** + * Compare the contents of two Streams to determine if they are equal or + * not. + *

+ * This method buffers the input internally using + * BufferedInputStream if they are not already buffered. + * + * @param input1 the first stream + * @param input2 the second stream + * @return true if the content of the streams are equal or they both don't + * exist, false otherwise + * @throws NullPointerException if either input is null + * @throws IOException if an I/O error occurs + */ + public static boolean contentEquals(InputStream input1, InputStream input2) + throws IOException { + if (!(input1 instanceof BufferedInputStream)) { + input1 = new BufferedInputStream(input1); + } + if (!(input2 instanceof BufferedInputStream)) { + input2 = new BufferedInputStream(input2); + } + + int ch = input1.read(); + while (EOF != ch) { + int ch2 = input2.read(); + if (ch != ch2) { + return false; + } + ch = input1.read(); + } + + int ch2 = input2.read(); + return ch2 == EOF; + } + + /** + * Compare the contents of two Readers to determine if they are equal or + * not. + *

+ * This method buffers the input internally using + * BufferedReader if they are not already buffered. + * + * @param input1 the first reader + * @param input2 the second reader + * @return true if the content of the readers are equal or they both don't + * exist, false otherwise + * @throws NullPointerException if either input is null + * @throws IOException if an I/O error occurs + * @since 1.1 + */ + public static boolean contentEquals(Reader input1, Reader input2) + throws IOException { + + input1 = toBufferedReader(input1); + input2 = toBufferedReader(input2); + + int ch = input1.read(); + while (EOF != ch) { + int ch2 = input2.read(); + if (ch != ch2) { + return false; + } + ch = input1.read(); + } + + int ch2 = input2.read(); + return ch2 == EOF; + } + + /** + * Compare the contents of two Readers to determine if they are equal or + * not, ignoring EOL characters. + *

+ * This method buffers the input internally using + * BufferedReader if they are not already buffered. + * + * @param input1 the first reader + * @param input2 the second reader + * @return true if the content of the readers are equal (ignoring EOL differences), false otherwise + * @throws NullPointerException if either input is null + * @throws IOException if an I/O error occurs + * @since 2.2 + */ + public static boolean contentEqualsIgnoreEOL(Reader input1, Reader input2) + throws IOException { + BufferedReader br1 = toBufferedReader(input1); + BufferedReader br2 = toBufferedReader(input2); + + String line1 = br1.readLine(); + String line2 = br2.readLine(); + while (line1 != null && line2 != null && line1.equals(line2)) { + line1 = br1.readLine(); + line2 = br2.readLine(); + } + return line1 == null ? line2 == null ? true : false : line1.equals(line2); + } + + /** + * Skip bytes from an input byte stream. + * This implementation guarantees that it will read as many bytes + * as possible before giving up; this may not always be the case for + * subclasses of {@link Reader}. + * + * @param input byte stream to skip + * @param toSkip number of bytes to skip. + * @return number of bytes actually skipped. + * @throws IOException if there is a problem reading the file + * @throws IllegalArgumentException if toSkip is negative + * @see InputStream#skip(long) + * @since 2.0 + */ + public static long skip(InputStream input, long toSkip) throws IOException { + if (toSkip < 0) { + throw new IllegalArgumentException("Skip count must be non-negative, actual: " + toSkip); + } + /* + * N.B. no need to synchronize this because: - we don't care if the buffer is created multiple times (the data + * is ignored) - we always use the same size buffer, so if it it is recreated it will still be OK (if the buffer + * size were variable, we would need to synch. to ensure some other thread did not create a smaller one) + */ + if (SKIP_BYTE_BUFFER == null) { + SKIP_BYTE_BUFFER = new byte[SKIP_BUFFER_SIZE]; + } + long remain = toSkip; + while (remain > 0) { + long n = input.read(SKIP_BYTE_BUFFER, 0, (int) Math.min(remain, SKIP_BUFFER_SIZE)); + if (n < 0) { // EOF + break; + } + remain -= n; + } + return toSkip - remain; + } + + /** + * Skip characters from an input character stream. + * This implementation guarantees that it will read as many characters + * as possible before giving up; this may not always be the case for + * subclasses of {@link Reader}. + * + * @param input character stream to skip + * @param toSkip number of characters to skip. + * @return number of characters actually skipped. + * @throws IOException if there is a problem reading the file + * @throws IllegalArgumentException if toSkip is negative + * @see Reader#skip(long) + * @since 2.0 + */ + public static long skip(Reader input, long toSkip) throws IOException { + if (toSkip < 0) { + throw new IllegalArgumentException("Skip count must be non-negative, actual: " + toSkip); + } + /* + * N.B. no need to synchronize this because: - we don't care if the buffer is created multiple times (the data + * is ignored) - we always use the same size buffer, so if it it is recreated it will still be OK (if the buffer + * size were variable, we would need to synch. to ensure some other thread did not create a smaller one) + */ + if (SKIP_CHAR_BUFFER == null) { + SKIP_CHAR_BUFFER = new char[SKIP_BUFFER_SIZE]; + } + long remain = toSkip; + while (remain > 0) { + long n = input.read(SKIP_CHAR_BUFFER, 0, (int) Math.min(remain, SKIP_BUFFER_SIZE)); + if (n < 0) { // EOF + break; + } + remain -= n; + } + return toSkip - remain; + } + + /** + * Skip the requested number of bytes or fail if there are not enough left. + *

+ * This allows for the possibility that {@link InputStream#skip(long)} may + * not skip as many bytes as requested (most likely because of reaching EOF). + * + * @param input stream to skip + * @param toSkip the number of bytes to skip + * @throws IOException if there is a problem reading the file + * @throws IllegalArgumentException if toSkip is negative + * @throws EOFException if the number of bytes skipped was incorrect + * @see InputStream#skip(long) + * @since 2.0 + */ + public static void skipFully(InputStream input, long toSkip) throws IOException { + if (toSkip < 0) { + throw new IllegalArgumentException("Bytes to skip must not be negative: " + toSkip); + } + long skipped = skip(input, toSkip); + if (skipped != toSkip) { + throw new EOFException("Bytes to skip: " + toSkip + " actual: " + skipped); + } + } + + /** + * Skip the requested number of characters or fail if there are not enough left. + *

+ * This allows for the possibility that {@link Reader#skip(long)} may + * not skip as many characters as requested (most likely because of reaching EOF). + * + * @param input stream to skip + * @param toSkip the number of characters to skip + * @throws IOException if there is a problem reading the file + * @throws IllegalArgumentException if toSkip is negative + * @throws EOFException if the number of characters skipped was incorrect + * @see Reader#skip(long) + * @since 2.0 + */ + public static void skipFully(Reader input, long toSkip) throws IOException { + long skipped = skip(input, toSkip); + if (skipped != toSkip) { + throw new EOFException("Chars to skip: " + toSkip + " actual: " + skipped); + } + } + + + /** + * Read characters from an input character stream. + * This implementation guarantees that it will read as many characters + * as possible before giving up; this may not always be the case for + * subclasses of {@link Reader}. + * + * @param input where to read input from + * @param buffer destination + * @param offset inital offset into buffer + * @param length length to read, must + * @return actual length read; may be less than requested if EOF was reached + * @throws IOException if a read error occurs + * @since 2.2 + */ + public static int read(Reader input, char[] buffer, int offset, int length) throws IOException { + if (length < 0) { + throw new IllegalArgumentException("Length must not be negative: " + length); + } + int remaining = length; + while (remaining > 0) { + int location = length - remaining; + int count = input.read(buffer, offset + location, remaining); + if (EOF == count) { // EOF + break; + } + remaining -= count; + } + return length - remaining; + } + + /** + * Read characters from an input character stream. + * This implementation guarantees that it will read as many characters + * as possible before giving up; this may not always be the case for + * subclasses of {@link Reader}. + * + * @param input where to read input from + * @param buffer destination + * @return actual length read; may be less than requested if EOF was reached + * @throws IOException if a read error occurs + * @since 2.2 + */ + public static int read(Reader input, char[] buffer) throws IOException { + return read(input, buffer, 0, buffer.length); + } + + /** + * Read bytes from an input stream. + * This implementation guarantees that it will read as many bytes + * as possible before giving up; this may not always be the case for + * subclasses of {@link InputStream}. + * + * @param input where to read input from + * @param buffer destination + * @param offset inital offset into buffer + * @param length length to read, + * @return actual length read; may be less than requested if EOF was reached + * @throws IOException if a read error occurs + * @since 2.2 + */ + public static int read(InputStream input, byte[] buffer, int offset, int length) throws IOException { + if (length < 0) { + throw new IllegalArgumentException("Length must not be negative: " + length); + } + int remaining = length; + while (remaining > 0) { + int location = length - remaining; + int count = input.read(buffer, offset + location, remaining); + if (EOF == count) { // EOF + break; + } + remaining -= count; + } + return length - remaining; + } + + /** + * Read bytes from an input stream. + * This implementation guarantees that it will read as many bytes + * as possible before giving up; this may not always be the case for + * subclasses of {@link InputStream}. + * + * @param input where to read input from + * @param buffer destination + * @return actual length read; may be less than requested if EOF was reached + * @throws IOException if a read error occurs + * @since 2.2 + */ + public static int read(InputStream input, byte[] buffer) throws IOException { + return read(input, buffer, 0, buffer.length); + } + + /** + * Read the requested number of characters or fail if there are not enough left. + *

+ * This allows for the possibility that {@link Reader#read(char[], int, int)} may + * not read as many characters as requested (most likely because of reaching EOF). + * + * @param input where to read input from + * @param buffer destination + * @param offset inital offset into buffer + * @param length length to read, must + * @throws IOException if there is a problem reading the file + * @throws IllegalArgumentException if length is negative + * @throws EOFException if the number of characters read was incorrect + * @since 2.2 + */ + public static void readFully(Reader input, char[] buffer, int offset, int length) throws IOException { + int actual = read(input, buffer, offset, length); + if (actual != length) { + throw new EOFException("Length to read: " + length + " actual: " + actual); + } + } + + /** + * Read the requested number of characters or fail if there are not enough left. + *

+ * This allows for the possibility that {@link Reader#read(char[], int, int)} may + * not read as many characters as requested (most likely because of reaching EOF). + * + * @param input where to read input from + * @param buffer destination + * @throws IOException if there is a problem reading the file + * @throws IllegalArgumentException if length is negative + * @throws EOFException if the number of characters read was incorrect + * @since 2.2 + */ + public static void readFully(Reader input, char[] buffer) throws IOException { + readFully(input, buffer, 0, buffer.length); + } + + /** + * Read the requested number of bytes or fail if there are not enough left. + *

+ * This allows for the possibility that {@link InputStream#read(byte[], int, int)} may + * not read as many bytes as requested (most likely because of reaching EOF). + * + * @param input where to read input from + * @param buffer destination + * @param offset inital offset into buffer + * @param length length to read + * @throws IOException if there is a problem reading the file + * @throws IllegalArgumentException if length is negative + * @throws EOFException if the number of bytes read was incorrect + * @since 2.2 + */ + public static void readFully(InputStream input, byte[] buffer, int offset, int length) throws IOException { + int actual = read(input, buffer, offset, length); + if (actual != length) { + throw new EOFException("Length to read: " + length + " actual: " + actual); + } + } + + /** + * Read the requested number of bytes or fail if there are not enough left. + *

+ * This allows for the possibility that {@link InputStream#read(byte[], int, int)} may + * not read as many bytes as requested (most likely because of reaching EOF). + * + * @param input where to read input from + * @param buffer destination + * @throws IOException if there is a problem reading the file + * @throws IllegalArgumentException if length is negative + * @throws EOFException if the number of bytes read was incorrect + * @since 2.2 + */ + public static void readFully(InputStream input, byte[] buffer) throws IOException { + readFully(input, buffer, 0, buffer.length); + } +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/util/LogExceptionHandler.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/LogExceptionHandler.java index 35550c5f6cd7b7e992e3ad0b5e2a3c50dfb602d1..01a1c4662bec6070c8e0a6d18741d8f4c57b9eac 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/util/LogExceptionHandler.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/util/LogExceptionHandler.java @@ -2,8 +2,8 @@ package com.egzosn.pay.common.util; import com.egzosn.pay.common.api.PayErrorExceptionHandler; import com.egzosn.pay.common.exception.PayErrorException; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @@ -20,12 +20,12 @@ import org.apache.commons.logging.LogFactory; */ public class LogExceptionHandler implements PayErrorExceptionHandler { - protected final Log log = LogFactory.getLog(PayErrorExceptionHandler.class); + protected final Logger LOGGER = LoggerFactory.getLogger(PayErrorExceptionHandler.class); @Override public void handle(PayErrorException e) { - log.error("Error happens", e); + LOGGER.error("Error happens", e); } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/util/MapGen.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/MapGen.java new file mode 100644 index 0000000000000000000000000000000000000000..545dae8642e06015f6411e6557fde4fef1f9184f --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/util/MapGen.java @@ -0,0 +1,41 @@ +package com.egzosn.pay.common.util; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Map生成工具 + * + * @author Egan + * email egzosn@gmail.com + * date 2021/8/1 + */ +public class MapGen { + + /** + * 属性 + */ + private Map attr; + + public MapGen(K key, V value) { + keyValue(key, value); + } + + public MapGen keyValue(K key, V value) { + if (null == attr){ + attr = new LinkedHashMap<>(); + } + attr.put(key, value); + return this; + } + + + public Map getAttr() { + return attr; + } + + private void setAttr(Map attr) { + this.attr = attr; + } +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/util/MatrixToImageWriter.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/MatrixToImageWriter.java index fc320497610a526f181b75eb76e2de8136b949eb..57b8e75b8430899c5b31b12e3496ea75e947cf35 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/util/MatrixToImageWriter.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/util/MatrixToImageWriter.java @@ -1,11 +1,5 @@ package com.egzosn.pay.common.util; -import com.google.zxing.BarcodeFormat; -import com.google.zxing.EncodeHintType; -import com.google.zxing.MultiFormatWriter; -import com.google.zxing.common.BitMatrix; - -import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; @@ -13,10 +7,18 @@ import java.io.OutputStream; import java.util.HashMap; import java.util.Map; +import javax.imageio.ImageIO; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.MultiFormatWriter; +import com.google.zxing.common.BitMatrix; + /** * 二维码生成工具 - * @author egan + * + * @author egan *

  * email egzosn@gmail.com
  * date  2017/2/7 10:35
@@ -25,104 +27,111 @@ import java.util.Map;
 public class MatrixToImageWriter {
 
 
-	   private static final int BLACK = 0xFF000000;
-	   private static final int WHITE = 0xFFFFFFFF;
-	 
-	   private MatrixToImageWriter() {}
+    private static final int BLACK = 0xFF000000;
+    private static final int WHITE = 0xFFFFFFFF;
+
+    private MatrixToImageWriter() {
+    }
+
+    /**
+     * 根据二维矩阵的碎片 生成对应的二维码图像缓冲
+     *
+     * @param matrix 二维矩阵的碎片 包含 宽高 行,字节
+     * @return 二维码图像缓冲
+     * @see com.google.zxing.common.BitMatrix
+     */
+    public static BufferedImage toBufferedImage(BitMatrix matrix) {
+        int width = matrix.getWidth();
+        int height = matrix.getHeight();
+        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+        for (int x = 0; x < width; x++) {
+            for (int y = 0; y < height; y++) {
+                image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE);
+            }
+        }
+        return image;
+    }
+
+
+    /**
+     * 二维码生成文件
+     *
+     * @param matrix 二维矩阵的碎片 包含 宽高 行,字节
+     * @param format 格式
+     * @param file   保持的文件地址
+     * @throws IOException 文件保存异常
+     */
+    public static void writeToFile(BitMatrix matrix, String format, File file)
+            throws IOException {
+        BufferedImage image = toBufferedImage(matrix);
+        if (!ImageIO.write(image, format, file)) {
+            throw new IOException("Could not write an image of format " + format + " to " + file);
+        }
+    }
+
+
+    /**
+     * 二维码生成流
+     *
+     * @param matrix 二维矩阵的碎片 包含 宽高 行,字节
+     * @param format 格式
+     * @param stream 保持的文件输出流
+     * @throws IOException 文件保存异常
+     */
+    public static void writeToStream(BitMatrix matrix, String format, OutputStream stream)
+            throws IOException {
+        BufferedImage image = toBufferedImage(matrix);
+        if (!ImageIO.write(image, format, stream)) {
+            throw new IOException("Could not write an image of format " + format);
+        }
+    }
+
 
     /**
-	 * 根据二维矩阵的碎片 生成对应的二维码图像缓冲
-	 * @param matrix  二维矩阵的碎片 包含 宽高 行,字节
-	 * @see com.google.zxing.common.BitMatrix
-	 * @return 二维码图像缓冲
+     * 二维码信息写成JPG文件
+     *
+     * @param content 二维码信息
+     * @param fileUrl 文件地址
      */
-	   public static BufferedImage toBufferedImage(BitMatrix matrix) {
-	     int width = matrix.getWidth();
-	     int height = matrix.getHeight();
-	     BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
-	     for (int x = 0; x < width; x++) {
-	       for (int y = 0; y < height; y++) {
-	         image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE);
-	       }
-	     }
-	     return image;
-	   }
-
-
-	/**
-	 * 二维码生成文件
-	 * @param matrix 二维矩阵的碎片 包含 宽高 行,字节
-	 * @param format 格式
-	 * @param file 保持的文件地址
-	 * @throws IOException 文件保存异常
-	 */
-	   public static void writeToFile(BitMatrix matrix, String format, File file)
-	       throws IOException {
-	     BufferedImage image = toBufferedImage(matrix);
-	     if (!ImageIO.write(image, format, file)) {
-	       throw new IOException("Could not write an image of format " + format + " to " + file);
-	     }
-	   }
-
-
-	/**
-	 * 二维码生成流
-	 * @param matrix 二维矩阵的碎片 包含 宽高 行,字节
-	 * @param format 格式
-	 * @param stream 保持的文件输出流
-	 * @throws IOException 文件保存异常
-	 */
-	   public static void writeToStream(BitMatrix matrix, String format, OutputStream stream)
-	       throws IOException {
-	     BufferedImage image = toBufferedImage(matrix);
-	     if (!ImageIO.write(image, format, stream)) {
-	       throw new IOException("Could not write an image of format " + format);
-	     }
-	   }
-	   
-	   
-	   /**
-	    * 二维码信息写成JPG文件
-	    * @param content  二维码信息
-	    * @param fileUrl 文件地址
-	    */
-	  public static void writeInfoToJpgFile(String content, String fileUrl){
-		    MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
-		    Map hints = new HashMap();
-		    hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
-		    try {
-				BitMatrix bitMatrix = multiFormatWriter.encode(content,
-						BarcodeFormat.QR_CODE, 250, 250, hints);
-				File file1 = new File(fileUrl);
-				MatrixToImageWriter.writeToFile(bitMatrix, "jpg", file1);
-			} catch (Exception e) {
-				e.printStackTrace();
-			}	 
-	   }
-	  
-	  
-	  /**
-	   * 二维码信息写成JPG BufferedImage
-	   * @param content 二维码信息
-	   * @return JPG BufferedImage
-	   */
-	  public static BufferedImage writeInfoToJpgBuff(String content){
-		    BufferedImage re=null;
-		  
-		    MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
-		    Map hints = new HashMap();
-		    hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
-		    try {
-				BitMatrix bitMatrix = multiFormatWriter.encode(content,
-						BarcodeFormat.QR_CODE, 250, 250, hints);
-				re=MatrixToImageWriter.toBufferedImage(bitMatrix);
-			} catch (Exception e) {
-				e.printStackTrace();
-			}	 
-		    
-		  return re;
-	  }
-	 
-	   
-	   
+    public static void writeInfoToJpgFile(String content, String fileUrl) {
+        MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
+        Map hints = new HashMap();
+        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
+        try {
+            BitMatrix bitMatrix = multiFormatWriter.encode(content,
+                    BarcodeFormat.QR_CODE, 250, 250, hints);
+            File file1 = new File(fileUrl);
+            MatrixToImageWriter.writeToFile(bitMatrix, "jpg", file1);
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    /**
+     * 二维码信息写成JPG BufferedImage
+     *
+     * @param content 二维码信息
+     * @return JPG BufferedImage
+     */
+    public static BufferedImage writeInfoToJpgBuff(String content) {
+        BufferedImage re = null;
+
+        MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
+        Map hints = new HashMap();
+        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
+        try {
+            BitMatrix bitMatrix = multiFormatWriter.encode(content,
+                    BarcodeFormat.QR_CODE, 250, 250, hints);
+            re = MatrixToImageWriter.toBufferedImage(bitMatrix);
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return re;
+    }
+
+
 }
diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/util/Util.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/Util.java
index e67ce7e6ec513a5ca676e7a96c7768ca7dcf4f50..53b9ae5a3885d499ab9b15ad7d80eb54724418c2 100644
--- a/pay-java-common/src/main/java/com/egzosn/pay/common/util/Util.java
+++ b/pay-java-common/src/main/java/com/egzosn/pay/common/util/Util.java
@@ -2,6 +2,8 @@ package com.egzosn.pay.common.util;
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.util.Collection;
+import java.util.Map;
 
 public class Util {
     /**
@@ -69,9 +71,11 @@ public class Util {
         if (n.toByteArray().length == 33) {
             tmpd = new byte[32];
             System.arraycopy(n.toByteArray(), 1, tmpd, 0, 32);
-        } else if (n.toByteArray().length == 32) {
+        }
+        else if (n.toByteArray().length == 32) {
             tmpd = n.toByteArray();
-        } else {
+        }
+        else {
             tmpd = new byte[32];
             for (int i = 0; i < 32 - n.toByteArray().length; i++) {
                 tmpd[i] = 0;
@@ -348,7 +352,8 @@ public class Util {
             int algorism = 0;
             if (c >= '0' && c <= '9') {
                 algorism = c - '0';
-            } else {
+            }
+            else {
                 algorism = c - 55;
             }
             result += Math.pow(16, max - i) * algorism;
@@ -451,7 +456,7 @@ public class Util {
      * @param algorism 十进制的数字
      * @return String 对应的十六进制字符串
      */
-    public static String algorismToHEXString(int algorism) {
+    public static String algorismToHexString(int algorism) {
         String result = "";
         result = Integer.toHexString(algorism);
 
@@ -493,7 +498,8 @@ public class Util {
         int i = 0;
         try {
             i = Integer.parseInt(s, radix);
-        } catch (NumberFormatException ex) {
+        }
+        catch (NumberFormatException ex) {
             i = defaultInt;
         }
         return i;
@@ -510,7 +516,8 @@ public class Util {
         int i = 0;
         try {
             i = Integer.parseInt(s);
-        } catch (NumberFormatException ex) {
+        }
+        catch (NumberFormatException ex) {
             i = defaultInt;
         }
         return i;
@@ -571,14 +578,19 @@ public class Util {
     }
 
 
+    /**
+     * 一百
+     */
+    public static final BigDecimal HUNDRED = new BigDecimal(100);
+
     /**
      * 元转分
      *
      * @param amount 元的金额
      * @return 分的金额
      */
-    public static final int conversionCentAmount(BigDecimal amount) {
-        return amount.multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_UP).intValue();
+    public static int conversionCentAmount(BigDecimal amount) {
+        return amount.multiply(HUNDRED).setScale(0, BigDecimal.ROUND_HALF_UP).intValue();
     }
 
     /**
@@ -587,9 +599,18 @@ public class Util {
      * @param amount 元的金额
      * @return 元的金额 两位小数
      */
-    public static final BigDecimal conversionAmount(BigDecimal amount) {
+    public static BigDecimal conversionAmount(BigDecimal amount) {
         return amount.setScale(2, BigDecimal.ROUND_HALF_UP);
     }
 
 
+    public static  boolean isEmpty(Map map) {
+        return null == map || map.isEmpty();
+    }
+
+    public static  boolean isEmpty(Collection collection) {
+        return null == collection || collection.isEmpty();
+    }
+
+
 }
\ No newline at end of file
diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/util/XML.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/XML.java
index 294a45790a000d8158a0665d6bad9dacd3866959..3c18fe898cfbbac194de5d9775c2388fb76ccd42 100644
--- a/pay-java-common/src/main/java/com/egzosn/pay/common/util/XML.java
+++ b/pay-java-common/src/main/java/com/egzosn/pay/common/util/XML.java
@@ -1,15 +1,13 @@
 package com.egzosn.pay.common.util;
 
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-import com.egzosn.pay.common.bean.result.PayException;
-import com.egzosn.pay.common.exception.PayErrorException;
-import com.egzosn.pay.common.util.str.StringUtils;
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.nio.charset.Charset;
+import java.util.List;
+import java.util.Map;
 
 import javax.xml.XMLConstants;
 import javax.xml.parsers.DocumentBuilder;
@@ -21,19 +19,25 @@ import javax.xml.transform.TransformerException;
 import javax.xml.transform.TransformerFactory;
 import javax.xml.transform.dom.DOMSource;
 import javax.xml.transform.stream.StreamResult;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.StringWriter;
-import java.nio.charset.Charset;
-import java.util.Map;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.egzosn.pay.common.bean.result.PayException;
+import com.egzosn.pay.common.exception.PayErrorException;
+import com.egzosn.pay.common.util.str.StringUtils;
 
 
 /**
  * XML工具
  *
  * @author egan
- *         
+ * 
  *         email egzosn@gmail.com
  *         date 2016-6-2 19:45:06
  *         
@@ -55,7 +59,8 @@ public class XML { try { return (JSONObject) inputStream2Map(in, null); - } catch (IOException e) { + } + catch (IOException e) { throw new PayErrorException(new PayException("IOException", e.getMessage())); } @@ -74,10 +79,12 @@ public class XML { } + /** * 解析xml并转化为Json值 * * @param content json字符串 + * @param charset 字符编码 * @return Json值 */ public static JSONObject toJSONObject(String content, Charset charset) { @@ -87,6 +94,7 @@ public class XML { } return toJSONObject(content.getBytes(charset)); } + /** * 解析xml并转化为Json值 * @@ -100,7 +108,8 @@ public class XML { } try (InputStream in = new ByteArrayInputStream(content)) { return (JSONObject) inputStream2Map(in, null); - } catch (IOException e) { + } + catch (IOException e) { throw new PayErrorException(new PayException("IOException", e.getMessage())); } @@ -110,18 +119,19 @@ public class XML { * 解析xml并转化为Json值 * * @param content json字符串 - * @param clazz 需要转化的类 - * @param 返回对应类型 + * @param clazz 需要转化的类 + * @param 返回对应类型 * @return Json值 */ public static T toBean(String content, Class clazz) { - if (null == content || "".equals(content)) { + if (StringUtils.isEmpty(content)) { return null; } try (InputStream in = new ByteArrayInputStream(content.getBytes("UTF-8"))) { return inputStream2Bean(in, clazz); - } catch (IOException e) { + } + catch (IOException e) { throw new PayErrorException(new PayException("IOException", e.getMessage())); } @@ -139,12 +149,9 @@ public class XML { for (int idx = 0; idx < children.getLength(); ++idx) { Node node = children.item(idx); NodeList nodeList = node.getChildNodes(); - if (node.getNodeType() == Node.ELEMENT_NODE && nodeList.getLength() <= 1) { - if (null == json) { - json = new JSONObject(); - } - ((JSONObject) json).put(node.getNodeName(), node.getTextContent()); - } else if (node.getNodeType() == Node.ELEMENT_NODE && nodeList.getLength() > 1) { + int length = nodeList.getLength(); + + if (node.getNodeType() == Node.ELEMENT_NODE && length >= 1 && nodeList.item(0).hasChildNodes()) { if (null == json) { json = new JSONObject(); } @@ -154,7 +161,8 @@ public class XML { JSONArray array = new JSONArray(); array.add(json); json = array; - } else { + } + else { j.put(node.getNodeName(), getChildren(nodeList)); } } @@ -165,6 +173,12 @@ public class XML { ((JSONArray) json).add(c); } } + else if (node.getNodeType() == Node.ELEMENT_NODE) { + if (null == json) { + json = new JSONObject(); + } + ((JSONObject) json).put(node.getNodeName(), node.getTextContent()); + } } return json; @@ -193,23 +207,10 @@ public class XML { * @param clazz 需要转化的类 * @param 类型 * @return 对应的对象 - * @throws IOException xml io转化异常 */ - public static T inputStream2Bean(InputStream in, Class clazz) throws IOException { - try { - - DocumentBuilder documentBuilder = newDocumentBuilder(); - org.w3c.dom.Document doc = documentBuilder.parse(in); - doc.getDocumentElement().normalize(); - NodeList children = doc.getDocumentElement().getChildNodes(); - JSON json = getChildren(children); - return json.toJavaObject(clazz); - } catch (Exception e) { - throw new PayErrorException(new PayException("XML failure", "XML解析失败\n" + e.getMessage())); - } finally { - in.close(); - } - + public static T inputStream2Bean(InputStream in, Class clazz) { + JSON json = toJSONObject(in); + return json.toJavaObject(clazz); } /** @@ -218,7 +219,7 @@ public class XML { * @return 整理完成的参数集 * @throws IOException xml io转化异常 */ - public static Map inputStream2Map(InputStream in, Map m) throws IOException { + public static Map inputStream2Map(InputStream in, Map m) throws IOException { if (null == m) { m = new JSONObject(); } @@ -230,23 +231,25 @@ public class XML { for (int idx = 0; idx < children.getLength(); ++idx) { Node node = children.item(idx); NodeList nodeList = node.getChildNodes(); - if (node.getNodeType() == Node.ELEMENT_NODE && nodeList.getLength() <= 1) { - m.put(node.getNodeName(), node.getTextContent()); - } else if (node.getNodeType() == Node.ELEMENT_NODE && nodeList.getLength() > 1) { + int length = nodeList.getLength(); + if (node.getNodeType() == Node.ELEMENT_NODE && (length > 1 || length == 1 && nodeList.item(0).hasChildNodes())) { m.put(node.getNodeName(), getChildren(nodeList)); } + else if (node.getNodeType() == Node.ELEMENT_NODE) { + m.put(node.getNodeName(), node.getTextContent()); + } } - } catch (Exception e) { -// e.printStackTrace(); + } + catch (ParserConfigurationException | SAXException e) { throw new PayErrorException(new PayException("XML failure", "XML解析失败\n" + e.getMessage())); - } finally { + } + finally { in.close(); } return m; } - /** * 将Map转换为XML格式的字符串 * @@ -254,43 +257,111 @@ public class XML { * @return XML格式的字符串 */ public static String getMap2Xml(Map data) { + return getMap2Xml(data, "xml", "UTF-8"); + } + /** + * 将Map转换为XML格式的字符串 + * + * @param data Map类型数据 + * @param rootElementName 最外层节点名称 + * @param encoding 字符编码 + * @return XML格式的字符串 + */ + public static String getMap2Xml(Map data, String rootElementName, String encoding) { Document document = null; try { document = newDocument(); - } catch (ParserConfigurationException e) { + } + catch (ParserConfigurationException e) { throw new PayErrorException(new PayException("ParserConfigurationException", e.getLocalizedMessage())); } - org.w3c.dom.Element root = document.createElement("xml"); + org.w3c.dom.Element root = document.createElement(rootElementName); document.appendChild(root); - for (Map.Entry entry : data.entrySet()) { + /* for (Map.Entry entry : data.entrySet()) { Object value = entry.getValue(); if (value == null) { value = ""; } + value = value.toString().trim(); org.w3c.dom.Element filed = document.createElement(entry.getKey()); filed.appendChild(document.createTextNode(value.toString())); root.appendChild(filed); - } + }*/ + + map2Xml(data, document, root); try { TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); DOMSource source = new DOMSource(document); - transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + transformer.setOutputProperty(OutputKeys.ENCODING, encoding); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); StringWriter writer = new StringWriter(); StreamResult result = new StreamResult(writer); transformer.transform(source, result); - String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", ""); + String output = writer.getBuffer().toString(); return output; - } catch (TransformerException e) { - e.printStackTrace(); + } + catch (TransformerException e) { + throw new PayErrorException(new PayException("XML failure", "XML生成失败\n" + e.getMessage())); + } + + + } + + /** + * 将Map转换为XML格式的字符串 + * + * @param data Map类型数据 + * @param document 文档 + * @param element 节点 + */ + public static void map2Xml(Map data, Document document, org.w3c.dom.Element element) { + for (Map.Entry entry : data.entrySet()) { + Object value = entry.getValue(); + if (value == null) { + value = ""; + } + org.w3c.dom.Element filed = document.createElement(entry.getKey()); + /* if (value instanceof Map){ + map2Xml((Map)value, document, filed); + }else if (value instanceof List){ + List vs = (List)value; + for (Object v : vs ){ + if (value instanceof Map){ + map2Xml((Map)value, document, filed); + } + } + map2Xml((Map)value, document, filed); + }else { + value = value.toString().trim(); + filed.appendChild(document.createTextNode(value.toString())); + }*/ + object2Xml(value, document, filed); + element.appendChild(filed); + } + } + + private static void object2Xml(Object value, Document document, org.w3c.dom.Element element) { + + if (value instanceof Map) { + map2Xml((Map) value, document, element); + } + else if (value instanceof List) { + List vs = (List) value; + for (Object v : vs) { + object2Xml(v, document, element); + } +// map2Xml((Map)value, document, element); + } + else { + value = value.toString().trim(); + element.appendChild(document.createTextNode(value.toString())); } - return ""; } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/CertDescriptor.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/CertDescriptor.java index c98a620409d6feff0b46171f87901f12d0b2f8d5..947c7e1b292c273832570a00023e69add4e2f2e5 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/CertDescriptor.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/CertDescriptor.java @@ -1,28 +1,37 @@ /** - * * Licensed Property to China UnionPay Co., Ltd. - * + *

* (C) Copyright of China UnionPay Co., Ltd. 2010 - * All Rights Reserved. - * - * + * All Rights Reserved. + *

+ *

* Modification History: * ============================================================================= - * Author Date Description - * ------------ ---------- --------------------------------------------------- - * xshu 2014-05-28 证书工具类. + * Author Date Description + * ------------ ---------- --------------------------------------------------- + * xshu 2014-05-28 证书工具类. * ============================================================================= */ package com.egzosn.pay.common.util.sign; -import com.egzosn.pay.common.util.str.StringUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Enumeration; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import java.io.*; -import java.security.*; -import java.security.cert.*; -import java.util.*; +import com.egzosn.pay.common.util.str.StringUtils; /** @@ -31,227 +40,340 @@ import java.util.*; * 声明:以下代码只是为了方便接入方测试而提供的样例代码,商户可以根据自己需要,按照技术文档编写。该代码仅供参考,不提供编码,性能,规范性等方面的保障 */ public class CertDescriptor { - protected static final Log LOG = LogFactory.getLog(CertDescriptor.class); - /** 证书容器,存储对商户请求报文签名私钥证书. */ - private KeyStore keyStore = null; - - /** 验签公钥/中级证书 */ - private X509Certificate publicKeyCert = null; - /** 验签根证书 */ - private X509Certificate rootKeyCert = null; - - - - /** - * 通过证书路径初始化为公钥证书 - * @param path 证书地址 - * @return X509 证书 - */ - private static X509Certificate initCert(String path) { - X509Certificate encryptCertTemp = null; - CertificateFactory cf = null; - FileInputStream in = null; - try { - cf = CertificateFactory.getInstance("X.509"); - in = new FileInputStream(path); - encryptCertTemp = (X509Certificate) cf.generateCertificate(in); - // 打印证书加载信息,供测试阶段调试 - if (LOG.isWarnEnabled()) { - LOG.warn("[" + path + "][CertId=" + encryptCertTemp.getSerialNumber().toString() + "]"); - } - } catch (CertificateException e) { - LOG.error("InitCert Error", e); - } catch (FileNotFoundException e) { - LOG.error("InitCert Error File Not Found", e); - }finally { - if (null != in) { - try { - in.close(); - } catch (IOException e) { - LOG.error(e.toString()); - } - } - } - return encryptCertTemp; - } - - /** - * 通过keyStore 获取私钥签名证书PrivateKey对象 - * - * @param pwd 证书对应密码 - * @return PrivateKey 私钥 - */ - public PrivateKey getSignCertPrivateKey(String pwd) { - try { - Enumeration aliasenum = keyStore.aliases(); - String keyAlias = null; - if (aliasenum.hasMoreElements()) { - keyAlias = aliasenum.nextElement(); - } - PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, - pwd.toCharArray()); - return privateKey; - } catch (KeyStoreException e) { - LOG.error("getSignCertPrivateKey Error", e); - return null; - } catch (UnrecoverableKeyException e) { - LOG.error("getSignCertPrivateKey Error", e); - return null; - } catch (NoSuchAlgorithmException e) { - LOG.error("getSignCertPrivateKey Error", e); - return null; - } - } - - - - - /** - * 配置的签名私钥证书certId - * - * @return 证书的物理编号 - */ - public String getSignCertId() { - try { - Enumeration aliasenum = keyStore.aliases(); - String keyAlias = null; - if (aliasenum.hasMoreElements()) { - keyAlias = aliasenum.nextElement(); - } - X509Certificate cert = (X509Certificate) keyStore - .getCertificate(keyAlias); - return cert.getSerialNumber().toString(); - } catch (Exception e) { - LOG.error("getSignCertId Error", e); - return null; - } - } - - - - - /** - * 将签名私钥证书文件读取为证书存储对象 - * - * @param signCertPath 证书文件名 - * @param signCertPwd 证书密码 - * @param signCertType 证书类型 - */ - public void initPrivateSignCert(String signCertPath, String signCertPwd, String signCertType) { - - if (null != keyStore) { - keyStore = null; - } - try { - keyStore = getKeyInfo(signCertPath, signCertPwd,signCertType); - if (LOG.isInfoEnabled()) { - LOG.info("InitSignCert Successful. CertId=[" + getSignCertId() + "]"); - } - } catch (IOException e) { - LOG.error("InitSignCert Error", e); - } - } - - /** - * 将签名私钥证书文件读取为证书存储对象 - * - * @param pfxkeyfile 证书文件名 - * @param keypwd 证书密码 - * @param type 证书类型 - * @return 证书对象 - * @throws IOException - */ - private KeyStore getKeyInfo(String pfxkeyfile, String keypwd, String type) throws IOException { - if (LOG.isWarnEnabled()) { - LOG.warn("加载签名证书==>" + pfxkeyfile); - } - try(FileInputStream fis = new FileInputStream(pfxkeyfile);) { - KeyStore ks = KeyStore.getInstance(type); - if (LOG.isWarnEnabled()) { - LOG.warn("Load RSA CertPath=[" + pfxkeyfile + "],Pwd=["+ keypwd + "],type=["+type+"]"); - } - - char[] nPassword = null; - nPassword = null == keypwd || "".equals(keypwd.trim()) ? null: keypwd.toCharArray(); - if (null != ks) { - ks.load(fis, nPassword); - } - return ks; - } catch (Exception e) { - LOG.error("getKeyInfo Error", e); - return null; - } - } - - - /** - * 通过keystore获取私钥证书的certId值 - * @param keyStore - * @return - */ - private String getCertIdIdByStore(KeyStore keyStore) { - Enumeration aliasenum = null; - try { - aliasenum = keyStore.aliases(); - String keyAlias = null; - if (aliasenum.hasMoreElements()) { - keyAlias = aliasenum.nextElement(); - } - X509Certificate cert = (X509Certificate) keyStore - .getCertificate(keyAlias); - return cert.getSerialNumber().toString(); - } catch (KeyStoreException e) { - LOG.error("getCertIdIdByStore Error", e); - return null; - } - } - - - - /** - * 加载中级证书 - * @param certPath 证书地址 - */ - public void initPublicCert(String certPath) { - if (!StringUtils.isEmpty(certPath)) { - publicKeyCert = initCert(certPath); - if (LOG.isInfoEnabled()) { - LOG.info("Load PublicKeyCert Successful"); - } - } else if (LOG.isInfoEnabled()) { - LOG.info("PublicKeyCert is empty"); - } - } - - /** - * 加载根证书 - * @param certPath 证书地址 - */ - public void initRootCert(String certPath) { - if (!StringUtils.isEmpty(certPath)) { - rootKeyCert = initCert(certPath); - if (LOG.isInfoEnabled()) { - LOG.info("Load RootCert Successful"); - } - } else if (LOG.isInfoEnabled()) { - LOG.info("RootCert is empty"); - } - } - - /** - * 获取公钥/中级证书 - * @return X509Certificate - */ - public X509Certificate getPublicCert() { - return publicKeyCert; - } - - /** - * 获取中级证书 - * @return X509Certificate - */ - public X509Certificate getRootCert() { - return rootKeyCert; - } - + protected static final Logger LOG = LoggerFactory.getLogger(CertDescriptor.class); + /** + * 证书容器,存储对商户请求报文签名私钥证书. + */ + private KeyStore keyStore = null; + + /** + * 验签公钥/中级证书 + */ + private X509Certificate publicKeyCert = null; + /** + * 验签根证书 + */ + private X509Certificate rootKeyCert = null; + + public CertDescriptor() { + } + + /** + * 通过证书路径初始化为公钥证书 + * + * @param certIn 证书流 + * @return X509 证书 + */ + private static X509Certificate initCert(InputStream certIn) { + X509Certificate encryptCertTemp = null; + CertificateFactory cf = null; + try { + cf = CertificateFactory.getInstance("X.509"); + encryptCertTemp = (X509Certificate) cf.generateCertificate(certIn); + // 打印证书加载信息,供测试阶段调试 + if (LOG.isWarnEnabled()) { + LOG.warn("[CertId=" + encryptCertTemp.getSerialNumber().toString() + "]"); + } + } + catch (CertificateException e) { + LOG.error("InitCert Error", e); + } + finally { + if (null != certIn) { + try { + certIn.close(); + } + catch (IOException e) { + LOG.error(e.toString()); + } + } + } + return encryptCertTemp; + } + + /** + * 通过证书路径初始化为公钥证书 + * + * @param path 证书地址 + * @return X509 证书 + */ + private static X509Certificate initCert(String path) { + X509Certificate encryptCertTemp = null; + CertificateFactory cf = null; + FileInputStream in = null; + try { + in = new FileInputStream(path); + encryptCertTemp = initCert(in); + } + catch (FileNotFoundException e) { + LOG.error("InitCert Error File Not Found", e); + } + return encryptCertTemp; + } + + /** + * 通过keyStore 获取私钥签名证书PrivateKey对象 + * + * @param pwd 证书对应密码 + * @return PrivateKey 私钥 + */ + public PrivateKey getSignCertPrivateKey(String pwd) { + try { + Enumeration aliasenum = keyStore.aliases(); + String keyAlias = null; + if (aliasenum.hasMoreElements()) { + keyAlias = aliasenum.nextElement(); + } + PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, pwd.toCharArray()); + return privateKey; + } + catch (KeyStoreException e) { + LOG.error("getSignCertPrivateKey Error", e); + return null; + } + catch (UnrecoverableKeyException e) { + LOG.error("getSignCertPrivateKey Error", e); + return null; + } + catch (NoSuchAlgorithmException e) { + LOG.error("getSignCertPrivateKey Error", e); + return null; + } + } + + + /** + * 配置的签名私钥证书certId + * + * @return 证书的物理编号 + */ + public String getSignCertId() { + try { + Enumeration aliasenum = keyStore.aliases(); + String keyAlias = null; + if (aliasenum.hasMoreElements()) { + keyAlias = aliasenum.nextElement(); + } + X509Certificate cert = (X509Certificate) keyStore.getCertificate(keyAlias); + return cert.getSerialNumber().toString(); + } + catch (Exception e) { + LOG.error("getSignCertId Error", e); + return null; + } + } + + + /** + * 将签名私钥证书文件读取为证书存储对象 + * + * @param signCertPath 证书文件名 + * @param signCertPwd 证书密码 + * @param signCertType 证书类型 + */ + public void initPrivateSignCert(String signCertPath, String signCertPwd, String signCertType) { + if (null != keyStore) { + keyStore = null; + } + try { + keyStore = getKeyInfo(signCertPath, signCertPwd, signCertType); + if (LOG.isInfoEnabled()) { + LOG.info("InitSignCert Successful. CertId=[" + getSignCertId() + "]"); + } + } + catch (IOException e) { + LOG.error("InitSignCert Error", e); + } + } + + /** + * 将签名私钥证书文件读取为证书存储对象 + * + * @param signCert 证书文件 + * @param signCertPwd 证书密码 + * @param signCertType 证书类型 + */ + public void initPrivateSignCert(InputStream signCert, String signCertPwd, String signCertType) { + + if (null != keyStore) { + keyStore = null; + } + keyStore = getKeyInfo(signCert, signCertPwd, signCertType); + if (LOG.isInfoEnabled()) { + LOG.info("InitSignCert Successful. CertId=[" + getSignCertId() + "]"); + } + } + + /** + * 将签名私钥证书文件读取为证书存储对象 + * + * @param fxKeyFile 证书文件名 + * @param keyPwd 证书密码 + * @param type 证书类型 + * @return 证书对象 + * @throws IOException + */ + private KeyStore getKeyInfo(String fxKeyFile, String keyPwd, String type) throws IOException { + if (LOG.isWarnEnabled()) { + LOG.warn("加载签名证书==>" + fxKeyFile); + } + FileInputStream fis = new FileInputStream(fxKeyFile); + return getKeyInfo(fis, keyPwd, type); + + } + + /** + * 将签名私钥证书文件读取为证书存储对象 + * + * @param fxKeyFile 证书文件 + * @param keyPwd 证书密码 + * @param type 证书类型 + * @return 证书对象 + */ + public KeyStore getKeyInfo(InputStream fxKeyFile, String keyPwd, String type) { + + try { + KeyStore ks = KeyStore.getInstance(type); + if (LOG.isWarnEnabled()) { + LOG.warn("Load RSA CertPath,Pwd=[" + keyPwd + "],type=[" + type + "]"); + } + + char[] nPassword = null; + nPassword = null == keyPwd || "".equals(keyPwd.trim()) ? null : keyPwd.toCharArray(); + if (null != ks) { + ks.load(fxKeyFile, nPassword); + } + return ks; + } + catch (Exception e) { + LOG.error("getKeyInfo Error", e); + return null; + } + finally { + if (null != fxKeyFile) { + try { + fxKeyFile.close(); + } + catch (IOException e) { + LOG.error("getKeyInfo Error", e); + } + } + } + } + + + /** + * 通过keystore获取私钥证书的certId值 + * + * @param keyStore + * @return + */ + private String getCertIdIdByStore(KeyStore keyStore) { + Enumeration aliasenum = null; + try { + aliasenum = keyStore.aliases(); + String keyAlias = null; + if (aliasenum.hasMoreElements()) { + keyAlias = aliasenum.nextElement(); + } + X509Certificate cert = (X509Certificate) keyStore + .getCertificate(keyAlias); + return cert.getSerialNumber().toString(); + } + catch (KeyStoreException e) { + LOG.error("getCertIdIdByStore Error", e); + return null; + } + } + + + /** + * 加载中级证书 + * + * @param certPath 证书地址 + */ + public void initPublicCert(String certPath) { + if (!StringUtils.isEmpty(certPath)) { + publicKeyCert = initCert(certPath); + if (LOG.isInfoEnabled()) { + LOG.info("Load PublicKeyCert Successful"); + } + } + else if (LOG.isInfoEnabled()) { + LOG.info("PublicKeyCert is empty"); + } + } + + /** + * 加载中级证书 + * + * @param cert 证书文件 + */ + public void initPublicCert(InputStream cert) { + if (null != cert) { + publicKeyCert = initCert(cert); + if (LOG.isInfoEnabled()) { + LOG.info("Load PublicKeyCert Successful"); + } + } + else if (LOG.isInfoEnabled()) { + LOG.info("PublicKeyCert is empty"); + } + } + + /** + * 加载根证书 + * + * @param certPath 证书地址 + */ + public void initRootCert(String certPath) { + if (!StringUtils.isEmpty(certPath)) { + try { + initRootCert(new FileInputStream(certPath)); + } + catch (FileNotFoundException e) { + LOG.info("RootCert is empty"); + } + + } + else if (LOG.isInfoEnabled()) { + LOG.info("RootCert is empty"); + } + } + + /** + * 加载根证书 + * + * @param cert 证书文件 + */ + public void initRootCert(InputStream cert) { + if (null != cert) { + rootKeyCert = initCert(cert); + if (LOG.isInfoEnabled()) { + LOG.info("Load RootCert Successful"); + } + } + else if (LOG.isInfoEnabled()) { + LOG.info("RootCert is empty"); + } + } + + /** + * 获取公钥/中级证书 + * + * @return X509Certificate + */ + public X509Certificate getPublicCert() { + return publicKeyCert; + } + + /** + * 获取中级证书 + * + * @return X509Certificate + */ + public X509Certificate getRootCert() { + return rootKeyCert; + } + } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/SecureUtil.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/SecureUtil.java deleted file mode 100644 index 096442b96647d2e7c063f1321ebf448eed0e4e4b..0000000000000000000000000000000000000000 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/SecureUtil.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.egzosn.pay.common.util.sign; - -import com.egzosn.pay.common.bean.result.PayException; -import com.egzosn.pay.common.exception.PayErrorException; -import com.egzosn.pay.common.util.sign.encrypt.sm3.SM3Digest; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.PublicKey; -import java.security.Signature; - -public class SecureUtil { - //日志 - protected static final Log log = LogFactory.getLog(SecureUtil.class); - /** - * 算法常量: SHA1 - */ - private static final String ALGORITHM_SHA1 = "SHA-1"; - /** - * 算法常量: SHA256 - */ - private static final String ALGORITHM_SHA256 = "SHA-256"; - /** - * 算法常量:SHA1withRSA - */ - private static final String BC_PROV_ALGORITHM_SHA1RSA = "SHA1withRSA"; - /** - * 算法常量:SHA256withRSA - */ - private static final String BC_PROV_ALGORITHM_SHA256RSA = "SHA256withRSA"; - - /** - * 获取摘要 - * - * @param data 待计算的数据 - * @param algorithm 算法名 - * @return 计算结果 - */ - private static byte[] digestByData (byte[] data,String algorithm) { - MessageDigest md = null; - try { - md = MessageDigest.getInstance(algorithm); - md.reset(); - md.update(data); - return md.digest(); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - /** - * sha1计算后进行16进制转换 - * - * @param data 待计算的数据 - * @param encoding 编码 - * @return 计算结果 - */ - public static byte[] sha1X16 (String data, String encoding) { - try { - byte[] bytes = digestByData(data.getBytes(encoding),ALGORITHM_SHA1); - StringBuilder sha1StrBuff = new StringBuilder(); - for (int i = 0; i < bytes.length; i++) { - if (Integer.toHexString(0xFF & bytes[i]).length() == 1) { - sha1StrBuff.append("0").append( - Integer.toHexString(0xFF & bytes[i])); - } else { - sha1StrBuff.append(Integer.toHexString(0xFF & bytes[i])); - } - } - return sha1StrBuff.toString().getBytes(encoding); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - return null; - } - } - - - - /** - * sha256计算后进行16进制转换 - * - * @param data - * 待计算的数据 - * @param encoding - * 编码 - * @return 计算结果 - */ - public static String sha256X16Str(String data, String encoding) { - byte[] bytes =null; - try { - bytes = digestByData(data.getBytes(encoding),ALGORITHM_SHA1); - } catch (UnsupportedEncodingException e) { - throw new PayErrorException(new PayException("error", e.getLocalizedMessage())); - } - StringBuilder sha256StrBuff = new StringBuilder(); - for (int i = 0; i < bytes.length; i++) { - if (Integer.toHexString(0xFF & bytes[i]).length() == 1) { - sha256StrBuff.append("0").append( - Integer.toHexString(0xFF & bytes[i])); - } else { - sha256StrBuff.append(Integer.toHexString(0xFF & bytes[i])); - } - } - return sha256StrBuff.toString(); - } - - /** - * SM3计算. - * - * @param data - * 待计算的数据 - * @return 计算结果 - */ - private static byte[] sm3(byte[] data) { - - SM3Digest sm3 = new SM3Digest(); - sm3.update(data, 0, data.length); - byte[] result = new byte[sm3.getDigestSize()]; - sm3.doFinal(result, 0); - return result; - } - - /** - * sm3计算后进行16进制转换 - * - * @param data - * 待计算的数据 - * @param encoding - * 编码 - * @return 计算结果 - */ - public static String sm3X16Str(String data, String encoding) { - byte[] bytes = new byte[new SM3Digest().getDigestSize()]; - try { - bytes = SecureUtil.sm3(data.getBytes(encoding)); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - StringBuilder sm3StrBuff = new StringBuilder(); - for (int i = 0; i < bytes.length; i++) { - if (Integer.toHexString(0xFF & bytes[i]).length() == 1) { - sm3StrBuff.append("0").append( - Integer.toHexString(0xFF & bytes[i])); - } else { - sm3StrBuff.append(Integer.toHexString(0xFF & bytes[i])); - } - } - return sm3StrBuff.toString(); - } - - public static boolean validateSignBySoft256(PublicKey publicKey, byte[] signData, byte[] srcData) throws Exception { - - Signature st = Signature.getInstance(BC_PROV_ALGORITHM_SHA256RSA, "BC"); - st.initVerify(publicKey); - st.update(srcData); - return st.verify(signData); - } - - public static boolean validateSignBySoft(PublicKey publicKey, - byte[] signData, byte[] srcData) throws Exception { - Signature st = Signature.getInstance(BC_PROV_ALGORITHM_SHA1RSA, "BC"); - st.initVerify(publicKey); - st.update(srcData); - return st.verify(signData); - } -} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/SignTextUtils.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/SignTextUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..dfe94dd9cd8073a6484b4fdaf2572ce8f4e954c9 --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/SignTextUtils.java @@ -0,0 +1,182 @@ +package com.egzosn.pay.common.util.sign; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; + +import org.apache.http.message.BasicNameValuePair; + +import com.egzosn.pay.common.util.str.StringUtils; + +/** + * 签名文本构建工具 + * + * @author Egan + * email egzosn@gmail.com + * date 2021/8/1 + */ +public final class SignTextUtils { + + private SignTextUtils() { + } + + + /** + * 把数组所有元素排序,并按照“参数=参数值”的模式用“@param separator”字符拼接成字符串 + * + * @param parameters 参数 + * @return 去掉空值与签名参数后的新签名,拼接后字符串 + */ + public static String parameterText(Map parameters) { + return parameterText(parameters, "&"); + + } + + /** + * 把数组所有元素排序,并按照“参数=参数值”的模式用“@param separator”字符拼接成字符串 + * + * @param parameters 参数 + * @param separator 分隔符 + * @return 去掉空值与签名参数后的新签名,拼接后字符串 + */ + public static String parameterText(Map parameters, String separator) { + return parameterText(parameters, separator, "signature", "sign", "key", "sign_type"); + } + + /** + * 把数组所有元素排序,并按照“参数=参数值”的模式用“@param separator”字符拼接成字符串 + * + * @param parameters 参数 + * @param separator 分隔符 + * @param ignoreKey 需要忽略添加的key + * @return 去掉空值与签名参数后的新签名,拼接后字符串 + */ + public static String parameterText(Map parameters, String separator, String... ignoreKey) { + return parameterText(parameters, separator, true, ignoreKey); + } + + /** + * 把数组所有元素排序,并按照“参数=参数值”的模式用“@param separator”字符拼接成字符串 + * + * @param parameters 参数 + * @param separator 分隔符 + * @param ignoreNullValue 需要忽略NULL值 + * @param ignoreKey 需要忽略添加的key + * @return 去掉空值与签名参数后的新签名,拼接后字符串 + */ + public static String parameterText(Map parameters, String separator, boolean ignoreNullValue, String... ignoreKey) { + if (parameters == null) { + return ""; + } + + if (null != ignoreKey) { + Arrays.sort(ignoreKey); + } + StringBuffer sb = new StringBuffer(); + // TODO 2016/11/11 10:14 author: egan 已经排序好处理 + if (parameters instanceof SortedMap) { + for (Map.Entry entry : parameters.entrySet()) { + Object v = entry.getValue(); + if (null == v) { + continue; + } + String valStr = v.toString().trim(); + if ("".equals(valStr) || (null != ignoreKey && Arrays.binarySearch(ignoreKey, entry.getKey()) >= 0)) { + continue; + } + sb.append(entry.getKey()).append("=").append(valStr).append(separator); + } + if (sb.length() > 0 && !"".equals(separator)) { + sb.deleteCharAt(sb.length() - 1); + } + return sb.toString(); + + } + + return sortMapParameterText(parameters, separator, ignoreNullValue, ignoreKey); + + } + + + private static String sortMapParameterText(Map parameters, String separator, boolean ignoreNullValue, String... ignoreKey) { + StringBuffer sb = new StringBuffer(); + // TODO 2016/11/11 10:14 author: egan 未排序须处理 + List keys = new ArrayList(parameters.keySet()); + //排序 + Collections.sort(keys); + for (String k : keys) { + String valueStr = ""; + Object o = parameters.get(k); + if (ignoreNullValue && null == o) { + continue; + } + if (o instanceof String[]) { + String[] values = (String[]) o; + + for (int i = 0; i < values.length; i++) { + String value = values[i].trim(); + if ("".equals(value)) { + continue; + } + valueStr += (i == values.length - 1) ? value : value + ","; + } + } + else { + valueStr = o.toString(); + } + if (StringUtils.isBlank(valueStr) || (null != ignoreKey && Arrays.binarySearch(ignoreKey, k) >= 0)) { + continue; + } + sb.append(k).append("=").append(valueStr).append(separator); + } + if (sb.length() > 0) { + sb.deleteCharAt(sb.length() - 1); + } + return sb.toString(); + } + + /** + * 将参数集合(事前做好排序)按分割符号拼凑字符串并加密为MD5 + * example: mchnt_cd+"|" +order_id+"|"+order_amt+"|"+order_pay_type+"|"+page_notify_url+"|"+back_notify_url+"|"+order_valid_time+"|"+iss_ins_cd+"|"+goods_name+"|"+"+goods_display_url+"|"+rem+"|"+ver+"|"+mchnt_key + * + * @param parameters 参数集合 + * @param separator 分隔符 + * @return 参数排序好的值 + */ + public static String parameters2Md5Str(Object parameters, String separator) { + StringBuffer sb = new StringBuffer(); + + if (parameters instanceof LinkedHashMap) { + Set keys = (Set) ((LinkedHashMap) parameters).keySet(); + for (String key : keys) { + String val = ((LinkedHashMap) parameters).get(key).toString(); + sb.append(val).append(separator); + + } + } + else if (parameters instanceof List) { + for (BasicNameValuePair bnv : ((List) parameters)) { + sb.append(bnv.getValue()).append(separator); + } + } + + return StringUtils.isBlank(sb.toString()) ? "" : sb.deleteCharAt(sb.length() - 1).toString(); + } + + + /** + * 获取随机字符串 + * + * @return 随机字符串 + */ + public static String randomStr() { + return StringUtils.randomStr(); + } + + +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/SignUtils.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/SignUtils.java index 3d13bf5b676124f8b68dbd44f0c2a65140e159d3..7d6aaa36c14638c980fee236e45ab804b66d96de 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/SignUtils.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/SignUtils.java @@ -1,28 +1,26 @@ package com.egzosn.pay.common.util.sign; -import com.egzosn.pay.common.bean.result.PayException; -import com.egzosn.pay.common.exception.PayErrorException; -import com.egzosn.pay.common.util.str.StringUtils; -import org.apache.http.message.BasicNameValuePair; +import java.security.Security; +import java.util.Map; -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import java.io.UnsupportedEncodingException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.*; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import static com.egzosn.pay.common.util.sign.SignTextUtils.parameterText; + +import com.egzosn.pay.common.bean.SignType; +import com.egzosn.pay.common.util.sign.encrypt.HmacSha256; /** * 签名 工具 * - * @author: egan + * @author egan *

  * email egzosn@gmail.com
  * date 2016/11/9 17:45
  * 
*/ -public enum SignUtils { +public enum SignUtils implements SignType { MD5 { /** @@ -50,7 +48,12 @@ public enum SignUtils { public boolean verify(String text, String sign, String key, String characterEncoding) { return com.egzosn.pay.common.util.sign.encrypt.MD5.verify(text, sign, key, characterEncoding); } - },HMACSHA256{ + }, HMACSHA256 { + @Override + public String getName() { + return "HMAC-SHA256"; + } + /** * 签名 * @@ -62,26 +65,7 @@ public enum SignUtils { */ @Override public String createSign(String content, String key, String characterEncoding) { - Mac sha256HMAC = null; - try { - sha256HMAC = Mac.getInstance("HmacSHA256"); - SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(characterEncoding), "HmacSHA256"); - sha256HMAC.init(secretKey); - byte[] array = sha256HMAC.doFinal(content.getBytes(characterEncoding)); - StringBuilder sb = new StringBuilder(); - for (byte item : array) { - sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3)); - } - return sb.toString().toUpperCase(); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - } catch (InvalidKeyException e) { - e.printStackTrace(); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - - throw new PayErrorException(new PayException("fail", "HMACSHA256 签名异常")); + return HmacSha256.createSign(content, key, characterEncoding); } /** @@ -157,189 +141,68 @@ public enum SignUtils { } }; - /** - * - * 把数组所有元素排序,并按照“参数=参数值”的模式用“@param separator”字符拼接成字符串 - * @param parameters 参数 - * @return 去掉空值与签名参数后的新签名,拼接后字符串 - */ - public static String parameterText(Map parameters) { - return parameterText(parameters, "&"); - - } - - /** - * - * 把数组所有元素排序,并按照“参数=参数值”的模式用“@param separator”字符拼接成字符串 - * @param parameters 参数 - * @param separator 分隔符 - * @return 去掉空值与签名参数后的新签名,拼接后字符串 - */ - public static String parameterText(Map parameters, String separator) { - return parameterText(parameters, separator, "signature", "sign", "key", "sign_type"); - } - - /** - * - * 把数组所有元素排序,并按照“参数=参数值”的模式用“@param separator”字符拼接成字符串 - * @param parameters 参数 - * @param separator 分隔符 - * @param ignoreKey 需要忽略添加的key - * @return 去掉空值与签名参数后的新签名,拼接后字符串 - */ - public static String parameterText(Map parameters, String separator, String... ignoreKey ) { - if(parameters == null){ - return ""; - } - StringBuffer sb = new StringBuffer(); - if (null != ignoreKey){ - Arrays.sort(ignoreKey); - } - // TODO 2016/11/11 10:14 author: egan 已经排序好处理 - if (parameters instanceof SortedMap) { - for (Map.Entry entry : (Set>)parameters.entrySet()) { - Object v = entry.getValue(); - if (null == v || "".equals(v.toString().trim()) || (null != ignoreKey && Arrays.binarySearch(ignoreKey, entry.getKey() ) >= 0)) { - continue; - } - sb.append(entry.getKey() ).append("=").append( v.toString().trim()).append(separator); - } - if (sb.length() > 0 && !"".equals(separator)) { - sb.deleteCharAt(sb.length() - 1); - } - return sb.toString(); - } - - // TODO 2016/11/11 10:14 author: egan 未排序须处理 - List keys = new ArrayList(parameters.keySet()); - //排序 - Collections.sort(keys); - for (String k : keys) { - String valueStr = ""; - Object o = parameters.get(k); - if (null == o) { - continue; - } - if (o instanceof String[]) { - String[] values = (String[]) o; - - for (int i = 0; i < values.length; i++) { - String value = values[i].trim(); - if ("".equals(value)){ continue;} - valueStr += (i == values.length - 1) ? value : value + ","; - } - } else { - valueStr = o.toString(); - } - if (null == valueStr || "".equals(valueStr.toString().trim()) || (null != ignoreKey && Arrays.binarySearch(ignoreKey, k ) >= 0)) { - continue; - } - sb.append(k ).append("=").append( valueStr).append(separator); - } - if (sb.length() > 0) { - sb.deleteCharAt(sb.length() - 1); - } - return sb.toString(); - } - - /** - * 将参数集合(事前做好排序)按分割符号拼凑字符串并加密为MD5 - * example: mchnt_cd+"|" +order_id+"|"+order_amt+"|"+order_pay_type+"|"+page_notify_url+"|"+back_notify_url+"|"+order_valid_time+"|"+iss_ins_cd+"|"+goods_name+"|"+"+goods_display_url+"|"+rem+"|"+ver+"|"+mchnt_key - * @param parameters 参数集合 - * @param separator 分隔符 - * @return 参数排序好的值 - */ - public static String parameters2MD5Str(Object parameters, String separator){ - StringBuffer sb = new StringBuffer(); - - if (parameters instanceof LinkedHashMap) { - Set keys = (Set) ((LinkedHashMap)parameters).keySet(); - for(String key : keys){ - String val = ((LinkedHashMap)parameters).get(key).toString(); - sb.append(val).append(separator); - - } - }else if(parameters instanceof List){ - for(BasicNameValuePair bnv :((List)parameters) ){ - sb.append(bnv.getValue()).append(separator); - } - } - - return StringUtils.isBlank(sb.toString())?"":sb.deleteCharAt(sb.length() - 1).toString(); - } - - - /** - * 获取随机字符串 - * @return 随机字符串 - */ - public static String randomStr(){ - return UUID.randomUUID().toString().replace("-", ""); + @Override + public String getName() { + return this.name(); } /** * 签名 * - * @param parameters 需要进行排序签名的参数 - * @param key 密钥 + * @param parameters 需要进行排序签名的参数 + * @param key 密钥 * @param characterEncoding 编码格式 * @return 签名值 */ - public String sign(Map parameters, String key, String characterEncoding) { + @Override + public String sign(Map parameters, String key, String characterEncoding) { return createSign(parameterText(parameters, "&"), key, characterEncoding); } + /** * 签名 - * @param parameters 需要进行排序签名的参数 - * @param key 密钥 - * @param separator 分隔符 默认 & + * + * @param parameters 需要进行排序签名的参数 + * @param key 密钥 + * @param separator 分隔符 默认 & * @param characterEncoding 编码格式 * @return 签名值 */ - public String sign(Map parameters, String key, String separator, String characterEncoding) { + @Override + public String sign(Map parameters, String key, String separator, String characterEncoding) { return createSign(parameterText(parameters, separator), key, characterEncoding); } - /** - * 签名 - * - * @param content 需要签名的内容 - * @param key 密钥 - * @param characterEncoding 字符编码 - * @return 签名值 - */ - public abstract String createSign(String content, String key, String characterEncoding); /** * 签名字符串 * - * @param params 需要签名的字符串 + * @param params 需要签名的字符串 * @param sign 签名结果 * @param key 密钥 * @param characterEncoding 编码格式 * @return 签名结果 */ - public boolean verify(Map params, String sign, String key, String characterEncoding){ + @Override + public boolean verify(Map params, String sign, String key, String characterEncoding) { //判断是否一样 return this.verify(parameterText(params), sign, key, characterEncoding); } - /** - * 签名字符串 - * - * @param text 需要签名的字符串 - * @param sign 签名结果 - * @param key 密钥 - * @param characterEncoding 编码格式 - * @return 签名结果 + * 初始化BC */ - public abstract boolean verify(String text, String sign, String key, String characterEncoding); + public static void initBc() { + if (null == Security.getProvider("BC")) { + Security.removeProvider("SunEC"); + Security.addProvider(new BouncyCastleProvider()); + } + } } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/AES.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/AES.java new file mode 100644 index 0000000000000000000000000000000000000000..9d9bb7ee92757d9916e1bd6c690c49f87de08f9c --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/AES.java @@ -0,0 +1,75 @@ +package com.egzosn.pay.common.util.sign.encrypt; + +import java.io.IOException; +import java.security.GeneralSecurityException; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.codec.digest.DigestUtils; + +import com.egzosn.pay.common.util.sign.SignUtils; + +/** + * AES 加解密 + * + * @author Egan + *
+ *  email egan@egzosn.com
+ *  date 2022/3/20
+ *  
+ */ +public class AES { + /** + * 密钥算法 + */ + private static final String ALGORITHM = "AES"; + /** + * 加解密算法/工作模式/填充方式 + */ + private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS7Padding"; + + static { + SignUtils.initBc(); + } + + + /** + * 解密 + * + * @param content 密文 + * @param privateKey 商户私钥 + * @param characterEncoding 编码格式 + * @return 解密后的字符串 + * @throws GeneralSecurityException 解密异常 + * @throws IOException IOException + */ + public static String decrypt(String content, String privateKey, String characterEncoding) throws GeneralSecurityException, IOException { + byte[] reqInfoB = Base64.decode(content); + String key$ = DigestUtils.md5Hex(privateKey).toLowerCase(); + Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC"); + SecretKeySpec secretKeySpec = new SecretKeySpec(key$.getBytes(), ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); + return new String(cipher.doFinal(reqInfoB), characterEncoding); + } + + /** + * 解密 + * + * @param content 密文 + * @param privateKey 商户私钥 + * @param characterEncoding 编码格式 + * @return 解密后的字符串 + * @throws GeneralSecurityException 解密异常 + * @throws IOException IOException + */ + public static String encrypt(String content, String privateKey, String characterEncoding) throws GeneralSecurityException, IOException { + String key$ = DigestUtils.md5Hex(privateKey).toLowerCase(); + Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC"); + SecretKeySpec secretKeySpec = new SecretKeySpec(key$.getBytes(), ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); + byte[] doFinal = cipher.doFinal(content.getBytes(characterEncoding)); + return Base64.encode(doFinal); + } + +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/Base64.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/Base64.java index 0d40b16cd03b2cfd9f8b31a91389ef7b15e0e6cd..243b915a7dff7276a4e14b55600b8c75abcedbee 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/Base64.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/Base64.java @@ -1,1053 +1,25 @@ -/* - * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. - * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - */ - -package com.egzosn.pay.common.util.sign.encrypt; -import java.io.FilterOutputStream; -import java.io.InputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Objects; +package com.egzosn.pay.common.util.sign.encrypt; /** - * This class consists exclusively of static methods for obtaining - * encoders and decoders for the Base64 encoding scheme. The - * implementation of this class supports the following types of Base64 - * as specified in - * RFC 4648 and - * RFC 2045. - * - *
    - *
  • Basic - *

    Uses "The Base64 Alphabet" as specified in Table 1 of - * RFC 4648 and RFC 2045 for encoding and decoding operation. - * The encoder does not add any line feed (line separator) - * character. The decoder rejects data that contains characters - * outside the base64 alphabet.

  • - * - *
  • URL and Filename safe - *

    Uses the "URL and Filename safe Base64 Alphabet" as specified - * in Table 2 of RFC 4648 for encoding and decoding. The - * encoder does not add any line feed (line separator) character. - * The decoder rejects data that contains characters outside the - * base64 alphabet.

  • + * Base64 + * @author egan + *
    + * email egzosn@gmail.com
      *
    - * 
  • MIME - *

    Uses the "The Base64 Alphabet" as specified in Table 1 of - * RFC 2045 for encoding and decoding operation. The encoded output - * must be represented in lines of no more than 76 characters each - * and uses a carriage return {@code '\r'} followed immediately by - * a linefeed {@code '\n'} as the line separator. No line separator - * is added to the end of the encoded output. All line separators - * or other characters not found in the base64 alphabet table are - * ignored in decoding operation.

  • - *
- * - *

Unless otherwise noted, passing a {@code null} argument to a - * method of this class will cause a {@link java.lang.NullPointerException - * NullPointerException} to be thrown. - * - * @author Xueming Shen - * @since 1.8 + * create 2019/05/15 12:50 + * */ - public class Base64 { private Base64() {} - /** - * Encodes hex octects into Base64 - * - * @param binaryData Array containing binaryData - * @return Encoded Base64 array - */ - public static String encode(byte[] binaryData) { - return Base64.getEncoder().encodeToString(binaryData); - } - - /** - * Decodes Base64 data into octects - * - * @param encoded string containing Base64 data - * @return Array containind decoded data. - */ - public static byte[] decode(String encoded) { - return Base64.getDecoder().decode(encoded); - } - - - /** - * Returns a {@link Encoder} that encodes using the - * Basic type base64 encoding scheme. - * - * @return A Base64 encoder. - */ - public static Encoder getEncoder() { - return Encoder.RFC4648; - } - /** - * Returns a {@link Encoder} that encodes using the - * URL and Filename safe type base64 - * encoding scheme. - * - * @return A Base64 encoder. - */ - public static Encoder getUrlEncoder() { - return Encoder.RFC4648_URLSAFE; + public static byte[] decode(String str) { + return org.apache.commons.codec.binary.Base64.decodeBase64(str); } - /** - * Returns a {@link Encoder} that encodes using the - * MIME type base64 encoding scheme. - * - * @return A Base64 encoder. - */ - public static Encoder getMimeEncoder() { - return Encoder.RFC2045; + public static String encode(byte[] bytes) { + return org.apache.commons.codec.binary.Base64.encodeBase64String(bytes); } - /** - * Returns a {@link Encoder} that encodes using the - * MIME type base64 encoding scheme - * with specified line length and line separators. - * - * @param lineLength - * the length of each output line (rounded down to nearest multiple - * of 4). If {@code lineLength <= 0} the output will not be separated - * in lines - * @param lineSeparator - * the line separator for each output line - * - * @return A Base64 encoder. - * - * @throws IllegalArgumentException if {@code lineSeparator} includes any - * character of "The Base64 Alphabet" as specified in Table 1 of - * RFC 2045. - */ - public static Encoder getMimeEncoder(int lineLength, byte[] lineSeparator) { - Objects.requireNonNull(lineSeparator); - int[] base64 = Decoder.fromBase64; - for (byte b : lineSeparator) { - if (base64[b & 0xff] != -1){ - throw new IllegalArgumentException( - "Illegal base64 line separator character 0x" + Integer.toString(b, 16)); - } - } - if (lineLength <= 0) { - return Encoder.RFC4648; - } - return new Encoder(false, lineSeparator, lineLength >> 2 << 2, true); - } - - /** - * Returns a {@link Decoder} that decodes using the - * Basic type base64 encoding scheme. - * - * @return A Base64 decoder. - */ - public static Decoder getDecoder() { - return Decoder.RFC4648; - } - - /** - * Returns a {@link Decoder} that decodes using the - * URL and Filename safe type base64 - * encoding scheme. - * - * @return A Base64 decoder. - */ - public static Decoder getUrlDecoder() { - return Decoder.RFC4648_URLSAFE; - } - - /** - * Returns a {@link Decoder} that decodes using the - * MIME type base64 decoding scheme. - * - * @return A Base64 decoder. - */ - public static Decoder getMimeDecoder() { - return Decoder.RFC2045; - } - - /** - * This class implements an encoder for encoding byte data using - * the Base64 encoding scheme as specified in RFC 4648 and RFC 2045. - * - *

Instances of {@link Encoder} class are safe for use by - * multiple concurrent threads. - * - *

Unless otherwise noted, passing a {@code null} argument to - * a method of this class will cause a - * {@link java.lang.NullPointerException NullPointerException} to - * be thrown. - * - * @see Decoder - * @since 1.8 - */ - public static class Encoder { - - private final byte[] newline; - private final int linemax; - private final boolean isURL; - private final boolean doPadding; - - private Encoder(boolean isURL, byte[] newline, int linemax, boolean doPadding) { - this.isURL = isURL; - this.newline = newline; - this.linemax = linemax; - this.doPadding = doPadding; - } - - /** - * This array is a lookup table that translates 6-bit positive integer - * index values into their "Base64 Alphabet" equivalents as specified - * in "Table 1: The Base64 Alphabet" of RFC 2045 (and RFC 4648). - */ - private static final char[] toBase64 = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' - }; - - /** - * It's the lookup table for "URL and Filename safe Base64" as specified - * in Table 2 of the RFC 4648, with the '+' and '/' changed to '-' and - * '_'. This table is used when BASE64_URL is specified. - */ - private static final char[] toBase64URL = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' - }; - - private static final int MIMELINEMAX = 76; - private static final byte[] CRLF = new byte[] {'\r', '\n'}; - - static final Encoder RFC4648 = new Encoder(false, null, -1, true); - static final Encoder RFC4648_URLSAFE = new Encoder(true, null, -1, true); - static final Encoder RFC2045 = new Encoder(false, CRLF, MIMELINEMAX, true); - - private final int outLength(int srclen) { - int len = 0; - if (doPadding) { - len = 4 * ((srclen + 2) / 3); - } else { - int n = srclen % 3; - len = 4 * (srclen / 3) + (n == 0 ? 0 : n + 1); - } - if (linemax > 0) { // line separators - len += (len - 1) / linemax * newline.length; - } - return len; - } - - /** - * Encodes all bytes from the specified byte array into a newly-allocated - * byte array using the {@link Base64} encoding scheme. The returned byte - * array is of the length of the resulting bytes. - * - * @param src - * the byte array to encode - * @return A newly-allocated byte array containing the resulting - * encoded bytes. - */ - public byte[] encode(byte[] src) { - int len = outLength(src.length); // dst array size - byte[] dst = new byte[len]; - int ret = encode0(src, 0, src.length, dst); - if (ret != dst.length) { - return Arrays.copyOf(dst, ret); - } - return dst; - } - - /** - * Encodes all bytes from the specified byte array using the - * {@link Base64} encoding scheme, writing the resulting bytes to the - * given output byte array, starting at offset 0. - * - *

It is the responsibility of the invoker of this method to make - * sure the output byte array {@code dst} has enough space for encoding - * all bytes from the input byte array. No bytes will be written to the - * output byte array if the output byte array is not big enough. - * - * @param src - * the byte array to encode - * @param dst - * the output byte array - * @return The number of bytes written to the output byte array - * - * @throws IllegalArgumentException if {@code dst} does not have enough - * space for encoding all input bytes. - */ - public int encode(byte[] src, byte[] dst) { - int len = outLength(src.length); // dst array size - if (dst.length < len) { - throw new IllegalArgumentException( - "Output byte array is too small for encoding all input bytes"); - } - return encode0(src, 0, src.length, dst); - } - - /** - * Encodes the specified byte array into a String using the {@link Base64} - * encoding scheme. - * - *

This method first encodes all input bytes into a base64 encoded - * byte array and then constructs a new String by using the encoded byte - * array and the {@link java.nio.charset.StandardCharsets#ISO_8859_1 - * ISO-8859-1} charset. - * - *

In other words, an invocation of this method has exactly the same - * effect as invoking - * {@code new String(encode(src), StandardCharsets.ISO_8859_1)}. - * - * @param src - * the byte array to encode - * @return A String containing the resulting Base64 encoded characters - */ - @SuppressWarnings("deprecation") - public String encodeToString(byte[] src) { - byte[] encoded = encode(src); - return new String(encoded, 0, 0, encoded.length); - } - - /** - * Encodes all remaining bytes from the specified byte buffer into - * a newly-allocated ByteBuffer using the {@link Base64} encoding - * scheme. - * - * Upon return, the source buffer's position will be updated to - * its limit; its limit will not have been changed. The returned - * output buffer's position will be zero and its limit will be the - * number of resulting encoded bytes. - * - * @param buffer - * the source ByteBuffer to encode - * @return A newly-allocated byte buffer containing the encoded bytes. - */ - public ByteBuffer encode(ByteBuffer buffer) { - int len = outLength(buffer.remaining()); - byte[] dst = new byte[len]; - int ret = 0; - if (buffer.hasArray()) { - ret = encode0(buffer.array(), - buffer.arrayOffset() + buffer.position(), - buffer.arrayOffset() + buffer.limit(), - dst); - buffer.position(buffer.limit()); - } else { - byte[] src = new byte[buffer.remaining()]; - buffer.get(src); - ret = encode0(src, 0, src.length, dst); - } - if (ret != dst.length) { - dst = Arrays.copyOf(dst, ret); - } - return ByteBuffer.wrap(dst); - } - - /** - * Wraps an output stream for encoding byte data using the {@link Base64} - * encoding scheme. - * - *

It is recommended to promptly close the returned output stream after - * use, during which it will flush all possible leftover bytes to the underlying - * output stream. Closing the returned output stream will close the underlying - * output stream. - * - * @param os - * the output stream. - * @return the output stream for encoding the byte data into the - * specified Base64 encoded format - */ - public OutputStream wrap(OutputStream os) { - Objects.requireNonNull(os); - return new EncOutputStream(os, isURL ? toBase64URL : toBase64, - newline, linemax, doPadding); - } - - /** - * Returns an encoder instance that encodes equivalently to this one, - * but without adding any padding character at the end of the encoded - * byte data. - * - *

The encoding scheme of this encoder instance is unaffected by - * this invocation. The returned encoder instance should be used for - * non-padding encoding operation. - * - * @return an equivalent encoder that encodes without adding any - * padding character at the end - */ - public Encoder withoutPadding() { - if (!doPadding) { - return this; - } - return new Encoder(isURL, newline, linemax, false); - } - - private int encode0(byte[] src, int off, int end, byte[] dst) { - char[] base64 = isURL ? toBase64URL : toBase64; - int sp = off; - int slen = (end - off) / 3 * 3; - int sl = off + slen; - if (linemax > 0 && slen > linemax / 4 * 3){ - slen = linemax / 4 * 3; - } - int dp = 0; - while (sp < sl) { - int sl0 = Math.min(sp + slen, sl); - for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) { - int bits = (src[sp0++] & 0xff) << 16 | - (src[sp0++] & 0xff) << 8 | - (src[sp0++] & 0xff); - dst[dp0++] = (byte)base64[(bits >>> 18) & 0x3f]; - dst[dp0++] = (byte)base64[(bits >>> 12) & 0x3f]; - dst[dp0++] = (byte)base64[(bits >>> 6) & 0x3f]; - dst[dp0++] = (byte)base64[bits & 0x3f]; - } - int dlen = (sl0 - sp) / 3 * 4; - dp += dlen; - sp = sl0; - if (dlen == linemax && sp < end) { - for (byte b : newline){ - dst[dp++] = b; - } - } - } - if (sp < end) { // 1 or 2 leftover bytes - int b0 = src[sp++] & 0xff; - dst[dp++] = (byte)base64[b0 >> 2]; - if (sp == end) { - dst[dp++] = (byte)base64[(b0 << 4) & 0x3f]; - if (doPadding) { - dst[dp++] = '='; - dst[dp++] = '='; - } - } else { - int b1 = src[sp++] & 0xff; - dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)]; - dst[dp++] = (byte)base64[(b1 << 2) & 0x3f]; - if (doPadding) { - dst[dp++] = '='; - } - } - } - return dp; - } - } - - /** - * This class implements a decoder for decoding byte data using the - * Base64 encoding scheme as specified in RFC 4648 and RFC 2045. - * - *

The Base64 padding character {@code '='} is accepted and - * interpreted as the end of the encoded byte data, but is not - * required. So if the final unit of the encoded byte data only has - * two or three Base64 characters (without the corresponding padding - * character(s) padded), they are decoded as if followed by padding - * character(s). If there is a padding character present in the - * final unit, the correct number of padding character(s) must be - * present, otherwise {@code IllegalArgumentException} ( - * {@code IOException} when reading from a Base64 stream) is thrown - * during decoding. - * - *

Instances of {@link Decoder} class are safe for use by - * multiple concurrent threads. - * - *

Unless otherwise noted, passing a {@code null} argument to - * a method of this class will cause a - * {@link java.lang.NullPointerException NullPointerException} to - * be thrown. - * - * @see Encoder - * @since 1.8 - */ - public static class Decoder { - - private final boolean isURL; - private final boolean isMIME; - - private Decoder(boolean isURL, boolean isMIME) { - this.isURL = isURL; - this.isMIME = isMIME; - } - - /** - * Lookup table for decoding unicode characters drawn from the - * "Base64 Alphabet" (as specified in Table 1 of RFC 2045) into - * their 6-bit positive integer equivalents. Characters that - * are not in the Base64 alphabet but fall within the bounds of - * the array are encoded to -1. - * - */ - private static final int[] fromBase64 = new int[256]; - static { - Arrays.fill(fromBase64, -1); - for (int i = 0; i < Encoder.toBase64.length; i++){ - fromBase64[Encoder.toBase64[i]] = i; - } - fromBase64['='] = -2; - } - - /** - * Lookup table for decoding "URL and Filename safe Base64 Alphabet" - * as specified in Table2 of the RFC 4648. - */ - private static final int[] fromBase64URL = new int[256]; - - static { - Arrays.fill(fromBase64URL, -1); - for (int i = 0; i < Encoder.toBase64URL.length; i++){ - fromBase64URL[Encoder.toBase64URL[i]] = i; - } - fromBase64URL['='] = -2; - } - - static final Decoder RFC4648 = new Decoder(false, false); - static final Decoder RFC4648_URLSAFE = new Decoder(true, false); - static final Decoder RFC2045 = new Decoder(false, true); - - /** - * Decodes all bytes from the input byte array using the {@link Base64} - * encoding scheme, writing the results into a newly-allocated output - * byte array. The returned byte array is of the length of the resulting - * bytes. - * - * @param src - * the byte array to decode - * - * @return A newly-allocated byte array containing the decoded bytes. - * - * @throws IllegalArgumentException - * if {@code src} is not in valid Base64 scheme - */ - public byte[] decode(byte[] src) { - byte[] dst = new byte[outLength(src, 0, src.length)]; - int ret = decode0(src, 0, src.length, dst); - if (ret != dst.length) { - dst = Arrays.copyOf(dst, ret); - } - return dst; - } - - /** - * Decodes a Base64 encoded String into a newly-allocated byte array - * using the {@link Base64} encoding scheme. - * - *

An invocation of this method has exactly the same effect as invoking - * {@code decode(src.getBytes(StandardCharsets.ISO_8859_1))} - * - * @param src - * the string to decode - * - * @return A newly-allocated byte array containing the decoded bytes. - * - * @throws IllegalArgumentException - * if {@code src} is not in valid Base64 scheme - */ - public byte[] decode(String src) { - if (null == src){ - return null; - } - src = src.replaceAll("[\r\n]", ""); - return decode(src.getBytes(StandardCharsets.ISO_8859_1)); - } - - /** - * Decodes all bytes from the input byte array using the {@link Base64} - * encoding scheme, writing the results into the given output byte array, - * starting at offset 0. - * - *

It is the responsibility of the invoker of this method to make - * sure the output byte array {@code dst} has enough space for decoding - * all bytes from the input byte array. No bytes will be be written to - * the output byte array if the output byte array is not big enough. - * - *

If the input byte array is not in valid Base64 encoding scheme - * then some bytes may have been written to the output byte array before - * IllegalargumentException is thrown. - * - * @param src - * the byte array to decode - * @param dst - * the output byte array - * - * @return The number of bytes written to the output byte array - * - * @throws IllegalArgumentException - * if {@code src} is not in valid Base64 scheme, or {@code dst} - * does not have enough space for decoding all input bytes. - */ - public int decode(byte[] src, byte[] dst) { - int len = outLength(src, 0, src.length); - if (dst.length < len) { - throw new IllegalArgumentException( - "Output byte array is too small for decoding all input bytes"); - } - return decode0(src, 0, src.length, dst); - } - - /** - * Decodes all bytes from the input byte buffer using the {@link Base64} - * encoding scheme, writing the results into a newly-allocated ByteBuffer. - * - *

Upon return, the source buffer's position will be updated to - * its limit; its limit will not have been changed. The returned - * output buffer's position will be zero and its limit will be the - * number of resulting decoded bytes - * - *

{@code IllegalArgumentException} is thrown if the input buffer - * is not in valid Base64 encoding scheme. The position of the input - * buffer will not be advanced in this case. - * - * @param buffer - * the ByteBuffer to decode - * - * @return A newly-allocated byte buffer containing the decoded bytes - * - * @throws IllegalArgumentException - * if {@code src} is not in valid Base64 scheme. - */ - public ByteBuffer decode(ByteBuffer buffer) { - int pos0 = buffer.position(); - try { - byte[] src; - int sp, sl; - if (buffer.hasArray()) { - src = buffer.array(); - sp = buffer.arrayOffset() + buffer.position(); - sl = buffer.arrayOffset() + buffer.limit(); - buffer.position(buffer.limit()); - } else { - src = new byte[buffer.remaining()]; - buffer.get(src); - sp = 0; - sl = src.length; - } - byte[] dst = new byte[outLength(src, sp, sl)]; - return ByteBuffer.wrap(dst, 0, decode0(src, sp, sl, dst)); - } catch (IllegalArgumentException iae) { - buffer.position(pos0); - throw iae; - } - } - - /** - * Returns an input stream for decoding {@link Base64} encoded byte stream. - * - *

The {@code read} methods of the returned {@code InputStream} will - * throw {@code IOException} when reading bytes that cannot be decoded. - * - *

Closing the returned input stream will close the underlying - * input stream. - * - * @param is - * the input stream - * - * @return the input stream for decoding the specified Base64 encoded - * byte stream - */ - public InputStream wrap(InputStream is) { - Objects.requireNonNull(is); - return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME); - } - - private int outLength(byte[] src, int sp, int sl) { - int[] base64 = isURL ? fromBase64URL : fromBase64; - int paddings = 0; - int len = sl - sp; - if (len == 0){ - return 0; - } - if (len < 2) { - if (isMIME && base64[0] == -1){ - return 0; - } - throw new IllegalArgumentException( - "Input byte[] should at least have 2 bytes for base64 bytes"); - } - if (isMIME) { - // scan all bytes to fill out all non-alphabet. a performance - // trade-off of pre-scan or Arrays.copyOf - int n = 0; - while (sp < sl) { - int b = src[sp++] & 0xff; - if (b == '=') { - len -= (sl - sp + 1); - break; - } - if ((b = base64[b]) == -1) { - n++; - } - } - len -= n; - } else { - if (src[sl - 1] == '=') { - paddings++; - if (src[sl - 2] == '=') { - paddings++; - } - } - } - if (paddings == 0 && (len & 0x3) != 0) { - paddings = 4 - (len & 0x3); - } - return 3 * ((len + 3) / 4) - paddings; - } - - private int decode0(byte[] src, int sp, int sl, byte[] dst) { - int[] base64 = isURL ? fromBase64URL : fromBase64; - int dp = 0; - int bits = 0; - int shiftto = 18; // pos of first byte of 4-byte atom - while (sp < sl) { - int b = src[sp++] & 0xff; - if ((b = base64[b]) < 0) { - if (b == -2) { // padding byte '=' - // = shiftto==18 unnecessary padding - // x= shiftto==12 a dangling single x - // x to be handled together with non-padding case - // xx= shiftto==6&&sp==sl missing last = - // xx=y shiftto==6 last is not = - if (shiftto == 6 && (sp == sl || src[sp++] != '=') || - shiftto == 18) { - throw new IllegalArgumentException( - "Input byte array has wrong 4-byte ending unit"); - } - break; - } - if (isMIME){ // skip if for rfc2045 - continue; - } - else{ - throw new IllegalArgumentException( - "Illegal base64 character " + - Integer.toString(src[sp - 1], 16)); - } - } - bits |= (b << shiftto); - shiftto -= 6; - if (shiftto < 0) { - dst[dp++] = (byte)(bits >> 16); - dst[dp++] = (byte)(bits >> 8); - dst[dp++] = (byte)(bits); - shiftto = 18; - bits = 0; - } - } - // reached end of byte array or hit padding '=' characters. - if (shiftto == 6) { - dst[dp++] = (byte)(bits >> 16); - } else if (shiftto == 0) { - dst[dp++] = (byte)(bits >> 16); - dst[dp++] = (byte)(bits >> 8); - } else if (shiftto == 12) { - // dangling single "x", incorrectly encoded. - throw new IllegalArgumentException( - "Last unit does not have enough valid bits"); - } - // anything left is invalid, if is not MIME. - // if MIME, ignore all non-base64 character - while (sp < sl) { - if (isMIME && base64[src[sp++]] < 0) { - continue; - } - throw new IllegalArgumentException( - "Input byte array has incorrect ending byte at " + sp); - } - return dp; - } - } - - /* - * An output stream for encoding bytes into the Base64. - */ - private static class EncOutputStream extends FilterOutputStream { - - private int leftover = 0; - private int b0, b1, b2; - private boolean closed = false; - - private final char[] base64; // byte->base64 mapping - private final byte[] newline; // line separator, if needed - private final int linemax; - private final boolean doPadding;// whether or not to pad - private int linepos = 0; - - EncOutputStream(OutputStream os, char[] base64, - byte[] newline, int linemax, boolean doPadding) { - super(os); - this.base64 = base64; - this.newline = newline; - this.linemax = linemax; - this.doPadding = doPadding; - } - - @Override - public void write(int b) throws IOException { - byte[] buf = new byte[1]; - buf[0] = (byte)(b & 0xff); - write(buf, 0, 1); - } - - private void checkNewline() throws IOException { - if (linepos == linemax) { - out.write(newline); - linepos = 0; - } - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - if (closed) { - throw new IOException("Stream is closed"); - } - if (off < 0 || len < 0 || off + len > b.length) { - throw new ArrayIndexOutOfBoundsException(); - } - if (len == 0) { - return; - } - if (leftover != 0) { - if (leftover == 1) { - b1 = b[off++] & 0xff; - len--; - if (len == 0) { - leftover++; - return; - } - } - b2 = b[off++] & 0xff; - len--; - checkNewline(); - out.write(base64[b0 >> 2]); - out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]); - out.write(base64[(b1 << 2) & 0x3f | (b2 >> 6)]); - out.write(base64[b2 & 0x3f]); - linepos += 4; - } - int nBits24 = len / 3; - leftover = len - (nBits24 * 3); - while (nBits24-- > 0) { - checkNewline(); - int bits = (b[off++] & 0xff) << 16 | - (b[off++] & 0xff) << 8 | - (b[off++] & 0xff); - out.write(base64[(bits >>> 18) & 0x3f]); - out.write(base64[(bits >>> 12) & 0x3f]); - out.write(base64[(bits >>> 6) & 0x3f]); - out.write(base64[bits & 0x3f]); - linepos += 4; - } - if (leftover == 1) { - b0 = b[off++] & 0xff; - } else if (leftover == 2) { - b0 = b[off++] & 0xff; - b1 = b[off++] & 0xff; - } - } - - @Override - public void close() throws IOException { - if (!closed) { - closed = true; - if (leftover == 1) { - checkNewline(); - out.write(base64[b0 >> 2]); - out.write(base64[(b0 << 4) & 0x3f]); - if (doPadding) { - out.write('='); - out.write('='); - } - } else if (leftover == 2) { - checkNewline(); - out.write(base64[b0 >> 2]); - out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]); - out.write(base64[(b1 << 2) & 0x3f]); - if (doPadding) { - out.write('='); - } - } - leftover = 0; - out.close(); - } - } - } - - /* - * An input stream for decoding Base64 bytes - */ - private static class DecInputStream extends InputStream { - - private final InputStream is; - private final boolean isMIME; - private final int[] base64; // base64 -> byte mapping - private int bits = 0; // 24-bit buffer for decoding - private int nextin = 18; // next available "off" in "bits" for input; - // -> 18, 12, 6, 0 - private int nextout = -8; // next available "off" in "bits" for output; - // -> 8, 0, -8 (no byte for output) - private boolean eof = false; - private boolean closed = false; - - DecInputStream(InputStream is, int[] base64, boolean isMIME) { - this.is = is; - this.base64 = base64; - this.isMIME = isMIME; - } - - private byte[] sbBuf = new byte[1]; - - @Override - public int read() throws IOException { - return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - if (closed) { - throw new IOException("Stream is closed"); - } - if (eof && nextout < 0) { // eof and no leftover - return -1; - } - if (off < 0 || len < 0 || len > b.length - off) { - throw new IndexOutOfBoundsException(); - } - int oldOff = off; - if (nextout >= 0) { // leftover output byte(s) in bits buf - do { - if (len == 0) { - return off - oldOff; - } - b[off++] = (byte)(bits >> nextout); - len--; - nextout -= 8; - } while (nextout >= 0); - bits = 0; - } - while (len > 0) { - int v = is.read(); - if (v == -1) { - eof = true; - if (nextin != 18) { - if (nextin == 12) { - throw new IOException("Base64 stream has one un-decoded dangling byte."); - } - // treat ending xx/xxx without padding character legal. - // same logic as v == '=' below - b[off++] = (byte)(bits >> (16)); - len--; - if (nextin == 0) { // only one padding byte - if (len == 0) { // no enough output space - bits >>= 8; // shift to lowest byte - nextout = 0; - } else { - b[off++] = (byte) (bits >> 8); - } - } - } - if (off == oldOff) { - return -1; - }else { - return off - oldOff; - } - } - if (v == '=') { // padding byte(s) - // = shiftto==18 unnecessary padding - // x= shiftto==12 dangling x, invalid unit - // xx= shiftto==6 && missing last '=' - // xx=y or last is not '=' - if (nextin == 18 || nextin == 12 || - nextin == 6 && is.read() != '=') { - throw new IOException("Illegal base64 ending sequence:" + nextin); - } - b[off++] = (byte)(bits >> (16)); - len--; - if (nextin == 0) { // only one padding byte - if (len == 0) { // no enough output space - bits >>= 8; // shift to lowest byte - nextout = 0; - } else { - b[off++] = (byte) (bits >> 8); - } - } - eof = true; - break; - } - if ((v = base64[v]) == -1) { - if (isMIME) { // skip if for rfc2045 - continue; - }else { - throw new IOException("Illegal base64 character " + - Integer.toString(v, 16)); - } - } - bits |= (v << nextin); - if (nextin == 0) { - nextin = 18; // clear for next - nextout = 16; - while (nextout >= 0) { - b[off++] = (byte)(bits >> nextout); - len--; - nextout -= 8; - if (len == 0 && nextout >= 0) { // don't clean "bits" - return off - oldOff; - } - } - bits = 0; - } else { - nextin -= 6; - } - } - return off - oldOff; - } - - @Override - public int available() throws IOException { - if (closed) { - throw new IOException("Stream is closed"); - } - return is.available(); // TBD: - } - - @Override - public void close() throws IOException { - if (!closed) { - closed = true; - is.close(); - } - } - } } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/HmacSha256.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/HmacSha256.java new file mode 100644 index 0000000000000000000000000000000000000000..2640915af59fdaa6cbf4cd721e1a0b48acc80d81 --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/HmacSha256.java @@ -0,0 +1,56 @@ +package com.egzosn.pay.common.util.sign.encrypt; + +import java.io.UnsupportedEncodingException; +import java.security.GeneralSecurityException; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.egzosn.pay.common.bean.result.PayException; +import com.egzosn.pay.common.exception.PayErrorException; + +/** + * + * HmacSHA256 + * @author Egan + * email egzosn@gmail.com + * date 2021/8/1 + */ +public class HmacSha256 { + private static final Logger LOG = LoggerFactory.getLogger(HmacSha256.class); + + /** + * 签名 + * + * @param content 需要签名的内容 + * @param key 密钥 + * @param characterEncoding 字符编码 + * + * @return 签名值 + */ + public static String createSign(String content, String key, String characterEncoding) { + Mac sha256HMAC = null; + try { + sha256HMAC = Mac.getInstance("HmacSHA256"); + SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(characterEncoding), "HmacSHA256"); + sha256HMAC.init(secretKey); + byte[] array = sha256HMAC.doFinal(content.getBytes(characterEncoding)); + StringBuilder sb = new StringBuilder(); + for (byte item : array) { + sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3)); + } + return sb.toString().toUpperCase(); + } + catch (UnsupportedEncodingException e) { + LOG.error("", e); + } + catch (GeneralSecurityException e) { + LOG.error("", e); + } + + throw new PayErrorException(new PayException("fail", "HMACSHA256 签名异常")); + } +} diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/RSA.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/RSA.java index ca29feacf173a2b867eb86a782204dbebe355e4e..4911deff9a436b21f0a68be80db477b6dd4add16 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/RSA.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/RSA.java @@ -1,291 +1,351 @@ package com.egzosn.pay.common.util.sign.encrypt; -import javax.crypto.Cipher; -import java.io.*; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.cert.Certificate; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; +import javax.crypto.Cipher; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * RSA + * * @author egan *

  * email egzosn@gmail.com
- *
+ * */ -public class RSA{ - - private static final String ALGORITHM = "RSA"; - - - private static final String SIGN_ALGORITHMS = "SHA1WithRSA"; - - - /** - * RSA签名 - * @param content 待签名数据 - * @param privateKey 私钥 - * @param signAlgorithms 签名算法 - * @param characterEncoding 编码格式 - * @return 签名值 - */ - public static String sign(String content, String privateKey, String signAlgorithms, String characterEncoding) { - try { - PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec( Base64.decode(privateKey)); - KeyFactory keyf = KeyFactory.getInstance(ALGORITHM); - PrivateKey priKey = keyf.generatePrivate(priPKCS8); - - java.security.Signature signature = java.security.Signature.getInstance(signAlgorithms); - - signature.initSign(priKey); - signature.update(content.getBytes(characterEncoding)); - - byte[] signed = signature.sign(); - - return Base64.encode(signed); - } catch (Exception e) { - e.printStackTrace(); - } - - return null; - } - - - - /** - * RSA签名 - * @param content 待签名数据 - * @param privateKey 私钥 - * @param signAlgorithms 签名算法 - * @param characterEncoding 编码格式 - * @return 签名值 - */ - public static String sign(String content, PrivateKey privateKey, String signAlgorithms, String characterEncoding) { - try { - java.security.Signature signature = java.security.Signature.getInstance(signAlgorithms); - signature.initSign(privateKey); - signature.update(content.getBytes(characterEncoding)); - byte[] signed = signature.sign(); - return Base64.encode(signed); - } catch (Exception e) { - e.printStackTrace(); - } - - return null; - } - - - /** - * RSA签名 - * @param content 待签名数据 - * @param privateKey 私钥 - * @param characterEncoding 编码格式 - * @return 签名值 - */ - public static String sign(String content, String privateKey ,String characterEncoding){ +public class RSA { + private static final Logger LOG = LoggerFactory.getLogger(RSA.class); + private static final String ALGORITHM = "RSA"; + + + private static final String SIGN_ALGORITHMS = "SHA1WithRSA"; + + + /** + * RSA签名 + * + * @param content 待签名数据 + * @param privateKey 私钥 + * @param signAlgorithms 签名算法 + * @param characterEncoding 编码格式 + * @return 签名值 + */ + public static String sign(String content, String privateKey, String signAlgorithms, String characterEncoding) { + try { + PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64.decode(privateKey)); + KeyFactory keyf = KeyFactory.getInstance(ALGORITHM); + PrivateKey priKey = keyf.generatePrivate(priPKCS8); + + java.security.Signature signature = java.security.Signature.getInstance(signAlgorithms); + + signature.initSign(priKey); + signature.update(content.getBytes(characterEncoding)); + + byte[] signed = signature.sign(); + + return Base64.encode(signed); + } + catch (GeneralSecurityException e) { + LOG.error("", e); + } + catch (UnsupportedEncodingException e) { + LOG.error("", e); + } + + return null; + } + + + /** + * RSA签名 + * + * @param content 待签名数据 + * @param privateKey 私钥 + * @param signAlgorithms 签名算法 + * @param characterEncoding 编码格式 + * @return 签名值 + */ + public static String sign(String content, PrivateKey privateKey, String signAlgorithms, String characterEncoding) { + try { + java.security.Signature signature = java.security.Signature.getInstance(signAlgorithms); + signature.initSign(privateKey); + signature.update(content.getBytes(characterEncoding)); + byte[] signed = signature.sign(); + return Base64.encode(signed); + } + catch (GeneralSecurityException e) { + LOG.error("", e); + } + catch (UnsupportedEncodingException e) { + LOG.error("", e); + } + + return null; + } + + + /** + * RSA签名 + * + * @param content 待签名数据 + * @param privateKey 私钥 + * @param characterEncoding 编码格式 + * @return 签名值 + */ + public static String sign(String content, String privateKey, String characterEncoding) { return sign(content, privateKey, SIGN_ALGORITHMS, characterEncoding); } - /** - * RSA签名 - * @param content 待签名数据 - * @param privateKey 私钥 - * @param characterEncoding 编码格式 - * @return 签名值 - */ - public static String sign(String content, PrivateKey privateKey ,String characterEncoding){ + /** + * RSA签名 + * + * @param content 待签名数据 + * @param privateKey 私钥 + * @param characterEncoding 编码格式 + * @return 签名值 + */ + public static String sign(String content, PrivateKey privateKey, String characterEncoding) { return sign(content, privateKey, SIGN_ALGORITHMS, characterEncoding); } - /** - * RSA验签名检查 - * @param content 待签名数据 - * @param sign 签名值 - * @param publicKey 公钥 - * @param signAlgorithms 签名算法 - * @param characterEncoding 编码格式 - * @return 布尔值 - */ - public static boolean verify(String content, String sign, String publicKey, String signAlgorithms, String characterEncoding){ - try { - PublicKey pubKey = getPublicKey(publicKey, ALGORITHM); - java.security.Signature signature = java.security.Signature.getInstance(signAlgorithms); - signature.initVerify(pubKey); - signature.update( content.getBytes(characterEncoding) ); - return signature.verify( Base64.decode(sign) ); - } catch (Exception e) { - e.printStackTrace(); - } - return false; - } - - /** - * RSA验签名检查 - * @param content 待签名数据 - * @param sign 签名值 - * @param publicKey 公钥 - * @param signAlgorithms 签名算法 - * @param characterEncoding 编码格式 - * @return 布尔值 - */ - public static boolean verify(String content, String sign, PublicKey publicKey, String signAlgorithms, String characterEncoding){ - try { - java.security.Signature signature = java.security.Signature.getInstance(signAlgorithms); - signature.initVerify(publicKey); - signature.update( content.getBytes(characterEncoding) ); - return signature.verify( Base64.decode(sign) ); - } catch (Exception e) { - e.printStackTrace(); - } - return false; - } - /** - * RSA验签名检查 - * @param content 待签名数据 - * @param sign 签名值 - * @param publicKey 公钥 - * @param characterEncoding 编码格式 - * @return 布尔值 - */ - public static boolean verify(String content, String sign, String publicKey, String characterEncoding){ - - return verify(content, sign, publicKey, SIGN_ALGORITHMS, characterEncoding); - } - - - /** - * RSA验签名检查 - * @param content 待签名数据 - * @param sign 签名值 - * @param publicKey 公钥 - * @param characterEncoding 编码格式 - * @return 布尔值 - */ - public static boolean verify(String content, String sign, PublicKey publicKey, String characterEncoding){ - return verify(content, sign, publicKey, SIGN_ALGORITHMS, characterEncoding); - } - - /** - * 解密 - * @param content 密文 - * @param privateKey 商户私钥 - * @param characterEncoding 编码格式 - * @return 解密后的字符串 - * @throws Exception 解密异常 - */ - public static String decrypt(String content, String privateKey, String characterEncoding) throws Exception { - PrivateKey prikey = getPrivateKey(privateKey); - Cipher cipher = Cipher.getInstance(ALGORITHM); + /** + * RSA验签名检查 + * + * @param content 待签名数据 + * @param sign 签名值 + * @param publicKey 公钥 + * @param signAlgorithms 签名算法 + * @param characterEncoding 编码格式 + * @return 布尔值 + */ + public static boolean verify(String content, String sign, String publicKey, String signAlgorithms, String characterEncoding) { + try { + PublicKey pubKey = getPublicKey(publicKey, ALGORITHM); + return verify(content, sign, pubKey, signAlgorithms, characterEncoding); + } + catch (GeneralSecurityException e) { + LOG.error("", e); + } + catch (IOException e) { + LOG.error("", e); + } + return false; + } + + /** + * RSA验签名检查 + * + * @param content 待签名数据 + * @param sign 签名值 + * @param publicKey 公钥 + * @param signAlgorithms 签名算法 + * @param characterEncoding 编码格式 + * @return 布尔值 + */ + public static boolean verify(String content, String sign, PublicKey publicKey, String signAlgorithms, String characterEncoding) { + try { + java.security.Signature signature = java.security.Signature.getInstance(signAlgorithms); + signature.initVerify(publicKey); + signature.update(content.getBytes(characterEncoding)); + return signature.verify(Base64.decode(sign)); + } + catch (GeneralSecurityException e) { + LOG.error("", e); + } + catch (IOException e) { + LOG.error("", e); + } + return false; + } + + + /** + * RSA验签名检查 + * + * @param content 待签名数据 + * @param sign 签名值 + * @param publicKey 公钥 + * @param characterEncoding 编码格式 + * @return 布尔值 + */ + public static boolean verify(String content, String sign, String publicKey, String characterEncoding) { + + return verify(content, sign, publicKey, SIGN_ALGORITHMS, characterEncoding); + } + + + /** + * RSA验签名检查 + * + * @param content 待签名数据 + * @param sign 签名值 + * @param publicKey 公钥 + * @param characterEncoding 编码格式 + * @return 布尔值 + */ + public static boolean verify(String content, String sign, PublicKey publicKey, String characterEncoding) { + return verify(content, sign, publicKey, SIGN_ALGORITHMS, characterEncoding); + } + + + /** + * RSA验签名检查 + * + * @param content 待签名数据 + * @param sign 签名值 + * @param publicKey 公钥 + * @param characterEncoding 编码格式 + * @return 布尔值 + */ + public static boolean verify(String content, String sign, Certificate publicKey, String characterEncoding) { + final PublicKey pubKey = publicKey.getPublicKey(); + return verify(content, sign, pubKey, SIGN_ALGORITHMS, characterEncoding); + } + /** + * 解密 + * + * @param content 密文 + * @param privateKey 商户私钥 + * @param characterEncoding 编码格式 + * @return 解密后的字符串 + * @throws GeneralSecurityException 解密异常 + * @throws IOException IOException + */ + public static String decrypt(String content, String privateKey, String characterEncoding) throws GeneralSecurityException, IOException { + PrivateKey prikey = getPrivateKey(privateKey); + Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, prikey); - try(InputStream ins = new ByteArrayInputStream(Base64.decode(content)); ByteArrayOutputStream writer = new ByteArrayOutputStream();) { - - //rsa解密的字节大小最多是128,将需要解密的内容,按128位拆开解密 - byte[] buf = new byte[128]; - int bufl; - while ((bufl = ins.read(buf)) != -1) { - byte[] block = null; - - if (buf.length == bufl) { - block = buf; - } else { - block = new byte[bufl]; - - for (int i = 0; i < bufl; i++) { - block[i] = buf[i]; - } - } - writer.write(cipher.doFinal(block)); - } - - return new String(writer.toByteArray(), characterEncoding); - } + try (InputStream ins = new ByteArrayInputStream(Base64.decode(content)); ByteArrayOutputStream writer = new ByteArrayOutputStream();) { + + //rsa解密的字节大小最多是128,将需要解密的内容,按128位拆开解密 + byte[] buf = new byte[128]; + int bufl; + while ((bufl = ins.read(buf)) != -1) { + byte[] block = null; + + if (buf.length == bufl) { + block = buf; + } + else { + block = new byte[bufl]; + + for (int i = 0; i < bufl; i++) { + block[i] = buf[i]; + } + } + writer.write(cipher.doFinal(block)); + } + + return new String(writer.toByteArray(), characterEncoding); + } + } + + + /** + * 得到私钥 + * + * @param key 密钥字符串(经过base64编码) + * @return 私钥 + * @throws GeneralSecurityException 加密异常 + */ + public static PrivateKey getPrivateKey(String key) throws GeneralSecurityException { + + byte[] keyBytes; + keyBytes = Base64.decode(key); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM); + PrivateKey privateKey = keyFactory.generatePrivate(keySpec); + return privateKey; + } + + /** + * 得到公钥 + * + * @param key 密钥字符串(经过base64编码) + * @param signAlgorithms 密钥类型 + * @return 公钥 + * @throws GeneralSecurityException 加密异常 + * @throws IOException 加密异常 + */ + public static PublicKey getPublicKey(String key, String signAlgorithms) throws GeneralSecurityException, IOException { + try (ByteArrayInputStream is = new ByteArrayInputStream(key.getBytes("ISO8859-1"))) { + return getPublicKey(is, signAlgorithms); + } } - - /** - * 得到私钥 - * @param key 密钥字符串(经过base64编码) - * @throws Exception 加密异常 - * @return 私钥 - */ - public static PrivateKey getPrivateKey(String key) throws Exception { - - byte[] keyBytes; - keyBytes = Base64.decode(key); - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); - KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM); - PrivateKey privateKey = keyFactory.generatePrivate(keySpec); - return privateKey; - } - - /** - * 得到公钥 - * @param key 密钥字符串(经过base64编码) - * @param signAlgorithms 密钥类型 - * @throws Exception 加密异常 - * @return 公钥 - */ - public static PublicKey getPublicKey(String key, String signAlgorithms) throws Exception { - return getPublicKey(new ByteArrayInputStream(key.getBytes("ISO8859-1")), signAlgorithms); - } - - - /** - * 得到公钥 - * @param key 密钥字符串(经过base64编码) - * @throws Exception 加密异常 - * @return 公钥 - */ - public static PublicKey getPublicKey(String key) throws Exception { - - return getPublicKey(key, ALGORITHM); - } - - public static PublicKey getPublicKey(InputStream inputStream, String keyAlgorithm) throws Exception { - try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));) { - StringBuilder sb = new StringBuilder(); - String readLine = null; - while ((readLine = br.readLine()) != null) { - if (readLine.charAt(0) == '-') { - continue; - } - sb.append(readLine); - sb.append('\r'); - } - X509EncodedKeySpec pubX509 = new X509EncodedKeySpec(Base64.decode(sb.toString())); - KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm); - PublicKey publicKey = keyFactory.generatePublic(pubX509); - return publicKey; - } - } - - public static byte[] encrypt(byte[] plainBytes, PublicKey publicKey, int keyLength, int reserveSize, String cipherAlgorithm) throws Exception { - int keyByteSize = keyLength / 8; - int encryptBlockSize = keyByteSize - reserveSize; - int nBlock = plainBytes.length / encryptBlockSize; - if ((plainBytes.length % encryptBlockSize) != 0) { - nBlock += 1; - } - try (ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * keyByteSize);) { - Cipher cipher = Cipher.getInstance(cipherAlgorithm); - cipher.init(Cipher.ENCRYPT_MODE, publicKey); - for (int offset = 0; offset < plainBytes.length; offset += encryptBlockSize) { - int inputLen = plainBytes.length - offset; - if (inputLen > encryptBlockSize) { - inputLen = encryptBlockSize; - } - byte[] encryptedBlock = cipher.doFinal(plainBytes, offset, inputLen); - outbuf.write(encryptedBlock); - } - outbuf.flush(); - return outbuf.toByteArray(); - } - } - public static String encrypt(String content, String publicKey, String cipherAlgorithm, String characterEncoding ) throws Exception { - return Base64.encode(RSA.encrypt(content.getBytes(characterEncoding), RSA.getPublicKey(publicKey),1024, 11, cipherAlgorithm)); - } + + /** + * 得到公钥 + * + * @param key 密钥字符串(经过base64编码) + * @return 公钥 + * @throws GeneralSecurityException 加密异常 + * @throws IOException 加密异常 + */ + public static PublicKey getPublicKey(String key) throws GeneralSecurityException, IOException { + + return getPublicKey(key, ALGORITHM); + } + + public static PublicKey getPublicKey(InputStream inputStream, String keyAlgorithm) throws IOException, GeneralSecurityException { + try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));) { + StringBuilder sb = new StringBuilder(); + String readLine = null; + while ((readLine = br.readLine()) != null) { + if (readLine.charAt(0) == '-') { + continue; + } + sb.append(readLine); + sb.append('\r'); + } + X509EncodedKeySpec pubX509 = new X509EncodedKeySpec(Base64.decode(sb.toString())); + KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm); + PublicKey publicKey = keyFactory.generatePublic(pubX509); + return publicKey; + } + } + + public static byte[] encrypt(byte[] plainBytes, PublicKey publicKey, int keyLength, int reserveSize, String cipherAlgorithm) throws IOException, GeneralSecurityException { + int keyByteSize = keyLength / 8; + int encryptBlockSize = keyByteSize - reserveSize; + int nBlock = plainBytes.length / encryptBlockSize; + if ((plainBytes.length % encryptBlockSize) != 0) { + nBlock += 1; + } + try (ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * keyByteSize)) { + Cipher cipher = Cipher.getInstance(cipherAlgorithm); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + for (int offset = 0; offset < plainBytes.length; offset += encryptBlockSize) { + int inputLen = plainBytes.length - offset; + if (inputLen > encryptBlockSize) { + inputLen = encryptBlockSize; + } + byte[] encryptedBlock = cipher.doFinal(plainBytes, offset, inputLen); + outbuf.write(encryptedBlock); + } + outbuf.flush(); + return outbuf.toByteArray(); + } + } + + public static String encrypt(String content, String publicKey, String cipherAlgorithm, String characterEncoding) throws IOException, GeneralSecurityException { + return Base64.encode(RSA.encrypt(content.getBytes(characterEncoding), RSA.getPublicKey(publicKey), 1024, 11, cipherAlgorithm)); + } } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/RSA2.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/RSA2.java index 680b7b0a084903363703928c8a703c0169e8671d..d25251c55507f6ab868e7ad1449a8f079a71662c 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/RSA2.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/RSA2.java @@ -1,85 +1,113 @@ package com.egzosn.pay.common.util.sign.encrypt; +import java.io.IOException; +import java.security.GeneralSecurityException; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.cert.Certificate; public class RSA2 { - private static final String SIGN_SHA256RSA_ALGORITHMS = "SHA256WithRSA"; + private static final String SIGN_SHA256RSA_ALGORITHMS = "SHA256WithRSA"; + public static String sign(String content, String privateKey, String characterEncoding) { - public static String sign(String content, String privateKey, String characterEncoding) { - - return RSA.sign(content, privateKey, SIGN_SHA256RSA_ALGORITHMS, characterEncoding); - } + return RSA.sign(content, privateKey, SIGN_SHA256RSA_ALGORITHMS, characterEncoding); + } + /** + * RSA签名 + * + * @param content 待签名数据 + * @param privateKey 私钥 + * @param characterEncoding 编码格式 + * @return 签名值 + */ + public static String sign(String content, PrivateKey privateKey, String characterEncoding) { + return RSA.sign(content, privateKey, SIGN_SHA256RSA_ALGORITHMS, characterEncoding); + } - /** - * RSA签名 - * @param content 待签名数据 - * @param privateKey 私钥 - * @param characterEncoding 编码格式 - * @return 签名值 - */ - public static String sign(String content, PrivateKey privateKey ,String characterEncoding){ - return RSA.sign(content, privateKey, SIGN_SHA256RSA_ALGORITHMS, characterEncoding); - } + /** + * RSA验签名检查 + * + * @param content 待签名数据 + * @param sign 签名值 + * @param publicKey 公钥 + * @param characterEncoding 编码格式 + * @return 布尔值 + */ + public static boolean verify(String content, String sign, String publicKey, String characterEncoding) { + + return RSA.verify(content, sign, publicKey, SIGN_SHA256RSA_ALGORITHMS, characterEncoding); + } - /** - * RSA验签名检查 - * @param content 待签名数据 - * @param sign 签名值 - * @param publicKey 公钥 - * @param characterEncoding 编码格式 - * @return 布尔值 - */ - public static boolean verify(String content, String sign, String publicKey, String characterEncoding){ - return RSA.verify(content, sign, publicKey, SIGN_SHA256RSA_ALGORITHMS, characterEncoding ); - } + /** + * RSA验签名检查 + * + * @param content 待签名数据 + * @param sign 签名值 + * @param publicKey 公钥 + * @param characterEncoding 编码格式 + * @return 布尔值 + */ + public static boolean verify(String content, String sign, PublicKey publicKey, String characterEncoding) { + return RSA.verify(content, sign, publicKey, SIGN_SHA256RSA_ALGORITHMS, characterEncoding); + } + /** + * RSA验签名检查 + * + * @param content 待签名数据 + * @param sign 签名值 + * @param publicKey 公钥 + * @param characterEncoding 编码格式 + * @return 布尔值 + */ + public static boolean verify(String content, String sign, Certificate publicKey, String characterEncoding) { + PublicKey pubKey = publicKey.getPublicKey(); + return verify(content, sign, pubKey, characterEncoding); + } + /** + * 解密 + * + * @param content 密文 + * @param privateKey 商户私钥 + * @param characterEncoding 编码格式 + * @return 解密后的字符串 + * @throws GeneralSecurityException 解密异常 + * @throws IOException 解密异常 + */ + public static String decrypt(String content, String privateKey, String characterEncoding) throws GeneralSecurityException, IOException { + return RSA.decrypt(content, privateKey, characterEncoding); + } - /** - * RSA验签名检查 - * @param content 待签名数据 - * @param sign 签名值 - * @param publicKey 公钥 - * @param characterEncoding 编码格式 - * @return 布尔值 - */ - public static boolean verify(String content, String sign, PublicKey publicKey, String characterEncoding){ - return RSA.verify(content, sign, publicKey, SIGN_SHA256RSA_ALGORITHMS, characterEncoding); - } - /** - * 解密 - * @param content 密文 - * @param privateKey 商户私钥 - * @param characterEncoding 编码格式 - * @return 解密后的字符串 - * @throws Exception 解密异常 - */ - public static String decrypt(String content, String privateKey, String characterEncoding) throws Exception { - return RSA.decrypt(content, privateKey, characterEncoding); + /** + * 得到私钥 + * + * @param key 密钥字符串(经过base64编码) + * @return 私钥 + * @throws GeneralSecurityException 加密异常 + */ + public static PrivateKey getPrivateKey(String key) throws GeneralSecurityException { + return RSA.getPrivateKey(key); } - - /** - * 得到私钥 - * @param key 密钥字符串(经过base64编码) - * @throws Exception 加密异常 - * @return 私钥 - */ - public static PrivateKey getPrivateKey(String key) throws Exception { - return RSA.getPrivateKey(key); - } - - - public static String encrypt(String content, String publicKey, String cipherAlgorithm, String characterEncoding ) throws Exception { - return Base64.encode(RSA.encrypt(content.getBytes(characterEncoding), RSA.getPublicKey(publicKey),2048, 11, cipherAlgorithm)); - } + /** + * @param content 加密文本 + * @param publicKey 公钥 + * @param cipherAlgorithm 算法 + * @param characterEncoding 编码类型 + * @return 加密后文本 + * @throws GeneralSecurityException 加密异常 + * @throws IOException IOException + */ + public static String encrypt(String content, String publicKey, String cipherAlgorithm, String characterEncoding) throws GeneralSecurityException, IOException { + return Base64.encode(RSA.encrypt(content.getBytes(characterEncoding), RSA.getPublicKey(publicKey), 2048, 11, cipherAlgorithm)); + } } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/sm3/SM3.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/sm3/SM3.java index 09f319b052eb2e5741f3f37ea474d2cce504f5d4..c0ded5bf63e19149a1834da81bdc803aa3d75b01 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/sm3/SM3.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/sm3/SM3.java @@ -137,7 +137,8 @@ public class SM3 { private static int FFj(int X, int Y, int Z, int j) { if (j >= 0 && j <= 15) { return FF1j(X, Y, Z); - } else { + } + else { return FF2j(X, Y, Z); } } @@ -145,7 +146,8 @@ public class SM3 { private static int GGj(int X, int Y, int Z, int j) { if (j >= 0 && j <= 15) { return GG1j(X, Y, Z); - } else { + } + else { return GG2j(X, Y, Z); } } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/sm3/SM3Digest.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/sm3/SM3Digest.java index cf62b2489a02115cf1c7ca44af21dc9de7c08046..6b3a792cfed0bfc118510435dac91ae90a6bc652 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/sm3/SM3Digest.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/sm3/SM3Digest.java @@ -1,22 +1,34 @@ package com.egzosn.pay.common.util.sign.encrypt.sm3; public class SM3Digest { - /** SM3值的长度 */ + /** + * SM3值的长度 + */ private static final int BYTE_LENGTH = 32; - /** SM3分组长度 */ + /** + * SM3分组长度 + */ private static final int BLOCK_LENGTH = 64; - /** 缓冲区长度 */ + /** + * 缓冲区长度 + */ private static final int BUFFER_LENGTH = BLOCK_LENGTH * 1; - /** 缓冲区 */ + /** + * 缓冲区 + */ private byte[] xBuf = new byte[BUFFER_LENGTH]; - /** 缓冲区偏移量 */ + /** + * 缓冲区偏移量 + */ private int xBufOff; - /** 初始向量 */ + /** + * 初始向量 + */ private byte[] V = SM3.iv.clone(); private int cntBlock = 0; @@ -25,7 +37,7 @@ public class SM3Digest { } - public SM3Digest(SM3Digest t){ + public SM3Digest(SM3Digest t) { System.arraycopy(t.xBuf, 0, this.xBuf, 0, t.xBuf.length); this.xBufOff = t.xBufOff; System.arraycopy(t.V, 0, this.V, 0, t.V.length); @@ -34,12 +46,11 @@ public class SM3Digest { /** * SM3结果输出 * - * @param out 保存SM3结构的缓冲区 + * @param out 保存SM3结构的缓冲区 * @param outOff 缓冲区偏移量 * @return 字节长度 */ - public int doFinal(byte[] out, int outOff) - { + public int doFinal(byte[] out, int outOff) { byte[] tmp = doFinal(); System.arraycopy(tmp, 0, out, 0, tmp.length); return BYTE_LENGTH; @@ -48,8 +59,7 @@ public class SM3Digest { /** * 重置 */ - public void reset() - { + public void reset() { xBufOff = 0; cntBlock = 0; V = SM3.iv.clone(); @@ -58,23 +68,20 @@ public class SM3Digest { /** * 明文输入 * - * @param in 明文输入缓冲区 + * @param in 明文输入缓冲区 * @param inOff 缓冲区偏移量 - * @param len 明文长度 + * @param len 明文长度 */ - public void update(byte[] in, int inOff, int len) - { + public void update(byte[] in, int inOff, int len) { int partLen = BUFFER_LENGTH - xBufOff; int inputLen = len; int dPos = inOff; - if (partLen < inputLen) - { + if (partLen < inputLen) { System.arraycopy(in, dPos, xBuf, xBufOff, partLen); inputLen -= partLen; dPos += partLen; doUpdate(); - while (inputLen > BUFFER_LENGTH) - { + while (inputLen > BUFFER_LENGTH) { System.arraycopy(in, dPos, xBuf, 0, BUFFER_LENGTH); inputLen -= BUFFER_LENGTH; dPos += BUFFER_LENGTH; @@ -89,11 +96,9 @@ public class SM3Digest { /** * 更新 */ - private void doUpdate() - { + private void doUpdate() { byte[] B = new byte[BLOCK_LENGTH]; - for (int i = 0; i < BUFFER_LENGTH; i += BLOCK_LENGTH) - { + for (int i = 0; i < BUFFER_LENGTH; i += BLOCK_LENGTH) { System.arraycopy(xBuf, i, B, 0, B.length); doHash(B); } @@ -102,37 +107,33 @@ public class SM3Digest { /** * 转16进制 + * * @param B 字节数组 */ - private void doHash(byte[] B) - { + private void doHash(byte[] B) { byte[] tmp = SM3.CF(V, B); System.arraycopy(tmp, 0, V, 0, V.length); cntBlock++; } - private byte[] doFinal() - { + private byte[] doFinal() { byte[] B = new byte[BLOCK_LENGTH]; byte[] buffer = new byte[xBufOff]; System.arraycopy(xBuf, 0, buffer, 0, buffer.length); byte[] tmp = SM3.padding(buffer, cntBlock); - for (int i = 0; i < tmp.length; i += BLOCK_LENGTH) - { + for (int i = 0; i < tmp.length; i += BLOCK_LENGTH) { System.arraycopy(tmp, i, B, 0, B.length); doHash(B); } return V; } - public void update(byte in) - { - byte[] buffer = new byte[] { in }; + public void update(byte in) { + byte[] buffer = new byte[]{in}; update(buffer, 0, 1); } - public int getDigestSize() - { + public int getDigestSize() { return BYTE_LENGTH; } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/util/str/StringUtils.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/str/StringUtils.java index 8b451a2ae5775ef50ff68823a3acc7a44eb98934..9bba1a21d46d5fd28b9ebc085a9ad9a643afc5b3 100644 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/util/str/StringUtils.java +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/util/str/StringUtils.java @@ -1,6 +1,7 @@ package com.egzosn.pay.common.util.str; import java.io.UnsupportedEncodingException; +import java.util.UUID; /** * Created by ZaoSheng on 2016/6/4. @@ -19,7 +20,6 @@ public class StringUtils { * * @param str1 要比较的字符串1 * @param str2 要比较的字符串2 - * * @return 如果两个字符串相同,或者都是null,则返回true */ public static boolean equals(String str1, String str2) { @@ -31,6 +31,7 @@ public class StringUtils { } // Empty checks //----------------------------------------------------------------------- + /** *

Checks if a CharSequence is empty ("") or null.

*
@@ -40,7 +41,8 @@ public class StringUtils {
      * StringUtils.isEmpty("bob")     = false
      * StringUtils.isEmpty("  bob  ") = false
      * 
- * @param cs the CharSequence to check, may be null + * + * @param cs the CharSequence to check, may be null * @return {@code true} if the CharSequence is empty or null */ public static boolean isEmpty(CharSequence cs) { @@ -58,7 +60,7 @@ public class StringUtils { * StringUtils.isNotEmpty(" bob ") = true * * - * @param cs the CharSequence to check, may be null + * @param cs the CharSequence to check, may be null * @return {@code true} if the CharSequence is not empty and not null */ public static boolean isNotEmpty(CharSequence cs) { @@ -75,7 +77,8 @@ public class StringUtils { * StringUtils.isBlank("bob") = false * StringUtils.isBlank(" bob ") = false * - * @param cs the CharSequence to check, may be null + * + * @param cs the CharSequence to check, may be null * @return {@code true} if the CharSequence is null, empty or whitespace * @since 2.0 */ @@ -102,9 +105,10 @@ public class StringUtils { * StringUtils.isNotBlank("bob") = true * StringUtils.isNotBlank(" bob ") = true * - * @param cs the CharSequence to check, may be null + * + * @param cs the CharSequence to check, may be null * @return {@code true} if the CharSequence is - * not empty and not null and not whitespace + * not empty and not null and not whitespace */ public static boolean isNotBlank(CharSequence cs) { return !StringUtils.isBlank(cs); @@ -121,9 +125,51 @@ public class StringUtils { } try { return content.getBytes(charset); - } catch (UnsupportedEncodingException e) { + } + catch (UnsupportedEncodingException e) { throw new RuntimeException("转码过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset); } } + /** + * 对 subject body 进行 trim 运算, + * 以防止在签名是可能造成的签名错误问题 + * + * @param str 字符 + * @return 去除空格之后的字符 + */ + public static String tryTrim(String str) { + return str == null ? null : str.trim(); + } + + + /** + * 字符串数组拼接为字符串 + * + * @param separator 分隔符 + * @param str 字符数组 + * @return 字符串 + */ + public static String joining(String separator, String... str) { + StringBuilder builder = new StringBuilder(); + for (String s : str) { + if (null == s) { + continue; + } + + builder.append(s).append(separator); + } + return builder.toString(); + } + + /** + * 获取随机字符串 + * + * @return 随机字符串 + */ + public static String randomStr() { + return UUID.randomUUID().toString().replace("-", ""); + } + + } diff --git a/pay-java-demo/README.md b/pay-java-demo/README.md index fe23432394577da200703eb2506c8cf1e91e6587..d4f530138bcdcacddba78aeae2465b489cc22a9c 100644 --- a/pay-java-demo/README.md +++ b/pay-java-demo/README.md @@ -7,14 +7,14 @@ /** * 支付类型 * @author egan - * @email egzosn@gmail.com - * @date 2016/11/20 0:30 + * email egzosn@gmail.com + * date 2016/11/20 0:30 */ public enum PayType implements BasePayType { aliPay{ /** - * @see com.egzosn.pay.ali.api.AliPayService 17年更新的版本,旧版本请自行切换{@link com.egzosn.pay.ali.before.api.AliPayService } + * @see com.egzosn.pay.ali.api.AliPayService * @param apyAccount * @return */ @@ -23,7 +23,7 @@ public enum PayType implements BasePayType { AliPayConfigStorage aliPayConfigStorage = new AliPayConfigStorage(); aliPayConfigStorage.setPid(apyAccount.getPartner()); aliPayConfigStorage.setAppId(apyAccount.getAppid()); - aliPayConfigStorage.setAliPublicKey(apyAccount.getPublicKey()); + aliPayConfigStorage.setKeyPublic(apyAccount.getPublicKey()); aliPayConfigStorage.setKeyPrivate(apyAccount.getPrivateKey()); aliPayConfigStorage.setNotifyUrl(apyAccount.getNotifyUrl()); aliPayConfigStorage.setReturnUrl(apyAccount.getReturnUrl()); @@ -49,7 +49,7 @@ public enum PayType implements BasePayType { wxPayConfigStorage.setMchId(apyAccount.getPartner()); wxPayConfigStorage.setAppSecret(apyAccount.getPublicKey()); wxPayConfigStorage.setKeyPublic(apyAccount.getPublicKey()); - wxPayConfigStorage.setAppid(apyAccount.getAppid()); + wxPayConfigStorage.setAppId(apyAccount.getAppid()); wxPayConfigStorage.setKeyPrivate(apyAccount.getPrivateKey()); wxPayConfigStorage.setNotifyUrl(apyAccount.getNotifyUrl()); wxPayConfigStorage.setSignType(apyAccount.getSignType()); @@ -107,8 +107,8 @@ public enum PayType implements BasePayType { /** * 支付响应对象 * @author: egan - * @email egzosn@gmail.com - * @date 2016/11/18 0:34 + * email egzosn@gmail.com + * date 2016/11/18 0:34 */ public class PayResponse { @Resource @@ -153,13 +153,13 @@ public class PayResponse { //代理端口 httpConfigStorage.setHttpProxyPort(3308); //代理用户名 - httpConfigStorage.setHttpProxyUsername("user"); + httpConfigStorage.setAuthUsername("user"); //代理密码 - httpConfigStorage.setHttpProxyPassword("password"); + httpConfigStorage.setAuthPassword("password"); */ //设置ssl证书路径 - httpConfigStorage.setKeystorePath(apyAccount.getKeystorePath()); + httpConfigStorage.setKeystore(apyAccount.getKeystorePath()); //设置ssl证书对应的密码 httpConfigStorage.setStorePassword(apyAccount.getStorePassword()); return httpConfigStorage; @@ -176,7 +176,6 @@ public class PayResponse { router = new PayMessageRouter(this.service); router .rule() - .async(false) .msgType(MsgType.text.name()) //消息类型 .payType(PayType.aliPay.name()) //支付账户事件类型 .transactionType(AliTransactionType.UNAWARE.name())//交易类型,有关回调的可在这处理 @@ -184,13 +183,11 @@ public class PayResponse { .handler(autowire(new AliPayMessageHandler(payId))) //处理器 .end() .rule() - .async(false) .msgType(MsgType.xml.name()) .payType(PayType.wxPay.name()) .handler(autowire(new WxPayMessageHandler(payId))) .end() .rule() - .async(false) .msgType(MsgType.json.name()) .payType(PayType.youdianPay.name()) .handler(autowire(new YouDianPayMessageHandler(payId))) @@ -247,8 +244,8 @@ public class PayResponse { /** * 支付宝回调信息拦截器 * @author: egan - * @email egzosn@gmail.com - * @date 2017/1/18 19:28 + * email egzosn@gmail.com + * date 2017/1/18 19:28 */ public class AliPayMessageInterceptor implements PayMessageInterceptor { /** @@ -337,7 +334,7 @@ public class ApyAccountService { //获取对应的支付账户操作工具(可根据账户id) PayResponse payResponse = service.getPayResponse(payId); - PayOrder order = new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price, UUID.randomUUID().toString().replace("-", ""), PayType.valueOf(payResponse.getStorage().getPayType()).getTransactionType(transactionType)); + PayOrder order = new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), PayType.valueOf(payResponse.getStorage().getPayType()).getTransactionType(transactionType)); //此处只有刷卡支付(银行卡支付)时需要 if (StringUtils.isNotEmpty(bankType)){ @@ -358,7 +355,7 @@ public class ApyAccountService { //获取对应的支付账户操作工具(可根据账户id) PayResponse payResponse = service.getPayResponse(payId); //获取订单信息 - Map orderInfo = payResponse.getService().orderInfo(new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price, UUID.randomUUID().toString().replace("-", ""), PayType.valueOf(payResponse.getStorage().getPayType()).getTransactionType(transactionType))); + Map orderInfo = payResponse.getService().orderInfo(new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), PayType.valueOf(payResponse.getStorage().getPayType()).getTransactionType(transactionType))); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(payResponse.getService().genQrPay(orderInfo), "JPEG", baos); @@ -379,7 +376,7 @@ public class ApyAccountService { PayResponse payResponse = service.getPayResponse(payId); Map data = new HashMap<>(); data.put("code", 0); - PayOrder order = new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price, UUID.randomUUID().toString().replace("-", ""), PayType.valueOf(payResponse.getStorage().getPayType()).getTransactionType(transactionType)); + PayOrder order = new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), PayType.valueOf(payResponse.getStorage().getPayType()).getTransactionType(transactionType)); data.put("orderInfo", payResponse.getService().orderInfo(order)); return data; } diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index 568501f756b913a98544589b900fd92c9818ecb5..2e0b44b9c26c051bf13b79198c3b1842e80bca8d 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.12.6-SNAPSHOT + 2.14.7 4.0.0 war @@ -16,9 +16,9 @@ 4.3.4.RELEASE 4.10 4.3.6.Final + 4.0.1 - @@ -31,6 +31,7 @@ pay-java-wx ${pay.version} + com.egzosn pay-java-ali @@ -58,20 +59,10 @@ ${pay.version} - - javax.servlet - javax.servlet-api - provided - 3.1.0 - - - javax.servlet - jsp-api - 2.0 - provided + com.egzosn + pay-java-web-support - org.springframework @@ -80,55 +71,26 @@ jar compile + + - org.springframework - spring-web - ${spring.version} - jar - compile + javax.servlet + javax.servlet-api + provided + ${servlet-api.version} - - com.fasterxml.jackson.core jackson-databind - 2.8.4 + 2.9.10.6 + + + org.slf4j + slf4j-log4j12 + 1.7.30 - - - - port8080 - - 8080 - - - true - - - - port9096 - - 9096 - - - - local - - local - - - true - - - - proc - - proc - - - pay-java-demo @@ -136,8 +98,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.7 - 1.7 + 1.8 + 1.8 UTF-8 @@ -146,7 +108,7 @@ tomcat7-maven-plugin 2.0 - ${port} + 8080 / UTF-8 diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/AliPayController.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/AliPayController.java index 60794cb1c21534ac8579e81b37e519dabe1fe0ba..4be22b6b90a40a025d9c6e3af83612cecdd9225b 100644 --- a/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/AliPayController.java +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/AliPayController.java @@ -2,58 +2,98 @@ package com.egzosn.pay.demo.controller; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import javax.imageio.ImageIO; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + import com.egzosn.pay.ali.api.AliPayConfigStorage; import com.egzosn.pay.ali.api.AliPayService; +import com.egzosn.pay.ali.bean.AliRefundResult; import com.egzosn.pay.ali.bean.AliTransactionType; +import com.egzosn.pay.ali.bean.AliTransferOrder; import com.egzosn.pay.ali.bean.AliTransferType; -import com.egzosn.pay.common.api.PayService; -import com.egzosn.pay.common.bean.*; +import com.egzosn.pay.ali.bean.OrderSettle; +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.common.bean.CertStoreType; +import com.egzosn.pay.common.bean.NoticeParams; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.RefundOrder; import com.egzosn.pay.common.http.HttpConfigStorage; import com.egzosn.pay.common.http.UriVariables; import com.egzosn.pay.common.util.sign.SignUtils; import com.egzosn.pay.demo.request.QueryOrder; import com.egzosn.pay.demo.service.handler.AliPayMessageHandler; import com.egzosn.pay.demo.service.interceptor.AliPayMessageInterceptor; -import org.springframework.beans.factory.config.AutowireCapableBeanFactory; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import javax.annotation.PostConstruct; -import javax.annotation.Resource; -import javax.imageio.ImageIO; -import javax.servlet.http.HttpServletRequest; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.math.BigDecimal; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; +import com.egzosn.pay.web.support.HttpRequestNoticeParams; /** * 发起支付入口 * - * @author: egan - * @email egzosn@gmail.com - * @date 2016/11/18 0:25 + * @author egan + * email egzosn@gmail.com + * date 2016/11/18 0:25 */ @RestController @RequestMapping("ali") public class AliPayController { - private PayService service = null; + private AliPayService service = null; @Resource private AutowireCapableBeanFactory spring; + /** + * 设置普通公钥的方式 + * 普通公钥方式与证书公钥方式为两者取其一的方式 + * + * @param aliPayConfigStorage 支付宝配置信息 + */ + private static void keyPublic(AliPayConfigStorage aliPayConfigStorage) { + aliPayConfigStorage.setKeyPublic("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIgHnOn7LLILlKETd6BFRJ0GqgS2Y3mn1wMQmyh9zEyWlz5p1zrahRahbXAfCfSqshSNfqOmAQzSHRVjCqjsAw1jyqrXaPdKBmr90DIpIxmIyKXv4GGAkPyJ/6FTFY99uhpiq0qadD/uSzQsefWo0aTvP/65zi3eof7TcZ32oWpwIDAQAB"); + } + + /** + * 设置证书公钥信息 + * 普通公钥方式与证书公钥方式为两者取其一的方式 + * + * @param aliPayConfigStorage 支付宝配置信息 + */ + private static void certKeyPublic(AliPayConfigStorage aliPayConfigStorage) { + //设置为证书方式 + aliPayConfigStorage.setCertSign(true); + //设置证书存储方式,这里为路径 + aliPayConfigStorage.setCertStoreType(CertStoreType.CLASS_PATH); + aliPayConfigStorage.setMerchantCert("ali/appCertPublicKey_2016080400165436.crt"); + aliPayConfigStorage.setAliPayRootCert("ali/alipayRootCert.crt"); + aliPayConfigStorage.setAliPayCert("ali/alipayCertPublicKey_RSA2.crt"); + } + @PostConstruct public void init() { AliPayConfigStorage aliPayConfigStorage = new AliPayConfigStorage(); aliPayConfigStorage.setPid("2088102169916436"); aliPayConfigStorage.setAppId("2016080400165436"); - aliPayConfigStorage.setKeyPublic("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIgHnOn7LLILlKETd6BFRJ0GqgS2Y3mn1wMQmyh9zEyWlz5p1zrahRahbXAfCfSqshSNfqOmAQzSHRVjCqjsAw1jyqrXaPdKBmr90DIpIxmIyKXv4GGAkPyJ/6FTFY99uhpiq0qadD/uSzQsefWo0aTvP/65zi3eof7TcZ32oWpwIDAQAB"); +// aliPayConfigStorage.setAppAuthToken("ISV代商户代用,指定appAuthToken"); + //普通公钥方式与证书公钥方式为两者取其一的方式 + keyPublic(aliPayConfigStorage); aliPayConfigStorage.setKeyPrivate("MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKroe/8h5vC4L6T+B2WdXiVwGsMvUKgb2XsKix6VY3m2wcf6tyzpNRDCNykbIwGtaeo7FshN+qZxdXHLiIam9goYncBit/8ojfLGy2gLxO/PXfzGxYGs0KsDZ+ryVPPmE34ZZ8jiJpR0ygzCFl8pN3QJPJRGTJn5+FTT9EF/9zyZAgMBAAECgYAktngcYC35u7cQXDk+jMVyiVhWYU2ULxdSpPspgLGzrZyG1saOcTIi/XVX8Spd6+B6nmLQeF/FbU3rOeuD8U2clzul2Z2YMbJ0FYay9oVZFfp5gTEFpFRTVfzqUaZQBIjJe/xHL9kQVqc5xHlE/LVA27/Kx3dbC35Y7B4EVBDYAQJBAOhsX8ZreWLKPhXiXHTyLmNKhOHJc+0tFH7Ktise/0rNspojU7o9prOatKpNylp9v6kux7migcMRdVUWWiVe+4ECQQC8PqsuEz7B0yqirQchRg1DbHjh64bw9Kj82EN1/NzOUd53tP9tg+SO97EzsibK1F7tOcuwqsa7n2aY48mQ+y0ZAkBndA2xcRcnvOOjtAz5VO8G7R12rse181HjGfG6AeMadbKg30aeaGCyIxN1loiSfNR5xsPJwibGIBg81mUrqzqBAkB+K6rkaPXJR9XtzvdWb/N3235yPkDlw7Z4MiOVM3RzvR/VMDV7m8lXoeDde2zQyeMOMYy6ztwA6WgE1bhGOnQRAkEAouUBv1sVdSBlsexX15qphOmAevzYrpufKgJIRLFWQxroXMS7FTesj+f+FmGrpPCxIde1dqJ8lqYLTyJmbzMPYw=="); +// certKeyPublic(aliPayConfigStorage); +// aliPayConfigStorage.setKeyPrivate("MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCw7MD2Cwv/jnXssFjXnGx3JlGF57gJa2aYbJRV8MnNiPVpX4Ha+8ZjnQDhvkrWH4hHmzcujOr213HqloMpUSYBzCPiXGVRUUvdimejcHHTod7nI4g6nztzzfey/TXNDHmp7vY3pOIcjB0Zn0pkNAz2tKAFkqb4raHOqTB0QA0zD24Cn+26J2UJyYRcgeH0GtSQuUrm7yaGsuKakh+qtgWF6R71n5PMGOTQ5LH3i0WVHfCBkNGgJC6yC96HR4D7cosoyKD0+lp8UB/NVUWl7Tt/KLOgFUwh0GKSYFfv56O/VBV2+xqCGE4PlZESfVuOqz5vjjxzw3xDAUJrV8hSX/AJAgMBAAECggEBAKE0d3U4B4yo/2XUIH8EdgfykCFUSum6RFbpyBauORHfksyaSzV+ZvtomN8XhhSn0oJ8OMFfgM+86nz2+zdwSxMkMCYWTfLUAi4v59KRqAVO3kz4oS3Y3FDeAK3D7XuRvGFL7GgzAhtEx1cLPrsiehVn6s5pG15GxsIIgq/JlL1J88wn1zENLrVHmD6z/JpXvfb/RS1yR+5lyoohp4g0Ph9jJ3bCyUbRpK0QkPEzgAuWL0K2ITCL7PYHNAplI8d2xHHOLF9Qdjyx+ZrQ/RxtqzfyWzhqjsmp2qlgNCxWlt3woS9UhDB+nRvjEoWTJmIOszAMYuj8wGlX+3Ui3ALOdQECgYEA25EqnFPFinUnzgNvB6NYmh5STmZun6s4bUOLqwefKtEvrOtRwTu7sB7NIf37fizG3/MJUWHxiLy2/3ub4d2JxdDNBtJoEqnp6QB12qglCNa4CajdjtJa1dR81F9QvytsqEkmPYXFPPyviB0FcSIDAGMb3IbwvIfzBPY9WY8dJnECgYEAzkg3yKEFBZ8BU0WQ+3hyfKUoAhBEnxouxRSTBcXxwstJRiqaGTVe5aoJGQI+0xS7Z6q07XDtN2t97s6DnRLWbljsX6B64itzNhXRyzjdD3iZDU/KSw7khjhXf8XOZaj9eXmACDiUnkEn1xsM8bLiRGqB8y5f3aMY/RpuACGXnxkCgYEAx/zwT9Vpr1RIfjfYcJ+Su0X0994K0roUukj0tUJK8qf4gcsQ+y1aJe/YLib1ZBaKyj7G9O5+HmqtUAUZld/AdoJZzOXmz2EeYhD+R7wxh1xz4rCBpW3qOKvDS3jJxmZaIOoHv6/RWFxb0WGFrGcrTrX3EaWDLmWxr4pNlP5qsbECgYATllntrBR8/ycyEAX/SuWcHlaZM5BAh0zvm8+GGdCmDYWMqxjs0duL9URd4o+ynWJaKqR5c2KjA4r2tRdcP+Cqo7j2L5fbiAKtnQ7JvEGJaYsm72+nBuf+MrVkRZUepBhFg5r7rNu31zoAO+pTvQetNWvXeozRz93ckrjlPEtYaQKBgQDFwbV92rlRMLjZzlY+o0knoeJBjPQmPdiBTpGNimdy9L4c2Ure7affjcUiYhkKqrK5k5SScJTATgyQ7JF346FdtUtZ/6Kkj1RwJmmprPrDa9CATLoTle7g9OVd4sHT2ITHZMzPaF3ILvzcwJ70AD1xcxCQb+/7sDPmw7Mc8gOA7Q=="); aliPayConfigStorage.setNotifyUrl("http://pay.egzosn.com/payBack.json"); aliPayConfigStorage.setReturnUrl("http://pay.egzosn.com/payBack.html"); - aliPayConfigStorage.setSignType(SignUtils.RSA.name()); + aliPayConfigStorage.setSignType(SignUtils.RSA2.name()); aliPayConfigStorage.setSeller("2088102169916436"); aliPayConfigStorage.setInputCharset("utf-8"); //是否为测试账号,沙箱环境 @@ -65,7 +105,7 @@ public class AliPayController { httpConfigStorage.setMaxTotal(20); //默认的每个路由的最大连接数 httpConfigStorage.setDefaultMaxPerRoute(10); - service = new AliPayService(aliPayConfigStorage, httpConfigStorage); + service = new AliPayService(aliPayConfigStorage, httpConfigStorage); //增加支付回调消息拦截器 service.addPayMessageInterceptor(new AliPayMessageInterceptor()); //设置回调消息处理 @@ -73,26 +113,25 @@ public class AliPayController { } - /** * 跳到支付页面 * 针对实时支付,即时付款 * - * @param price 金额 + * @param price 金额 * @return 跳到支付页面 */ @RequestMapping(value = "toPay.html", produces = "text/html;charset=UTF-8") - public String toPay( BigDecimal price) { + public String toPay(BigDecimal price) { //及时收款 - PayOrder order = new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price, UUID.randomUUID().toString().replace("-", ""), AliTransactionType.DIRECT); + PayOrder order = new PayOrder("订单title", "摘'要", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), AliTransactionType.PAGE); //WAP -// PayOrder order = new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price, UUID.randomUUID().toString().replace("-", ""), AliTransactionType.WAP); - - Map orderInfo = service.orderInfo(order); - return service.buildRequest(orderInfo, MethodType.POST); - } +// PayOrder order = new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), AliTransactionType.WAP); +// Map orderInfo = service.orderInfo(order); +// return service.buildRequest(orderInfo, MethodType.POST); + return service.toPay(order); + } /** @@ -104,47 +143,64 @@ public class AliPayController { public Map app() { Map data = new HashMap<>(); data.put("code", 0); - PayOrder order = new PayOrder("订单title", "摘要", new BigDecimal(0.01), UUID.randomUUID().toString().replace("-", "")); + PayOrder order = new PayOrder("订单title", "摘要", BigDecimal.valueOf(0.01), UUID.randomUUID().toString().replace("-", "")); //App支付 order.setTransactionType(AliTransactionType.APP); - data.put("orderInfo", UriVariables.getMapToParameters(service.orderInfo(order))); + data.put("orderInfo", UriVariables.getMapToParameters(service.app(order))); return data; } /** * 获取二维码图像 * 二维码支付 - * @param price 金额 + * + * @param price 金额 * @return 二维码图像 + * @throws IOException IOException */ @RequestMapping(value = "toQrPay.jpg", produces = "image/jpeg;charset=UTF-8") - public byte[] toWxQrPay( BigDecimal price) throws IOException { + public byte[] toQrPay(BigDecimal price) throws IOException { //获取对应的支付账户操作工具(可根据账户id) ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ImageIO.write(service.genQrPay( new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price, System.currentTimeMillis()+"", AliTransactionType.SWEEPPAY)), "JPEG", baos); + ImageIO.write(service.genQrPay(new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, System.currentTimeMillis() + "", AliTransactionType.SWEEPPAY)), "JPEG", baos); return baos.toByteArray(); } + /** + * 获取二维码地址 + * 二维码支付 + * + * @param price 金额 + * @return 二维码图像 + * @throws IOException IOException + */ + @RequestMapping(value = "getQrPay.json") + public String getQrPay(BigDecimal price) throws IOException { + //获取对应的支付账户操作工具(可根据账户id) + return service.getQrPay(new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, System.currentTimeMillis() + "", AliTransactionType.SWEEPPAY)); + } + /** * 刷卡付,pos主动扫码付款(条码付) - * @param authCode 授权码,条码等 - * @param price 金额 + * + * @param authCode 授权码,条码等 + * @param price 金额 * @return 支付结果 */ @RequestMapping(value = "microPay") - public Map microPay(BigDecimal price, String authCode) throws IOException { + public Map microPay(BigDecimal price, String authCode) { //获取对应的支付账户操作工具(可根据账户id) //条码付 - PayOrder order = new PayOrder("huodull order", "huodull order", null == price ? new BigDecimal(0.01) : price, UUID.randomUUID().toString().replace("-", ""), AliTransactionType.BAR_CODE); - //声波付 -// PayOrder order = new PayOrder("huodull order", "huodull order", null == price ? new BigDecimal(0.01) : price, UUID.randomUUID().toString().replace("-", ""), AliTransactionType.WAVE_CODE); + PayOrder order = new PayOrder("egan order", "egan order", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), AliTransactionType.BAR_CODE); + //声波付 +// PayOrder order = new PayOrder("egan order", "egan order", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), AliTransactionType.WAVE_CODE); //设置授权码,条码等 order.setAuthCode(authCode); //支付结果 Map params = service.microPay(order); //校验 - if (service.verify(params)) { + if (service.verify(new NoticeParams(params))) { //支付校验通过后的处理 //......业务逻辑处理块........ @@ -157,13 +213,12 @@ public class AliPayController { /** * 支付回调地址 方式一 - * + *

* 方式二,{@link #payBack(HttpServletRequest)} 是属于简化方式, 试用与简单的业务场景 * - * * @param request 请求 - * * @return 返回对应的响应码 + * @throws IOException IOException * @see #payBack(HttpServletRequest) */ @Deprecated @@ -171,13 +226,13 @@ public class AliPayController { public String payBackBefore(HttpServletRequest request) throws IOException { //获取支付方返回的对应参数 - Map params = service.getParameter2Map(request.getParameterMap(), request.getInputStream()); - if (null == params) { + NoticeParams noticeParams = service.getNoticeParams(new HttpRequestNoticeParams(request)); + if (null == noticeParams) { return service.getPayOutMessage("fail", "失败").toMessage(); } //校验 - if (service.verify(params)) { + if (service.verify(noticeParams)) { //这里处理业务逻辑 //......业务逻辑处理块........ return service.successPayOutMessage(null).toMessage(); @@ -185,24 +240,41 @@ public class AliPayController { return service.getPayOutMessage("fail", "失败").toMessage(); } + /** * 支付回调地址 * * @param request 请求 - * - * @return 返回对应的响应码 - * + * @return 是否成功 + *

* 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link com.egzosn.pay.common.api.PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} - * + *

* 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler} - * + * @throws IOException IOException */ - @RequestMapping(value = "payBack.json") - public String payBack(HttpServletRequest request) throws IOException { + @Deprecated + @RequestMapping(value = "payBackOld.json") + public String payBackOld(HttpServletRequest request) throws IOException { //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() return service.payBack(request.getParameterMap(), request.getInputStream()).toMessage(); } + /** + * 支付回调地址 + * + * @param request 请求 + * @return 是否成功 + *

+ * 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link com.egzosn.pay.common.api.PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} + *

+ * 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler} + * @throws IOException IOException + */ + @RequestMapping(value = "payBack.json") + public String payBack(HttpServletRequest request) { + //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() + return service.payBack(new HttpRequestNoticeParams(request)).toMessage(); + } /** * 查询 @@ -212,7 +284,23 @@ public class AliPayController { */ @RequestMapping("query") public Map query(QueryOrder order) { - return service.query(order.getTradeNo(), order.getOutTradeNo()); + return service.query(new AssistOrder(order.getTradeNo(), order.getOutTradeNo())); + } + + /** + * 统一收单交易结算接口 + * + * @param order 订单的请求体 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @RequestMapping("settle") + public Map settle(OrderSettle order) { + /* OrderSettle order = new OrderSettle(); + order.setTradeNo("支付宝单号"); + order.setOutRequestNo("商户单号"); + order.setAmount(new BigDecimal(100)); + order.setDesc("线下转账");*/ + return service.settle(order); } @@ -224,8 +312,9 @@ public class AliPayController { */ @RequestMapping("close") public Map close(QueryOrder order) { - return service.close(order.getTradeNo(), order.getOutTradeNo()); + return service.close(new AssistOrder(order.getTradeNo(), order.getOutTradeNo())); } + /** * 交易c撤销接口 * @@ -244,19 +333,25 @@ public class AliPayController { * @return 返回支付方申请退款后的结果 */ @RequestMapping("refund") - public Map refund(RefundOrder order) { + public AliRefundResult refund(RefundOrder order) { return service.refund(order); } /** * 查询退款 * - * @param order 订单的请求体 * @return 返回支付方查询退款后的结果 */ @RequestMapping("refundquery") - public Map refundquery(QueryOrder order) { - return service.refundquery(order.getTradeNo(), order.getOutTradeNo()); + public Map refundquery() { + RefundOrder order = new RefundOrder(); + order.setOutTradeNo("我方系统商户单号"); + order.setTradeNo("支付宝单号"); + //退款金额 + order.setRefundAmount(new BigDecimal(1)); + order.setRefundNo("退款单号"); + order.setDescription(""); + return service.refundquery(order); } /** @@ -266,40 +361,30 @@ public class AliPayController { * @return 返回支付方下载对账单的结果 */ @RequestMapping("downloadbill") - public Object downloadbill(QueryOrder order) { - return service.downloadbill(order.getBillDate(), order.getBillType()); + public Object downloadBill(QueryOrder order) { + return service.downloadBill(order.getBillDate(), order.getBillType()); } - /** - * 通用查询接口,根据 AliTransactionType 类型进行实现,此接口不包括退款 - * - * @param order 订单的请求体 - * @return 返回支付方对应接口的结果 - */ - @RequestMapping("secondaryInterface") - public Map secondaryInterface(QueryOrder order) { - TransactionType type = AliTransactionType.valueOf(order.getTransactionType()); - return service.secondaryInterface(order.getTradeNoOrBillDate(), order.getOutTradeNoBillType(), type); - } - /** * 转账 * * @param order 转账订单 - * * @return 对应的转账结果 */ @RequestMapping("transfer") - public Map transfer(TransferOrder order) { -// order.setOutNo("转账单号"); -// order.setPayeeAccount("收款方账户,支付宝登录号,支持邮箱和手机号格式"); -// order.setAmount(new BigDecimal(10)); -// order.setPayerName("付款方姓名, 非必填"); -// order.setPayeeName("收款方真实姓名, 非必填"); -// order.setRemark("转账备注, 非必填"); - //收款方账户类型 ,默认值 ALIPAY_LOGONID:支付宝登录号,支持邮箱和手机号格式。 - order.setTransferType(AliTransferType.ALIPAY_LOGONID); + public Map transfer(AliTransferOrder order) { + order.setOutBizNo("转账单号"); + order.setTransAmount(new BigDecimal(10)); + order.setOrderTitle("转账业务的标题"); + order.setIdentity("参与方的唯一标识"); + order.setIdentityType("参与方的标识类型,目前支持如下类型:"); + order.setName("参与方真实姓名"); + order.setRemark("转账备注, 非必填"); + //单笔无密转账到支付宝账户 + order.setTransferType(AliTransferType.TRANS_ACCOUNT_NO_PWD); + //单笔无密转账到银行卡 +// order.setTransferType(AliTransferType.TRANS_BANKCARD_NO_PWD); return service.transfer(order); } @@ -308,11 +393,10 @@ public class AliPayController { * * @param outNo 商户转账订单号 * @param tradeNo 支付平台转账订单号 - * * @return 对应的转账订单 */ @RequestMapping("transferQuery") public Map transferQuery(String outNo, String tradeNo) { - return service.transferQuery(outNo, tradeNo); + return service.transferQuery(new AssistOrder(tradeNo, outNo)); } } diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/FuiouPayController.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/FuiouPayController.java new file mode 100644 index 0000000000000000000000000000000000000000..0ea09e5bc94083fd71b50bef48d978a1a4a4469c --- /dev/null +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/FuiouPayController.java @@ -0,0 +1,145 @@ + +package com.egzosn.pay.demo.controller; + + +import com.egzosn.pay.common.api.PayService; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.fuiou.api.FuiouPayConfigStorage; +import com.egzosn.pay.fuiou.api.FuiouPayService; +import com.egzosn.pay.fuiou.bean.FuiouTransactionType; +import com.egzosn.pay.web.support.HttpRequestNoticeParams; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Map; +import java.util.UUID; + +/** + * 发起支付入口 + * + * @author egan + * email egzosn@gmail.com + * date 2016/11/18 0:25 + */ +@RestController +@RequestMapping("fuiou") +public class FuiouPayController { + + private PayService service = null; + + + @PostConstruct + public void init() { + FuiouPayConfigStorage fuiouPayConfigStorage = new FuiouPayConfigStorage(); + fuiouPayConfigStorage.setMchntCd("合作者id"); + fuiouPayConfigStorage.setKeyPublic("支付密钥"); + fuiouPayConfigStorage.setKeyPrivate("支付密钥"); + fuiouPayConfigStorage.setNotifyUrl("异步回调地址"); + fuiouPayConfigStorage.setReturnUrl("同步回调地址"); + fuiouPayConfigStorage.setSignType("MD5"); + fuiouPayConfigStorage.setInputCharset("utf-8"); + //是否为测试账号,沙箱环境 + fuiouPayConfigStorage.setTest(true); + + + service = new FuiouPayService(fuiouPayConfigStorage); + + + //设置回调消息处理 + //TODO {@link com.egzosn.pay.demo.controller.FuiouPayController#payBack} +// service.setPayMessageHandler(new FuiouPayMessageHandler(null)); + } + + + + /** + * 跳到支付页面 + * 针对实时支付 + * + * @param price 金额 + * @return 跳到支付页面 + */ + @RequestMapping(value = "toPay.html", produces = "text/html;charset=UTF-8") + public String toPay( BigDecimal price) { + //支付订单基础信息 + PayOrder order = new PayOrder("订单title", "摘要", BigDecimal.valueOf(0.01) , UUID.randomUUID().toString().replace("-", "").substring(2)); + order.setTransactionType(FuiouTransactionType.B2C); + //获取支付所需的信息 +// Map directOrderInfo = service.orderInfo(order); + //获取表单提交对应的字符串,将其序列化到页面即可, +// return service.buildRequest(directOrderInfo, MethodType.POST); + return service.toPay(order); + } + + + /** + * 支付回调地址 方式一 + * + * 方式二,{@link #payBack(HttpServletRequest)} 是属于简化方式, 试用与简单的业务场景 + * + * @param request 请求 + * + * @return 是否成功 + * @throws IOException IOException + * @see #payBack(HttpServletRequest) + */ + @Deprecated + @RequestMapping(value = "payBackBefore.json") + public String payBackBefore(HttpServletRequest request) throws IOException { + + //获取支付方返回的对应参数 + Map params = service.getParameter2Map(request.getParameterMap(), request.getInputStream()); + if (null == params) { + return service.getPayOutMessage("fail", "失败").toMessage(); + } + + //校验 + if (service.verify(params)) { + //这里处理业务逻辑 + //......业务逻辑处理块........ + return service.successPayOutMessage(null).toMessage(); + } + + return service.getPayOutMessage("fail", "失败").toMessage(); + } + /** + * 支付回调地址 + * + * @param request 请求 + * + * @return 是否成功 + * + * 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link com.egzosn.pay.common.api.PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} + * + * 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler} + * @throws IOException IOException + */ + @Deprecated + @RequestMapping(value = "payBackOld.json") + public String payBackOld(HttpServletRequest request) throws IOException { + //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() + return service.payBack(request.getParameterMap(), request.getInputStream()).toMessage(); + } + /** + * 支付回调地址 + * + * @param request 请求 + * @return 是否成功 + *

+ * 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link com.egzosn.pay.common.api.PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} + *

+ * 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler} + * @throws IOException IOException + */ + @RequestMapping(value = "payBack.json") + public String payBack(HttpServletRequest request) { + //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() + return service.payBack(new HttpRequestNoticeParams(request)).toMessage(); + } + +} diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/PayController.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/PayController.java index 51824b55212e5e67251c8d84822adfe7c9596b7c..93b09e70ab158a3a9fbd1938843904f17ea48d24 100644 --- a/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/PayController.java +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/PayController.java @@ -2,14 +2,39 @@ package com.egzosn.pay.demo.controller; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import javax.annotation.Resource; +import javax.imageio.ImageIO; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.ModelAndView; + +import static com.egzosn.pay.demo.dao.ApyAccountRepository.apyAccounts; + import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.ali.api.AliPayService; import com.egzosn.pay.ali.bean.AliTransactionType; -import com.egzosn.pay.common.api.Callback; import com.egzosn.pay.common.api.PayConfigStorage; import com.egzosn.pay.common.api.PayMessageInterceptor; import com.egzosn.pay.common.api.PayService; -import com.egzosn.pay.common.bean.*; +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.common.bean.MethodType; +import com.egzosn.pay.common.bean.NoticeParams; +import com.egzosn.pay.common.bean.PayMessage; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.PayOutMessage; +import com.egzosn.pay.common.bean.RefundOrder; +import com.egzosn.pay.common.bean.RefundResult; +import com.egzosn.pay.common.bean.TransferOrder; import com.egzosn.pay.common.http.UriVariables; import com.egzosn.pay.common.util.MatrixToImageWriter; import com.egzosn.pay.common.util.str.StringUtils; @@ -18,37 +43,15 @@ import com.egzosn.pay.demo.entity.PayType; import com.egzosn.pay.demo.request.QueryOrder; import com.egzosn.pay.demo.service.ApyAccountService; import com.egzosn.pay.demo.service.PayResponse; -import com.egzosn.pay.payoneer.api.PayoneerPayService; +import com.egzosn.pay.web.support.HttpRequestNoticeParams; import com.egzosn.pay.wx.bean.WxTransactionType; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.view.RedirectView; -import org.springframework.web.servlet.view.json.MappingJackson2JsonView; - -import javax.annotation.Resource; -import javax.imageio.ImageIO; -import javax.servlet.http.HttpServletRequest; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.math.BigDecimal; -import java.net.URLEncoder; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.UUID; - -import static com.egzosn.pay.demo.dao.ApyAccountRepository.apyAccounts; /** * 发起支付入口 * - * @author: egan - * @email egzosn@gmail.com - * @date 2016/11/18 0:25 + * @author egan + * email egzosn@gmail.com + * date 2016/11/18 0:25 */ @RestController @RequestMapping @@ -58,17 +61,15 @@ public class PayController { private ApyAccountService service; @RequestMapping("/") - public ModelAndView index(){ + public ModelAndView index() { return new ModelAndView("/index.html"); } - - /** * 这里模拟账户信息增加 * - * @param account + * @param account 支付账户信息 * @return 支付账户信息 */ @RequestMapping("add") @@ -86,23 +87,24 @@ public class PayController { * 跳到支付页面 * 针对实时支付,即时付款 * + * @param request 请求 * @param payId 账户id * @param transactionType 交易类型, 这个针对于每一个 支付类型的对应的几种交易方式 * @param bankType 针对刷卡支付,卡的类型,类型值 - * @param price 金额 + * @param price 金额 * @return 跳到支付页面 */ @RequestMapping(value = "toPay.html", produces = "text/html;charset=UTF-8") - public String toPay(HttpServletRequest request,Integer payId, String transactionType, String bankType, BigDecimal price) { + public String toPay(HttpServletRequest request, Integer payId, String transactionType, String bankType, BigDecimal price) { //获取对应的支付账户操作工具(可根据账户id) PayResponse payResponse = service.getPayResponse(payId); - PayOrder order = new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price, UUID.randomUUID().toString().replace("-", ""), PayType.valueOf(payResponse.getStorage().getPayType()).getTransactionType(transactionType)); + PayOrder order = new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), PayType.valueOf(payResponse.getStorage().getPayType()).getTransactionType(transactionType)); // ------ 微信H5使用---- order.setSpbillCreateIp(request.getHeader("X-Real-IP")); StringBuffer requestURL = request.getRequestURL(); //设置网页地址 - order.setWapUrl(requestURL.substring(0, requestURL.indexOf("/") > 0 ? requestURL.indexOf("/") : requestURL.length() )); + order.setWapUrl(requestURL.substring(0, requestURL.indexOf("/") > 0 ? requestURL.indexOf("/") : requestURL.length())); //设置网页名称 order.setWapName("在线充值"); // ------ 微信H5使用---- @@ -112,12 +114,20 @@ public class PayController { order.setBankType(bankType); } Map orderInfo = payResponse.getService().orderInfo(order); + + //某些支付下单时无法设置单号,通过下单后返回对应单号,如 paypal,友店。 + String outTradeNo = order.getOutTradeNo(); + + System.out.println("支付订单号:" + outTradeNo + " 这里可以进行回存"); + return payResponse.getService().buildRequest(orderInfo, MethodType.POST); } /** * 跳到支付页面 * 针对实时支付,即时付款 + * + * @param request 请求 * @return 跳到支付页面 */ @RequestMapping(value = "toWxPay.html", produces = "text/html;charset=UTF-8") @@ -125,57 +135,76 @@ public class PayController { //获取对应的支付账户操作工具(可根据账户id) PayResponse payResponse = service.getPayResponse(2); - PayOrder order = new PayOrder("订单title", "摘要", new BigDecimal(0.01) , UUID.randomUUID().toString().replace("-", ""), WxTransactionType.MWEB); + PayOrder order = new PayOrder("订单title", "摘要", BigDecimal.valueOf(0.01), UUID.randomUUID().toString().replace("-", ""), WxTransactionType.MWEB); order.setSpbillCreateIp(request.getHeader("X-Real-IP")); StringBuffer requestURL = request.getRequestURL(); //设置网页地址 - order.setWapUrl(requestURL.substring(0, requestURL.indexOf("/") > 0 ? requestURL.indexOf("/") : requestURL.length() )); + order.setWapUrl(requestURL.substring(0, requestURL.indexOf("/") > 0 ? requestURL.indexOf("/") : requestURL.length())); //设置网页名称 order.setWapName("在线充值"); - Map orderInfo = payResponse.getService().orderInfo(order); - return payResponse.getService().buildRequest(orderInfo, MethodType.POST); +// Map orderInfo = payResponse.getService().orderInfo(order); +// return payResponse.getService().buildRequest(orderInfo, MethodType.POST); + return payResponse.getService().toPay(order); } /** * 公众号支付 * - * - * @param payId 账户id + * @param payId 账户id * @param openid openid - * @param price 金额 + * @param price 金额 * @return 返回jsapi所需参数 */ - @RequestMapping(value = "jsapi" ) + @RequestMapping(value = "jsapi") public Map toPay(Integer payId, String openid, BigDecimal price) { //获取对应的支付账户操作工具(可根据账户id) PayResponse payResponse = service.getPayResponse(payId); - PayOrder order = new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price, UUID.randomUUID().toString().replace("-", ""), PayType.valueOf(payResponse.getStorage().getPayType()).getTransactionType("JSAPI")); + PayOrder order = new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), PayType.valueOf(payResponse.getStorage().getPayType()).getTransactionType("JSAPI")); order.setOpenid(openid); Map orderInfo = payResponse.getService().orderInfo(order); orderInfo.put("code", 0); - return orderInfo; + return orderInfo; } + /** + * 获取支付预订单信息 + * + * @param payId 支付账户id + * @param transactionType 交易类型 + * @param price 金额 + * @return 支付预订单信息 + */ + @RequestMapping("app") + public Map getOrderInfo(Integer payId, String transactionType, BigDecimal price) { + //获取对应的支付账户操作工具(可根据账户id) + PayResponse payResponse = service.getPayResponse(payId); + Map data = new HashMap<>(); + data.put("code", 0); + PayOrder order = new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), PayType.valueOf(payResponse.getStorage().getPayType()).getTransactionType(transactionType)); + data.put("orderInfo", payResponse.getService().app(order)); + return data; + } /** * 刷卡付,pos主动扫码付款(条码付) + * * @param payId 账户id * @param transactionType 交易类型, 这个针对于每一个 支付类型的对应的几种交易方式 * @param authCode 授权码,条码等 - * @param price 金额 + * @param price 金额 * @return 支付结果 */ @RequestMapping(value = "microPay") - public Map microPay(Integer payId, String transactionType, BigDecimal price, String authCode) throws IOException { + public Map microPay(Integer payId, String transactionType, BigDecimal price, String authCode) { //获取对应的支付账户操作工具(可根据账户id) PayResponse payResponse = service.getPayResponse(payId); - PayOrder order = new PayOrder("huodull order", "huodull order", null == price ? new BigDecimal(0.01) : price, UUID.randomUUID().toString().replace("-", ""), PayType.valueOf(payResponse.getStorage().getPayType()).getTransactionType(transactionType)); + PayOrder order = new PayOrder("egan order", "egan order", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), PayType.valueOf(payResponse.getStorage().getPayType()).getTransactionType(transactionType)); //设置授权码,条码等 order.setAuthCode(authCode); //支付结果 @@ -183,7 +212,7 @@ public class PayController { PayConfigStorage storage = payResponse.getService().getPayConfigStorage(); //校验 if (payResponse.getService().verify(params)) { - PayMessage message = new PayMessage(params, storage.getPayType(), storage.getMsgType().name()); + PayMessage message = new PayMessage(params, storage.getPayType()); //支付校验通过后的处理 payResponse.getRouter().route(message); } @@ -194,10 +223,12 @@ public class PayController { /** * 获取二维码图像 * 二维码支付 + * * @param payId 账户id * @param transactionType 交易类型, 这个针对于每一个 支付类型的对应的几种交易方式 - * @param price 金额 + * @param price 金额 * @return 二维码图像 + * @throws IOException IOException */ @RequestMapping(value = "toQrPay.jpg", produces = "image/jpeg;charset=UTF-8") public byte[] toWxQrPay(Integer payId, String transactionType, BigDecimal price) throws IOException { @@ -205,30 +236,49 @@ public class PayController { PayResponse payResponse = service.getPayResponse(payId); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ImageIO.write(payResponse.getService().genQrPay(new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price, System.currentTimeMillis()+"", PayType.valueOf(payResponse.getStorage().getPayType()).getTransactionType(transactionType))), "JPEG", baos); + ImageIO.write(payResponse.getService().genQrPay(new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, System.currentTimeMillis() + "", PayType.valueOf(payResponse.getStorage().getPayType()).getTransactionType(transactionType))), "JPEG", baos); return baos.toByteArray(); } + /** + * 获取二维码地址 + * 二维码支付 + * + * @param price 金额 + * @return 二维码图像 + * @throws IOException IOException + */ + @RequestMapping(value = "getQrPay.json") + public String getQrPay(Integer payId, String transactionType, BigDecimal price) throws IOException { + //获取对应的支付账户操作工具(可根据账户id) + //获取对应的支付账户操作工具(可根据账户id) + PayResponse payResponse = service.getPayResponse(payId); + return payResponse.getService().getQrPay(new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, System.currentTimeMillis() + "", PayType.valueOf(payResponse.getStorage().getPayType()).getTransactionType(transactionType))); + } + /** * 获取一码付二维码图像 * 二维码支付 - * @param wxPayId 微信账户id - * @param aliPayId 支付宝id - * @param price 金额 + * + * @param wxPayId 微信账户id + * @param aliPayId 支付宝id + * @param price 金额 + * @param request 请求 * @return 二维码图像 + * @throws IOException IOException */ @RequestMapping(value = "toWxAliQrPay.jpg", produces = "image/jpeg;charset=UTF-8") - public byte[] toWxAliQrPay(Integer wxPayId,Integer aliPayId, BigDecimal price, HttpServletRequest request) throws IOException { + public byte[] toWxAliQrPay(Integer wxPayId, Integer aliPayId, BigDecimal price, HttpServletRequest request) throws IOException { //获取对应的支付账户操作工具(可根据账户id) ByteArrayOutputStream baos = new ByteArrayOutputStream(); //这里为需要生成二维码的地址 StringBuffer url = request.getRequestURL(); url = new StringBuffer(url.substring(0, url.lastIndexOf(request.getRequestURI()))); - url .append("/toWxAliPay.html?"); - if (null != wxPayId){ + url.append("/toWxAliPay.html?"); + if (null != wxPayId) { url.append("wxPayId=").append(wxPayId).append("&"); } - if (null != aliPayId){ + if (null != aliPayId) { url.append("aliPayId=").append(aliPayId).append("&"); } url.append("price=").append(price); @@ -238,88 +288,99 @@ public class PayController { } /** - * * 支付宝与微信平台的判断 并进行支付的转跳 - * @param wxPayId 微信账户id - * @param aliPayId 支付宝id - * @param price 金额 + * + * @param wxPayId 微信账户id + * @param aliPayId 支付宝id + * @param price 金额 + * @param request 请求 * @return 支付宝与微信平台的判断 + * @throws IOException IOException */ @RequestMapping(value = "toWxAliPay.html", produces = "text/html;charset=UTF-8") - public String toWxAliPay(Integer wxPayId,Integer aliPayId, BigDecimal price, HttpServletRequest request) throws IOException { + public String toWxAliPay(Integer wxPayId, Integer aliPayId, BigDecimal price, HttpServletRequest request) throws IOException { StringBuilder html = new StringBuilder(); //订单 - PayOrder payOrder = new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price, System.currentTimeMillis() + ""); + PayOrder payOrder = new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, System.currentTimeMillis() + ""); String ua = request.getHeader("user-agent"); - if(ua.contains("MicroMessenger")){ + if (ua.contains("MicroMessenger")) { payOrder.setTransactionType(WxTransactionType.NATIVE); PayService service = this.service.getPayResponse(wxPayId).getService(); - return String.format("",(String) service.orderInfo(payOrder).get("code_url")); + return String.format("", (String) service.orderInfo(payOrder).get("code_url")); } - if(ua.contains("AlipayClient")){ + if (ua.contains("AlipayClient")) { payOrder.setTransactionType(AliTransactionType.SWEEPPAY); - AliPayService service = (AliPayService)this.service.getPayResponse(aliPayId).getService(); + AliPayService service = (AliPayService) this.service.getPayResponse(aliPayId).getService(); JSONObject result = service.getHttpRequestTemplate().postForObject(service.getReqUrl() + "?" + UriVariables.getMapToParameters(service.orderInfo(payOrder)), null, JSONObject.class); - result = result.getJSONObject("alipay_trade_precreate_response"); + result = result.getJSONObject("alipay_trade_precreate_response"); return String.format("", result.getString("qr_code")); } - return String.format("", ua); + return String.format("", ua); } - - /** - * 获取支付预订单信息 + * 支付回调地址 + * 方式三 * - * @param payId 支付账户id - * @param transactionType 交易类型 - * @return 支付预订单信息 + * @param request 请求 + * @param payId 账户id + * @return 支付是否成功 + * @throws IOException IOException + * 拦截器相关增加, 详情查看{@link com.egzosn.pay.common.api.PayService#addPayMessageInterceptor(PayMessageInterceptor)} + *

+ * 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link com.egzosn.pay.common.api.PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} + *

+ * 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler} */ - @RequestMapping("getOrderInfo") - public Map getOrderInfo(Integer payId, String transactionType, BigDecimal price) { - //获取对应的支付账户操作工具(可根据账户id) + @RequestMapping(value = "payBack{payId}.json") + public String payBack(HttpServletRequest request, @PathVariable Integer payId) throws IOException { + //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() PayResponse payResponse = service.getPayResponse(payId); - Map data = new HashMap<>(); - data.put("code", 0); - PayOrder order = new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price, UUID.randomUUID().toString().replace("-", ""), PayType.valueOf(payResponse.getStorage().getPayType()).getTransactionType(transactionType)); - data.put("orderInfo", payResponse.getService().orderInfo(order)); - return data; + return payResponse.getService().payBack(new HttpRequestNoticeParams(request)).toMessage(); } - /** * 支付回调地址 方式一 + *

+ * 建议使用 方式三,{@link #payBack(HttpServletRequest, Integer)} 是属于简化方式, 试用与简单的业务场景 * - * 方式二,{@link #payBack(HttpServletRequest, Integer)} 是属于简化方式, 试用与简单的业务场景 - * - * - * @param request - * @param payId + * @param request 请求 + * @param payId 账户id * @return 支付是否成功 - * - * */ @RequestMapping(value = "payBackOne{payId}.json") - public String payBackOne(HttpServletRequest request, @PathVariable Integer payId) throws IOException { + public String payBackOne(HttpServletRequest request, @PathVariable Integer payId) { //根据账户id,获取对应的支付账户操作工具 PayResponse payResponse = service.getPayResponse(payId); PayConfigStorage storage = payResponse.getStorage(); //获取支付方返回的对应参数 - Map params = payResponse.getService().getParameter2Map(request.getParameterMap(), request.getInputStream()); -// Map params = JSONObject.parseObject("{\"bizType\":\"000201\",\"signPubKeyCert\":\"-----BEGIN CERTIFICATE-----\\r\\nMIIEQzCCAyugAwIBAgIFEBJJZVgwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UEBhMC\\r\\nQ04xMDAuBgNVBAoTJ0NoaW5hIEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1dGhv\\r\\ncml0eTEXMBUGA1UEAxMOQ0ZDQSBURVNUIE9DQTEwHhcNMTcxMTAxMDcyNDA4WhcN\\r\\nMjAxMTAxMDcyNDA4WjB3MQswCQYDVQQGEwJjbjESMBAGA1UEChMJQ0ZDQSBPQ0Ex\\r\\nMQ4wDAYDVQQLEwVDVVBSQTEUMBIGA1UECxMLRW50ZXJwcmlzZXMxLjAsBgNVBAMU\\r\\nJTA0MUBaMjAxNy0xMS0xQDAwMDQwMDAwOlNJR05AMDAwMDAwMDEwggEiMA0GCSqG\\r\\nSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDDIWO6AESrg+34HgbU9mSpgef0sl6avr1d\\r\\nbD/IjjZYM63SoQi3CZHZUyoyzBKodRzowJrwXmd+hCmdcIfavdvfwi6x+ptJNp9d\\r\\nEtpfEAnJk+4quriQFj1dNiv6uP8ARgn07UMhgdYB7D8aA1j77Yk1ROx7+LFeo7rZ\\r\\nDdde2U1opPxjIqOPqiPno78JMXpFn7LiGPXu75bwY2rYIGEEImnypgiYuW1vo9UO\\r\\nG47NMWTnsIdy68FquPSw5FKp5foL825GNX3oJSZui8d2UDkMLBasf06Jz0JKz5AV\\r\\nblaI+s24/iCfo8r+6WaCs8e6BDkaijJkR/bvRCQeQpbX3V8WoTLVAgMBAAGjgfQw\\r\\ngfEwHwYDVR0jBBgwFoAUz3CdYeudfC6498sCQPcJnf4zdIAwSAYDVR0gBEEwPzA9\\r\\nBghggRyG7yoBATAxMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmNmY2EuY29tLmNu\\r\\nL3VzL3VzLTE0Lmh0bTA5BgNVHR8EMjAwMC6gLKAqhihodHRwOi8vdWNybC5jZmNh\\r\\nLmNvbS5jbi9SU0EvY3JsMjQ4NzIuY3JsMAsGA1UdDwQEAwID6DAdBgNVHQ4EFgQU\\r\\nmQQLyuqYjES7qKO+zOkzEbvdFwgwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUF\\r\\nBwMEMA0GCSqGSIb3DQEBBQUAA4IBAQAujhBuOcuxA+VzoUH84uoFt5aaBM3vGlpW\\r\\nKVMz6BUsLbIpp1ho5h+LaMnxMs6jdXXDh/du8X5SKMaIddiLw7ujZy1LibKy2jYi\\r\\nYYfs3tbZ0ffCKQtv78vCgC+IxUUurALY4w58fRLLdu8u8p9jyRFHsQEwSq+W5+bP\\r\\nMTh2w7cDd9h+6KoCN6AMI1Ly7MxRIhCbNBL9bzaxF9B5GK86ARY7ixkuDCEl4XCF\\r\\nJGxeoye9R46NqZ6AA/k97mJun//gmUjStmb9PUXA59fR5suAB5o/5lBySZ8UXkrI\\r\\npp/iLT8vIl1hNgLh0Ghs7DBSx99I+S3VuUzjHNxL6fGRhlix7Rb8\\r\\n-----END CERTIFICATE-----\",\"orderId\":\"20171213224128\",\"signature\":\"l8xBYSoMNzt01DDa9/JYcrQKWxN5tasUgSxf6NNsQK5t+DqMr2G9qhHXnDg5bEzeRyTFP4bM3htX9RTRhXYDy7EEsL46ZD4ib5I6mp2wXx+26zscUcLdJUiddkY5eFvQK4tPC8blw7Y6p858yiVJpHgbOK3cONhS7vwPJtK2jMbkY+GATu3aZ4iygkQc75cG+EW8nJQVwLNh7q9A6A6II18EFxR7XubdlIHXv/InVaS6ux8Wh2nmQlhRRnLtHq1ri7v1QPlu2FzM+kaf7/fn61iGr8zEPj62NzWDXue62LUfb4kTRgdkcJnfJBJl8vjZ/w93UtsnK3zjzJC/Nu+wCw==\",\"txnSubType\":\"01\",\"traceNo\":\"492156\",\"accNo\":\"6221********0000\",\"settleAmt\":\"1000\",\"settleCurrencyCode\":\"156\",\"settleDate\":\"1213\",\"txnType\":\"01\",\"encoding\":\"UTF-8\",\"version\":\"5.1.0\",\"queryId\":\"511712132241284921568\",\"accessType\":\"0\",\"exchangeRate\":\"0\",\"respMsg\":\"success\",\"traceTime\":\"1213224128\",\"txnTime\":\"20171213224128\",\"merId\":\"777290058154626\",\"currencyCode\":\"156\",\"respCode\":\"00\",\"signMethod\":\"01\",\"txnAmt\":\"1000\"}"); + final PayService service = payResponse.getService(); + final NoticeParams noticeParams = service.getNoticeParams(new HttpRequestNoticeParams(request)); - if (null == params) { + if (null == noticeParams) { return payResponse.getService().getPayOutMessage("fail", "失败").toMessage(); } //校验 - if (payResponse.getService().verify(params)) { - PayMessage message = new PayMessage(params, storage.getPayType(), storage.getMsgType().name()); - PayOutMessage outMessage = payResponse.getRouter().route(message); + if (payResponse.getService().verify(noticeParams)) { + Map params = noticeParams.getBody(); + //方式一 或者创建PayMessage的子类,AliPayMessage,WxPayMessage等等 + /* PayMessage message = new PayMessage(params, storage.getPayType(), storage.getMsgType().name()); + PayOutMessage outMessage = payResponse.getRouter().route(message);*/ + + //方式二 + /*PayMessage message = payResponse.getService().createMessage(params); + message.setPayType(storage.getPayType()); + message.setMsgType(storage.getMsgType().name()); + PayOutMessage outMessage = payResponse.getRouter().route(message); + */ + //方式三 + PayOutMessage outMessage = payResponse.getRouter().route(params, storage); + return outMessage.toMessage(); } @@ -327,25 +388,23 @@ public class PayController { } - - - /** * 支付回调地址 * 方式二 - * @param request - * - * @return - * - * 拦截器相关增加, 详情查看{@link com.egzosn.pay.common.api.PayService#addPayMessageInterceptor(PayMessageInterceptor)} - *

- * 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link com.egzosn.pay.common.api.PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} - * - * 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler} * + * @param request 请求 + * @param payId 账户id + * @return 支付是否成功 + * @throws IOException IOException + * 拦截器相关增加, 详情查看{@link com.egzosn.pay.common.api.PayService#addPayMessageInterceptor(PayMessageInterceptor)} + *

+ * 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link com.egzosn.pay.common.api.PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} + *

+ * 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler} */ - @RequestMapping(value = "payBack{payId}.json") - public String payBack(HttpServletRequest request, @PathVariable Integer payId) throws IOException { + @Deprecated + @RequestMapping(value = "payBackOld{payId}.json") + public String payBackOld(HttpServletRequest request, @PathVariable Integer payId) throws IOException { //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() PayResponse payResponse = service.getPayResponse(payId); return payResponse.getService().payBack(request.getParameterMap(), request.getInputStream()).toMessage(); @@ -361,7 +420,7 @@ public class PayController { @RequestMapping("query") public Map query(QueryOrder order) { PayResponse payResponse = service.getPayResponse(order.getPayId()); - return payResponse.getService().query(order.getTradeNo(), order.getOutTradeNo()); + return payResponse.getService().query(new AssistOrder(order.getTradeNo(), order.getOutTradeNo())); } /** * 查询 @@ -386,21 +445,23 @@ public class PayController { @RequestMapping("close") public Map close(QueryOrder order) { PayResponse payResponse = service.getPayResponse(order.getPayId()); - return payResponse.getService().close(order.getTradeNo(), order.getOutTradeNo()); + return payResponse.getService().close(new AssistOrder(order.getTradeNo(), order.getOutTradeNo())); } /** * 申请退款接口 * + * @param payId 账户id * @param order 订单的请求体 * @return 返回支付方申请退款后的结果 */ @RequestMapping("refund") - public Map refund(Integer payId ,RefundOrder order) { + public RefundResult refund(Integer payId, RefundOrder order) { PayResponse payResponse = service.getPayResponse(payId); // return payResponse.getService().refund(order.getTradeNo(), order.getOutTradeNo(), order.getRefundAmount(), order.getTotalAmount()); - return payResponse.getService().refund(order); + final PayService service = payResponse.getService(); + return service.refund(order); } /** @@ -410,9 +471,11 @@ public class PayController { * @return 返回支付方查询退款后的结果 */ @RequestMapping("refundquery") - public Map refundquery(QueryOrder order) { - PayResponse payResponse = service.getPayResponse(order.getPayId()); - return payResponse.getService().refundquery(order.getTradeNo(), order.getOutTradeNo()); + public Map refundquery(Integer payId, RefundOrder order) { + PayResponse payResponse = service.getPayResponse(payId); + + + return payResponse.getService().refundquery(order); } /** @@ -421,33 +484,19 @@ public class PayController { * @param order 订单的请求体 * @return 返回支付方下载对账单的结果 */ - @RequestMapping("downloadbill") - public Object downloadbill(QueryOrder order) { + @RequestMapping("downloadBill") + public Object downloadBill(QueryOrder order) { PayResponse payResponse = service.getPayResponse(order.getPayId()); - return payResponse.getService().downloadbill(order.getBillDate(), order.getBillType()); - } - - - /** - * 通用查询接口,根据 TransactionType 类型进行实现,此接口不包括退款 - * - * @param order 订单的请求体 - * @return 返回支付方对应接口的结果 - */ - @RequestMapping("secondaryInterface") - public Map secondaryInterface(QueryOrder order) { - PayResponse payResponse = service.getPayResponse(order.getPayId()); - TransactionType type = PayType.valueOf(payResponse.getStorage().getPayType()).getTransactionType(order.getTransactionType()); - return payResponse.getService().secondaryInterface(order.getTradeNoOrBillDate(), order.getOutTradeNoBillType(), type); + return payResponse.getService().downloadBill(order.getBillDate(), order.getBillType()); } /** * 转账 * + * @param payId 账户id * @param order 转账订单 - * * @return 对应的转账结果 */ @RequestMapping("transfer") @@ -459,9 +508,9 @@ public class PayController { /** * 转账查询 * + * @param payId 账户id * @param outNo 商户转账订单号 * @param tradeNo 支付平台转账订单号 - * * @return 对应的转账订单 */ @RequestMapping("transferQuery") diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/PayPalPayController.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/PayPalPayController.java index 84611a80d36b4802a14b566c4f0f7a03e9d5f188..01af6e9dfd5f380dc27fe24ee9466414f0ad090c 100644 --- a/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/PayPalPayController.java +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/PayPalPayController.java @@ -1,34 +1,35 @@ package com.egzosn.pay.demo.controller; -import com.egzosn.pay.ali.bean.AliTransactionType; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.util.UUID; + +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + import com.egzosn.pay.common.api.PayService; -import com.egzosn.pay.common.bean.CurType; -import com.egzosn.pay.common.bean.MethodType; +import com.egzosn.pay.common.bean.DefaultCurType; import com.egzosn.pay.common.bean.PayOrder; import com.egzosn.pay.common.bean.RefundOrder; +import com.egzosn.pay.common.bean.RefundResult; import com.egzosn.pay.common.http.HttpConfigStorage; import com.egzosn.pay.paypal.api.PayPalConfigStorage; import com.egzosn.pay.paypal.api.PayPalPayService; import com.egzosn.pay.paypal.bean.PayPalTransactionType; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.PostConstruct; -import javax.servlet.http.HttpServletRequest; -import java.io.IOException; -import java.io.InputStream; -import java.math.BigDecimal; -import java.util.Map; -import java.util.UUID; +import com.egzosn.pay.web.support.HttpRequestNoticeParams; /** * 发起支付入口 * - * @author: egan - * @email egzosn@gmail.com - * @date 2018/05/06 10:30 + * @author egan + * email egzosn@gmail.com + * date 2018/05/06 10:30 */ @RestController @RequestMapping("payPal") @@ -40,13 +41,15 @@ public class PayPalPayController { @PostConstruct public void init() { PayPalConfigStorage storage = new PayPalConfigStorage(); - storage.setClientID("商户id"); - storage.setClientSecret("商户密钥"); + storage.setClientId("AZDS0IhUZvJTO99unlvSDMfbZIP-p-UecYXZdJoweha9LFuqKXKcQIGZgfVaX6oGiAOJAUuJD7JwyTl1"); + storage.setClientSecret("EK2YaOrw3oLSDWIRzvb9BWGTjiPPhY1fFUu5ylhUsGYLc_h_dlpJ0hr_LDEkbO9MyKP2P83YcywbPaem"); + //webhook回调时验签使用必须https://developer.paypal.com/dashboard/webhooksSimulator + storage.setWebHookId("AZDS0IhUZvJTO99unlvSDMfbZIP"); storage.setTest(true); //发起付款后的页面转跳地址 - storage.setReturnUrl("http://127.0.0.1:8088/pay/success"); + storage.setReturnUrl("http://www.egzosn.com/payPal/payBack.json"); //取消按钮转跳地址,这里用异步通知地址的兼容的做法 - storage.setNotifyUrl("http://127.0.0.1:8088/pay/cancel"); + storage.setCancelUrl("http://www.egzosn.com/pay/cancel"); service = new PayPalPayService(storage); //请求连接池配置 @@ -69,34 +72,47 @@ public class PayPalPayController { @RequestMapping(value = "toPay.html", produces = "text/html;charset=UTF-8") public String toPay(BigDecimal price) { //及时收款 - PayOrder order = new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price, UUID.randomUUID().toString().replace("-", ""), PayPalTransactionType.sale); + PayOrder order = new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), PayPalTransactionType.sale); + +// Map orderInfo = service.orderInfo(order); +// return service.buildRequest(orderInfo, MethodType.POST); + + String toPayHtml = service.toPay(order); - Map orderInfo = service.orderInfo(order); - return service.buildRequest(orderInfo, MethodType.POST); + //某些支付下单时无法设置单号,通过下单后返回对应单号,如 paypal,友店。 + String tradeNo = order.getTradeNo(); + System.out.println("支付订单号:" + tradeNo + " 这里可以进行回存"); + + return toPayHtml; } + /** * 申请退款接口 * * @return 返回支付方申请退款后的结果 */ @RequestMapping("refund") - public Map refund() { + public RefundResult refund() { // TODO 这里需要 refundAmount, curType, description, tradeNo RefundOrder order = new RefundOrder(); - order.setCurType(CurType.USD); + order.setCurType(DefaultCurType.USD); order.setDescription(" description "); order.setTradeNo("paypal 平台的单号"); - order.setRefundAmount(new BigDecimal(0.01)); + order.setRefundAmount(BigDecimal.valueOf(0.01)); return service.refund(order); } + /* */ + /** * return url * PayPal确认付款调用的接口 * 用户确认付款后,paypal调用的这个方法执行付款 * + * @param request 请求 * @return 付款成功信息 + * @throws IOException IOException */ @GetMapping(value = "payBackBefore.json") public String payBackBefore(HttpServletRequest request) throws IOException { @@ -110,22 +126,40 @@ public class PayPalPayController { return "failure"; } + /* */ + /** * 支付回调地址 * - * @param request + * @param request 请求 * - * @return + * @return 是否成功 * * 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link com.egzosn.pay.common.api.PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} * * 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler} + * @throws IOException IOException + */ + @RequestMapping(value = "payBackOld.json") + public String payBackOld(HttpServletRequest request) throws IOException { + //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() + return service.payBack(request.getParameterMap(), request.getInputStream()).toMessage(); + } + /** + * 支付回调地址 * + * @param request 请求 + * @return 是否成功 + *

+ * 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link com.egzosn.pay.common.api.PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} + *

+ * 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler} + * @throws IOException IOException */ @RequestMapping(value = "payBack.json") - public String payBack(HttpServletRequest request) throws IOException { + public String payBack(HttpServletRequest request) { //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() - return service.payBack(request.getParameterMap(), request.getInputStream()).toMessage(); + return service.payBack(new HttpRequestNoticeParams(request)).toMessage(); } diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/PayPalV2PayController.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/PayPalV2PayController.java new file mode 100644 index 0000000000000000000000000000000000000000..13938470d1eea22706625f96e6e8f9cc134771f7 --- /dev/null +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/PayPalV2PayController.java @@ -0,0 +1,151 @@ +package com.egzosn.pay.demo.controller; + + +import java.math.BigDecimal; +import java.util.Map; + +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.egzosn.pay.common.api.PayService; +import com.egzosn.pay.common.bean.DefaultCurType; +import com.egzosn.pay.common.bean.RefundOrder; +import com.egzosn.pay.common.bean.RefundResult; +import com.egzosn.pay.paypal.api.PayPalConfigStorage; +import com.egzosn.pay.paypal.v2.api.PayPalPayService; +import com.egzosn.pay.paypal.v2.bean.PayPalOrder; +import com.egzosn.pay.paypal.v2.bean.order.AddressPortable; +import com.egzosn.pay.paypal.v2.bean.order.Name; +import com.egzosn.pay.paypal.v2.bean.order.ShippingDetail; +import com.egzosn.pay.web.support.HttpRequestNoticeParams; + +/** + * 发起支付入口 + * + * @author egan + * email egzosn@gmail.com + * date 2018/05/06 10:30 + */ +@RestController +@RequestMapping("payPalV2") +public class PayPalV2PayController { + + + private PayService service = null; + + @PostConstruct + public void init() { + PayPalConfigStorage storage = new PayPalConfigStorage(); + storage.setClientID("AZDS0IhUZvJTO99unlvSDMfbZIP-p-UecYXZdJoweha9LFuqKXKcQIGZgfVaX6oGiAOJAUuJD7JwyTl1"); + storage.setClientSecret("EK2YaOrw3oLSDWIRzvb9BWGTjiPPhY1fFUu5ylhUsGYLc_h_dlpJ0hr_LDEkbO9MyKP2P83YcywbPaem"); + storage.setTest(true); + //发起付款后的页面转跳地址 + storage.setReturnUrl("http://www.egzosn.com/payPal/payBack.json"); + // 注意:这里不是异步回调的通知 IPN 地址设置的路径:https://developer.paypal.com/developer/ipnSimulator/ + //取消按钮转跳地址, + storage.setCancelUrl("http://www.egzosn.com/pay/cancel"); + service = new PayPalPayService(storage); + } + + + /** + * 跳到支付页面 + * 针对实时支付,即时付款 + * + * @param price 金额 + * @return 跳到支付页面 + */ + @RequestMapping(value = "toPay.html", produces = "text/html;charset=UTF-8") + public String toPay(BigDecimal price) { + //及时收款 + PayPalOrder order = new PayPalOrder(); + order.setBrandName("该标签将覆盖PayPal网站上PayPal帐户中的公司名称,非必填"); + order.setDescription("订单说明"); + order.setInvoiceId("非必填 API调用者为该订单提供的外部发票号码。出现在付款人的交易历史记录和付款人收到的电子邮件中。"); + order.setCustomId("非必填 api调用中没发现有任何用处 API调用者提供的外部ID。用于协调客户端交易与PayPal交易。出现在交易和结算报告中,但付款人不可见"); + order.setPrice(price); + order.setShippingDetail(new ShippingDetail() + .name(new Name().fullName("RATTA")) + .addressPortable(new AddressPortable() + .addressLine1("梅陇镇") + .addressLine2("集心路168号") + .adminArea2("闵行区") + .adminArea1("上海市") + .postalCode("20000") + .countryCode("CN"))); + String toPayHtml = service.toPay(order); + + //某些支付下单时无法设置单号,通过下单后返回对应单号,如 paypal,友店。 + String tradeNo = order.getTradeNo(); + System.out.println("支付订单号:" + tradeNo + " 这里可以进行回存"); + + return toPayHtml; + } + + /** + * 申请退款接口 + * + * @return 返回支付方申请退款后的结果 + */ + @RequestMapping("refund") + public RefundResult refund() { + // TODO 这里需要 refundAmount, curType, description, tradeNo + RefundOrder order = new RefundOrder(); + order.setCurType(DefaultCurType.USD); + order.setDescription(" description "); + order.setTradeNo("paypal 平台的单号, 支付下单返回的单号"); + order.setRefundAmount(BigDecimal.valueOf(0.01)); + RefundResult refundResult = service.refund(order); + System.out.println("退款成功之后返回退款单号:" + refundResult.getRefundNo()); + return refundResult; + } + + /** + * 查询退款 + * + * @return 返回支付方查询退款后的结果 + */ + @RequestMapping("refundquery") + public Map refundquery() { + RefundOrder order = new RefundOrder(); + order.setRefundNo("退款成功之后返回的退款单号"); + return service.refundquery(order); + } + + + /** + * 支付回调地址 + * 请求方式必须为post, 回调详情文档:https://developer.paypal.com/api/rest/webhooks + * 回调成功之后必须返回http状态码 200 才能行,不然一直会重复回调,注意:如果您的应用程序以任何其他状态码响应,贝宝会在三天内重试25次通知消息 + * + * @param request 请求 + * @param response 响应 + * @return 是否成功 + *

+ * 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link com.egzosn.pay.common.api.PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} + *

+ * 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler} + * 付款之后不会进行扣款,需要调用 {@link PayPalPayService#ordersCapture(java.lang.String)}进行扣款,并返回 captureId使用,后续退款,查订单等等使用,用来替换下单返回的id + * 注意:最好在付款成功之后回调时进行调用 {@link PayPalPayService#ordersCapture(java.lang.String)} + * 确认订单并返回确认后订单信息 + * 注意:此方法一个订单只能调用一次, 建议在支付回调时进行调用 + * 这里主要用来获取captureId使用,后续退款,查订单等等使用,用来替换下单返回的id + * 详情: https://developer.paypal.com/docs/api/orders/v2/#orders_capture + */ + @PostMapping(value = "payBack.json") + public String payBack(HttpServletRequest request, HttpServletResponse response) { + //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() + final String message = service.payBack(new HttpRequestNoticeParams(request)).toMessage(); + if (!"200".equals(message)) { + response.setStatus(400); + } + return message; + } + + +} diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/PayoneerPayController.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/PayoneerPayController.java index f1b34be45becabc51f93ca638e6241cbe4b1e4a2..caaee058663d24ee3e94c2b3a2a5ff084eeac951 100644 --- a/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/PayoneerPayController.java +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/PayoneerPayController.java @@ -1,27 +1,35 @@ package com.egzosn.pay.demo.controller; -import com.egzosn.pay.common.bean.*; -import com.egzosn.pay.common.http.HttpConfigStorage; -import com.egzosn.pay.demo.request.QueryOrder; -import com.egzosn.pay.payoneer.api.PayoneerConfigStorage; -import com.egzosn.pay.payoneer.api.PayoneerPayService; -import com.egzosn.pay.payoneer.bean.PayoneerTransactionType; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.PostConstruct; -import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.math.BigDecimal; import java.util.LinkedHashMap; import java.util.Map; import java.util.UUID; +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.common.bean.DefaultCurType; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.RefundOrder; +import com.egzosn.pay.common.bean.RefundResult; +import com.egzosn.pay.common.bean.TransactionType; +import com.egzosn.pay.common.bean.TransferOrder; +import com.egzosn.pay.common.http.HttpConfigStorage; +import com.egzosn.pay.demo.request.QueryOrder; +import com.egzosn.pay.payoneer.api.PayoneerConfigStorage; +import com.egzosn.pay.payoneer.api.PayoneerPayService; +import com.egzosn.pay.payoneer.bean.PayoneerTransactionType; + /** * @author egan - * @email egzosn@gmail.com - * @date 2018/2/5 + * email egzosn@gmail.com + * date 2018/2/5 */ @RestController @RequestMapping("payoneer") @@ -34,7 +42,6 @@ public class PayoneerPayController { public void init() { PayoneerConfigStorage configStorage = new PayoneerConfigStorage(); configStorage.setProgramId("商户id"); - configStorage.setMsgType(MsgType.json); configStorage.setInputCharset("utf-8"); configStorage.setUserName("PayoneerPay 用户名"); configStorage.setApiPassword("PayoneerPay API password"); @@ -63,11 +70,12 @@ public class PayoneerPayController { /** * 获取授权页面 - * @param payeeId - * @return + * + * @param payeeId 用户id + * @return 获取授权页面 */ @RequestMapping("getAuthorizationPage.json") - public Map getAuthorizationPage( String payeeId ){ + public Map getAuthorizationPage(String payeeId) { Map data = new LinkedHashMap<>(); data.put("code", 0); @@ -75,13 +83,14 @@ public class PayoneerPayController { return data; } - /** + /** * 获取授权用户信息,包含用户状态,注册时间,联系人信息,地址信息等等 + * * @param payeeId 用户id - * @return + * @return 获取授权用户信息 */ @RequestMapping("getAuthorizationUser.json") - public Map getAuthorizationUser( String payeeId ){ + public Map getAuthorizationUser(String payeeId) { Map data = new LinkedHashMap<>(); data.put("code", 0); @@ -92,37 +101,38 @@ public class PayoneerPayController { /** * 主动收款 - * @param price 金额 - * @param userId 付款用户 + * + * @param price 金额 + * @param userId 付款用户 * @return 支付结果 */ @ResponseBody @RequestMapping(value = "microPay.json") - public Map microPay(BigDecimal price, String userId) throws IOException { + public Map microPay(BigDecimal price, String userId) { PayOrder order = new PayOrder("Order_payment:", "Order payment", price, UUID.randomUUID().toString().replace("-", ""), PayoneerTransactionType.CHARGE); //币种 - order.setCurType(CurType.USD); + order.setCurType(DefaultCurType.USD); //设置授权码,条码等 - order.setAuthCode( userId); + order.setAuthCode(userId); //支付结果 Map params = service.microPay(order); - if (10700 == (Integer) params.get(PayoneerPayService.CODE)){ + if (10700 == (Integer) params.get(PayoneerPayService.CODE)) { System.out.println("未授权"); - }else if (0 == (Integer) params.get(PayoneerPayService.CODE)){ + } + else if (0 == (Integer) params.get(PayoneerPayService.CODE)) { System.out.println("收款成功"); } return params; } - /** * 用户授权回调地址 * - * @param request - * - * @return + * @param request 请求 + * @return 是否成功 + * @throws IOException IOException */ @RequestMapping(value = "payBack.json") public String payBack(HttpServletRequest request) throws IOException { @@ -151,7 +161,7 @@ public class PayoneerPayController { */ @RequestMapping("query") public Map query(QueryOrder order) { - return service.query(order.getTradeNo(), order.getOutTradeNo()); + return service.query(new AssistOrder(order.getTradeNo(), order.getOutTradeNo())); } @@ -163,8 +173,9 @@ public class PayoneerPayController { */ @RequestMapping("close") public Map close(QueryOrder order) { - return service.close(order.getTradeNo(), order.getOutTradeNo()); + return service.close(new AssistOrder(order.getTradeNo(), order.getOutTradeNo())); } + /** * 申请退款接口 * @@ -172,7 +183,7 @@ public class PayoneerPayController { * @return 返回支付方申请退款后的结果 */ @RequestMapping("refund") - public Map refund(RefundOrder order) { + public RefundResult refund(RefundOrder order) { return service.refund(order); } @@ -194,13 +205,12 @@ public class PayoneerPayController { * 转账 * * @param order 转账订单 - * * @return 对应的转账结果 */ @RequestMapping("transfer") public Map transfer(TransferOrder order) { order.setOutNo("商户转账订单号"); - order.setCurType(CurType.USD); + order.setCurType(DefaultCurType.USD); order.setPayeeAccount("收款方账户,用户授权所使用的userId"); order.setAmount(new BigDecimal(10)); order.setRemark("转账备注, 非必填"); @@ -212,7 +222,6 @@ public class PayoneerPayController { * * @param outNo 商户转账订单号 * @param tradeNo 支付平台转账订单号 - * * @return 对应的转账订单 */ @RequestMapping("transferQuery") diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/UnionPayController.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/UnionPayController.java index 67792e4ea18a5b6e8e366049d44921652c953f5d..46e1b4aac7072ee5159b07d4b26ca464e74ea55b 100644 --- a/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/UnionPayController.java +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/UnionPayController.java @@ -2,22 +2,6 @@ package com.egzosn.pay.demo.controller; -import com.egzosn.pay.common.api.PayService; -import com.egzosn.pay.common.bean.MethodType; -import com.egzosn.pay.common.bean.PayOrder; -import com.egzosn.pay.common.bean.RefundOrder; -import com.egzosn.pay.common.http.HttpConfigStorage; -import com.egzosn.pay.common.util.sign.SignUtils; -import com.egzosn.pay.demo.request.QueryOrder; -import com.egzosn.pay.union.api.UnionPayConfigStorage; -import com.egzosn.pay.union.api.UnionPayService; -import com.egzosn.pay.union.bean.UnionTransactionType; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.PostConstruct; -import javax.imageio.ImageIO; -import javax.servlet.http.HttpServletRequest; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.math.BigDecimal; @@ -25,14 +9,34 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; +import javax.imageio.ImageIO; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + import static com.egzosn.pay.union.bean.UnionTransactionType.WEB; +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.common.bean.CertStoreType; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.RefundOrder; +import com.egzosn.pay.common.http.HttpConfigStorage; +import com.egzosn.pay.common.util.sign.SignTextUtils; +import com.egzosn.pay.common.util.sign.SignUtils; +import com.egzosn.pay.demo.request.QueryOrder; +import com.egzosn.pay.union.api.UnionPayConfigStorage; +import com.egzosn.pay.union.api.UnionPayService; +import com.egzosn.pay.union.bean.UnionRefundResult; +import com.egzosn.pay.union.bean.UnionTransactionType; +import com.egzosn.pay.web.support.HttpRequestNoticeParams; + /** * 银联相关 * - * @author: egan - * @email egzosn@gmail.com - * @date 2016/11/18 0:25 + * @author egan + * email egzosn@gmail.com + * date 2016/11/18 0:25 */ @RestController @RequestMapping("union") @@ -40,24 +44,23 @@ public class UnionPayController { private UnionPayService service = null; - @PostConstruct +// @PostConstruct public void init() { UnionPayConfigStorage unionPayConfigStorage = new UnionPayConfigStorage(); unionPayConfigStorage.setMerId("700000000000001"); - //设置CertSign必须在设置证书前 + //是否为证书签名 unionPayConfigStorage.setCertSign(true); - //公钥,验签证书链格式: 中级证书路径;根证书路径 -// unionPayConfigStorage.setKeyPublic("D:/certs/acp_test_middle.cer;D:/certs/acp_test_root.cer"); //中级证书路径 - unionPayConfigStorage.setAcpMiddleCert("D:/certs/acp_test_middle.cer"); + unionPayConfigStorage.setAcpMiddleCert("http://www.egzosn.com/certs/acp_test_middle.cer"); //根证书路径 - unionPayConfigStorage.setAcpRootCert("D:/certs/acp_test_root.cer"); - //私钥, 私钥证书格式: 私钥证书路径;私钥证书对应的密码 -// unionPayConfigStorage.setKeyPrivate("D:/certs/acp_test_sign.pfx;000000"); + unionPayConfigStorage.setAcpRootCert("http://www.egzosn.com/certs/acp_test_root.cer"); // 私钥证书路径 - unionPayConfigStorage.setKeyPrivateCert("D:/certs/acp_test_sign.pfx"); + unionPayConfigStorage.setKeyPrivateCert("http://www.egzosn.com/certs/acp_test_sign.pfx"); //私钥证书对应的密码 unionPayConfigStorage.setKeyPrivateCertPwd("000000"); + //设置证书对应的存储方式,这里默认为文件地址 + unionPayConfigStorage.setCertStoreType(CertStoreType.URL); + //前台通知网址 即SDKConstants.param_frontUrl unionPayConfigStorage.setReturnUrl("http://www.pay.egzosn.com/payBack.json"); //后台通知地址 即SDKConstants.param_backUrl @@ -93,13 +96,14 @@ public class UnionPayController { @RequestMapping(value = "toPay.html", produces = "text/html;charset=UTF-8") public String toPay( BigDecimal price) { //网关支付(WEB)/手机网页支付 - PayOrder order = new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price, UUID.randomUUID().toString().replace("-", ""), + PayOrder order = new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), WEB); //企业网银支付(B2B支付) -// PayOrder order = new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price, UUID.randomUUID().toString().replace("-", ""), UnionTransactionType.B2B); +// PayOrder order = new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), UnionTransactionType.B2B); - Map orderInfo = service.orderInfo(order); - return service.buildRequest(orderInfo, MethodType.POST); +// Map orderInfo = service.orderInfo(order); +// return service.buildRequest(orderInfo, MethodType.POST); + return service.toPay(order); } /** @@ -112,9 +116,9 @@ public class UnionPayController { @RequestMapping(value = "toPay.json") public Map sendHttpRequest( BigDecimal price) { //手机控件支付产品 - PayOrder order = new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price, UUID.randomUUID().toString().replace("-", "") + PayOrder order = new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", "") ,UnionTransactionType.WAP); - return service.sendHttpRequest(order); + return service.app(order); } @@ -128,14 +132,14 @@ public class UnionPayController { public Map app() { Map data = new HashMap<>(); data.put("code", 0); - PayOrder order = new PayOrder("订单title", "摘要", new BigDecimal(0.01), SignUtils.randomStr()); + PayOrder order = new PayOrder("订单title", "摘要", BigDecimal.valueOf(0.01), SignTextUtils.randomStr()); //App支付 order.setTransactionType(UnionTransactionType.APP); //APPLE支付 苹果付 // order.setTransactionType(UnionTransactionType.APPLE); - data.put("orderInfo", service.orderInfo(order)); + data.put("orderInfo", service.app(order)); return data; } @@ -144,15 +148,27 @@ public class UnionPayController { * 二维码支付 * @param price 金额 * @return 二维码图像 + * @throws IOException IOException */ @RequestMapping(value = "toQrPay.jpg", produces = "image/jpeg;charset=UTF-8") public byte[] toWxQrPay( BigDecimal price) throws IOException { //获取对应的支付账户操作工具(可根据账户id) ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ImageIO.write(service.genQrPay( new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price, System.currentTimeMillis()+"", UnionTransactionType.APPLY_QR_CODE)), "JPEG", baos); + ImageIO.write(service.genQrPay( new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, System.currentTimeMillis()+"", UnionTransactionType.APPLY_QR_CODE)), "JPEG", baos); return baos.toByteArray(); } - + /** + * 获取二维码地址 + * 二维码支付 + * @param price 金额 + * @return 二维码图像 + * @throws IOException IOException + */ + @RequestMapping(value = "getQrPay.json") + public String getQrPay(BigDecimal price) throws IOException { + //获取对应的支付账户操作工具(可根据账户id) + return service.getQrPay( new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, System.currentTimeMillis()+"", UnionTransactionType.APPLY_QR_CODE)); + } /** * 刷卡付,pos主动扫码付款(条码付) CONSUME @@ -161,10 +177,10 @@ public class UnionPayController { * @return 支付结果 */ @RequestMapping(value = "microPay") - public Map microPay(BigDecimal price, String authCode) throws IOException { + public Map microPay(BigDecimal price, String authCode) { //获取对应的支付账户操作工具(可根据账户id) //条码付 - PayOrder order = new PayOrder("huodull order", "huodull order", null == price ? new BigDecimal(0.01) : price, SignUtils.randomStr(), UnionTransactionType.CONSUME); + PayOrder order = new PayOrder("egan order", "egan order", null == price ? BigDecimal.valueOf(0.01) : price, SignTextUtils.randomStr(), UnionTransactionType.CONSUME); //设置授权码,条码等 order.setAuthCode(authCode); //支付结果 @@ -186,10 +202,11 @@ public class UnionPayController { * * 方式二,{@link #payBack(HttpServletRequest)} 是属于简化方式, 试用与简单的业务场景 * - * @param request + * @param request 请求 * - * @return + * @return 是否成功 * @see #payBack(HttpServletRequest) + * @throws IOException IOException */ @Deprecated @RequestMapping(value = "payBackBefore.json") @@ -213,19 +230,35 @@ public class UnionPayController { /** * 支付回调地址 * - * @param request + * @param request 请求 * - * @return + * @return 是否成功 * - * 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} + * 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link com.egzosn.pay.common.api.PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} * * 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler} + * @throws IOException IOException + */ + @RequestMapping(value = "payBackOld.json") + public String payBackOld(HttpServletRequest request) throws IOException { + //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() + return service.payBack(request.getParameterMap(), request.getInputStream()).toMessage(); + } + /** + * 支付回调地址 * + * @param request 请求 + * @return 是否成功 + *

+ * 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link com.egzosn.pay.common.api.PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} + *

+ * 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler} + * @throws IOException IOException */ @RequestMapping(value = "payBack.json") - public String payBack(HttpServletRequest request) throws IOException { + public String payBack(HttpServletRequest request) { //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() - return service.payBack(request.getParameterMap(), request.getInputStream()).toMessage(); + return service.payBack(new HttpRequestNoticeParams(request)).toMessage(); } @@ -237,7 +270,7 @@ public class UnionPayController { */ @RequestMapping("query") public Map query(QueryOrder order) { - return service.query(order.getTradeNo(), order.getOutTradeNo()); + return service.query(new AssistOrder(order.getTradeNo(), order.getOutTradeNo())); } @@ -248,20 +281,10 @@ public class UnionPayController { * @return 返回支付方申请退款后的结果 */ @RequestMapping("refund") - public Map refund(RefundOrder order) { + public UnionRefundResult refund(RefundOrder order) { return service.refund(order); } - /** - * 查询退款 - * - * @param order 订单的请求体 - * @return 返回支付方查询退款后的结果 - */ - @RequestMapping("refundquery") - public Map refundquery(QueryOrder order) { - return service.refundquery(order.getTradeNo(), order.getOutTradeNo()); - } /** * 下载对账单 @@ -269,9 +292,9 @@ public class UnionPayController { * @param order 订单的请求体 * @return 返回支付方下载对账单的结果 */ - @RequestMapping("downloadbill") - public Object downloadbill(QueryOrder order) { - return service.downloadbill(order.getBillDate(), order.getBillType()); + @RequestMapping("downloadBill") + public Object downloadBill(QueryOrder order) { + return service.downloadBill(order.getBillDate(), order.getBillType()); } diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxPayController.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxPayController.java index 34c90a1ef201c92cc75ef1e3736ef9d100a95883..879494e888b510509c3cbfd11ae3039dda992c0a 100644 --- a/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxPayController.java +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxPayController.java @@ -2,27 +2,17 @@ package com.egzosn.pay.demo.controller; -import com.egzosn.pay.common.api.Callback; -import com.egzosn.pay.common.api.PayService; import com.egzosn.pay.common.bean.*; import com.egzosn.pay.common.http.HttpConfigStorage; -import com.egzosn.pay.common.http.UriVariables; -import com.egzosn.pay.demo.entity.PayType; import com.egzosn.pay.demo.request.QueryOrder; -import com.egzosn.pay.demo.service.PayResponse; -import com.egzosn.pay.demo.service.handler.AliPayMessageHandler; -import com.egzosn.pay.demo.service.handler.WxPayMessageHandler; +import com.egzosn.pay.web.support.HttpRequestNoticeParams; import com.egzosn.pay.wx.api.WxPayConfigStorage; import com.egzosn.pay.wx.api.WxPayService; -import com.egzosn.pay.wx.bean.WxBank; -import com.egzosn.pay.wx.bean.WxTransactionType; -import com.egzosn.pay.wx.bean.WxTransferType; -import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import com.egzosn.pay.wx.bean.*; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.PostConstruct; -import javax.annotation.Resource; import javax.imageio.ImageIO; import javax.servlet.http.HttpServletRequest; import java.io.ByteArrayOutputStream; @@ -35,26 +25,25 @@ import java.util.UUID; /** * 发起支付入口 * - * @author: egan - * @email egzosn@gmail.com - * @date 2016/11/18 0:25 + * @author egan + * email egzosn@gmail.com + * date 2016/11/18 0:25 */ @RestController @RequestMapping("wx") public class WxPayController { - private PayService service = null; + private WxPayService service = null; //ssl 退款证书相关 不使用可注释 private static String KEYSTORE = "ssl 退款证书"; - private static String STORE_PASSWORD = "ssl 证书对应的密码, 默认为商户号"; @PostConstruct public void init() { WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage(); - wxPayConfigStorage.setAppid("公众账号ID"); + wxPayConfigStorage.setAppId("公众账号ID"); wxPayConfigStorage.setMchId("合作者id(商户号)"); //以下两个参数在 服务商版模式中必填-------- @@ -78,8 +67,9 @@ public class WxPayController { //TODO 这里也支持输入流的入参。 // httpConfigStorage.setKeystore(WxPayController.class.getResourceAsStream("/证书文件")); httpConfigStorage.setKeystore(KEYSTORE); - httpConfigStorage.setStorePassword(STORE_PASSWORD); - httpConfigStorage.setPath(true); + httpConfigStorage.setStorePassword("ssl 证书对应的密码, 默认为商户号"); + //设置ssl证书对应的存储方式,这里默认为文件地址 + httpConfigStorage.setCertStoreType(CertStoreType.PATH); } @@ -101,12 +91,13 @@ public class WxPayController { * 跳到支付页面 * 针对实时支付 * + * @param request 请求 * @param price 金额 * @return 跳到支付页面 */ @RequestMapping(value = "toPay.html", produces = "text/html;charset=UTF-8") public String toPay( HttpServletRequest request, BigDecimal price) { - PayOrder order = new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price , UUID.randomUUID().toString().replace("-", ""), WxTransactionType.MWEB); + PayOrder order = new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price , UUID.randomUUID().toString().replace("-", ""), WxTransactionType.MWEB); order.setSpbillCreateIp(request.getHeader("X-Real-IP")); StringBuffer requestURL = request.getRequestURL(); //设置网页地址 @@ -114,8 +105,9 @@ public class WxPayController { //设置网页名称 order.setWapName("在线充值"); - Map orderInfo = service.orderInfo(order); - return service.buildRequest(orderInfo, MethodType.POST); +// Map orderInfo = service.orderInfo(order); +// return service.buildRequest(orderInfo, MethodType.POST); + return service.toPay(order); } /** @@ -129,7 +121,7 @@ public class WxPayController { @RequestMapping(value = "jsapi" ) public Map toPay(String openid, BigDecimal price) { - PayOrder order = new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price, UUID.randomUUID().toString().replace("-", ""), WxTransactionType.JSAPI); + PayOrder order = new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), WxTransactionType.JSAPI); order.setOpenid(openid); Map orderInfo = service.orderInfo(order); @@ -149,10 +141,10 @@ public class WxPayController { public Map app() { Map data = new HashMap<>(); data.put("code", 0); - PayOrder order = new PayOrder("订单title", "摘要", new BigDecimal(0.01), UUID.randomUUID().toString().replace("-", "")); + PayOrder order = new PayOrder("订单title", "摘要", BigDecimal.valueOf(0.01), UUID.randomUUID().toString().replace("-", "")); //App支付 order.setTransactionType(WxTransactionType.APP); - data.put("orderInfo", service.orderInfo(order)); + data.put("orderInfo", service.app(order)); return data; } @@ -161,16 +153,28 @@ public class WxPayController { * 二维码支付 * @param price 金额 * @return 二维码图像 + * @throws IOException IOException */ @RequestMapping(value = "toQrPay.jpg", produces = "image/jpeg;charset=UTF-8") public byte[] toWxQrPay( BigDecimal price) throws IOException { //获取对应的支付账户操作工具(可根据账户id) ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ImageIO.write(service.genQrPay( new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price, System.currentTimeMillis()+"", WxTransactionType.NATIVE)), "JPEG", baos); + ImageIO.write(service.genQrPay( new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, System.currentTimeMillis()+"", WxTransactionType.NATIVE)), "JPEG", baos); return baos.toByteArray(); } - + /** + * 获取二维码地址 + * 二维码支付 + * @param price 金额 + * @return 二维码图像 + * @throws IOException IOException + */ + @RequestMapping(value = "getQrPay.json") + public String getQrPay(BigDecimal price) throws IOException { + //获取对应的支付账户操作工具(可根据账户id) + return service.getQrPay( new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, System.currentTimeMillis()+"", WxTransactionType.NATIVE)); + } /** * 刷卡付,pos主动扫码付款(条码付) * @param authCode 授权码,条码等 @@ -178,10 +182,10 @@ public class WxPayController { * @return 支付结果 */ @RequestMapping(value = "microPay") - public Map microPay( BigDecimal price, String authCode) throws IOException { + public Map microPay( BigDecimal price, String authCode) { //获取对应的支付账户操作工具(可根据账户id) //条码付 - PayOrder order = new PayOrder("huodull order", "huodull order", null == price ? new BigDecimal(0.01) : price, UUID.randomUUID().toString().replace("-", ""), WxTransactionType.MICROPAY); + PayOrder order = new PayOrder("egan order", "egan order", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), WxTransactionType.MICROPAY); //设置授权码,条码等 order.setAuthCode(authCode); //支付结果 @@ -198,14 +202,43 @@ public class WxPayController { return params; } + /** + * 刷脸付 + * @param price 金额 + * @param authCode 人脸凭证 + * @param openid 用户在商户 appid下的唯一标识 + * @return 支付结果 + */ + @RequestMapping(value = "facePay") + public Map facePay(BigDecimal price, String authCode, String openid) { + //获取对应的支付账户操作工具(可根据账户id) + PayOrder order = new PayOrder("egan order", "egan order", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), WxTransactionType.FACEPAY); + //设置人脸凭证 + order.setAuthCode(authCode); + // 用户在商户 appid下的唯一标识 + order.setOpenid(openid); + //支付结果 + Map params = service.microPay(order); + //校验 + if (service.verify(params)) { + //支付校验通过后的处理 + //......业务逻辑处理块........ + + + } + //这里开发者自行处理 + return params; + } + /** * 支付回调地址 方式一 * * 方式二,{@link #payBack(HttpServletRequest)} 是属于简化方式, 试用与简单的业务场景 * - * @param request + * @param request 请求 * - * @return + * @return 是否成功 + * @throws IOException IOException * @see #payBack(HttpServletRequest) */ @Deprecated @@ -213,13 +246,13 @@ public class WxPayController { public String payBackBefore(HttpServletRequest request) throws IOException { //获取支付方返回的对应参数 - Map params = service.getParameter2Map(request.getParameterMap(), request.getInputStream()); - if (null == params) { + NoticeParams noticeParams = service.getNoticeParams(new HttpRequestNoticeParams(request)); + if (null == noticeParams) { return service.getPayOutMessage("fail", "失败").toMessage(); } //校验 - if (service.verify(params)) { + if (service.verify(noticeParams)) { //这里处理业务逻辑 //......业务逻辑处理块........ return service.successPayOutMessage(null).toMessage(); @@ -230,19 +263,35 @@ public class WxPayController { /** * 支付回调地址 * - * @param request + * @param request 请求 * - * @return + * @return 是否成功 * * 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link com.egzosn.pay.common.api.PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} * * 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler} + * @throws IOException IOException + */ + @RequestMapping(value = "payBackOld.json") + public String payBackOld(HttpServletRequest request) throws IOException { + //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() + return service.payBack(request.getParameterMap(), request.getInputStream()).toMessage(); + } + /** + * 支付回调地址 * + * @param request 请求 + * @return 是否成功 + *

+ * 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link com.egzosn.pay.common.api.PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} + *

+ * 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler} + * @throws IOException IOException */ @RequestMapping(value = "payBack.json") - public String payBack(HttpServletRequest request) throws IOException { + public String payBack(HttpServletRequest request) { //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() - return service.payBack(request.getParameterMap(), request.getInputStream()).toMessage(); + return service.payBack(new HttpRequestNoticeParams(request)).toMessage(); } @@ -254,7 +303,7 @@ public class WxPayController { */ @RequestMapping("query") public Map query(QueryOrder order) { - return service.query(order.getTradeNo(), order.getOutTradeNo()); + return service.query(new AssistOrder(order.getTradeNo(), order.getOutTradeNo())); } @@ -266,7 +315,7 @@ public class WxPayController { */ @RequestMapping("close") public Map close(QueryOrder order) { - return service.close(order.getTradeNo(), order.getOutTradeNo()); + return service.close(new AssistOrder(order.getTradeNo(), order.getOutTradeNo())); } /** @@ -276,7 +325,7 @@ public class WxPayController { * @return 返回支付方申请退款后的结果 */ @RequestMapping("refund") - public Map refund(RefundOrder order) { + public WxRefundResult refund(RefundOrder order) { if("ssl 退款证书".equals(KEYSTORE)){ throw new RuntimeException("请设置好SSL退款证书"); } @@ -290,8 +339,8 @@ public class WxPayController { * @return 返回支付方查询退款后的结果 */ @RequestMapping("refundquery") - public Map refundquery(QueryOrder order) { - return service.refundquery(order.getTradeNo(), order.getOutTradeNo()); + public Map refundquery(RefundOrder order) { + return service.refundquery(order); } /** @@ -301,23 +350,11 @@ public class WxPayController { * @return 返回支付方下载对账单的结果 */ @RequestMapping("downloadbill") - public Object downloadbill(QueryOrder order) { - return service.downloadbill(order.getBillDate(), order.getBillType()); + public Object downloadBill(QueryOrder order) { + return service.downloadBill(order.getBillDate(), order.getBillType()); } - /** - * 通用查询接口,根据 WxTransactionType 类型进行实现,此接口不包括退款 - * - * @param order 订单的请求体 - * @return 返回支付方对应接口的结果 - */ - @RequestMapping("secondaryInterface") - public Map secondaryInterface(QueryOrder order) { - TransactionType type = WxTransactionType.valueOf(order.getTransactionType()); - return service.secondaryInterface(order.getTradeNoOrBillDate(), order.getOutTradeNoBillType(), type); - } - /** @@ -373,10 +410,10 @@ public class WxPayController { * {@link com.egzosn.pay.wx.bean.WxTransferType#QUERY_BANK} * {@link com.egzosn.pay.wx.bean.WxTransferType#GETTRANSFERINFO} * - *
+ *

* 企业付款到零钱 * 商户企业付款到银行卡 - *
+ *

* @return 对应的转账订单 */ @RequestMapping("transferQuery") @@ -384,4 +421,51 @@ public class WxPayController { //默认查询银行卡的记录 com.egzosn.pay.wx.bean.WxTransferType#QUERY_BANK return service.transferQuery(outNo, wxTransferType); } + + /** + * 微信发红包 + * @param redpackOrder 红包订单 + * @return 结果 + */ + public Map sendredpack(RedpackOrder redpackOrder) { + redpackOrder.setTransferType(WxSendredpackType.SENDREDPACK); + return service.sendredpack(redpackOrder); + } + + /** + * 发放裂变红包 + * @param redpackOrder 红包订单 + * @return 结果 + */ + public Map sendgroupredpack(RedpackOrder redpackOrder) { + redpackOrder.setTransferType(WxSendredpackType.SENDGROUPREDPACK); + return service.sendredpack(redpackOrder); + } + + + /** + * 小程序发红包 + * @param redpackOrder 红包订单 + * @return 结果 + */ + public Map sendminiprogramhb(RedpackOrder redpackOrder) { + redpackOrder.setTransferType(WxSendredpackType.SENDMINIPROGRAMHB); + return service.sendredpack(redpackOrder); + } + + + /** + * 查询红包记录 + * 用于商户对已发放的红包进行查询红包的具体信息,可支持普通红包和裂变包 + * 查询红包记录API只支持查询30天内的红包订单,30天之前的红包订单请登录商户平台查询。 + * + * @param mchBillno 商户发放红包的商户订单号 + * @return 返回查询结果 + */ + public Map gethbinfo(String mchBillno) { + return service.gethbinfo(mchBillno); + } + + + } diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxV3CombinePayController.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxV3CombinePayController.java new file mode 100644 index 0000000000000000000000000000000000000000..2724601c4508c4b9940adc4ac5171134cadeea09 --- /dev/null +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxV3CombinePayController.java @@ -0,0 +1,308 @@ + +package com.egzosn.pay.demo.controller; + + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.annotation.PostConstruct; +import javax.imageio.ImageIO; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.common.bean.CertStoreType; +import com.egzosn.pay.common.bean.RefundOrder; +import com.egzosn.pay.common.bean.RefundResult; +import com.egzosn.pay.demo.request.QueryOrder; +import com.egzosn.pay.demo.service.handler.WxV3CombinePayMessageHandler; +import com.egzosn.pay.web.support.HttpRequestNoticeParams; +import com.egzosn.pay.wx.v3.api.WxCombinePayService; +import com.egzosn.pay.wx.v3.api.WxPayConfigStorage; +import com.egzosn.pay.wx.v3.bean.WxTransactionType; +import com.egzosn.pay.wx.v3.bean.combine.CombineAmount; +import com.egzosn.pay.wx.v3.bean.combine.CombineCloseOrder; +import com.egzosn.pay.wx.v3.bean.combine.CombinePayOrder; +import com.egzosn.pay.wx.v3.bean.combine.CombineSubOrder; +import com.egzosn.pay.wx.v3.bean.order.H5Info; +import com.egzosn.pay.wx.v3.bean.order.SceneInfo; +import com.egzosn.pay.wx.v3.bean.order.SubOrder; + +/** + * 微信V3合单发起支付入口 + * + * @author egan + * email egzosn@gmail.com + * date 2016/11/18 0:25 + */ +@RestController +@RequestMapping("wxV3combine") +public class WxV3CombinePayController { + + private WxCombinePayService service = null; + +// @PostConstruct //没有证书的情况下注释掉,避免启动报错 + public void init() { + WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage(); + wxPayConfigStorage.setAppId("wxc7b993ff15a9f26c"); + wxPayConfigStorage.setMchId("1602947765"); + //V3密钥 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_2.shtml + wxPayConfigStorage.setV3ApiKey("9bd8f0e7af4841299d782406b7774f57"); + wxPayConfigStorage.setNotifyUrl("http://sailinmu.iok.la/wxV3combine/payBack.json"); + wxPayConfigStorage.setReturnUrl("http://sailinmu.iok.la/wxV3combine/payBack.json"); + wxPayConfigStorage.setInputCharset("utf-8"); + //使用证书时设置为true + wxPayConfigStorage.setCertSign(true); + //商户API证书 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_1.shtml + wxPayConfigStorage.setApiClientKeyP12("yifenli_mall.p12"); + wxPayConfigStorage.setCertStoreType(CertStoreType.PATH); + service = new WxCombinePayService(wxPayConfigStorage); + //设置回调消息处理 + //TODO {@link com.egzosn.pay.demo.controller.WxPayController#payBack} + service.setPayMessageHandler(new WxV3CombinePayMessageHandler()); + } + + + /** + * 跳到支付页面 + * 针对实时支付 + * + * @return 跳到支付页面 + */ + @RequestMapping(value = "toPay.html", produces = "text/html;charset=UTF-8") + public String toPay() { + CombinePayOrder order = new CombinePayOrder(); + SceneInfo sceneInfo = new SceneInfo(); + sceneInfo.setPayerClientIp("用户终端IP "); + sceneInfo.setDeviceId("终端设备号(门店号或收银设备ID) 。为了方便问题定位,H5支付场景下,该字段必填"); + sceneInfo.setH5Info(new H5Info("场景类型,枚举值:\n" + + "iOS:IOS移动应用;\n" + + "Android:安卓移动应用;\n" + + "Wap:WAP网站应用;")); + order.setSceneInfo(sceneInfo); + order.setCombineOutTradeNo("合单商户订单号"); + //子单信息,最多50单. + List subOrders = new ArrayList<>(); + SubOrder subOrder = new SubOrder(); + subOrder.setMchid("子单商户号"); + subOrder.setAttach("附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。 "); + //"子单金额,单位为分。 " + subOrder.setAmount(new CombineAmount(121)); + subOrder.setOutTradeNo("子单商户订单号 "); + subOrder.setDescription("商品描述"); + subOrder.setSubMchid("服务商必填----二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 "); + subOrders.add(subOrder); + order.setSubOrders(subOrders); + order.setTransactionType(WxTransactionType.COMBINE_H5); + return service.toPay(order); + } + + /** + * 公众号支付,小程序 + * + * @return 返回jsapi所需参数 + */ + @RequestMapping(value = "jsapi") + public Map jsapi() { + + CombinePayOrder order = new CombinePayOrder(); + order.setTransactionType(WxTransactionType.COMBINE_JSAPI); + order.setCombineOutTradeNo("合单商户订单号"); + order.setOpenid("使用合单appid获取的对应用户openid。是用户在商户appid下的唯一标识。 "); + //子单信息,最多50单. + List subOrders = new ArrayList<>(); + SubOrder subOrder = new SubOrder(); + subOrder.setMchid("子单商户号"); + subOrder.setAttach("附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。 "); + //"子单金额,单位为分。 " + subOrder.setAmount(new CombineAmount(111)); + subOrder.setOutTradeNo("子单商户订单号 "); + subOrder.setDescription("商品描述"); + subOrder.setSubMchid("服务商必填----二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 "); + subOrders.add(subOrder); + order.setSubOrders(subOrders); + Map orderInfo = service.orderInfo(order); + orderInfo.put("code", 0); + return orderInfo; + } + + + /** + * 获取支付预订单信息 + * + * @return 支付预订单信息 + */ + @RequestMapping("app") + public Map app() { + + CombinePayOrder order = new CombinePayOrder(); + order.setTransactionType(WxTransactionType.COMBINE_APP); + order.setCombineOutTradeNo("合单商户订单号"); + //子单信息,最多50单. + List subOrders = new ArrayList<>(); + SubOrder subOrder = new SubOrder(); + subOrder.setMchid("子单商户号"); + subOrder.setAttach("附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。 "); + //"子单金额,单位为分。 " + subOrder.setAmount(new CombineAmount(211)); + subOrder.setOutTradeNo("子单商户订单号 "); + subOrder.setDescription("商品描述"); + subOrder.setSubMchid("服务商必填----二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 "); + subOrders.add(subOrder); + order.setSubOrders(subOrders); + Map orderInfo = service.orderInfo(order); + orderInfo.put("code", 0); + return orderInfo; + } + + /** + * 获取二维码图像 + * 二维码支付 + * + * @return 二维码图像 + * @throws IOException IOException + */ + @RequestMapping(value = "toQrPay.jpg", produces = "image/jpeg;charset=UTF-8") + public byte[] toWxQrPay() throws IOException { + CombinePayOrder order = new CombinePayOrder(); + order.setTransactionType(WxTransactionType.COMBINE_NATIVE); + order.setCombineOutTradeNo("合单商户订单号"); + //子单信息,最多50单. + List subOrders = new ArrayList<>(); + SubOrder subOrder = new SubOrder(); + subOrder.setMchid("子单商户号"); + subOrder.setAttach("附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。 "); + //"子单金额,单位为分。 " + subOrder.setAmount(new CombineAmount(131)); + subOrder.setOutTradeNo("子单商户订单号 "); + subOrder.setDescription("商品描述"); + subOrder.setSubMchid("服务商必填----二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 "); + subOrders.add(subOrder); + order.setSubOrders(subOrders); + + //获取对应的支付账户操作工具(可根据账户id) + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(service.genQrPay(order), "JPEG", baos); + return baos.toByteArray(); + } + + /** + * 获取二维码地址 + * 二维码支付 + * + * @return 二维码图像 + * @throws IOException IOException + */ + @RequestMapping(value = "getQrPay.json") + public String getQrPay() { + CombinePayOrder order = new CombinePayOrder(); + order.setTransactionType(WxTransactionType.COMBINE_NATIVE); + order.setCombineOutTradeNo("合单商户订单号"); + //子单信息,最多50单. + List subOrders = new ArrayList<>(); + SubOrder subOrder = new SubOrder(); + subOrder.setMchid("子单商户号"); + subOrder.setAttach("附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。1 "); + //"子单金额,单位为分。 " + subOrder.setAmount(new CombineAmount(115)); + subOrder.setOutTradeNo("子单商户订单号 "); + subOrder.setDescription("商品描述"); + subOrder.setSubMchid("服务商必填----二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 "); + subOrders.add(subOrder); + order.setSubOrders(subOrders); + //获取对应的支付账户操作工具(可根据账户id) + return service.getQrPay(order); + } + + + /** + * 支付回调地址 + * + * @param request 请求 + * @return 是否成功 + *

+ * 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link com.egzosn.pay.common.api.PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} + *

+ * 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler} + * @throws IOException IOException + */ + @RequestMapping(value = "payBack.json") + public String payBack(HttpServletRequest request) { + //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() + return service.payBack(new HttpRequestNoticeParams(request)).toMessage(); + } + + + /** + * 查询 + * + * @param order 订单的请求体 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @RequestMapping("query") + public Map query(QueryOrder order) { + return service.query(new AssistOrder(order.getTradeNo(), order.getOutTradeNo())); + } + + + /** + * 交易关闭接口 + * + * @return 返回支付方交易关闭后的结果 + */ + @RequestMapping("close") + public Map close() { + CombineCloseOrder order = new CombineCloseOrder(); + order.setOutTradeNo("合单商户订单号"); + //子单信息,最多50单. + List subOrders = new ArrayList<>(); + CombineSubOrder subOrder = new CombineSubOrder(); + subOrder.setMchid("子单商户号"); + subOrder.setOutTradeNo("子单商户订单号 "); + subOrder.setSubMchid("服务商必填----二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 "); + subOrders.add(subOrder); + order.setSubOrders(subOrders); + return service.close(order); + } + + /** + * 申请退款接口 + * + * @param order 订单的请求体 + * @return 返回支付方申请退款后的结果 + */ + @RequestMapping("refund") + public RefundResult refund(RefundOrder order) { + + return service.refund(order); + } + + /** + * 查询退款 + * + * @param order 订单的请求体 + * @return 返回支付方查询退款后的结果 + */ + @RequestMapping("refundquery") + public Map refundquery(RefundOrder order) { + return service.refundquery(order); + } + + /** + * 下载对账单 + * + * @param order 订单的请求体 + * @return 返回支付方下载对账单的结果 + */ + @RequestMapping("downloadbill") + public Object downloadBill(QueryOrder order) { + return service.downloadBill(order.getBillDate(), order.getBillType()); + } + + +} diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxV3PayController.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxV3PayController.java new file mode 100644 index 0000000000000000000000000000000000000000..f3d508822cc3ee10ac755b950830609eca16f547 --- /dev/null +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxV3PayController.java @@ -0,0 +1,373 @@ + +package com.egzosn.pay.demo.controller; + + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import javax.imageio.ImageIO; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.common.bean.CertStoreType; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.RefundOrder; +import com.egzosn.pay.common.bean.RefundResult; +import com.egzosn.pay.demo.request.QueryOrder; +import com.egzosn.pay.demo.service.handler.WxV3PayMessageHandler; +import com.egzosn.pay.web.support.HttpRequestNoticeParams; +import com.egzosn.pay.wx.v3.api.WxPayConfigStorage; +import com.egzosn.pay.wx.v3.api.WxPayService; +import com.egzosn.pay.wx.v3.bean.WxTransactionType; +import com.egzosn.pay.wx.v3.bean.WxTransferType; +import com.egzosn.pay.wx.v3.bean.order.H5Info; +import com.egzosn.pay.wx.v3.bean.order.SceneInfo; +import com.egzosn.pay.wx.v3.bean.transfer.TransferDetail; +import com.egzosn.pay.wx.v3.bean.transfer.WxTransferOrder; +import com.egzosn.pay.wx.v3.bean.transfer.WxTransferQueryOrder; +import com.egzosn.pay.wx.v3.utils.WxConst; + +/** + * 微信V3发起支付入口 + * + * @author egan + * email egzosn@gmail.com + * date 2016/11/18 0:25 + */ +@RestController +@RequestMapping("wxV3") +public class WxV3PayController { + + private WxPayService service = null; + + +// @PostConstruct //没有证书的情况下注释掉,避免启动报错 + public void init() { + WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage(); + wxPayConfigStorage.setAppId("wxc7b993ff15a9f26c"); + wxPayConfigStorage.setMchId("1602947765"); + //V3密钥 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_2.shtml + wxPayConfigStorage.setV3ApiKey("9bd8f0e7af4841299d782406b7774f57"); + wxPayConfigStorage.setNotifyUrl("http://sailinmu.iok.la/wxV3/payBack.json"); + wxPayConfigStorage.setReturnUrl("http://sailinmu.iok.la/wxV3/payBack.json"); + wxPayConfigStorage.setInputCharset("utf-8"); + //使用证书时设置为true +// wxPayConfigStorage.setCertSign(true); + //商户API证书 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_1.shtml + wxPayConfigStorage.setApiClientKeyP12("yifenli_mall.p12"); + wxPayConfigStorage.setCertStoreType(CertStoreType.PATH); + service = new WxPayService(wxPayConfigStorage); + //微信海外支付:东南亚 +// service.setApiServerUrl("https://apihk.mch.weixin.qq.com"); + //设置回调消息处理 + //TODO {@link com.egzosn.pay.demo.controller.WxPayController#payBack} + service.setPayMessageHandler(new WxV3PayMessageHandler()); + } + + + /** + * 跳到支付页面 + * 针对实时支付 + * + * @param request 请求 + * @param price 金额 + * @return 跳到支付页面 + */ + @RequestMapping(value = "toPay.html", produces = "text/html;charset=UTF-8") + public String toPay(HttpServletRequest request, BigDecimal price) { + PayOrder order = new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), WxTransactionType.H5); + StringBuffer requestURL = request.getRequestURL(); + SceneInfo sceneInfo = new SceneInfo(); + sceneInfo.setPayerClientIp(request.getHeader("X-Real-IP")); + sceneInfo.setH5Info(new H5Info("在线充值", requestURL.substring(0, requestURL.indexOf("/") > 0 ? requestURL.indexOf("/") : requestURL.length()))); + order.addAttr(WxConst.SCENE_INFO, sceneInfo); + +// Map orderInfo = service.orderInfo(order); +// return service.buildRequest(orderInfo, MethodType.POST); + return service.toPay(order); + } + + /** + * 公众号支付 + * + * @param openid openid + * @param price 金额 + * @return 返回jsapi所需参数 + */ + @RequestMapping(value = "jsapi") + public Map toPay(String openid, BigDecimal price) { + + PayOrder order = new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), WxTransactionType.JSAPI); + order.setOpenid(openid); + + Map orderInfo = service.orderInfo(order); + orderInfo.put("code", 0); + + return orderInfo; + } + + + /** + * 获取支付预订单信息 + * + * @return 支付预订单信息 + */ + @RequestMapping("app") + public Map app() { + Map data = new HashMap<>(); + data.put("code", 0); + PayOrder order = new PayOrder("订单title", "摘要", BigDecimal.valueOf(0.01), UUID.randomUUID().toString().replace("-", "")); + //App支付 + order.setTransactionType(WxTransactionType.APP); + data.put("orderInfo", service.app(order)); + return data; + } + + /** + * 获取二维码图像 + * 二维码支付 + * + * @param price 金额 + * @return 二维码图像 + * @throws IOException IOException + */ + @RequestMapping(value = "toQrPay.jpg", produces = "image/jpeg;charset=UTF-8") + public byte[] toWxQrPay(BigDecimal price) throws IOException { + //获取对应的支付账户操作工具(可根据账户id) + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(service.genQrPay(new PayOrder("测试商品", "测试商品", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), WxTransactionType.NATIVE)), "JPEG", baos); + return baos.toByteArray(); + } + + /** + * 获取二维码地址 + * 二维码支付 + * + * @param price 金额 + * @return 二维码图像 + * @throws IOException IOException + */ + @RequestMapping(value = "getQrPay.json") + public String getQrPay(BigDecimal price) throws IOException { + //获取对应的支付账户操作工具(可根据账户id) + return service.getQrPay(new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, System.currentTimeMillis() + "", WxTransactionType.NATIVE)); + } + + + /** + * 支付回调地址 + * + * @param request 请求 + * @return 是否成功 + *

+ * 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link com.egzosn.pay.common.api.PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} + *

+ * 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler} + * @throws IOException IOException + */ + @RequestMapping(value = "payBack.json") + public String payBack(HttpServletRequest request) { + //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() + return service.payBack(new HttpRequestNoticeParams(request)).toMessage(); + } + + + /** + * 查询 + * + * @param order 订单的请求体 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @RequestMapping("query") + public Map query(QueryOrder order) { + return service.query(new AssistOrder(order.getTradeNo(), order.getOutTradeNo())); + } + + + /** + * 交易关闭接口 + * + * @param order 订单的请求体 + * @return 返回支付方交易关闭后的结果 + */ + @RequestMapping("close") + public Map close(QueryOrder order) { + return service.close(new AssistOrder(order.getTradeNo(), order.getOutTradeNo())); + } + + /** + * 申请退款接口 + * + * @param order 订单的请求体 + * @return 返回支付方申请退款后的结果 + */ + @RequestMapping("refund") + public RefundResult refund(RefundOrder order) { + + return service.refund(order); + } + + /** + * 查询退款 + * + * @param order 订单的请求体 + * @return 返回支付方查询退款后的结果 + */ + @RequestMapping("refundquery") + public Map refundquery(RefundOrder order) { + return service.refundquery(order); + } + + /** + * 下载对账单 + * + * @param order 订单的请求体 + * @return 返回支付方下载对账单的结果 + */ + @RequestMapping("downloadbill") + public Object downloadBill(QueryOrder order) { + return service.downloadBill(order.getBillDate(), order.getBillType()); + } + + /** + * 转账到余额 + * + * + * @return 对应的转账结果 + */ + @RequestMapping("transfer") + public Map transfer() { + + WxTransferOrder order = new WxTransferOrder(); + order.setOutBatchNo("商户系统内部的商家批次单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一"); + order.setBatchName("该笔批量转账的名称"); + order.setBatchRemark("转账说明,UTF8编码,最多允许32个字符"); + // 转账金额单位为“分”。转账总金额必须与批次内所有明细转账金额之和保持一致,否则无法发起转账操作 + order.setTotalAmount(100); + //一个转账批次单最多发起一千笔转账。转账总笔数必须与批次内所有明细之和保持一致,否则无法发起转账操作 + order.setTotalNum(1); + TransferDetail transferDetail = new TransferDetail(); + transferDetail.setOutDetailNo("商户系统内部区分转账批次单下不同转账明细单的唯一标识,要求此参数只能由数字、大小写字母组成"); + // 转账金额单位为“分”。转账总金额必须与批次内所有明细转账金额之和保持一致,否则无法发起转账操作 + transferDetail.setTransferAmount(100); + transferDetail.setTransferRemark("单条转账备注(微信用户会收到该备注),UTF8编码,最多允许32个字符"); + transferDetail.setOpenid("商户appid下,某用户的openid"); + transferDetail.setUserName("收款方真实姓名: 张三"); + transferDetail.setUserIdCard("当填入收款方身份证号时,姓名字段必须填入。"); + order.setTransferDetailList(Collections.singletonList(transferDetail)); + //发起商家转账,转账到零钱 + order.setTransferType(WxTransferType.TRANSFER_BATCHES); + order.setTransferSceneId("必填,指定该笔转账使用的转账场景ID"); + return service.transfer(order); + } + /** + * 转账账单电子回单申请受理接口 + * + * + * @return 转账账单电子回单申请受理接口结果 + */ + @RequestMapping("billReceipt") + public Map billReceipt() { + WxTransferOrder order = new WxTransferOrder(); + order.setOutBatchNo("商户系统内部的商家批次单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一"); + //转账账单电子回单申请受理接口 + order.setTransferType(WxTransferType.TRANSFER_BILL_RECEIPT); + return service.transfer(order); + } + + + /** + * 通过微信批次单号查询批次单 + * + *

+ * 通过微信批次单号查询批次单 + *

+ * @return 对应的转账订单 + */ + @RequestMapping("getTransferBatchByNo") + public Map getTransferBatchByNo() { + WxTransferQueryOrder queryOrder = new WxTransferQueryOrder(); + queryOrder.setBatchId("1030000071100999991182020050700019480001"); + queryOrder.setNeedQueryDetail(true); + queryOrder.setOffset(0); + queryOrder.setLimit(20); + queryOrder.setDetailStatus("FAIL"); + queryOrder.setTransactionType(WxTransferType.QUERY_BATCH_BY_BATCH_ID); + return service.transferQuery(queryOrder); + } + /** + * 通过微信批次单号查询批次单 + * + *

+ * 通过商家批次单号查询批次单 + *

+ * @return 对应的转账订单 + */ + @RequestMapping("getTransferBatchByOutNo") + public Map getTransferBatchByOutNo() { + WxTransferQueryOrder queryOrder = new WxTransferQueryOrder(); + queryOrder.setOutBatchNo("1030000071100999991182020050700019480001"); + queryOrder.setNeedQueryDetail(true); + queryOrder.setOffset(0); + queryOrder.setLimit(20); + queryOrder.setDetailStatus("FAIL"); + queryOrder.setTransactionType(WxTransferType.QUERY_BATCH_BY_OUT_BATCH_NO); + return service.transferQuery(queryOrder); + } + /** + * 通过微信明细单号查询明细单 + * + *

+ * 通过微信明细单号查询明细单 + *

+ * @return 对应的转账订单 + */ + @RequestMapping("getTransferDetailByNo") + public Map getTransferDetailByNo() { + WxTransferQueryOrder queryOrder = new WxTransferQueryOrder(); + queryOrder.setBatchId("1030000071100999991182020050700019480001"); + queryOrder.setDetailId("1040000071100999991182020050700019500100"); + queryOrder.setTransactionType(WxTransferType.QUERY_BATCH_DETAIL_BY_BATCH_ID); + return service.transferQuery(queryOrder); + } + /** + * 通过商家明细单号查询明细单 + * + *

+ * 通过商家明细单号查询明细单 + *

+ * @return 对应的转账订单 + */ + @RequestMapping("getTransferDetailByOutNo") + public Map getTransferDetailByOutNo() { + WxTransferQueryOrder queryOrder = new WxTransferQueryOrder(); + queryOrder.setOutDetailNo("x23zy545Bd5436"); + queryOrder.setOutBatchNo("plfk2020042013"); + queryOrder.setTransactionType(WxTransferType.QUERY_BATCH_DETAIL_BY_OUT_BATCH_NO); + return service.transferQuery(queryOrder); + } + /** + * 查询转账账单电子回单接口 + * + *

+ * 查询转账账单电子回单接口 + *

+ * @return 对应的转账订单 + */ + @RequestMapping("getElectronicSignatureByOutNo") + public Map getElectronicSignatureByOutNo() { + WxTransferQueryOrder queryOrder = new WxTransferQueryOrder(); + queryOrder.setOutBatchNo("plfk2020042013"); + queryOrder.setTransactionType(WxTransferType.QUERY_TRANSFER_BILL_RECEIPT); + return service.transferQuery(queryOrder); + } + +} diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxV3PayScoreController.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxV3PayScoreController.java new file mode 100644 index 0000000000000000000000000000000000000000..695e4ff97358458f1c0280232992f1c45d3becf9 --- /dev/null +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxV3PayScoreController.java @@ -0,0 +1,142 @@ +package com.egzosn.pay.demo.controller; + +import com.egzosn.pay.common.bean.*; +import com.egzosn.pay.common.http.HttpConfigStorage; +import com.egzosn.pay.wx.v3.api.WxPayScoreService; +import com.egzosn.pay.wx.v3.api.WxPayConfigStorage; +import com.egzosn.pay.wx.v3.api.WxPayService; +import com.egzosn.pay.wx.v3.bean.payscore.*; +import org.springframework.web.bind.annotation.*; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Date; +import java.util.Map; + +@RestController +@RequestMapping("/wxV3CreditScore") +public class WxV3PayScoreController { + + private WxPayService service3 = null; + private WxPayScoreService wxPayScoreService = null; + + private static final String APPID = "wxc7b993ff15a9f26c"; + private static final String SERVICE_ID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + + private static final String MCH_ID = "1602947765"; // 商户号 + + private static final String V3_API_KEY = "9bd8f0e7af4841299d782406b7774f57"; + // @PostConstruct + public void init() { + WxPayConfigStorage paymentStandardConfig = new WxPayConfigStorage(); + paymentStandardConfig.setAppid(APPID); + paymentStandardConfig.setServiceId(SERVICE_ID); + paymentStandardConfig.setMchId(MCH_ID); + paymentStandardConfig.setV3ApiKey(V3_API_KEY); + paymentStandardConfig.setNotifyUrl("http://sailinmu.iok.la/wxV3combine/payBack.json"); + paymentStandardConfig.setInputCharset("UTF-8"); + paymentStandardConfig.setSignType("MD5"); + paymentStandardConfig.setCertStoreType(CertStoreType.PATH); + paymentStandardConfig.setApiClientKeyP12("apiclient_cert.p12"); + + HttpConfigStorage httpConfigStorage = new HttpConfigStorage(); + httpConfigStorage.setStorePassword(MCH_ID); + httpConfigStorage.setKeystore("apiclient_cert.p12"); + service3 = new WxPayService(paymentStandardConfig,httpConfigStorage); + wxPayScoreService = new WxPayScoreService(paymentStandardConfig,httpConfigStorage); + } + @PostMapping("/refund") + public RefundResult refund() { + RefundOrder refundOrder = new RefundOrder(); + + refundOrder.setRefundNo("R2023082416493947872"); + + refundOrder.setTradeNo("4200001930202308240314610507"); + //订单号 + refundOrder.setOutTradeNo("P2023082416243247872"); + //退款金额 + refundOrder.setRefundAmount(new BigDecimal("0.01")); + //退款备注 + refundOrder.setDescription("退款测试"); + + refundOrder.setCurType(DefaultCurType.CNY); + //总金额 + refundOrder.setTotalAmount(new BigDecimal("0.01")); + refundOrder.addAttr("funds_account","AVAILABLE"); + return service3.refund(refundOrder); + } + + + + + @GetMapping("/queryOrder") + public Map queryOrder(AssistOrder order) { + return wxPayScoreService.query(order); + } + @GetMapping("/queryRefundOrder") + public Map queryRefundOrder(RefundOrder refundOrder) { + return service3.refundquery(refundOrder); + } + + + + @PostMapping("/create") + public Map create() { + CreateOrder createOrder = new CreateOrder(); + createOrder.setOutTradeNo("P2023091301010100000"); + createOrder.setStartTime("OnAccept"); + //paymentRequest.setStartTime(DateUtils.formatDate(new Date(),DateUtils.YYYYMMDDHHMMSS)); + createOrder.setServiceIntroduction("测试"); + createOrder.setRiskFundAmount(new BigDecimal("1.11")); + createOrder.setRiskFundName("ESTIMATE_ORDER_COST"); + createOrder.setOpenId("oZu615JDX_H9Ni4KXmiXzuCKiBqQ"); + return wxPayScoreService.create(createOrder); + } + + + @PostMapping("/cancel") + public Map cancel() { + //撤销智慧零售 + CancelOrder cancelOrder = new CancelOrder(); + cancelOrder.setOutTradeNo("商户订单号"); + cancelOrder.setReason("测试"); + return wxPayScoreService.cancel(cancelOrder.getOutTradeNo(),cancelOrder.getReason()); + } + + + @PostMapping("/modify") + public Map modify() { + //修改订单金额 + ModifyOrder modifyOrder = new ModifyOrder(); + modifyOrder.setOutTradeNo("P2023091301010100000"); + PostPayment postPayment = new PostPayment(); + postPayment.setAmount(BigDecimal.ONE); + postPayment.setName("ESTIMATE_ORDER_COST"); + modifyOrder.setPostPayments(Arrays.asList(postPayment)); + modifyOrder.setTotalAmount(BigDecimal.ONE); + modifyOrder.setReason("test"); + return wxPayScoreService.modify(modifyOrder); + } + + @PostMapping("/complete") + public Map complete() { + //修改订单金额 + CompleteOrder completeOrder = new CompleteOrder(); + completeOrder.setOutTradeNo("P2023091301010100000"); + PostPayment postPayment = new PostPayment(); + postPayment.setAmount(BigDecimal.ONE); + postPayment.setName("ESTIMATE_ORDER_COST"); + completeOrder.setPostPayments(Arrays.asList(postPayment)); + completeOrder.setTotalAmount(BigDecimal.ONE); + return wxPayScoreService.complete(completeOrder); + } + + @PostMapping("/sync") + public Map sync() { + //修改订单金额 + SyncOrder syncOrder = new SyncOrder(); + syncOrder.setOutTradeNo("商户订单号"); + syncOrder.setPaidTime(new Date()); + return wxPayScoreService.sync(syncOrder.getOutTradeNo(),syncOrder.getPaidTime()); + } +} diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxV3ProfitSharingController.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxV3ProfitSharingController.java new file mode 100644 index 0000000000000000000000000000000000000000..793864df59d8a6871b835270001dde6851935a6d --- /dev/null +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxV3ProfitSharingController.java @@ -0,0 +1,264 @@ + +package com.egzosn.pay.demo.controller; + + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.common.bean.CertStoreType; +import com.egzosn.pay.common.bean.RefundResult; +import com.egzosn.pay.demo.request.QueryOrder; +import com.egzosn.pay.demo.service.handler.WxV3ProfitSharingMessageHandler; +import com.egzosn.pay.web.support.HttpRequestNoticeParams; +import com.egzosn.pay.wx.v3.api.WxPayConfigStorage; +import com.egzosn.pay.wx.v3.api.WxProfitSharingService; +import com.egzosn.pay.wx.v3.bean.WxProfitSharingTransactionType; +import com.egzosn.pay.wx.v3.bean.sharing.ProfitSharingBillType; +import com.egzosn.pay.wx.v3.bean.sharing.ProfitSharingOrder; +import com.egzosn.pay.wx.v3.bean.sharing.ProfitSharingReturnOrder; +import com.egzosn.pay.wx.v3.bean.sharing.Receiver; +import com.egzosn.pay.wx.v3.bean.sharing.ReceiverType; +import com.egzosn.pay.wx.v3.bean.sharing.ReceiversOrder; +import com.egzosn.pay.wx.v3.bean.sharing.RelationType; + +/** + * 微信V3分账发起支付入口 + * + * @author egan + * email egzosn@gmail.com + * date 2016/11/18 0:25 + */ +@RestController +@RequestMapping("wxV3profitSharing") +public class WxV3ProfitSharingController { + + private WxProfitSharingService service = null; + +// @PostConstruct //没有证书的情况下注释掉,避免启动报错 + public void init() { + WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage(); + wxPayConfigStorage.setAppId("wxc7b993ff15a9f26c"); + wxPayConfigStorage.setMchId("1602947765"); + //V3密钥 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_2.shtml + wxPayConfigStorage.setV3ApiKey("9bd8f0e7af4841299d782406b7774f57"); + wxPayConfigStorage.setNotifyUrl("http://sailinmu.iok.la/wxV3profitSharing/payBack.json"); + wxPayConfigStorage.setReturnUrl("http://sailinmu.iok.la/wxV3profitSharing/payBack.json"); + wxPayConfigStorage.setInputCharset("utf-8"); + //使用证书时设置为true + wxPayConfigStorage.setCertSign(true); + //商户API证书 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_1.shtml + wxPayConfigStorage.setApiClientKeyP12("yifenli_mall.p12"); + wxPayConfigStorage.setCertStoreType(CertStoreType.PATH); + service = new WxProfitSharingService(wxPayConfigStorage); + //设置回调消息处理 + //TODO {@link com.egzosn.pay.demo.controller.WxPayController#payBack} + service.setPayMessageHandler(new WxV3ProfitSharingMessageHandler()); + } + + + /** + * 请求分账API + * + * @return 分账 + */ + @RequestMapping(value = "orders") + public Map orders() { + ProfitSharingOrder order = new ProfitSharingOrder(); + order.setTransactionType(WxProfitSharingTransactionType.ORDERS); + order.setSubMchid("服务商必填----微信支付分配的子商户号,即分账的出资商户号。"); + order.setSubAppid("服务商必填----微信分配的子商户公众账号ID,分账接收方类型包含PERSONAL_SUB_OPENID时必填。"); + order.setTransactionId("微信支付订单号"); + order.setOutOrderNo("务商系统内部的分账单号,在服务商系统内部唯一,同一分账单号多次请求等同一次。只能是数字、大小写字母_-|*@ "); + List receivers = new ArrayList<>(); + Receiver receiver = new Receiver(); + //1、MERCHANT_ID:商户号 + //2、PERSONAL_OPENID:个人openid(由父商户APPID转换得到) + //3、PERSONAL_SUB_OPENID: 个人sub_openid(由子商户APPID转换得到) + //示例值:MERCHANT_ID + receiver.setType(ReceiverType.MERCHANT_ID); + receiver.setName("分账个人接收方姓名"); + receiver.setAccount("分账接收方账号"); + //分账金额,单位为分,只能为整数,不能超过原订单支付金额及最大分账比例金额 + receiver.setAmount(1); + receiver.setDescription("分账的原因描述,分账账单中需要体现"); + + order.setReceivers(receivers); + //1、如果为true,该笔订单剩余未分账的金额会解冻回分账方商户; + //2、如果为false,该笔订单剩余未分账的金额不会解冻回分账方商户,可以对该笔订单再次进行分账。 + order.setUnfreezeUnsplit(true); + Map orderInfo = service.orderInfo(order); + orderInfo.put("code", 0); + return orderInfo; + } + + + /** + * 获取支付预订单信息 + * + * @return 支付预订单信息 + */ + @RequestMapping("unfreeze") + public Map unfreeze() { + ProfitSharingOrder order = new ProfitSharingOrder(); + order.setTransactionType(WxProfitSharingTransactionType.ORDERS_UNFREEZE); + order.setSubMchid("服务商必填----微信支付分配的子商户号,即分账的出资商户号。"); + order.setTransactionId("微信支付订单号"); + order.setOutOrderNo("务商系统内部的分账单号,在服务商系统内部唯一,同一分账单号多次请求等同一次。只能是数字、大小写字母_-|*@ "); + order.setSubject("分账的原因描述,分账账单中需要体现"); + return service.orderInfo(order); + } + /** + * 添加分账接收方 + * + * @return 添加分账接收方 + */ + @RequestMapping("add") + public Map add() { + ReceiversOrder order = new ReceiversOrder(); + order.setTransactionType(WxProfitSharingTransactionType.RECEIVERS_ADD); + order.setSubMchid("服务商必填----微信支付分配的子商户号,即分账的出资商户号。"); + order.setSubAppid("服务商必填----子商户应用ID"); + //分账接收方类型: + order.setType(ReceiverType.MERCHANT_ID); + order.setAccount("分账接收方账号"); + order.setName("分账个人接收方姓名"); + //与分账方的关系类型 + order.setRelationType(RelationType.BRAND); + order.setCustomRelation("自定义的分账关系,子商户与接收方具体的关系,本字段最多10个字。\n" + + "当字段relation_type的值为CUSTOM时,本字段必填;\n" + + "当字段relation_type的值不为CUSTOM时,本字段无需填写"); + return service.orderInfo(order); + } + /** + * 删除分账接收方 + * + * @return 删除分账接收方 + */ + @RequestMapping("delete") + public Map delete() { + ReceiversOrder order = new ReceiversOrder(); + order.setTransactionType(WxProfitSharingTransactionType.RECEIVERS_DELETE); + order.setSubMchid("服务商必填----微信支付分配的子商户号,即分账的出资商户号。"); + order.setSubAppid("服务商必填----子商户应用ID"); + //分账接收方类型: + order.setType(ReceiverType.MERCHANT_ID); + order.setAccount("分账接收方账号"); + + + return service.orderInfo(order); + } + + + /** + * 分账回调地址 + * + * @param request 请求 + * @return 是否成功 + *

+ * 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link com.egzosn.pay.common.api.PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} + *

+ * 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler} + * @throws IOException IOException + */ + @RequestMapping(value = "payBack.json") + public String payBack(HttpServletRequest request) { + //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() + return service.payBack(new HttpRequestNoticeParams(request)).toMessage(); + } + + + /** + * 查询分账结果 + * + * @return 返回查询回来的结果集,支付方原值返回 + */ + @RequestMapping("query") + public Map query() { + AssistOrder assistOrder = new AssistOrder(); + assistOrder.setOutTradeNo("商户分账单号"); + assistOrder.setTradeNo("微信订单号"); + assistOrder.addAttr("sub_mchid", "服务商必填---子商户号"); + return service.query(assistOrder); + } + + + /** + * 查询剩余待分金额 + * + * @return 返回查询回来的结果集,支付方原值返回 + */ + @RequestMapping("amounts") + public Map amounts() { + AssistOrder assistOrder = new AssistOrder(); + assistOrder.setTradeNo("微信订单号"); + return service.query(assistOrder); + } + + + /** + * 查询最大分账比例 + * 服务商使用 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @RequestMapping("merchantConfigs") + public Map merchantConfigs() { + AssistOrder assistOrder = new AssistOrder(); + assistOrder.addAttr("sub_mchid", "服务商必填---子商户号"); + return service.query(assistOrder); + } + + /** + * 请求分账回退 + * + * @return 返回支付方申请退款后的结果 + */ + @RequestMapping("refund") + public RefundResult refund() { + ProfitSharingReturnOrder returnOrder = new ProfitSharingReturnOrder(); + returnOrder.setSubMchid("服务商必填---子商户号"); + returnOrder.setRefundNo("商户回退单号"); + returnOrder.setTradeNo("微信分账单号"); + returnOrder.setOutTradeNo("商户分账单号"); + returnOrder.setRefundAmount(new BigDecimal(1)); + + returnOrder.setDescription("分账回退的原因描述"); + return service.refund(returnOrder); + } + + /** + * 查询分账回退结果 + * + * @return 返回支付方查询退款后的结果 + */ + @RequestMapping("refundquery") + public Map refundquery() { + ProfitSharingReturnOrder returnOrder = new ProfitSharingReturnOrder(); + returnOrder.setSubMchid("服务商必填---子商户号"); + returnOrder.setRefundNo("商户回退单号"); + returnOrder.setOutTradeNo("商户分账单号"); + return service.refundquery(returnOrder); + } + + /** + * 下载对账单 + * + * @param order 订单的请求体 + * @return 返回支付方下载对账单的结果 + */ + @RequestMapping("downloadbill") + public Object downloadBill(QueryOrder order) { + return service.downloadBill(order.getBillDate(), ProfitSharingBillType.GZIP); + } + + +} diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/dao/ApyAccountRepository.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/dao/ApyAccountRepository.java index d43e5a1701b6532d21fcc8fa9c0a435c77059ed4..067a18073710fa846ae4042eb189e84ef5480825 100644 --- a/pay-java-demo/src/main/java/com/egzosn/pay/demo/dao/ApyAccountRepository.java +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/dao/ApyAccountRepository.java @@ -3,45 +3,43 @@ package com.egzosn.pay.demo.dao; -import com.egzosn.pay.common.bean.MsgType; +import java.util.HashMap; +import java.util.Map; + import com.egzosn.pay.common.util.sign.SignUtils; import com.egzosn.pay.demo.entity.ApyAccount; import com.egzosn.pay.demo.entity.PayType; -import java.util.HashMap; -import java.util.Map; - /** * 账户 - * @author: egan - * @email egzosn@gmail.com - * @date 2016/11/18 1:21 + * + * @author egan + * email egzosn@gmail.com + * date 2016/11/18 1:21 */ //@Repository public class ApyAccountRepository { // 这里简单模拟,引入orm等框架之后可自行删除 - public static Map apyAccounts = new HashMap<>(); + public static Map apyAccounts = new HashMap<>(); /** * 这里简单初始化,引入orm等框架之后可自行删除 - */ - { + */ { ApyAccount apyAccount1 = new ApyAccount(); apyAccount1.setPayId(1); apyAccount1.setPartner("2088102169916436"); - apyAccount1.setAppid("2016080400165436"); + apyAccount1.setAppId("2016080400165436"); // TODO 2017/2/9 16:20 author: egan sign_type只有单一key时public_key与private_key相等,比如sign_type=MD5的情况 apyAccount1.setPublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIgHnOn7LLILlKETd6BFRJ0GqgS2Y3mn1wMQmyh9zEyWlz5p1zrahRahbXAfCfSqshSNfqOmAQzSHRVjCqjsAw1jyqrXaPdKBmr90DIpIxmIyKXv4GGAkPyJ/6FTFY99uhpiq0qadD/uSzQsefWo0aTvP/65zi3eof7TcZ32oWpwIDAQAB"); - apyAccount1.setPrivateKey("MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKroe/8h5vC4L6T+B2WdXiVwGsMvUKgb2XsKix6VY3m2wcf6tyzpNRDCNykbIwGtaeo7FshN+qZxdXHLiIam9goYncBit/8ojfLGy2gLxO/PXfzGxYGs0KsDZ+ryVPPmE34ZZ8jiJpR0ygzCFl8pN3QJPJRGTJn5+FTT9EF/9zyZAgMBAAECgYAktngcYC35u7cQXDk+jMVyiVhWYU2ULxdSpPspgLGzrZyG1saOcTIi/XVX8Spd6+B6nmLQeF/FbU3rOeuD8U2clzul2Z2YMbJ0FYay9oVZFfp5gTEFpFRTVfzqUaZQBIjJe/xHL9kQVqc5xHlE/LVA27/Kx3dbC35Y7B4EVBDYAQJBAOhsX8ZreWLKPhXiXHTyLmNKhOHJc+0tFH7Ktise/0rNspojU7o9prOatKpNylp9v6kux7migcMRdVUWWiVe+4ECQQC8PqsuEz7B0yqirQchRg1DbHjh64bw9Kj82EN1/NzOUd53tP9tg+SO97EzsibK1F7tOcuwqsa7n2aY48mQ+y0ZAkBndA2xcRcnvOOjtAz5VO8G7R12rse181HjGfG6AeMadbKg30aeaGCyIxN1loiSfNR5xsPJwibGIBg81mUrqzqBAkB+K6rkaPXJR9XtzvdWb/N3235yPkDlw7Z4MiOVM3RzvR/VMDV7m8lXoeDde2zQyeMOMYy6ztwA6WgE1bhGOnQRAkEAouUBv1sVdSBlsexX15qphOmAevzYrpufKgJIRLFWQxroXMS7FTesj+f+FmGrpPCxIde1dqJ8lqYLTyJmbzMPYw==\n"); - apyAccount1.setNotifyUrl("http://pay.egan.in/payBack1.json"); + apyAccount1.setPrivateKey("MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKroe/8h5vC4L6T+B2WdXiVwGsMvUKgb2XsKix6VY3m2wcf6tyzpNRDCNykbIwGtaeo7FshN+qZxdXHLiIam9goYncBit/8ojfLGy2gLxO/PXfzGxYGs0KsDZ+ryVPPmE34ZZ8jiJpR0ygzCFl8pN3QJPJRGTJn5+FTT9EF/9zyZAgMBAAECgYAktngcYC35u7cQXDk+jMVyiVhWYU2ULxdSpPspgLGzrZyG1saOcTIi/XVX8Spd6+B6nmLQeF/FbU3rOeuD8U2clzul2Z2YMbJ0FYay9oVZFfp5gTEFpFRTVfzqUaZQBIjJe/xHL9kQVqc5xHlE/LVA27/Kx3dbC35Y7B4EVBDYAQJBAOhsX8ZreWLKPhXiXHTyLmNKhOHJc+0tFH7Ktise/0rNspojU7o9prOatKpNylp9v6kux7migcMRdVUWWiVe+4ECQQC8PqsuEz7B0yqirQchRg1DbHjh64bw9Kj82EN1/NzOUd53tP9tg+SO97EzsibK1F7tOcuwqsa7n2aY48mQ+y0ZAkBndA2xcRcnvOOjtAz5VO8G7R12rse181HjGfG6AeMadbKg30aeaGCyIxN1loiSfNR5xsPJwibGIBg81mUrqzqBAkB+K6rkaPXJR9XtzvdWb/N3235yPkDlw7Z4MiOVM3RzvR/VMDV7m8lXoeDde2zQyeMOMYy6ztwA6WgE1bhGOnQRAkEAouUBv1sVdSBlsexX15qphOmAevzYrpufKgJIRLFWQxroXMS7FTesj+f+FmGrpPCxIde1dqJ8lqYLTyJmbzMPYw=="); + apyAccount1.setNotifyUrl("http://pay.egzosn.com/payBack1.json"); // 无需同步回调可不填 - apyAccount1.setReturnUrl("http://pay.egan.in/payBack1.json"); + apyAccount1.setReturnUrl("http://pay.egzosn.com/payBack1.json"); apyAccount1.setInputCharset("UTF-8"); apyAccount1.setSeller("2088102169916436"); apyAccount1.setSignType(SignUtils.RSA.name()); apyAccount1.setPayType(PayType.aliPay); - apyAccount1.setMsgType(MsgType.text); //设置测试环境 apyAccount1.setTest(true); apyAccounts.put(apyAccount1.getPayId(), apyAccount1); @@ -49,7 +47,7 @@ public class ApyAccountRepository { ApyAccount apyAccount2 = new ApyAccount(); apyAccount2.setPayId(2); apyAccount2.setPartner("1469188802"); - apyAccount2.setAppid("wx3344f4aed352deae"); + apyAccount2.setAppId("wx3344f4aed352de09"); // TODO 2017/2/9 16:20 author: egan sign_type只有单一key时public_key与private_key相等,比如sign_type=MD5的情况 apyAccount2.setPublicKey("991ded080***************f7fc61095"); apyAccount2.setPrivateKey("991ded080***************f7fc61095"); @@ -60,7 +58,6 @@ public class ApyAccountRepository { apyAccount2.setSeller("1469188802"); apyAccount2.setSignType(SignUtils.MD5.name()); apyAccount2.setPayType(PayType.wxPay); - apyAccount2.setMsgType(MsgType.xml); //设置测试环境 apyAccount2.setTest(false); apyAccounts.put(apyAccount2.getPayId(), apyAccount2); @@ -68,7 +65,7 @@ public class ApyAccountRepository { ApyAccount apyAccount3 = new ApyAccount(); apyAccount3.setPayId(3); apyAccount3.setPartner("12****601"); - apyAccount3.setAppid("wxa39*****ba9e9"); + apyAccount3.setAppId("wxa39*****ba9e9"); apyAccount3.setPublicKey("48gf0i************h9eiut9"); apyAccount3.setPrivateKey("48gf0i************h9eiut9"); apyAccount3.setNotifyUrl("http://pay.egan.in/payBack3.json"); @@ -78,7 +75,6 @@ public class ApyAccountRepository { apyAccount3.setInputCharset("UTF-8"); apyAccount3.setSignType(SignUtils.MD5.name()); apyAccount3.setPayType(PayType.wxPay); - apyAccount3.setMsgType(MsgType.xml); apyAccounts.put(apyAccount3.getPayId(), apyAccount3); ApyAccount apyAccount4 = new ApyAccount(); @@ -95,30 +91,38 @@ public class ApyAccountRepository { apyAccount4.setInputCharset("UTF-8"); apyAccount4.setSignType(SignUtils.RSA2.name()); apyAccount4.setPayType(PayType.unionPay); - apyAccount4.setMsgType(MsgType.json); apyAccount4.setTest(true); apyAccounts.put(apyAccount4.getPayId(), apyAccount4); ApyAccount apyAccount5 = new ApyAccount(); apyAccount5.setPayId(5); apyAccount5.setPartner("100086190");//Program ID - apyAccount5.setSeller("Huodull6190");//Username + apyAccount5.setSeller("egan6190");//Username apyAccount5.setStorePassword("12BkDT8152Zj");//API password apyAccount5.setInputCharset("UTF-8"); apyAccount5.setPayType(PayType.payoneer); - apyAccount5.setMsgType(MsgType.json); apyAccount5.setTest(true); apyAccounts.put(apyAccount5.getPayId(), apyAccount5); + + ApyAccount apyAccount6 = new ApyAccount(); + apyAccount6.setPayId(6); + apyAccount6.setAppId("1AZ7HTcvrEAxYbzYx_iDZAi06GdqbjhqqQzFgPBFLxm2VUMzwlmiNUBk_y_5QNP4zWKblTuM6ZBAmxScd");//Program ID + apyAccount6.setPrivateKey("1EBMIjAag6NiRdXZxteTv0amEsmKN345xJv3bN7f_HRXSqcRJlW7PXhYXjI9sk5I4nKYOHgeqzhXCXKFo");//API password + apyAccount6.setInputCharset("UTF-8"); + apyAccount6.setPayType(PayType.payPal); + apyAccount6.setTest(true); + apyAccounts.put(apyAccount6.getPayId(), apyAccount6); } //_____________________________________________________________ /** * 根据id获取对应的账户信息 + * * @param payId 账户id - * @return + * @return 账户信息 */ - public ApyAccount findByPayId(Integer payId){ + public ApyAccount findByPayId(Integer payId) { // TODO 2016/11/18 1:23 author: egan 这里简单模拟 具体实现 略。。 return apyAccounts.get(payId); } diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/entity/ApyAccount.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/entity/ApyAccount.java index 71e479486b6a6a23fb492d651d64745dc328f7a1..8aec899705ed9c6dfa7632d3f550f650870e3c72 100644 --- a/pay-java-demo/src/main/java/com/egzosn/pay/demo/entity/ApyAccount.java +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/entity/ApyAccount.java @@ -2,15 +2,14 @@ package com.egzosn.pay.demo.entity; -import com.egzosn.pay.common.bean.MsgType; - //import javax.persistence.*; /** * 支付账户 - * @author: egan - * @email egzosn@gmail.com - * @date 2016/11/18 0:36 + * + * @author egan + * email egzosn@gmail.com + * date 2016/11/18 0:36 */ //@Table(name = "apy_account") //@Entity @@ -25,7 +24,7 @@ public class ApyAccount { private String partner; // 应用id // @Column(name = "appid") - private String appid; + private String appId; // 支付平台公钥(签名校验使用),sign_type只有单一key时public_key与private_key相等,比如sign_type=MD5的情况 private String publicKey; // 应用私钥(生成签名) @@ -59,12 +58,9 @@ public class ApyAccount { // @Enumerated(EnumType.STRING) // @Column(name = "pay_type") private PayType payType; - // 消息类型,text,xml,json -// @Enumerated(EnumType.STRING) -// @Column(name = "msg_type") - private MsgType msgType; //是否为测试环境 private boolean isTest = false; + public Integer getPayId() { return payId; } @@ -81,12 +77,12 @@ public class ApyAccount { this.partner = partner; } - public String getAppid() { - return appid; + public String getAppId() { + return appId; } - public void setAppid(String appid) { - this.appid = appid; + public void setAppId(String appId) { + this.appId = appId; } public String getPublicKey() { @@ -145,14 +141,6 @@ public class ApyAccount { this.payType = payType; } - public MsgType getMsgType() { - return msgType; - } - - public void setMsgType(MsgType msgType) { - this.msgType = msgType; - } - public String getInputCharset() { return inputCharset; } @@ -190,7 +178,7 @@ public class ApyAccount { return "ApyAccount{" + "payId=" + payId + ", partner='" + partner + '\'' + - ", appid='" + appid + '\'' + + ", appId='" + appId + '\'' + ", publicKey='" + publicKey + '\'' + ", privateKey='" + privateKey + '\'' + ", notifyUrl='" + notifyUrl + '\'' + @@ -199,7 +187,6 @@ public class ApyAccount { ", signType='" + signType + '\'' + ", inputCharset='" + inputCharset + '\'' + ", payType=" + payType + - ", msgType=" + msgType + '}'; } } diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/entity/PayType.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/entity/PayType.java index 2f1e868cbb20a592b4ffd4cc508608a88f6a79eb..dbfa77fd56379c654434ac36b42b9f347ab2f55f 100644 --- a/pay-java-demo/src/main/java/com/egzosn/pay/demo/entity/PayType.java +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/entity/PayType.java @@ -5,9 +5,10 @@ import com.egzosn.pay.ali.api.AliPayService; import com.egzosn.pay.ali.bean.AliTransactionType; import com.egzosn.pay.common.api.PayService; import com.egzosn.pay.common.bean.BasePayType; -import com.egzosn.pay.common.bean.MsgType; +import com.egzosn.pay.common.bean.CertStoreType; import com.egzosn.pay.common.bean.TransactionType; -import com.egzosn.pay.common.http.HttpConfigStorage; +import com.egzosn.pay.common.util.sign.SignUtils; +import com.egzosn.pay.demo.service.handler.WxPayMessageHandler; import com.egzosn.pay.fuiou.api.FuiouPayConfigStorage; import com.egzosn.pay.fuiou.api.FuiouPayService; import com.egzosn.pay.fuiou.bean.FuiouTransactionType; @@ -28,7 +29,6 @@ import com.egzosn.pay.wx.youdian.api.WxYouDianPayService; import com.egzosn.pay.wx.youdian.bean.YoudianTransactionType; - /** * 支付类型 * @@ -39,36 +39,32 @@ import com.egzosn.pay.wx.youdian.bean.YoudianTransactionType; public enum PayType implements BasePayType { - aliPay{ + aliPay { /** - * @see com.egzosn.pay.ali.api.AliPayService 17年更新的版本,旧版本请自行切换 + * @see com.egzosn.pay.ali.api.AliPayService * @param apyAccount * @return */ @Override public PayService getPayService(ApyAccount apyAccount) { - AliPayConfigStorage configStorage = new AliPayConfigStorage(); - //配置的附加参数的使用 - configStorage.setAttach(apyAccount.getPayId()); - configStorage.setPid(apyAccount.getPartner()); - configStorage.setAppId(apyAccount.getAppid()); - configStorage.setKeyPublic(apyAccount.getPublicKey()); - configStorage.setKeyPrivate(apyAccount.getPrivateKey()); - configStorage.setNotifyUrl(apyAccount.getNotifyUrl()); - configStorage.setReturnUrl(apyAccount.getReturnUrl()); - configStorage.setSignType(apyAccount.getSignType()); - configStorage.setSeller(apyAccount.getSeller()); - configStorage.setPayType(apyAccount.getPayType().toString()); - configStorage.setMsgType(apyAccount.getMsgType()); - configStorage.setInputCharset(apyAccount.getInputCharset()); - configStorage.setTest(apyAccount.isTest()); - //请求连接池配置 - HttpConfigStorage httpConfigStorage = new HttpConfigStorage(); - //最大连接数 - httpConfigStorage.setMaxTotal(20); - //默认的每个路由的最大连接数 - httpConfigStorage.setDefaultMaxPerRoute(10); - return new AliPayService(configStorage, httpConfigStorage); + AliPayConfigStorage aliPayConfigStorage = new AliPayConfigStorage(); + aliPayConfigStorage.setPid("2088102169916436"); + aliPayConfigStorage.setAppId("2016080400165436"); + aliPayConfigStorage.setCertSign(true); + //设置证书存储方式,这里为路径 + aliPayConfigStorage.setCertStoreType(CertStoreType.CLASS_PATH); + aliPayConfigStorage.setMerchantCert("ali/appCertPublicKey_2016080400165436.crt"); + aliPayConfigStorage.setAliPayCert("ali/alipayCertPublicKey_RSA2.crt"); + aliPayConfigStorage.setAliPayRootCert("ali/alipayRootCert.crt"); + aliPayConfigStorage.setKeyPrivate("MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCw7MD2Cwv/jnXssFjXnGx3JlGF57gJa2aYbJRV8MnNiPVpX4Ha+8ZjnQDhvkrWH4hHmzcujOr213HqloMpUSYBzCPiXGVRUUvdimejcHHTod7nI4g6nztzzfey/TXNDHmp7vY3pOIcjB0Zn0pkNAz2tKAFkqb4raHOqTB0QA0zD24Cn+26J2UJyYRcgeH0GtSQuUrm7yaGsuKakh+qtgWF6R71n5PMGOTQ5LH3i0WVHfCBkNGgJC6yC96HR4D7cosoyKD0+lp8UB/NVUWl7Tt/KLOgFUwh0GKSYFfv56O/VBV2+xqCGE4PlZESfVuOqz5vjjxzw3xDAUJrV8hSX/AJAgMBAAECggEBAKE0d3U4B4yo/2XUIH8EdgfykCFUSum6RFbpyBauORHfksyaSzV+ZvtomN8XhhSn0oJ8OMFfgM+86nz2+zdwSxMkMCYWTfLUAi4v59KRqAVO3kz4oS3Y3FDeAK3D7XuRvGFL7GgzAhtEx1cLPrsiehVn6s5pG15GxsIIgq/JlL1J88wn1zENLrVHmD6z/JpXvfb/RS1yR+5lyoohp4g0Ph9jJ3bCyUbRpK0QkPEzgAuWL0K2ITCL7PYHNAplI8d2xHHOLF9Qdjyx+ZrQ/RxtqzfyWzhqjsmp2qlgNCxWlt3woS9UhDB+nRvjEoWTJmIOszAMYuj8wGlX+3Ui3ALOdQECgYEA25EqnFPFinUnzgNvB6NYmh5STmZun6s4bUOLqwefKtEvrOtRwTu7sB7NIf37fizG3/MJUWHxiLy2/3ub4d2JxdDNBtJoEqnp6QB12qglCNa4CajdjtJa1dR81F9QvytsqEkmPYXFPPyviB0FcSIDAGMb3IbwvIfzBPY9WY8dJnECgYEAzkg3yKEFBZ8BU0WQ+3hyfKUoAhBEnxouxRSTBcXxwstJRiqaGTVe5aoJGQI+0xS7Z6q07XDtN2t97s6DnRLWbljsX6B64itzNhXRyzjdD3iZDU/KSw7khjhXf8XOZaj9eXmACDiUnkEn1xsM8bLiRGqB8y5f3aMY/RpuACGXnxkCgYEAx/zwT9Vpr1RIfjfYcJ+Su0X0994K0roUukj0tUJK8qf4gcsQ+y1aJe/YLib1ZBaKyj7G9O5+HmqtUAUZld/AdoJZzOXmz2EeYhD+R7wxh1xz4rCBpW3qOKvDS3jJxmZaIOoHv6/RWFxb0WGFrGcrTrX3EaWDLmWxr4pNlP5qsbECgYATllntrBR8/ycyEAX/SuWcHlaZM5BAh0zvm8+GGdCmDYWMqxjs0duL9URd4o+ynWJaKqR5c2KjA4r2tRdcP+Cqo7j2L5fbiAKtnQ7JvEGJaYsm72+nBuf+MrVkRZUepBhFg5r7rNu31zoAO+pTvQetNWvXeozRz93ckrjlPEtYaQKBgQDFwbV92rlRMLjZzlY+o0knoeJBjPQmPdiBTpGNimdy9L4c2Ure7affjcUiYhkKqrK5k5SScJTATgyQ7JF346FdtUtZ/6Kkj1RwJmmprPrDa9CATLoTle7g9OVd4sHT2ITHZMzPaF3ILvzcwJ70AD1xcxCQb+/7sDPmw7Mc8gOA7Q=="); + aliPayConfigStorage.setNotifyUrl("http://pay.egzosn.com/payBack.json"); + aliPayConfigStorage.setReturnUrl("http://pay.egzosn.com/payBack.html"); + aliPayConfigStorage.setSignType(SignUtils.RSA2.name()); + aliPayConfigStorage.setSeller("2088102169916436"); + aliPayConfigStorage.setInputCharset("utf-8"); + //是否为测试账号,沙箱环境 + aliPayConfigStorage.setTest(true); + return new AliPayService(aliPayConfigStorage); } @Override @@ -80,12 +76,12 @@ public enum PayType implements BasePayType { } - },wxPay { + }, wxPay { @Override public PayService getPayService(ApyAccount apyAccount) { WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage(); wxPayConfigStorage.setMchId(apyAccount.getPartner()); - wxPayConfigStorage.setAppid(apyAccount.getAppid()); + wxPayConfigStorage.setAppId(apyAccount.getAppId()); //转账公钥,转账时必填 wxPayConfigStorage.setKeyPublic(apyAccount.getPublicKey()); wxPayConfigStorage.setSecretKey(apyAccount.getPrivateKey()); @@ -93,7 +89,6 @@ public enum PayType implements BasePayType { wxPayConfigStorage.setReturnUrl(apyAccount.getReturnUrl()); wxPayConfigStorage.setSignType(apyAccount.getSignType()); wxPayConfigStorage.setPayType(apyAccount.getPayType().toString()); - wxPayConfigStorage.setMsgType(apyAccount.getMsgType()); wxPayConfigStorage.setInputCharset(apyAccount.getInputCharset()); wxPayConfigStorage.setTest(apyAccount.isTest()); @@ -103,10 +98,12 @@ public enum PayType implements BasePayType { // httpConfigStorage.setKeystore(PayType.class.getResourceAsStream("/证书文件")); httpConfigStorage.setKeystore("证书信息串"); httpConfigStorage.setStorePassword("证书密码"); - //是否为证书地址 - httpConfigStorage.setPath(false); + //设置ssl证书对应的存储方式,这里默认为文件地址 + httpConfigStorage.setCertStoreType(CertStoreType.PATH); return new WxPayService(wxPayConfigStorage, httpConfigStorage);*/ - return new WxPayService(wxPayConfigStorage); + WxPayService wxPayService = new WxPayService(wxPayConfigStorage); + wxPayService.setPayMessageHandler(new WxPayMessageHandler(1)); + return wxPayService; } /** @@ -120,7 +117,7 @@ public enum PayType implements BasePayType { return WxTransactionType.valueOf(transactionType); } - },youdianPay { + }, youdianPay { @Override public PayService getPayService(ApyAccount apyAccount) { // TODO 2017/1/23 14:12 author: egan 集群的话,友店可能会有bug。暂未测试集群环境 @@ -131,11 +128,10 @@ public enum PayType implements BasePayType { // wxPayConfigStorage.setReturnUrl(apyAccount.getReturnUrl()); wxPayConfigStorage.setSignType(apyAccount.getSignType()); wxPayConfigStorage.setPayType(apyAccount.getPayType().toString()); - wxPayConfigStorage.setMsgType(apyAccount.getMsgType()); wxPayConfigStorage.setSeller(apyAccount.getSeller()); wxPayConfigStorage.setInputCharset(apyAccount.getInputCharset()); wxPayConfigStorage.setTest(apyAccount.isTest()); - return new WxYouDianPayService(wxPayConfigStorage); + return new WxYouDianPayService(wxPayConfigStorage); } /** @@ -149,8 +145,7 @@ public enum PayType implements BasePayType { return YoudianTransactionType.valueOf(transactionType); } - },fuiou{ - + }, fuiou { @Override public PayService getPayService(ApyAccount apyAccount) { FuiouPayConfigStorage fuiouPayConfigStorage = new FuiouPayConfigStorage(); @@ -160,7 +155,6 @@ public enum PayType implements BasePayType { fuiouPayConfigStorage.setReturnUrl(apyAccount.getReturnUrl()); fuiouPayConfigStorage.setSignType(apyAccount.getSignType()); fuiouPayConfigStorage.setPayType(apyAccount.getPayType().toString()); - fuiouPayConfigStorage.setMsgType(apyAccount.getMsgType()); fuiouPayConfigStorage.setInputCharset(apyAccount.getInputCharset()); fuiouPayConfigStorage.setTest(apyAccount.isTest()); return new FuiouPayService(fuiouPayConfigStorage); @@ -172,20 +166,30 @@ public enum PayType implements BasePayType { } - },unionPay{ - + }, unionPay { @Override public PayService getPayService(ApyAccount apyAccount) { UnionPayConfigStorage unionPayConfigStorage = new UnionPayConfigStorage(); unionPayConfigStorage.setMerId(apyAccount.getPartner()); unionPayConfigStorage.setCertSign(true); - unionPayConfigStorage.setKeyPublic(apyAccount.getPublicKey()); - unionPayConfigStorage.setKeyPrivate(apyAccount.getPrivateKey()); +// unionPayConfigStorage.setKeyPublic(apyAccount.getPublicKey()); +// unionPayConfigStorage.setKeyPrivate(apyAccount.getPrivateKey()); + + //中级证书路径 + unionPayConfigStorage.setAcpMiddleCert("D:/certs/acp_test_middle.cer"); + //根证书路径 + unionPayConfigStorage.setAcpRootCert("D:/certs/acp_test_root.cer"); + // 私钥证书路径 + unionPayConfigStorage.setKeyPrivateCert("D:/certs/acp_test_sign.pfx"); + //私钥证书对应的密码 + unionPayConfigStorage.setKeyPrivateCertPwd("000000"); + //设置证书对应的存储方式,这里默认为文件地址 + unionPayConfigStorage.setCertStoreType(CertStoreType.PATH); + unionPayConfigStorage.setNotifyUrl(apyAccount.getNotifyUrl()); unionPayConfigStorage.setReturnUrl(apyAccount.getReturnUrl()); unionPayConfigStorage.setSignType(apyAccount.getSignType()); unionPayConfigStorage.setPayType(apyAccount.getPayType().toString()); - unionPayConfigStorage.setMsgType(apyAccount.getMsgType()); unionPayConfigStorage.setInputCharset(apyAccount.getInputCharset()); unionPayConfigStorage.setTest(apyAccount.isTest()); return new UnionPayService(unionPayConfigStorage); @@ -197,13 +201,12 @@ public enum PayType implements BasePayType { } - },payoneer{ + }, payoneer { @Override public PayService getPayService(ApyAccount apyAccount) { PayoneerConfigStorage configStorage = new PayoneerConfigStorage(); //设置商户Id configStorage.setProgramId(apyAccount.getPartner()); - configStorage.setMsgType(MsgType.json); configStorage.setInputCharset("utf-8"); //"PayoneerPay 用户名" configStorage.setUserName(apyAccount.getSeller()); @@ -227,13 +230,13 @@ public enum PayType implements BasePayType { } - },payPal{ + }, payPal { @Override public PayService getPayService(ApyAccount apyAccount) { PayPalConfigStorage storage = new PayPalConfigStorage(); //配置的附加参数的使用 storage.setAttach(apyAccount.getPayId()); - storage.setClientID(apyAccount.getAppid()); + storage.setClientID(apyAccount.getAppId()); storage.setClientSecret(apyAccount.getPrivateKey()); storage.setTest(true); //发起付款后的页面转跳地址 diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/request/QueryOrder.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/request/QueryOrder.java index e9e76314359622ef1420338a7952e506e5773507..5e8579dcfb133bd1c6d7a85bcb8332fca3c01cb4 100644 --- a/pay-java-demo/src/main/java/com/egzosn/pay/demo/request/QueryOrder.java +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/request/QueryOrder.java @@ -5,9 +5,9 @@ import java.util.Date; /** * 订单辅助接口 - * @author: egan - * @email egzosn@gmail.com - * @date 2017/3/12 14:50 + * @author egan + * email egzosn@gmail.com + * date 2017/3/12 14:50 */ public class QueryOrder { diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/ApyAccountService.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/ApyAccountService.java index 496d52c984ee558fbebbe29dc0a4adbfd0f852e6..ea3b4075eb6f68e21245d45c082e87d5d835bd8f 100644 --- a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/ApyAccountService.java +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/ApyAccountService.java @@ -10,9 +10,9 @@ import java.util.HashMap; import java.util.Map; /** - * @author: egan - * @email egzosn@gmail.com - * @date 2016/11/18 1:11 + * @author egan + * email egzosn@gmail.com + * date 2016/11/18 1:11 */ @Service public class ApyAccountService { @@ -40,7 +40,7 @@ public class ApyAccountService { /** * 获取支付响应 * @param id 账户id - * @return + * @return 支付响应 */ public PayResponse getPayResponse(Integer id) { diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/PayResponse.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/PayResponse.java index a5e9a4128883631d8c319c930be7c7c284a88c1c..c085fe5ce376b27bf694647259430c584441cd75 100644 --- a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/PayResponse.java +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/PayResponse.java @@ -1,25 +1,31 @@ package com.egzosn.pay.demo.service; +import javax.annotation.Resource; + +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; + import com.egzosn.pay.common.api.PayConfigStorage; import com.egzosn.pay.common.api.PayMessageHandler; import com.egzosn.pay.common.api.PayMessageRouter; import com.egzosn.pay.common.api.PayService; -import com.egzosn.pay.common.bean.MsgType; import com.egzosn.pay.common.http.HttpConfigStorage; import com.egzosn.pay.demo.entity.ApyAccount; import com.egzosn.pay.demo.entity.PayType; -import com.egzosn.pay.demo.service.handler.*; +import com.egzosn.pay.demo.service.handler.AliPayMessageHandler; +import com.egzosn.pay.demo.service.handler.FuiouPayMessageHandler; +import com.egzosn.pay.demo.service.handler.PayoneerMessageHandler; +import com.egzosn.pay.demo.service.handler.UnionPayMessageHandler; +import com.egzosn.pay.demo.service.handler.WxPayMessageHandler; +import com.egzosn.pay.demo.service.handler.YouDianPayMessageHandler; import com.egzosn.pay.demo.service.interceptor.AliPayMessageInterceptor; import com.egzosn.pay.demo.service.interceptor.YoudianPayMessageInterceptor; -import org.springframework.beans.factory.config.AutowireCapableBeanFactory; - -import javax.annotation.Resource; /** * 支付响应对象 - * @author: egan - * @email egzosn@gmail.com - * @date 2016/11/18 0:34 + * + * @author egan + * email egzosn@gmail.com + * date 2016/11/18 0:34 */ public class PayResponse { @@ -38,6 +44,7 @@ public class PayResponse { /** * 初始化支付配置 + * * @param apyAccount 账户信息 * @see ApyAccount 对应表结构详情--》 /pay-java-demo/resources/apy_account.sql */ @@ -52,13 +59,14 @@ public class PayResponse { /** * 获取http配置,如果配置为null则为默认配置,无代理,无证书的请求方式。 - * 此处非必需 + * 此处非必需 + * * @param apyAccount 账户信息 * @return 请求配置 */ - public HttpConfigStorage getHttpConfigStorage(ApyAccount apyAccount){ + public HttpConfigStorage getHttpConfigStorage(ApyAccount apyAccount) { HttpConfigStorage httpConfigStorage = new HttpConfigStorage(); - /* 网路代理配置 根据需求进行设置*/ + /* 网路代理配置 根据需求进行设置*/ // //http代理地址 // httpConfigStorage.setHttpProxyHost("192.168.1.69"); // //代理端口 @@ -68,7 +76,7 @@ public class PayResponse { // //代理密码 // httpConfigStorage.setHttpProxyPassword("password"); //设置ssl证书路径 https证书设置 方式二 - httpConfigStorage.setKeystorePath(apyAccount.getKeystorePath()); + httpConfigStorage.setKeystore(apyAccount.getKeystorePath()); //设置ssl证书对应的密码 httpConfigStorage.setStorePassword(apyAccount.getStorePassword()); return httpConfigStorage; @@ -77,6 +85,7 @@ public class PayResponse { /** * 配置路由 + * * @param payId 指定账户id,用户多微信支付多支付宝支付 */ private void buildRouter(Integer payId) { @@ -84,7 +93,6 @@ public class PayResponse { router .rule() //消息类型 - .msgType(MsgType.text.name()) //支付账户事件类型 .payType(PayType.aliPay.name()) //拦截器 @@ -93,33 +101,27 @@ public class PayResponse { .handler(spring.getBean(AliPayMessageHandler.class)) .end() .rule() - .msgType(MsgType.xml.name()) .payType(PayType.wxPay.name()) .handler(autowire(new WxPayMessageHandler(payId))) .end() .rule() - .msgType(MsgType.json.name()) .payType(PayType.youdianPay.name()) .interceptor(new YoudianPayMessageInterceptor()) //拦截器 .handler(autowire(new YouDianPayMessageHandler(payId))) .end() .rule() - .msgType(MsgType.xml.name()) .payType(PayType.fuiou.name()) .handler(autowire(new FuiouPayMessageHandler(payId))) .end() .rule() - .msgType(MsgType.json.name()) .payType(PayType.unionPay.name()) .handler(autowire(new UnionPayMessageHandler(payId))) .end() .rule() - .msgType(MsgType.json.name()) .payType(PayType.payoneer.name()) .handler(autowire(new PayoneerMessageHandler(payId))) .end() .rule() - .msgType(MsgType.text.name()) .payType(PayType.payPal.name()) .handler(spring.getBean(AliPayMessageHandler.class)) .end() diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/AliPayMessageHandler.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/AliPayMessageHandler.java index 8ad88ad2862f8e7985dd86d942288af902031b05..b994349ec31904ceec7dce1c27c95fae852a41a7 100644 --- a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/AliPayMessageHandler.java +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/AliPayMessageHandler.java @@ -1,5 +1,7 @@ package com.egzosn.pay.demo.service.handler; +import com.egzosn.pay.ali.api.AliPayService; +import com.egzosn.pay.ali.bean.AliPayMessage; import com.egzosn.pay.common.api.PayMessageHandler; import com.egzosn.pay.common.api.PayService; import com.egzosn.pay.common.bean.PayMessage; @@ -17,14 +19,20 @@ import java.util.Map; * */ @Component -public class AliPayMessageHandler implements PayMessageHandler { - - - - - +public class AliPayMessageHandler implements PayMessageHandler { + + + /** + * 处理支付回调消息的处理器接口 + * + * @param payMessage 支付消息 + * @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 + * @param payService 支付服务 + * @return xml, text格式的消息,如果在异步规则里处理的话,可以返回null + * @throws PayErrorException 支付错误异常 + */ @Override - public PayOutMessage handle(PayMessage payMessage, Map context, PayService payService) throws PayErrorException { + public PayOutMessage handle(AliPayMessage payMessage, Map context, AliPayService payService) throws PayErrorException { //com.egzosn.pay.demo.entity.PayType.getPayService()#48 Object payId = payService.getPayConfigStorage().getAttach(); diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/BasePayMessageHandler.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/BasePayMessageHandler.java index a55947edaf0d67683a931a8e7984a4b2c2192c43..bcaad7bb26f5da431353d4448efcc7c6c5241bee 100644 --- a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/BasePayMessageHandler.java +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/BasePayMessageHandler.java @@ -1,12 +1,14 @@ package com.egzosn.pay.demo.service.handler; import com.egzosn.pay.common.api.PayMessageHandler; +import com.egzosn.pay.common.api.PayService; +import com.egzosn.pay.common.bean.PayMessage; /** * * Created by ZaoSheng on 2016/6/1. */ -public abstract class BasePayMessageHandler implements PayMessageHandler { +public abstract class BasePayMessageHandler implements PayMessageHandler { //支付账户id private Integer payId; diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/FuiouPayMessageHandler.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/FuiouPayMessageHandler.java index a027c07527044212554becdc406cd89f59f90314..eae307f5a4c4c97166e49610caab4c0dc20af12c 100644 --- a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/FuiouPayMessageHandler.java +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/FuiouPayMessageHandler.java @@ -9,7 +9,7 @@ import java.util.Map; /** * @author Fuzx - * @create 2017 2017/1/24 0024 + * create 2017 2017/1/24 0024 */ public class FuiouPayMessageHandler extends BasePayMessageHandler { @@ -21,7 +21,7 @@ public class FuiouPayMessageHandler extends BasePayMessageHandler { } @Override - public PayOutMessage handle(PayMessage payMessage, Map context, PayService payService) throws PayErrorException { + public PayOutMessage handle(PayMessage payMessage, Map context, PayService payService) throws PayErrorException { //交易状态 if ("0000".equals(payMessage.getPayMessage().get("order_pay_code"))){ /////这里进行成功的处理 diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/PayPalPayMessageHandler.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/PayPalPayMessageHandler.java index 9067bc5ae9dd5c5253c83d022995b072610aa93c..912df2e626fdb48e4ee0580f79839d376a122a71 100644 --- a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/PayPalPayMessageHandler.java +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/PayPalPayMessageHandler.java @@ -5,6 +5,7 @@ import com.egzosn.pay.common.api.PayService; import com.egzosn.pay.common.bean.PayMessage; import com.egzosn.pay.common.bean.PayOutMessage; import com.egzosn.pay.common.exception.PayErrorException; +import com.egzosn.pay.paypal.api.PayPalPayService; import org.springframework.stereotype.Component; import java.math.BigDecimal; @@ -16,14 +17,14 @@ import java.util.Map; * */ @Component -public class PayPalPayMessageHandler implements PayMessageHandler { +public class PayPalPayMessageHandler implements PayMessageHandler { @Override - public PayOutMessage handle(PayMessage payMessage, Map context, PayService payService) throws PayErrorException { + public PayOutMessage handle(PayMessage payMessage, Map context, PayPalPayService payService) throws PayErrorException { return payService.getPayOutMessage("fail", "失败"); diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/PayoneerMessageHandler.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/PayoneerMessageHandler.java index d1ee66c364f2bde38d485aa2da7efbfa72767489..49a7afbf9fe51c7df89d7fa582a7f76aa98818ec 100644 --- a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/PayoneerMessageHandler.java +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/PayoneerMessageHandler.java @@ -9,10 +9,10 @@ import com.egzosn.pay.payoneer.api.PayoneerPayService; import java.util.Map; /** - * @descrption * @author Actinia - * @email hayesfu@qq.com - * @date 2018-01-19 + * email hayesfu@qq.com + * date 2018-01-19 + * */ public class PayoneerMessageHandler extends BasePayMessageHandler { @@ -22,7 +22,7 @@ public class PayoneerMessageHandler extends BasePayMessageHandler { } @Override - public PayOutMessage handle(PayMessage payMessage, Map context, PayService payService) throws PayErrorException { + public PayOutMessage handle(PayMessage payMessage, Map context, PayService payService) throws PayErrorException { //交易状态 if ("0".equals(payMessage.getPayMessage().get(PayoneerPayService.CODE))) { /////这里进行成功的处理 diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/UnionPayMessageHandler.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/UnionPayMessageHandler.java index 00a25b4bd6cde587bd6c453300597269a0dd216e..6770d23e5b00b64569134c4423239489e279c1f2 100644 --- a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/UnionPayMessageHandler.java +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/UnionPayMessageHandler.java @@ -1,21 +1,21 @@ package com.egzosn.pay.demo.service.handler; -import com.egzosn.pay.common.api.PayService; -import com.egzosn.pay.common.bean.PayMessage; import com.egzosn.pay.common.bean.PayOutMessage; import com.egzosn.pay.common.exception.PayErrorException; +import com.egzosn.pay.union.api.UnionPayService; import com.egzosn.pay.union.bean.SDKConstants; +import com.egzosn.pay.union.bean.UnionPayMessage; import java.util.Map; /** * @author Actinia - * @email hayesfu@qq.com + * email hayesfu@qq.com *

  * create 2017 2017/11/4 0004
  * 
*/ -public class UnionPayMessageHandler extends BasePayMessageHandler { +public class UnionPayMessageHandler extends BasePayMessageHandler { public UnionPayMessageHandler(Integer payId) { @@ -23,7 +23,7 @@ public class UnionPayMessageHandler extends BasePayMessageHandler { } @Override - public PayOutMessage handle(PayMessage payMessage, Map context, PayService payService) throws PayErrorException { + public PayOutMessage handle(UnionPayMessage payMessage, Map context, UnionPayService payService) throws PayErrorException { //交易状态 if (SDKConstants.OK_RESP_CODE.equals(payMessage.getPayMessage().get(SDKConstants.param_respCode))) { /////这里进行成功的处理 diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/WxPayMessageHandler.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/WxPayMessageHandler.java index 0bf21ad73a6782747ea9f4478e3cf4ffddfdb690..b44b0203b1d118b5e4e0ed353d28261cd001e827 100644 --- a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/WxPayMessageHandler.java +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/WxPayMessageHandler.java @@ -1,9 +1,9 @@ package com.egzosn.pay.demo.service.handler; import com.egzosn.pay.common.api.PayService; -import com.egzosn.pay.common.bean.PayMessage; import com.egzosn.pay.common.bean.PayOutMessage; import com.egzosn.pay.common.exception.PayErrorException; +import com.egzosn.pay.wx.bean.WxPayMessage; import java.util.Map; @@ -11,7 +11,7 @@ import java.util.Map; * 微信支付回调处理器 * Created by ZaoSheng on 2016/6/1. */ -public class WxPayMessageHandler extends BasePayMessageHandler { +public class WxPayMessageHandler extends BasePayMessageHandler { @@ -21,7 +21,7 @@ public class WxPayMessageHandler extends BasePayMessageHandler { } @Override - public PayOutMessage handle(PayMessage payMessage, Map context, PayService payService) throws PayErrorException { + public PayOutMessage handle(WxPayMessage payMessage, Map context, PayService payService) throws PayErrorException { //交易状态 if ("SUCCESS".equals(payMessage.getPayMessage().get("result_code"))){ /////这里进行成功的处理 diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/WxV3CombinePayMessageHandler.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/WxV3CombinePayMessageHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..ef6c341d82a4b0edd302d2c7ea817bab12a999fa --- /dev/null +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/WxV3CombinePayMessageHandler.java @@ -0,0 +1,29 @@ +package com.egzosn.pay.demo.service.handler; + +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.fastjson.JSON; +import com.egzosn.pay.common.api.DefaultPayMessageHandler; +import com.egzosn.pay.common.api.PayMessageHandler; +import com.egzosn.pay.common.api.PayService; +import com.egzosn.pay.common.bean.PayOutMessage; +import com.egzosn.pay.common.exception.PayErrorException; +import com.egzosn.pay.wx.v3.bean.sharing.ProfitSharingPayMessage; + +/** + * 微信合单支付回调处理器 + * Created by ZaoSheng on 2016/6/1. + */ +public class WxV3CombinePayMessageHandler implements PayMessageHandler { + + private final Logger LOG = LoggerFactory.getLogger(DefaultPayMessageHandler.class); + + @Override + public PayOutMessage handle(ProfitSharingPayMessage payMessage, Map context, PayService payService) throws PayErrorException { + LOG.info("回调支付消息处理器,回调消息:{}", JSON.toJSONString(payMessage)); + return payService.successPayOutMessage(payMessage); + } +} diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/WxV3PayMessageHandler.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/WxV3PayMessageHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..64e938177c99a90995cfac41b14f8330620d36fe --- /dev/null +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/WxV3PayMessageHandler.java @@ -0,0 +1,29 @@ +package com.egzosn.pay.demo.service.handler; + +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.fastjson.JSON; +import com.egzosn.pay.common.api.DefaultPayMessageHandler; +import com.egzosn.pay.common.api.PayMessageHandler; +import com.egzosn.pay.common.api.PayService; +import com.egzosn.pay.common.bean.PayOutMessage; +import com.egzosn.pay.common.exception.PayErrorException; +import com.egzosn.pay.wx.v3.bean.response.WxPayMessage; + +/** + * 微信支付回调处理器 + * Created by ZaoSheng on 2016/6/1. + */ +public class WxV3PayMessageHandler implements PayMessageHandler { + + private final Logger LOG = LoggerFactory.getLogger(DefaultPayMessageHandler.class); + + @Override + public PayOutMessage handle(WxPayMessage payMessage, Map context, PayService payService) throws PayErrorException { + LOG.info("回调支付消息处理器,回调消息:{}", JSON.toJSONString(payMessage)); + return payService.successPayOutMessage(payMessage); + } +} diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/WxV3ProfitSharingMessageHandler.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/WxV3ProfitSharingMessageHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..ad083cce4d159c92c5b2916dddc66eb125077ab2 --- /dev/null +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/WxV3ProfitSharingMessageHandler.java @@ -0,0 +1,29 @@ +package com.egzosn.pay.demo.service.handler; + +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.fastjson.JSON; +import com.egzosn.pay.common.api.DefaultPayMessageHandler; +import com.egzosn.pay.common.api.PayMessageHandler; +import com.egzosn.pay.common.api.PayService; +import com.egzosn.pay.common.bean.PayOutMessage; +import com.egzosn.pay.common.exception.PayErrorException; +import com.egzosn.pay.wx.v3.bean.sharing.ProfitSharingPayMessage; + +/** + * 微信合单支付回调处理器 + * Created by ZaoSheng on 2016/6/1. + */ +public class WxV3ProfitSharingMessageHandler implements PayMessageHandler { + + private final Logger LOG = LoggerFactory.getLogger(DefaultPayMessageHandler.class); + + @Override + public PayOutMessage handle(ProfitSharingPayMessage payMessage, Map context, PayService payService) throws PayErrorException { + LOG.info("回调支付消息处理器,回调消息:{}", JSON.toJSONString(payMessage)); + return payService.successPayOutMessage(payMessage); + } +} diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/YouDianPayMessageHandler.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/YouDianPayMessageHandler.java index 3270f0d2ad1e308e6879def80443f989939865ed..4737df935326e0666edb2acbae62150f9724f81b 100644 --- a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/YouDianPayMessageHandler.java +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/YouDianPayMessageHandler.java @@ -2,17 +2,17 @@ package com.egzosn.pay.demo.service.handler; import com.alibaba.fastjson.JSON; import com.egzosn.pay.common.api.PayService; -import com.egzosn.pay.common.bean.PayMessage; import com.egzosn.pay.common.bean.PayOutMessage; import com.egzosn.pay.common.exception.PayErrorException; +import com.egzosn.pay.wx.youdian.bean.WxYoudianPayMessage; import java.util.Map; /** * @author Fuzx - * @create 2017 2017/1/24 0024 + * create 2017 2017/1/24 0024 */ -public class YouDianPayMessageHandler extends BasePayMessageHandler { +public class YouDianPayMessageHandler extends BasePayMessageHandler { @@ -22,7 +22,7 @@ public class YouDianPayMessageHandler extends BasePayMessageHandler { } @Override - public PayOutMessage handle(PayMessage payMessage, Map context, PayService payService) throws PayErrorException { + public PayOutMessage handle(WxYoudianPayMessage payMessage, Map context, PayService payService) throws PayErrorException { //交易状态 Map message = payMessage.getPayMessage(); //上下文对象中获取账单 diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/interceptor/AliPayMessageInterceptor.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/interceptor/AliPayMessageInterceptor.java index 3308ad640dea88d55750529e2e3265c92e83b21a..849dec19ad6b37b1f9b7f7f9990d9c99bee39fd2 100644 --- a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/interceptor/AliPayMessageInterceptor.java +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/interceptor/AliPayMessageInterceptor.java @@ -1,6 +1,8 @@ package com.egzosn.pay.demo.service.interceptor; +import com.egzosn.pay.ali.api.AliPayService; +import com.egzosn.pay.ali.bean.AliPayMessage; import com.egzosn.pay.common.api.PayMessageHandler; import com.egzosn.pay.common.api.PayMessageInterceptor; import com.egzosn.pay.common.api.PayService; @@ -11,23 +13,24 @@ import java.util.Map; /** * 支付宝回调信息拦截器 - * @author: egan + * @author egan * email egzosn@gmail.com * date 2017/1/18 19:28 */ -public class AliPayMessageInterceptor implements PayMessageInterceptor { +public class AliPayMessageInterceptor implements PayMessageInterceptor { /** * 拦截支付消息 * * @param payMessage 支付回调消息 * @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 - * @param payService + * @param payService 支付服务 * @return true代表OK,false代表不OK并直接中断对应的支付处理器 * @see PayMessageHandler 支付处理器 + * @throws PayErrorException PayErrorException* */ @Override - public boolean intercept(PayMessage payMessage, Map context, PayService payService) throws PayErrorException { + public boolean intercept(AliPayMessage payMessage, Map context, AliPayService payService) throws PayErrorException { //这里进行拦截器处理,自行实现 String outTradeNo = payMessage.getOutTradeNo(); diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/interceptor/YoudianPayMessageInterceptor.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/interceptor/YoudianPayMessageInterceptor.java index 0387ecf2c944fef2736aa34597443fe0b1533cca..02b4e05f79f11ee8682ab02463fc74c3ba2e50ac 100644 --- a/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/interceptor/YoudianPayMessageInterceptor.java +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/interceptor/YoudianPayMessageInterceptor.java @@ -6,16 +6,17 @@ import com.egzosn.pay.common.api.PayMessageInterceptor; import com.egzosn.pay.common.api.PayService; import com.egzosn.pay.common.bean.PayMessage; import com.egzosn.pay.common.exception.PayErrorException; +import com.egzosn.pay.wx.youdian.bean.WxYoudianPayMessage; import java.util.Map; /** * 回调信息拦截器 - * @author: egan + * @author egan * email egzosn@gmail.com * date 2017/1/18 19:28 */ -public class YoudianPayMessageInterceptor implements PayMessageInterceptor { +public class YoudianPayMessageInterceptor implements PayMessageInterceptor { // @Autowired // private AmtApplyService amtApplyService; @@ -25,12 +26,13 @@ public class YoudianPayMessageInterceptor implements PayMessageInterceptor { * * @param payMessage 支付回调消息 * @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 - * @param payService + * @param payService 支付服务 * @return true代表OK,false代表不OK并直接中断对应的支付处理器 * @see PayMessageHandler 支付处理器 + * @throws PayErrorException PayErrorException */ @Override - public boolean intercept(PayMessage payMessage, Map context, PayService payService) throws PayErrorException { + public boolean intercept(WxYoudianPayMessage payMessage, Map context, PayService payService) throws PayErrorException { //这里进行拦截器处理,自行实现 diff --git a/pay-java-demo/src/main/resources/ali/alipayCertPublicKey_RSA2.crt b/pay-java-demo/src/main/resources/ali/alipayCertPublicKey_RSA2.crt new file mode 100644 index 0000000000000000000000000000000000000000..75b39f244442b00a6580afa884fbc9e8a855cda8 --- /dev/null +++ b/pay-java-demo/src/main/resources/ali/alipayCertPublicKey_RSA2.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIQIBkQFQ/dyVOPUjiooDnPNjANBgkqhkiG9w0BAQsFADCBkTELMAkGA1UE +BhMCQ04xGzAZBgNVBAoMEkFudCBGaW5hbmNpYWwgdGVzdDElMCMGA1UECwwcQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkgdGVzdDE+MDwGA1UEAww1QW50IEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eSBDbGFzcyAyIFIxIHRlc3QwHhcNMTkxMDE1MTMzMjEzWhcNMjIxMDEzMTMzMjEzWjB6 +MQswCQYDVQQGEwJDTjEVMBMGA1UECgwM5rKZ566x546v5aKDMQ8wDQYDVQQLDAZBbGlwYXkxQzBB +BgNVBAMMOuaUr+S7mOWunSjkuK3lm70p572R57uc5oqA5pyv5pyJ6ZmQ5YWs5Y+4LTIwODgxMDIx +Njk5MTY0MzYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0iWE/WO7qDt8whynlxH6/ +tuTtFQj6KtHcdSbR6ff0r9ScYHIC6UYUtlT3/8jAi7fEJN8aREdZXy/2IwmE2bFapvHB6FuVMKXh +/i4QPP2cQJzVDM56wY/HqcyxsDHz7eP9nanU4KT4azOz+dQESNVj+Xw05CgTmQQ3JaXRYFMU8841 +abxXTZg7x03HS7b16CHR2TXs55L6LkbQX45jvFad+NKs6o9gC2ij1xwRBQhuGJbPlfdOqaTOM0vc +miOy+vKYvKb3vPQI5h+XVGU/y2piE0oszQWZWd1F9w70eBPHWY7i2PaGwNk38CcBve9QtoyURLEi +D6LrJQGkQ6LCP7MtAgMBAAGjEjAQMA4GA1UdDwEB/wQEAwIE8DANBgkqhkiG9w0BAQsFAAOCAQEA +j6xIDkX/GbcQoeMTrJIP2sI4AsStxwG5QjYh021hA74agx0XGQyWrRhXtKpIyEfNxwSZaWgoJqZ3 +uBGTMfiVxjoI1xvHrv5kLQAU8lAt3thdwMYkwP56u51jT15V56OzwvWycGplEwGBw/ln6nc6lwcA +FTPnBR82Wv0J3+B6iSyw/bqXAiWM9ftIv9ex10YIjBRGC0kK0OFS2h1Cq/lEuFSW59cbo5h901Jx +yPVVa6qPYdkLatI/Cxexan+oKJG1BpUoRsKx95wPPV/jxtjP/b8i4qvq71V/h35LaX0uMkgVMJj0 +7SFBl8ciLbSrWZfmMt/sXQszNIhXU/hZwslNQA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIE4jCCAsqgAwIBAgIIddq/0OOwJzIwDQYJKoZIhvcNAQELBQAwejELMAkGA1UEBhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNVBAsMF0NlcnRpZmljYXRpb24gQXV0aG9yaXR5MTEwLwYDVQQDDChBbnQgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFIxMB4XDTE4MDMyMjE0MzAzMVoXDTM3MTEyNjE0MzAzMVowgYIxCzAJBgNVBAYTAkNOMRYwFAYDVQQKDA1BbnQgRmluYW5jaWFsMSAwHgYDVQQLDBdDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTE5MDcGA1UEAwwwQW50IEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBDbGFzcyAxIFIxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3OruCD7d4evJiEKxzJQYmwp5ziF7lT4fMpBb+WLme42Ulrkh4cJCDEOTUL29Yb8NyQ6cRe9UHLupI2HbByDZoSJl7nkxyi5NGJLaADC7wnFBJq39WMaVBzouFo0yQkkYNbbkJm+MsV4obu3l2xFGQx72bz6ThDJLpfYJbnGXqC4Bcyn8ubj1ddrJ0VsGdj/3Knmuo7XWLYqqN/qomK3LJIpfhVozi0b2FWQl+lE9urch+FVhXSg0AlRGn8FTOVNlKrY+hAKZGZhqC+J+BD4GL3hQZzVeNl0tMmSGz474lnt7DExNq33WfyJkn5UIoCfg8Tno7XTnocmBzbNYPq1aSQIDAQABo2MwYTAfBgNVHSMEGDAWgBRfdLQEwE8HWurlsdsio4dBspzhATAdBgNVHQ4EFgQUcQfiBGEW5OXyZesxD8ng9Dya1ZEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAGkc6bDB4YQCa5D6IbgRtLfiqTQb5DeCp38uVKKrUl8ZCE5U+wXSu/fFhOkIs+Aq9tEOdPWgi7TAxPMMbfJRTAF20qK1qF/X6lwRYGSi7LBbEhmI7dYFKj7i7z6fupBMVaI4C4O8KJGfovp3qD/FkXyb13Lo8vL85Ll0BFk89Qbim0qYhW2JRsf9G46vBeIZaoMm8iMv9vvlVgMs30R96W+gZBgvlIE4ah+oOEUd+G/V74vTbaXtWI8gkmwCzs/yUGW2g2ERHqZ3ksq4xwL+mNmqRNzq3aC9iA4p0uoHp/els89vWHCUaPjHmEnhx+M843/WVjN8LWpoeQ+wc7Wz1jfYy0e+JXidqWkPn7qorlEQTfzcFBZh+YHnV6oVtcG5iYatRKVTAPA+RrjJnEEzn6hAIPsiYsLmdA18f6ruuUUuKRukAEbCQ9q9L1gyOkaz2LxZj5kOFyemDa3pjqESuHuazztnOvs6u4YrH03CPyK3G/6MhCNEJTxGDYy+8bRtNsTGRUbdmhZm/u8tjYIreNEy55f4WYlb72R6PODBLXmf4HWWPpyX1Zy9TEhmFsuPfelhBrdmBVM1iTwVFLW7gLqoEwzYhMt5KRPjmfCc2P2pcbpLnYNcbSYiykFsCa7jHG0137Jv8Z/QH2N9r4+xdER7SW40ndmD59ynmGvrWUMj +-----END CERTIFICATE----- diff --git a/pay-java-demo/src/main/resources/ali/alipayRootCert.crt b/pay-java-demo/src/main/resources/ali/alipayRootCert.crt new file mode 100644 index 0000000000000000000000000000000000000000..76417c538f5305efe694e9b7ae4dd978045be20f --- /dev/null +++ b/pay-java-demo/src/main/resources/ali/alipayRootCert.crt @@ -0,0 +1,88 @@ +-----BEGIN CERTIFICATE----- +MIIBszCCAVegAwIBAgIIaeL+wBcKxnswDAYIKoEcz1UBg3UFADAuMQswCQYDVQQG +EwJDTjEOMAwGA1UECgwFTlJDQUMxDzANBgNVBAMMBlJPT1RDQTAeFw0xMjA3MTQw +MzExNTlaFw00MjA3MDcwMzExNTlaMC4xCzAJBgNVBAYTAkNOMQ4wDAYDVQQKDAVO +UkNBQzEPMA0GA1UEAwwGUk9PVENBMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE +MPCca6pmgcchsTf2UnBeL9rtp4nw+itk1Kzrmbnqo05lUwkwlWK+4OIrtFdAqnRT +V7Q9v1htkv42TsIutzd126NdMFswHwYDVR0jBBgwFoAUTDKxl9kzG8SmBcHG5Yti +W/CXdlgwDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFEwysZfZ +MxvEpgXBxuWLYlvwl3ZYMAwGCCqBHM9VAYN1BQADSAAwRQIgG1bSLeOXp3oB8H7b +53W+CKOPl2PknmWEq/lMhtn25HkCIQDaHDgWxWFtnCrBjH16/W3Ezn7/U/Vjo5xI +pDoiVhsLwg== +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIF0zCCA7ugAwIBAgIIH8+hjWpIDREwDQYJKoZIhvcNAQELBQAwejELMAkGA1UE +BhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNVBAsMF0NlcnRpZmlj +YXRpb24gQXV0aG9yaXR5MTEwLwYDVQQDDChBbnQgRmluYW5jaWFsIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5IFIxMB4XDTE4MDMyMTEzNDg0MFoXDTM4MDIyODEzNDg0 +MFowejELMAkGA1UEBhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNV +BAsMF0NlcnRpZmljYXRpb24gQXV0aG9yaXR5MTEwLwYDVQQDDChBbnQgRmluYW5j +aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFIxMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEAtytTRcBNuur5h8xuxnlKJetT65cHGemGi8oD+beHFPTk +rUTlFt9Xn7fAVGo6QSsPb9uGLpUFGEdGmbsQ2q9cV4P89qkH04VzIPwT7AywJdt2 +xAvMs+MgHFJzOYfL1QkdOOVO7NwKxH8IvlQgFabWomWk2Ei9WfUyxFjVO1LVh0Bp +dRBeWLMkdudx0tl3+21t1apnReFNQ5nfX29xeSxIhesaMHDZFViO/DXDNW2BcTs6 +vSWKyJ4YIIIzStumD8K1xMsoaZBMDxg4itjWFaKRgNuPiIn4kjDY3kC66Sl/6yTl +YUz8AybbEsICZzssdZh7jcNb1VRfk79lgAprm/Ktl+mgrU1gaMGP1OE25JCbqli1 +Pbw/BpPynyP9+XulE+2mxFwTYhKAwpDIDKuYsFUXuo8t261pCovI1CXFzAQM2w7H +DtA2nOXSW6q0jGDJ5+WauH+K8ZSvA6x4sFo4u0KNCx0ROTBpLif6GTngqo3sj+98 +SZiMNLFMQoQkjkdN5Q5g9N6CFZPVZ6QpO0JcIc7S1le/g9z5iBKnifrKxy0TQjtG +PsDwc8ubPnRm/F82RReCoyNyx63indpgFfhN7+KxUIQ9cOwwTvemmor0A+ZQamRe +9LMuiEfEaWUDK+6O0Gl8lO571uI5onYdN1VIgOmwFbe+D8TcuzVjIZ/zvHrAGUcC +AwEAAaNdMFswCwYDVR0PBAQDAgEGMAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFF90 +tATATwda6uWx2yKjh0GynOEBMB8GA1UdIwQYMBaAFF90tATATwda6uWx2yKjh0Gy +nOEBMA0GCSqGSIb3DQEBCwUAA4ICAQCVYaOtqOLIpsrEikE5lb+UARNSFJg6tpkf +tJ2U8QF/DejemEHx5IClQu6ajxjtu0Aie4/3UnIXop8nH/Q57l+Wyt9T7N2WPiNq +JSlYKYbJpPF8LXbuKYG3BTFTdOVFIeRe2NUyYh/xs6bXGr4WKTXb3qBmzR02FSy3 +IODQw5Q6zpXj8prYqFHYsOvGCEc1CwJaSaYwRhTkFedJUxiyhyB5GQwoFfExCVHW +05ZFCAVYFldCJvUzfzrWubN6wX0DD2dwultgmldOn/W/n8at52mpPNvIdbZb2F41 +T0YZeoWnCJrYXjq/32oc1cmifIHqySnyMnavi75DxPCdZsCOpSAT4j4lAQRGsfgI +kkLPGQieMfNNkMCKh7qjwdXAVtdqhf0RVtFILH3OyEodlk1HYXqX5iE5wlaKzDop +PKwf2Q3BErq1xChYGGVS+dEvyXc/2nIBlt7uLWKp4XFjqekKbaGaLJdjYP5b2s7N +1dM0MXQ/f8XoXKBkJNzEiM3hfsU6DOREgMc1DIsFKxfuMwX3EkVQM1If8ghb6x5Y +jXayv+NLbidOSzk4vl5QwngO/JYFMkoc6i9LNwEaEtR9PhnrdubxmrtM+RjfBm02 +77q3dSWFESFQ4QxYWew4pHE0DpWbWy/iMIKQ6UZ5RLvB8GEcgt8ON7BBJeMc+Dyi +kT9qhqn+lw== +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIICiDCCAgygAwIBAgIIQX76UsB/30owDAYIKoZIzj0EAwMFADB6MQswCQYDVQQG +EwJDTjEWMBQGA1UECgwNQW50IEZpbmFuY2lhbDEgMB4GA1UECwwXQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkxMTAvBgNVBAMMKEFudCBGaW5hbmNpYWwgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkgRTEwHhcNMTkwNDI4MTYyMDQ0WhcNNDkwNDIwMTYyMDQ0 +WjB6MQswCQYDVQQGEwJDTjEWMBQGA1UECgwNQW50IEZpbmFuY2lhbDEgMB4GA1UE +CwwXQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxMTAvBgNVBAMMKEFudCBGaW5hbmNp +YWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRTEwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAASCCRa94QI0vR5Up9Yr9HEupz6hSoyjySYqo7v837KnmjveUIUNiuC9pWAU +WP3jwLX3HkzeiNdeg22a0IZPoSUCpasufiLAnfXh6NInLiWBrjLJXDSGaY7vaokt +rpZvAdmjXTBbMAsGA1UdDwQEAwIBBjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBRZ +4ZTgDpksHL2qcpkFkxD2zVd16TAfBgNVHSMEGDAWgBRZ4ZTgDpksHL2qcpkFkxD2 +zVd16TAMBggqhkjOPQQDAwUAA2gAMGUCMQD4IoqT2hTUn0jt7oXLdMJ8q4vLp6sg +wHfPiOr9gxreb+e6Oidwd2LDnC4OUqCWiF8CMAzwKs4SnDJYcMLf2vpkbuVE4dTH +Rglz+HGcTLWsFs4KxLsq7MuU+vJTBUeDJeDjdA== +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIUEMdk6dVgOEIS2cCP0Q43P90Ps5YwDQYJKoZIhvcNAQEF +BQAwajELMAkGA1UEBhMCQ04xEzARBgNVBAoMCmlUcnVzQ2hpbmExHDAaBgNVBAsM +E0NoaW5hIFRydXN0IE5ldHdvcmsxKDAmBgNVBAMMH2lUcnVzQ2hpbmEgQ2xhc3Mg +MiBSb290IENBIC0gRzMwHhcNMTMwNDE4MDkzNjU2WhcNMzMwNDE4MDkzNjU2WjBq +MQswCQYDVQQGEwJDTjETMBEGA1UECgwKaVRydXNDaGluYTEcMBoGA1UECwwTQ2hp +bmEgVHJ1c3QgTmV0d29yazEoMCYGA1UEAwwfaVRydXNDaGluYSBDbGFzcyAyIFJv +b3QgQ0EgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOPPShpV +nJbMqqCw6Bz1kehnoPst9pkr0V9idOwU2oyS47/HjJXk9Rd5a9xfwkPO88trUpz5 +4GmmwspDXjVFu9L0eFaRuH3KMha1Ak01citbF7cQLJlS7XI+tpkTGHEY5pt3EsQg +wykfZl/A1jrnSkspMS997r2Gim54cwz+mTMgDRhZsKK/lbOeBPpWtcFizjXYCqhw +WktvQfZBYi6o4sHCshnOswi4yV1p+LuFcQ2ciYdWvULh1eZhLxHbGXyznYHi0dGN +z+I9H8aXxqAQfHVhbdHNzi77hCxFjOy+hHrGsyzjrd2swVQ2iUWP8BfEQqGLqM1g +KgWKYfcTGdbPB1MCAwEAAaNjMGEwHQYDVR0OBBYEFG/oAMxTVe7y0+408CTAK8hA +uTyRMB8GA1UdIwQYMBaAFG/oAMxTVe7y0+408CTAK8hAuTyRMA8GA1UdEwEB/wQF +MAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBLnUTfW7hp +emMbuUGCk7RBswzOT83bDM6824EkUnf+X0iKS95SUNGeeSWK2o/3ALJo5hi7GZr3 +U8eLaWAcYizfO99UXMRBPw5PRR+gXGEronGUugLpxsjuynoLQu8GQAeysSXKbN1I +UugDo9u8igJORYA+5ms0s5sCUySqbQ2R5z/GoceyI9LdxIVa1RjVX8pYOj8JFwtn +DJN3ftSFvNMYwRuILKuqUYSHc2GPYiHVflDh5nDymCMOQFcFG3WsEuB+EYQPFgIU +1DHmdZcz7Llx8UOZXX2JupWCYzK1XhJb+r4hK5ncf/w8qGtYlmyJpxk3hr1TfUJX +Yf4Zr0fJsGuv +-----END CERTIFICATE----- \ No newline at end of file diff --git a/pay-java-demo/src/main/resources/ali/appCertPublicKey_2016080400165436.crt b/pay-java-demo/src/main/resources/ali/appCertPublicKey_2016080400165436.crt new file mode 100644 index 0000000000000000000000000000000000000000..e5107d3c7b29998950c619c89fccb7e2d938061b --- /dev/null +++ b/pay-java-demo/src/main/resources/ali/appCertPublicKey_2016080400165436.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDjzCCAnegAwIBAgIQICARMFLSkg9etOjTIGp0hjANBgkqhkiG9w0BAQsFADCBkTELMAkGA1UE +BhMCQ04xGzAZBgNVBAoMEkFudCBGaW5hbmNpYWwgdGVzdDElMCMGA1UECwwcQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkgdGVzdDE+MDwGA1UEAww1QW50IEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eSBDbGFzcyAyIFIxIHRlc3QwHhcNMjAxMTMwMDMwMzAyWhcNMjMxMTI5MDMwMzAyWjBh +MQswCQYDVQQGEwJDTjEVMBMGA1UECgwM5rKZ566x546v5aKDMQ8wDQYDVQQLDAZBbGlwYXkxKjAo +BgNVBAMMITIwODgxMDIxNjk5MTY0MzYtMjAxNjA4MDQwMDE2NTQzNjCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBALDswPYLC/+OdeywWNecbHcmUYXnuAlrZphslFXwyc2I9Wlfgdr7xmOd +AOG+StYfiEebNy6M6vbXceqWgylRJgHMI+JcZVFRS92KZ6NwcdOh3ucjiDqfO3PN97L9Nc0Meanu +9jek4hyMHRmfSmQ0DPa0oAWSpvitoc6pMHRADTMPbgKf7bonZQnJhFyB4fQa1JC5SubvJoay4pqS +H6q2BYXpHvWfk8wY5NDksfeLRZUd8IGQ0aAkLrIL3odHgPtyiyjIoPT6WnxQH81VRaXtO38os6AV +TCHQYpJgV+/no79UFXb7GoIYTg+VkRJ9W46rPm+OPHPDfEMBQmtXyFJf8AkCAwEAAaMSMBAwDgYD +VR0PAQH/BAQDAgTwMA0GCSqGSIb3DQEBCwUAA4IBAQChVg6GZf8LcjlMlIt7RhQZjbycGlRB5pp2 +bckojulTEoHbmjvjL9LRJAtjuHQ1Teuy3meK6iT/J+O8PWXEigLtX+K+5Xne3JpRcztAciRmWhLe +dAEvh1aPPmiG11ZDPC2dDOtTQVIleLpbFtYkqn9IVsU2n+2lmM7aMbw1969wADE4mov2X3m6bnuI +CzrPGpWiI0MwLp00e0sNHnHXpzENxtabBqabYvL/AH7QZgAmX0JTxYwx0zW7r1JiAc+42Eh/qwqE +TJa+tOY+Iig5GcFkc6fE7guTNGxz2OhKr2whNq6uRnLbA+Xwx11NhE1K3NHNrZZpSf3QW1//xJoC +zlBJ +-----END CERTIFICATE----- \ No newline at end of file diff --git "a/pay-java-demo/src/main/resources/ali/www.egzosn.com_\347\247\201\351\222\245.txt" "b/pay-java-demo/src/main/resources/ali/www.egzosn.com_\347\247\201\351\222\245.txt" new file mode 100644 index 0000000000000000000000000000000000000000..33cfb633f1e4f5434f7ee8224bfdf91ed9537bb4 --- /dev/null +++ "b/pay-java-demo/src/main/resources/ali/www.egzosn.com_\347\247\201\351\222\245.txt" @@ -0,0 +1 @@ +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCw7MD2Cwv/jnXssFjXnGx3JlGF57gJa2aYbJRV8MnNiPVpX4Ha+8ZjnQDhvkrWH4hHmzcujOr213HqloMpUSYBzCPiXGVRUUvdimejcHHTod7nI4g6nztzzfey/TXNDHmp7vY3pOIcjB0Zn0pkNAz2tKAFkqb4raHOqTB0QA0zD24Cn+26J2UJyYRcgeH0GtSQuUrm7yaGsuKakh+qtgWF6R71n5PMGOTQ5LH3i0WVHfCBkNGgJC6yC96HR4D7cosoyKD0+lp8UB/NVUWl7Tt/KLOgFUwh0GKSYFfv56O/VBV2+xqCGE4PlZESfVuOqz5vjjxzw3xDAUJrV8hSX/AJAgMBAAECggEBAKE0d3U4B4yo/2XUIH8EdgfykCFUSum6RFbpyBauORHfksyaSzV+ZvtomN8XhhSn0oJ8OMFfgM+86nz2+zdwSxMkMCYWTfLUAi4v59KRqAVO3kz4oS3Y3FDeAK3D7XuRvGFL7GgzAhtEx1cLPrsiehVn6s5pG15GxsIIgq/JlL1J88wn1zENLrVHmD6z/JpXvfb/RS1yR+5lyoohp4g0Ph9jJ3bCyUbRpK0QkPEzgAuWL0K2ITCL7PYHNAplI8d2xHHOLF9Qdjyx+ZrQ/RxtqzfyWzhqjsmp2qlgNCxWlt3woS9UhDB+nRvjEoWTJmIOszAMYuj8wGlX+3Ui3ALOdQECgYEA25EqnFPFinUnzgNvB6NYmh5STmZun6s4bUOLqwefKtEvrOtRwTu7sB7NIf37fizG3/MJUWHxiLy2/3ub4d2JxdDNBtJoEqnp6QB12qglCNa4CajdjtJa1dR81F9QvytsqEkmPYXFPPyviB0FcSIDAGMb3IbwvIfzBPY9WY8dJnECgYEAzkg3yKEFBZ8BU0WQ+3hyfKUoAhBEnxouxRSTBcXxwstJRiqaGTVe5aoJGQI+0xS7Z6q07XDtN2t97s6DnRLWbljsX6B64itzNhXRyzjdD3iZDU/KSw7khjhXf8XOZaj9eXmACDiUnkEn1xsM8bLiRGqB8y5f3aMY/RpuACGXnxkCgYEAx/zwT9Vpr1RIfjfYcJ+Su0X0994K0roUukj0tUJK8qf4gcsQ+y1aJe/YLib1ZBaKyj7G9O5+HmqtUAUZld/AdoJZzOXmz2EeYhD+R7wxh1xz4rCBpW3qOKvDS3jJxmZaIOoHv6/RWFxb0WGFrGcrTrX3EaWDLmWxr4pNlP5qsbECgYATllntrBR8/ycyEAX/SuWcHlaZM5BAh0zvm8+GGdCmDYWMqxjs0duL9URd4o+ynWJaKqR5c2KjA4r2tRdcP+Cqo7j2L5fbiAKtnQ7JvEGJaYsm72+nBuf+MrVkRZUepBhFg5r7rNu31zoAO+pTvQetNWvXeozRz93ckrjlPEtYaQKBgQDFwbV92rlRMLjZzlY+o0knoeJBjPQmPdiBTpGNimdy9L4c2Ure7affjcUiYhkKqrK5k5SScJTATgyQ7JF346FdtUtZ/6Kkj1RwJmmprPrDa9CATLoTle7g9OVd4sHT2ITHZMzPaF3ILvzcwJ70AD1xcxCQb+/7sDPmw7Mc8gOA7Q== \ No newline at end of file diff --git a/pay-java-demo/src/main/resources/applicationContext.xml b/pay-java-demo/src/main/resources/applicationContext.xml index 1bea38a0d006f3178898d056afc7c3ee5a705f74..3352d53fe66d0736e6a7dfeb91aa974183c586a6 100644 --- a/pay-java-demo/src/main/resources/applicationContext.xml +++ b/pay-java-demo/src/main/resources/applicationContext.xml @@ -10,7 +10,9 @@ - + + + diff --git a/pay-java-demo/src/main/resources/apy_account.sql b/pay-java-demo/src/main/resources/apy_account.sql index 2180cf188f930a5daf99806e1ce379fb540f9e57..c248a8e37d5a2273a60052006e055379a94b01ad 100644 --- a/pay-java-demo/src/main/resources/apy_account.sql +++ b/pay-java-demo/src/main/resources/apy_account.sql @@ -6,7 +6,7 @@ DROP TABLE IF EXISTS `pay_account`; CREATE TABLE `pay_account` ( `pay_id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '支付账号id', `partner` VARCHAR(32) DEFAULT NULL COMMENT '支付合作id,商户id,差不多是支付平台的账号或id', - `appid` VARCHAR(32) DEFAULT NULL COMMENT '应用id', + `app_Id` VARCHAR(32) DEFAULT NULL COMMENT '应用id', `public_key` VARCHAR(1204) DEFAULT NULL COMMENT '支付平台公钥(签名校验使用),sign_type只有单一key时public_key与private_key相等,比如sign_type=MD5(友店支付除外)的情况', `private_key` VARCHAR(2048) DEFAULT NULL COMMENT '应用私钥(生成签名)', `notify_url` VARCHAR(1024) DEFAULT NULL COMMENT '异步回调地址', diff --git a/pay-java-demo/src/main/resources/log4j.properties b/pay-java-demo/src/main/resources/log4j.properties new file mode 100644 index 0000000000000000000000000000000000000000..5eb158dd79f71d050d2bd61aa21259fabab22ae5 --- /dev/null +++ b/pay-java-demo/src/main/resources/log4j.properties @@ -0,0 +1,10 @@ +log4j.rootLogger=debug,A1 +log4j.appender.A1=org.apache.log4j.ConsoleAppender +log4j.appender.A1.layout=org.apache.log4j.PatternLayout +log4j.appender.A1.layout.ConversionPattern=%d %5p [%t] (%F:%L) - %m%n + + +log4j.logger.com.egzosn.pay=DEBUG +log4j.logger.org.springframework=INFO +log4j.logger.edu.yale=INFO + diff --git a/pay-java-demo/src/main/webapp/gzh.png b/pay-java-demo/src/main/webapp/gzh.png new file mode 100644 index 0000000000000000000000000000000000000000..e607cb9ba904d1ac409680f6d8e7c282a0607ff8 Binary files /dev/null and b/pay-java-demo/src/main/webapp/gzh.png differ diff --git a/pay-java-demo/src/main/webapp/index.html b/pay-java-demo/src/main/webapp/index.html index 09dc853c4be94bc88e7b7041dea2a5d07cedf295..6aed16013a2be6a941ab1b633349351d3d2c654c 100644 --- a/pay-java-demo/src/main/webapp/index.html +++ b/pay-java-demo/src/main/webapp/index.html @@ -69,13 +69,12 @@
-
各个支付对应的交易类型可自行查看对应的官方文档,本项目已实现几种交易类型,对应各个支付类型的com.egzosn.pay.common.bean.TransactionType具体实现
-
旧版支付宝(com.egzosn.pay.ali.before.bean.AliTransactionType): 即时付款=DIRECT , 移动支付=APP , 手机网站支付=WAP
-
新版支付宝(com.egzosn.pay.ali.bean.AliTransactionType): 即时付款=DIRECT , app支付=APP , 手机网站支付=WAP , 扫码付=SWEEPPAY, 条码付=BAR_CODE, 声波付=WAVE_CODE
+
新版支付宝(com.egzosn.pay.ali.bean.AliTransactionType): 即时付款=PAGE , app支付=APP , 手机网站支付=WAP , 扫码付=SWEEPPAY, 条码付=BAR_CODE, 声波付=WAVE_CODE
微信(com.egzosn.pay.wx.bean.WxTransactionType): 公众号支付=JSAPI , 移动支付=APP , 扫码付=NATIVE
银联(com.egzosn.pay.union.bean.UnionTransactionType):苹果支付=APPLE,手机控件=APP,WAP支付=WAP,网关支付=WEB,无跳转支付=NO_JUMP,B2B支付=B2B,申码(主扫场景)=APPLY_QR_CODE,消费(被扫场景)=CONSUME
友店微信(com.egzosn.pay.wx.youdian.bean.YoudianTransactionType): 扫码付=NATIVE
富友(com.egzosn.pay.fuiou.bean.FuiouTransactionType): B2B,B2C
+
详情请查看 com.egzosn.pay.common.bean.TransactionType对应的子类


diff --git a/pay-java-demo/src/main/webapp/wx.jpg b/pay-java-demo/src/main/webapp/wx.jpg new file mode 100644 index 0000000000000000000000000000000000000000..35c20dea9618701db1dc3ec85477e9264d510aee Binary files /dev/null and b/pay-java-demo/src/main/webapp/wx.jpg differ diff --git a/pay-java-demo/src/main/webapp/xcx.png b/pay-java-demo/src/main/webapp/xcx.png new file mode 100644 index 0000000000000000000000000000000000000000..2d0024c60c63198c1cea4d8fb86b2f25e694e2f2 Binary files /dev/null and b/pay-java-demo/src/main/webapp/xcx.png differ diff --git a/pay-java-fuiou/README.md b/pay-java-fuiou/README.md index deb86df185ee0879d2587e7e9eb7702f1b2b8d6b..ea87ca4b34defb52e4e66b2e8ad6adf1b594e2b8 100644 --- a/pay-java-fuiou/README.md +++ b/pay-java-fuiou/README.md @@ -51,7 +51,7 @@ ```java //支付订单基础信息 - PayOrder payOrder = new PayOrder("订单title", "摘要", new BigDecimal(0.01) , UUID.randomUUID().toString().replace("-", "").substring(2)); + PayOrder payOrder = new PayOrder("订单title", "摘要", BigDecimal.valueOf(0.01) , UUID.randomUUID().toString().replace("-", "").substring(2)); ``` diff --git a/pay-java-fuiou/pom.xml b/pay-java-fuiou/pom.xml index e46c5aebf75b57563a99a55e9e12a10ba25a907d..59192fead7cf21f669ac111b7c9116996ec50510 100644 --- a/pay-java-fuiou/pom.xml +++ b/pay-java-fuiou/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.12.6-SNAPSHOT + 2.14.7 4.0.0 pay-java-fuiou diff --git a/pay-java-fuiou/src/main/java/com/egzosn/pay/fuiou/api/FuiouPayConfigStorage.java b/pay-java-fuiou/src/main/java/com/egzosn/pay/fuiou/api/FuiouPayConfigStorage.java index 480ee7d104b284b644c922b6e59000d236485c91..af8f37ca16e7435fcb6768c431a64f39176a0ad2 100644 --- a/pay-java-fuiou/src/main/java/com/egzosn/pay/fuiou/api/FuiouPayConfigStorage.java +++ b/pay-java-fuiou/src/main/java/com/egzosn/pay/fuiou/api/FuiouPayConfigStorage.java @@ -1,19 +1,23 @@ package com.egzosn.pay.fuiou.api; + import com.egzosn.pay.common.api.BasePayConfigStorage; /** * @author Actinia - *
+ * 
  * email hayesfu@qq.com
  * create 2017 2017/1/16 0016
  * 
*/ public class FuiouPayConfigStorage extends BasePayConfigStorage { - - public String mchntCd;//商户代码 + /** + * 商户代码 + */ + private String mchntCd; /** - * 应用id + * 应用id + * * @return 空 */ @Override @@ -21,21 +25,31 @@ public class FuiouPayConfigStorage extends BasePayConfigStorage { return null; } + /** + * 应用id + * 纠正名称 + * + * @return 应用id + */ + @Override + public String getAppId() { + return null; + } + /** * 合作商唯一标识 - * */ @Override - public String getPid () { + public String getPid() { return mchntCd; } - public String getMchntCd () { + public String getMchntCd() { return mchntCd; } - public void setMchntCd (String mchntCd) { + public void setMchntCd(String mchntCd) { this.mchntCd = mchntCd; } diff --git a/pay-java-fuiou/src/main/java/com/egzosn/pay/fuiou/api/FuiouPayService.java b/pay-java-fuiou/src/main/java/com/egzosn/pay/fuiou/api/FuiouPayService.java index a04c6cd6c2fa58226acadbe7a030332072908f6a..e942664aca2daf7f1e23fa26af2c0235b28b0ac5 100644 --- a/pay-java-fuiou/src/main/java/com/egzosn/pay/fuiou/api/FuiouPayService.java +++ b/pay-java-fuiou/src/main/java/com/egzosn/pay/fuiou/api/FuiouPayService.java @@ -1,23 +1,40 @@ package com.egzosn.pay.fuiou.api; + +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.common.api.BasePayService; -import com.egzosn.pay.common.bean.*; +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.common.bean.BillType; +import com.egzosn.pay.common.bean.MethodType; +import com.egzosn.pay.common.bean.NoticeParams; +import com.egzosn.pay.common.bean.NoticeRequest; +import com.egzosn.pay.common.bean.OrderParaStructure; +import com.egzosn.pay.common.bean.PayMessage; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.PayOutMessage; +import com.egzosn.pay.common.bean.RefundOrder; +import com.egzosn.pay.common.bean.RefundResult; +import com.egzosn.pay.common.bean.TransactionType; import com.egzosn.pay.common.exception.PayErrorException; import com.egzosn.pay.common.http.HttpConfigStorage; import com.egzosn.pay.common.http.UriVariables; import com.egzosn.pay.common.util.DateUtils; import com.egzosn.pay.common.util.Util; +import com.egzosn.pay.common.util.sign.SignTextUtils; import com.egzosn.pay.common.util.sign.SignUtils; import com.egzosn.pay.common.util.str.StringUtils; -import java.awt.image.BufferedImage; -import java.io.InputStream; -import java.math.BigDecimal; -import java.util.*; +import com.egzosn.pay.fuiou.bean.FuiouRefundResult; +import com.egzosn.pay.fuiou.bean.FuiouTransactionType; /** * @author Actinia - *
- *email hayesfu@qq.com
+ * 
+ * email hayesfu@qq.com
  * create 2017 2017/1/16 0016
  * 
*/ @@ -26,16 +43,16 @@ public class FuiouPayService extends BasePayService { /** * 正式域名 */ - public static final String URL_FuiouBaseDomain = "https://pay.fuiou.com/"; + public static final String URL_FUIOU_BASE_DOMAIN = "https://pay.fuiou.com/"; /** * 测试域名 */ - public static final String DEV_URL_FUIOUBASEDOMAIN = "http://www-1.fuiou.com:8888/wg1_run/"; + public static final String DEV_URL_FUIOU_BASE_DOMAIN = "http://www-1.fuiou.com:8888/wg1_run/"; /** * B2C/B2B支付 */ - public static final String URL_FuiouSmpGate = "smpGate.do"; + public static final String URL_FuiouSmpGate = "smpGate.do"; /** * B2C/B2B支付(跨境支付) */ @@ -56,40 +73,73 @@ public class FuiouPayService extends BasePayService { * 3.4订单退款 */ public static final String URL_NewSmpRefundGate = "newSmpRefundGate.do"; + /** + * 异步通知 + */ + public static final String BACK_NOTIFY_URL = "back_notify_url"; + + /** + * 获取对应的请求地址 + * + * @return 请求地址 + */ + @Override + public String getReqUrl(TransactionType transactionType) { + return payConfigStorage.isTest() ? DEV_URL_FUIOU_BASE_DOMAIN : URL_FUIOU_BASE_DOMAIN; + } /** * 获取对应的请求地址 + * * @return 请求地址 */ - public String getReqUrl(){ - return payConfigStorage.isTest() ? DEV_URL_FUIOUBASEDOMAIN : URL_FuiouBaseDomain; + public String getReqUrl() { + return getReqUrl(null); } /** * 构造函数,初始化时候使用 + * * @param payConfigStorage 支付账户配置信息 - * @param configStorage 网络代理配置 + * @param configStorage 网络代理配置 */ - public FuiouPayService (FuiouPayConfigStorage payConfigStorage, HttpConfigStorage configStorage) { + public FuiouPayService(FuiouPayConfigStorage payConfigStorage, HttpConfigStorage configStorage) { super(payConfigStorage, configStorage); } + /** * 构造函数,初始化时候使用 + * * @param payConfigStorage 支付账户配置信息 */ - public FuiouPayService (FuiouPayConfigStorage payConfigStorage) { + public FuiouPayService(FuiouPayConfigStorage payConfigStorage) { super(payConfigStorage); } /** * 回调校验 + * * @param params 回调回来的参数集 * @return 返回检验结果 0000 成功 其他失败 */ + @Deprecated @Override public boolean verify(Map params) { + + return verify(new NoticeParams(params)); + } + + /** + * 回调校验 + * + * @param noticeParams 回调回来的参数集 + * @return 签名校验 true通过 + */ + @Override + public boolean verify(NoticeParams noticeParams) { + final Map params = noticeParams.getBody(); if (!"0000".equals(params.get("order_pay_code"))) { LOG.debug(String.format("富友支付异常:order_pay_code=%s,错误原因=%s,参数集=%s", params.get("order_pay_code"), params.get("order_pay_error"), params)); return false; @@ -97,8 +147,9 @@ public class FuiouPayService extends BasePayService { try { //返回参数校验 和 重新请求订单检查数据是否合法 return (signVerify(params, (String) params.remove("md5")) && verifySource((String) params.get("order_id"))); - } catch (PayErrorException e) { - e.printStackTrace(); + } + catch (PayErrorException e) { + LOG.error("", e); } return false; } @@ -106,14 +157,13 @@ public class FuiouPayService extends BasePayService { /** * 回调签名校验 * - * @param params 参数集 - * @param responseSign 响应的签名串 + * @param params 参数集 + * @param responseSign 响应的签名串 * @return 校验结果 */ - @Override public boolean signVerify(Map params, String responseSign) { - String sign = createSign(SignUtils.parameters2MD5Str(params, "|"), payConfigStorage.getInputCharset()); + String sign = createSign(SignTextUtils.parameters2Md5Str(params, "|"), payConfigStorage.getInputCharset()); return responseSign.equals(sign); } @@ -121,17 +171,16 @@ public class FuiouPayService extends BasePayService { /** * 校验回调数据来源是否合法 * - * @param order_id 业务id, 数据的真实性. + * @param orderId 业务id, 数据的真实性. * @return 返回校验结果 */ - @Override - public boolean verifySource(String order_id) { - LinkedHashMap params = new LinkedHashMap<>(); + public boolean verifySource(String orderId) { + LinkedHashMap params = new LinkedHashMap(3); params.put("mchnt_cd", payConfigStorage.getPid()); - params.put("order_id", order_id); - params.put("md5", createSign(SignUtils.parameters2MD5Str(params, "|"), payConfigStorage.getInputCharset())); + params.put("order_id", orderId); + params.put("md5", createSign(SignTextUtils.parameters2Md5Str(params, "|"), payConfigStorage.getInputCharset())); JSONObject resultJson = getHttpRequestTemplate().postForObject(getReqUrl() + URL_FuiouSmpAQueryGate + "?" + UriVariables.getMapToParameters(params), null, JSONObject.class); - if (null == resultJson){ + if (null == resultJson) { return false; } return "0000".equals(resultJson.getJSONObject("plain").getString("order_pay_code")); @@ -140,23 +189,35 @@ public class FuiouPayService extends BasePayService { /** * 将支付请求参数加密成md5 + * * @param order 支付订单 * @return 返回支付请求参数集合 */ @Override public Map orderInfo(PayOrder order) { - LinkedHashMap parameters = getOrderInfo(order); - String sign = createSign(SignUtils.parameters2MD5Str(parameters, "|"), payConfigStorage.getInputCharset()); + if (null == order.getTransactionType()) { + order.setTransactionType(FuiouTransactionType.B2C); + } + + Map parameters = getOrderInfo(order); + String sign = createSign(SignTextUtils.parameters2Md5Str(parameters, "|"), payConfigStorage.getInputCharset()); parameters.put("md5", sign); return parameters; } - + private Map initNotifyUrl(Map parameters, AssistOrder order) { + OrderParaStructure.loadParameters(parameters, BACK_NOTIFY_URL, payConfigStorage.getNotifyUrl()); + OrderParaStructure.loadParameters(parameters, BACK_NOTIFY_URL, order.getNotifyUrl()); + OrderParaStructure.loadParameters(parameters, BACK_NOTIFY_URL, order); + return parameters; + } /** * 按序添加请求参数 + * * @param order 支付订单 * @return 返回支付请求参数集合 */ - private LinkedHashMap getOrderInfo(PayOrder order) { + private Map getOrderInfo(PayOrder order) { + LinkedHashMap parameters = new LinkedHashMap(); //商户代码 parameters.put("mchnt_cd", payConfigStorage.getPid()); @@ -171,11 +232,11 @@ public class FuiouPayService extends BasePayService { //商户接受支付结果通知地址 parameters.put("page_notify_url", payConfigStorage.getReturnUrl()); //商户接受的支付结果后台通知地址 //非必填 - parameters.put("back_notify_url", StringUtils.isBlank(payConfigStorage.getNotifyUrl()) ? "" : payConfigStorage.getNotifyUrl()); - - if (null != order.getExpirationTime()){ + initNotifyUrl(parameters, order); + if (null != order.getExpirationTime()) { parameters.put("order_valid_time", DateUtils.minutesRemaining(order.getExpirationTime()) + "m"); - }else { + } + else { //超时时间 1m-15天,m:分钟、h:小时、d天、1c当天有效, parameters.put("order_valid_time", "30m"); } @@ -188,11 +249,13 @@ public class FuiouPayService extends BasePayService { parameters.put("rem", ""); //版本号 parameters.put("ver", "1.0.1"); - return parameters; + parameters.putAll(order.getAttrs()); + return preOrderHandler(parameters, order); } /** * 对内容进行加密 + * * @param content 需要加密的内容 * @param characterEncoding 字符编码 * @return 加密后的字符串 @@ -202,47 +265,51 @@ public class FuiouPayService extends BasePayService { return SignUtils.valueOf(payConfigStorage.getSignType().toUpperCase()).createSign(content, "|" + payConfigStorage.getKeyPrivate(), characterEncoding); } + /** * 将请求参数或者请求流转化为 Map - * @param parameterMap 请求参数 - * @param is 请求流 - * @return 返回参数集合 + * + * @param request 通知请求 + * @return 获得回调的请求参数 */ @Override - public Map getParameter2Map(Map parameterMap, InputStream is) { - Map params = conversion(parameterMap, new LinkedHashMap(), "mchnt_cd"); - conversion(parameterMap, params, "order_id"); - conversion(parameterMap, params, "order_date"); - conversion(parameterMap, params, "order_amt"); - conversion(parameterMap, params, "order_st"); - conversion(parameterMap, params, "order_pay_code"); - conversion(parameterMap, params, "order_pay_error"); - conversion(parameterMap, params, "resv1"); - conversion(parameterMap, params, "fy_ssn"); - conversion(parameterMap, params, "md5"); - return params; + public NoticeParams getNoticeParams(NoticeRequest request) { + Map parameterMap = request.getParameterMap(); + Map params = conversion(parameterMap, new LinkedHashMap(), "mchnt_cd"); + conversion(parameterMap, params, "order_id"); + conversion(parameterMap, params, "order_date"); + conversion(parameterMap, params, "order_amt"); + conversion(parameterMap, params, "order_st"); + conversion(parameterMap, params, "order_pay_code"); + conversion(parameterMap, params, "order_pay_error"); + conversion(parameterMap, params, "resv1"); + conversion(parameterMap, params, "fy_ssn"); + conversion(parameterMap, params, "md5"); + return new NoticeParams(params); } /** - * 将parameterMap对应的key存放至params + * 将parameterMap对应的key存放至params + * * @param parameterMap 请求参数 - * @param params 转化的对象 - * @param key 需要取值的key + * @param params 转化的对象 + * @param key 需要取值的key * @return params */ - public Map conversion(Map parameterMap, Map params ,String key){ + public Map conversion(Map parameterMap, Map params, String key) { String[] values = parameterMap.get(key); String valueStr = ""; - for (int i = 0,len = values.length; i < len; i++) { - valueStr += (i == len - 1) ? values[i] : values[i] + ","; + for (int i = 0, len = values.length; i < len; i++) { + valueStr += (i == len - 1) ? values[i] : values[i] + ","; } params.put(key, valueStr); return params; } /** - * 获取输出消息,用户返回给支付端 - * @param code 返回代码 + * 获取输出消息,用户返回给支付端 + * + * @param code 返回代码 * @param message 返回信息 * @return 消息实体 */ @@ -251,9 +318,11 @@ public class FuiouPayService extends BasePayService { public PayOutMessage getPayOutMessage(String code, String message) { return PayOutMessage.TEXT().content(code.toLowerCase()).build(); } + /** * 获取成功输出消息,用户返回给支付端 * 主要用于拦截器中返回 + * * @param payMessage 支付回调消息 * @return 返回输出消息 */ @@ -264,6 +333,7 @@ public class FuiouPayService extends BasePayService { /** * 发送支付请求(form表单) + * * @param orderInfo 发起支付的订单信息 * @param method 请求方式 "post" "get", * @return form表单提交的html字符串 @@ -271,22 +341,24 @@ public class FuiouPayService extends BasePayService { @Override public String buildRequest(Map orderInfo, MethodType method) { - return getFormString(orderInfo, method,getReqUrl() + URL_FuiouSmpGate ); + return getFormString(orderInfo, method, getReqUrl() + URL_FuiouSmpGate); } /** * 获取输出二维码,用户返回给支付端, * 暂未实现或无此功能 + * * @param order 发起支付的订单信息 * @return 空 */ @Override - public BufferedImage genQrPay (PayOrder order) { + public String getQrPay(PayOrder order) { throw new UnsupportedOperationException(); } /** * 暂未实现或无此功能 + * * @param order 发起支付的订单信息 * @return 不支持的操作异常 */ @@ -297,20 +369,21 @@ public class FuiouPayService extends BasePayService { /** * 根据参数形成form表单 - * @param param 参数 + * + * @param param 参数 * @param method 请求方式 get_post - * @param url 支付请求url地址 + * @param url 支付请求url地址 * @return form表单html代码 */ - private String getFormString(Map param, MethodType method,String url) { + private String getFormString(Map param, MethodType method, String url) { StringBuffer formHtml = new StringBuffer(); formHtml.append(""); - formHtml.append( "提交到富友交易系统"); - formHtml.append( ""); - formHtml.append( ""); - formHtml.append( "
"); + formHtml.append("提交到富友交易系统"); + formHtml.append(""); + formHtml.append(""); + formHtml.append(""); for (Map.Entry entry : param.entrySet()) { Object o = entry.getValue(); @@ -324,6 +397,7 @@ public class FuiouPayService extends BasePayService { /** * 交易查询接口 + * * @param tradeNo 支付平台订单号 * @param outTradeNo 商户单号 * @return 空 @@ -331,55 +405,57 @@ public class FuiouPayService extends BasePayService { @Override public Map query(String tradeNo, String outTradeNo) { + return query(new AssistOrder(tradeNo, outTradeNo)); + } + + /** + * 交易查询接口 + * + * @param assistOrder 查询条件 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(AssistOrder assistOrder) { + LinkedHashMap params = new LinkedHashMap<>(); params.put("mchnt_cd", payConfigStorage.getPid()); - params.put("order_id", outTradeNo); - params.put("md5", createSign(SignUtils.parameters2MD5Str(params, "|"), payConfigStorage.getInputCharset())); - JSONObject resultJson = getHttpRequestTemplate().postForObject(getReqUrl() + URL_FuiouSmpAQueryGate + "?" + UriVariables.getMapToParameters(params), null, JSONObject.class); - return resultJson; + params.put("order_id", assistOrder.getOutTradeNo()); + params.put("md5", createSign(SignTextUtils.parameters2Md5Str(params, "|"), payConfigStorage.getInputCharset())); + return getHttpRequestTemplate().postForObject(getReqUrl() + URL_FuiouSmpAQueryGate + "?" + UriVariables.getMapToParameters(params), null, JSONObject.class); } - /** * 交易关闭接口 + * * @param tradeNo 支付平台订单号 * @param outTradeNo 商户单号 * @return 空 */ @Override public Map close(String tradeNo, String outTradeNo) { - return Collections.EMPTY_MAP; + throw new UnsupportedOperationException("不支持该操作"); } - - - /** - * 申请退款接口 + * 交易关闭接口 * - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * @param refundAmount 退款金额 - * @param totalAmount 总金额 - * @return 退款返回结果集 + * @param assistOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 */ @Override - public Map refund (String tradeNo, String outTradeNo, BigDecimal refundAmount, BigDecimal totalAmount) { - return refund(new RefundOrder(tradeNo, outTradeNo, refundAmount, totalAmount)); + public Map close(AssistOrder assistOrder) { + throw new UnsupportedOperationException("不支持该操作"); } - - - /** * 申请退款接口 * - * @param refundOrder 退款订单信息 + * @param refundOrder 退款订单信息 * @return 退款返回结果集 */ @Override - public Map refund(RefundOrder refundOrder) { + public RefundResult refund(RefundOrder refundOrder) { Map params = new HashMap<>(); //商户代码 params.put("mchnt_cd", payConfigStorage.getPid()); @@ -391,26 +467,13 @@ public class FuiouPayService extends BasePayService { params.put("refund_amt", Util.conversionCentAmount(refundOrder.getRefundAmount())); //备注 params.put("rem", ""); - params.put("md5", createSign(SignUtils.parameters2MD5Str(params, "|"), payConfigStorage.getInputCharset())); + params.putAll(refundOrder.getAttrs()); + params.put("md5", createSign(SignTextUtils.parameters2Md5Str(params, "|"), payConfigStorage.getInputCharset())); JSONObject resultJson = getHttpRequestTemplate().postForObject(getReqUrl() + URL_FuiouSmpRefundGate, params, JSONObject.class); - return resultJson; + return FuiouRefundResult.create(resultJson); } - - /** - * 查询退款 - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * @return 空 - * - */ - - @Override - public Map refundquery(String tradeNo, String outTradeNo) { - return Collections.emptyMap(); - } - /** * 查询退款 * @@ -425,28 +488,15 @@ public class FuiouPayService extends BasePayService { /** * 下载对账单 + * * @param billDate 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 * @param billType 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; * @return 空 */ @Override - public Map downloadbill(Date billDate, String billType) { + public Map downloadBill(Date billDate, BillType billType) { return Collections.emptyMap(); } - /** - * @param tradeNoOrBillDate 支付平台订单号或者账单类型, 具体请 - * 类型为{@link String }或者 {@link Date },类型须强制限制,类型不对应则抛出异常{@link PayErrorException} - * @param outTradeNoBillType 商户单号或者 账单类型 - * @param transactionType 交易类型 - * - * @return 返回支付方对应接口的结果 - */ - @Override - public Map secondaryInterface(Object tradeNoOrBillDate, String outTradeNoBillType, TransactionType transactionType) { - return Collections.emptyMap(); - } - - } diff --git a/pay-java-fuiou/src/main/java/com/egzosn/pay/fuiou/bean/FuiouRefundResult.java b/pay-java-fuiou/src/main/java/com/egzosn/pay/fuiou/bean/FuiouRefundResult.java new file mode 100644 index 0000000000000000000000000000000000000000..e85dfc1d85c7ac0cb50376b1d29ef813317558bd --- /dev/null +++ b/pay-java-fuiou/src/main/java/com/egzosn/pay/fuiou/bean/FuiouRefundResult.java @@ -0,0 +1,166 @@ +package com.egzosn.pay.fuiou.bean; + +import java.math.BigDecimal; +import java.util.Map; + +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.annotation.JSONField; +import com.egzosn.pay.common.bean.BaseRefundResult; +import com.egzosn.pay.common.bean.CurType; + +/** + * 富友退款结果 + * @author Egan、 + *
+ * email egzosn@gmail.com
+ * date 2020/8/16 21:17
+ * 
+ */ +public class FuiouRefundResult extends BaseRefundResult { + + private String originOrderId; + /** + * 退款状态 + * ‘0’ – 已受理 + * ‘1’ – 成功 + * ‘2’ – 失败 + */ + @JSONField(name = "order_st") + private String orderSt; + /** + * 错误代码 + * 5341表示退款成功 + */ + @JSONField(name = "order_pay_code") + private String orderPayCode; + /** + * 错误中文描述 + * 5341表示退款成功 + */ + @JSONField(name = "order_pay_error") + private String orderPayError; + /** + * 富友流水号 + * 供商户查询支付交易状态及对账用 + */ + @JSONField(name = "fy_ssn") + private String fySsn; + /** + * 保留字段 + */ + private String resv1; + /** + * MD5摘要数据 + * mchnt_cd + "|" + order_st + "|" + + * order_pay_code + "|" + + * order_pay_error + "|" + fy_ssn + "|" + + * resv1 + "|" + mchnt_key + * 做MD5摘要 + * 其中mchnt_key 为32位的商户密钥,系统分配 + */ + private String md5; + + /** + * 退款金额 + */ + private BigDecimal refundAmt; + + /** + * 获取退款请求结果状态码 + * + * @return 状态码 + */ + @Override + public String getCode() { + return orderSt; + } + + /** + * 获取退款请求结果状态提示信息 + * + * @return 提示信息 + */ + @Override + public String getMsg() { + return orderPayError; + } + + /** + * 返回业务结果状态码 + * + * @return 业务结果状态码 + */ + @Override + public String getResultCode() { + return orderPayCode; + } + + /** + * 返回业务结果状态提示信息 + * + * @return 业务结果状态提示信息 + */ + @Override + public String getResultMsg() { + return orderPayError; + } + + /** + * 退款金额 + * + * @return 退款金额 + */ + @Override + public BigDecimal getRefundFee() { + return null; + } + + /** + * 退款币种信息 + * + * @return 币种信息 + */ + @Override + public CurType getRefundCurrency() { + return null; + } + + /** + * 支付平台交易号 + * 发起支付时 支付平台(如支付宝)返回的交易订单号 + * + * @return 支付平台交易号 + */ + @Override + public String getTradeNo() { + return fySsn; + } + + /** + * 支付订单号 + * 发起支付时,用户系统的订单号 + * + * @return 支付订单号 + */ + @Override + public String getOutTradeNo() { + return originOrderId; + } + + /** + * 商户退款单号 + * + * @return 商户退款单号 + */ + @Override + public String getRefundNo() { + return null; + } + + public static final FuiouRefundResult create(Map result){ + FuiouRefundResult refundResult = new JSONObject(result).toJavaObject(FuiouRefundResult.class); + refundResult.setAttrs(result); + return refundResult; + } + +} diff --git a/pay-java-fuiou/src/test/java/PayTest.java b/pay-java-fuiou/src/test/java/PayTest.java index a59cca0102a1cc52ed492857a1a2373731a21dfe..f838a2622c332de41b071814dc3a31db48c6af27 100644 --- a/pay-java-fuiou/src/test/java/PayTest.java +++ b/pay-java-fuiou/src/test/java/PayTest.java @@ -16,8 +16,8 @@ import java.util.UUID; * * 富友支付测试 * @author egan - * @email egzosn@gmail.com - * @date 2017/8/18 + * email egzosn@gmail.com + * date 2017/8/18 */ public class PayTest { @@ -37,7 +37,7 @@ public class PayTest { //支付服务 PayService service = new FuiouPayService(fuiouPayConfigStorage); //支付订单基础信息 - PayOrder payOrder = new PayOrder("订单title", "摘要", new BigDecimal(0.01) , UUID.randomUUID().toString().replace("-", "").substring(2)); + PayOrder payOrder = new PayOrder("订单title", "摘要", BigDecimal.valueOf(0.01) , UUID.randomUUID().toString().replace("-", "").substring(2)); /*----------- 网页支付-------------------*/ diff --git a/pay-java-payoneer/README.md b/pay-java-payoneer/README.md index 40143db413ae6baf3537de187329251d3ac069f7..c8094678979a4d752d0d6a5a24615632cdfca984 100644 --- a/pay-java-payoneer/README.md +++ b/pay-java-payoneer/README.md @@ -102,7 +102,7 @@ ```java - Map result = service..query(null, "我方系统单号"); + Map result = service.query(null, "我方系统单号"); ``` @@ -120,7 +120,7 @@ //Map result = service.refund(null, "我方系统单号", null, null); //支付宝单号与我方系统单号二选一 RefundOrder order = new RefundOrder(null, "我方系统单号", null, null); - Map result = service.refund(order); + RefundResult result = service.refund(order); ``` diff --git a/pay-java-payoneer/pom.xml b/pay-java-payoneer/pom.xml index 84b050beb4d1ff6bbe9c7c6e77abcb94c87a3a3a..683ac4982c48ff1790751e4ff39539fbff34e38e 100644 --- a/pay-java-payoneer/pom.xml +++ b/pay-java-payoneer/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.12.6-SNAPSHOT + 2.14.7 4.0.0 pay-java-payoneer diff --git a/pay-java-payoneer/src/main/java/com/egzosn/pay/payoneer/api/AdvancedPayService.java b/pay-java-payoneer/src/main/java/com/egzosn/pay/payoneer/api/AdvancedPayService.java index 9ef401788b4ab026aff89bb102230c563b6b6846..bd04effbb6b94216f37fe50ccfc15a258a8c20fd 100644 --- a/pay-java-payoneer/src/main/java/com/egzosn/pay/payoneer/api/AdvancedPayService.java +++ b/pay-java-payoneer/src/main/java/com/egzosn/pay/payoneer/api/AdvancedPayService.java @@ -1,6 +1,7 @@ package com.egzosn.pay.payoneer.api; import com.egzosn.pay.common.api.PayService; +import com.egzosn.pay.common.bean.PayOrder; import java.util.Map; diff --git a/pay-java-payoneer/src/main/java/com/egzosn/pay/payoneer/api/PayoneerConfigStorage.java b/pay-java-payoneer/src/main/java/com/egzosn/pay/payoneer/api/PayoneerConfigStorage.java index 20324de31dfb819e3c9bc9b75bab73b01f7b3212..979eea2eb39a5ae38c8abc514f3cdd263855d210 100644 --- a/pay-java-payoneer/src/main/java/com/egzosn/pay/payoneer/api/PayoneerConfigStorage.java +++ b/pay-java-payoneer/src/main/java/com/egzosn/pay/payoneer/api/PayoneerConfigStorage.java @@ -1,10 +1,12 @@ package com.egzosn.pay.payoneer.api; + import com.egzosn.pay.common.api.BasePayConfigStorage; /** * Payoneer P卡 支付 配置 + * * @author Actinia - * @author egan + * @author egan *
  * email hayesfu@qq.com
  * date 2018-01-19
@@ -14,29 +16,40 @@ public class PayoneerConfigStorage extends BasePayConfigStorage {
     /**
      * 商户Id
      */
-    public String programId;
+    private String programId;
     /**
-     *  PayoneerPay 用户名
+     * PayoneerPay 用户名
      */
-    public String userName;
+    private String userName;
 
     /**
-     *  应用id
+     * 应用id
+     *
      * @return 空
      */
     @Override
+    @Deprecated
     public String getAppid() {
         return null;
     }
 
+    /**
+     * 应用id
+     * 纠正名称
+     *
+     * @return 应用id
+     */
+    @Override
+    public String getAppId() {
+        return null;
+    }
 
 
     /**
      * 合作商唯一标识
-     *
      */
     @Override
-    public String getPid () {
+    public String getPid() {
         return programId;
     }
 
@@ -46,7 +59,8 @@ public class PayoneerConfigStorage extends BasePayConfigStorage {
     }
 
     /**
-     *  获取商户Id
+     * 获取商户Id
+     *
      * @return 商户Id
      */
     public String getProgramId() {
@@ -54,7 +68,8 @@ public class PayoneerConfigStorage extends BasePayConfigStorage {
     }
 
     /**
-     *  设置商户Id
+     * 设置商户Id
+     *
      * @param programId 商户Id
      */
     public void setProgramId(String programId) {
@@ -62,10 +77,11 @@ public class PayoneerConfigStorage extends BasePayConfigStorage {
     }
 
     /**
-     *  PayoneerPay 用户名
+     * PayoneerPay 用户名
+     *
      * @param userName 用户名
      */
-    public void setUserName(String userName){
+    public void setUserName(String userName) {
         this.userName = userName;
     }
 
@@ -74,16 +90,19 @@ public class PayoneerConfigStorage extends BasePayConfigStorage {
     }
 
     /**
-     *  设置PayoneerPay API password
+     * 设置PayoneerPay API password
+     *
      * @param apiPassword api密钥
      */
-    public void setApiPassword(String apiPassword){
+    public void setApiPassword(String apiPassword) {
         setKeyPrivate(apiPassword);
     }
+
     /**
-     *  获取 PayoneerPay API password
+     * 获取 PayoneerPay API password
+     * @return PayoneerPay API password
      */
-    public String getApiPassword(){
-       return getKeyPrivate();
+    public String getApiPassword() {
+        return getKeyPrivate();
     }
 }
diff --git a/pay-java-payoneer/src/main/java/com/egzosn/pay/payoneer/api/PayoneerPayService.java b/pay-java-payoneer/src/main/java/com/egzosn/pay/payoneer/api/PayoneerPayService.java
index 49e5b71bf35dd97f45d6da02c1532dcc0e5bf85f..fbc74a9a149ac6c997f4363c18d6bf635a57d42e 100644
--- a/pay-java-payoneer/src/main/java/com/egzosn/pay/payoneer/api/PayoneerPayService.java
+++ b/pay-java-payoneer/src/main/java/com/egzosn/pay/payoneer/api/PayoneerPayService.java
@@ -1,9 +1,35 @@
 package com.egzosn.pay.payoneer.api;
 
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.http.Header;
+import org.apache.http.entity.ContentType;
+import org.apache.http.message.BasicHeader;
+
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.egzosn.pay.common.api.BasePayService;
-import com.egzosn.pay.common.bean.*;
+import com.egzosn.pay.common.api.TransferService;
+import com.egzosn.pay.common.bean.AssistOrder;
+import com.egzosn.pay.common.bean.BaseRefundResult;
+import com.egzosn.pay.common.bean.BillType;
+import com.egzosn.pay.common.bean.CurType;
+import com.egzosn.pay.common.bean.DefaultCurType;
+import com.egzosn.pay.common.bean.MethodType;
+import com.egzosn.pay.common.bean.NoticeParams;
+import com.egzosn.pay.common.bean.PayMessage;
+import com.egzosn.pay.common.bean.PayOrder;
+import com.egzosn.pay.common.bean.PayOutMessage;
+import com.egzosn.pay.common.bean.RefundOrder;
+import com.egzosn.pay.common.bean.RefundResult;
+import com.egzosn.pay.common.bean.TransactionType;
+import com.egzosn.pay.common.bean.TransferOrder;
 import com.egzosn.pay.common.bean.outbuilder.PayTextOutMessage;
 import com.egzosn.pay.common.bean.result.PayException;
 import com.egzosn.pay.common.exception.PayErrorException;
@@ -14,26 +40,19 @@ import com.egzosn.pay.common.http.UriVariables;
 import com.egzosn.pay.common.util.DateUtils;
 import com.egzosn.pay.common.util.Util;
 import com.egzosn.pay.payoneer.bean.PayoneerTransactionType;
-import org.apache.http.Header;
-import org.apache.http.entity.ContentType;
-import org.apache.http.message.BasicHeader;
-
-import java.awt.image.BufferedImage;
-import java.math.BigDecimal;
-import java.util.*;
 
 /**
  * payoneer业务逻辑
  *
  * @author Actinia
  * @author egan
- *         
+ * 
  *         email: egzosn@gmail.com
  *         email: hayesfu@qq.com
  *         create 2018-01-19
  *                 
*/ -public class PayoneerPayService extends BasePayService implements AdvancedPayService { +public class PayoneerPayService extends BasePayService implements AdvancedPayService, TransferService { /** * 测试地址 */ @@ -62,20 +81,21 @@ public class PayoneerPayService extends BasePayService im /** * 获取授权请求头 + * * @return 授权请求头 */ - private HttpHeader authHeader(){ + private HttpHeader authHeader() { List
headers = new ArrayList<>(); - headers.add(new BasicHeader("Authorization", "Basic " + authorizationString(getPayConfigStorage().getSeller(), getPayConfigStorage().getKeyPrivate()))); + headers.add(new BasicHeader("Authorization", "Basic " + authorizationString(getPayConfigStorage().getSeller(), getPayConfigStorage().getKeyPrivate()))); return new HttpHeader(headers); } + /** * 获取授权页面 * * @param payeeId 收款id - * * @return 返回请求结果 */ @Override @@ -98,7 +118,6 @@ public class PayoneerPayService extends BasePayService im * 授权状态 * * @param payeeId 用户id - * * @return 返回是否认证 true 已认证 */ @Override @@ -111,7 +130,6 @@ public class PayoneerPayService extends BasePayService im * 获取授权用户信息 * * @param payeeId 用户id - * * @return 获取授权用户信息,包含用户状态,注册时间,联系人信息,地址信息等等 */ @Override @@ -124,31 +142,31 @@ public class PayoneerPayService extends BasePayService im * 回调校验 * * @param params 回调回来的参数集 - * * @return 签名校验 true通过 */ + @Deprecated @Override public boolean verify(Map params) { - if (params != null && 0 == Integer.parseInt(params.get(CODE).toString())) { - if (params.containsKey(OUT_TRADE_NO)) { - return verifySource((String) params.get(OUT_TRADE_NO)); - } - return true; - } - return false; + + return verify(new NoticeParams(params)); } /** - * 签名校验 - * - * @param params 参数集 - * @param sign 签名原文 + * 回调校验 * + * @param noticeParams 回调回来的参数集 * @return 签名校验 true通过 */ @Override - public boolean signVerify(Map params, String sign) { - return true; + public boolean verify(NoticeParams noticeParams) { + final Map params = noticeParams.getBody(); + if (params != null && 0 == Integer.parseInt(params.get(CODE).toString())) { + if (params.containsKey(OUT_TRADE_NO)) { + return verifySource((String) params.get(OUT_TRADE_NO)); + } + return true; + } + return false; } /** @@ -156,10 +174,8 @@ public class PayoneerPayService extends BasePayService im * 校验数据来源 * * @param outTradeNo 业务id, 数据的真实性. - * * @return true通过 */ - @Override public boolean verifySource(String outTradeNo) { Map data = query(null, outTradeNo); return "DONE".equals(data.get("status")); @@ -169,7 +185,6 @@ public class PayoneerPayService extends BasePayService im * 返回创建的订单信息 * * @param order 支付订单 - * * @return 订单信息 * @see PayOrder 支付订单信息 */ @@ -180,12 +195,12 @@ public class PayoneerPayService extends BasePayService im params.put("amount", Util.conversionAmount(order.getPrice())); params.put("client_reference_id", order.getOutTradeNo()); if (null == order.getCurType()) { - order.setCurType(CurType.USD); + order.setCurType(DefaultCurType.USD); } params.put("currency", order.getCurType()); params.put("description", order.getSubject()); - - return params; + params.putAll(order.getAttrs()); + return preOrderHandler(params, order); } /** @@ -193,7 +208,6 @@ public class PayoneerPayService extends BasePayService im * * @param content 需要签名的内容 * @param characterEncoding 字符编码 - * * @return 签名 */ @Override @@ -207,7 +221,6 @@ public class PayoneerPayService extends BasePayService im * * @param code 状态 * @param message 消息 - * * @return 返回输出消息 */ @Override @@ -220,7 +233,6 @@ public class PayoneerPayService extends BasePayService im * 主要用于拦截器中返回 * * @param payMessage 支付回调消息 - * * @return 返回输出消息 */ @Override @@ -233,7 +245,6 @@ public class PayoneerPayService extends BasePayService im * * @param orderInfo 发起支付的订单信息 * @param method 请求方式 "post" "get", - * * @return 获取输出消息,用户返回给支付端, 针对于web端 * @see MethodType 请求类型 */ @@ -246,11 +257,10 @@ public class PayoneerPayService extends BasePayService im * 获取输出二维码,用户返回给支付端, * * @param order 发起支付的订单信息 - * * @return 返回图片信息,支付时需要的 */ @Override - public BufferedImage genQrPay(PayOrder order) { + public String getQrPay(PayOrder order) { throw new UnsupportedOperationException(); } @@ -258,11 +268,12 @@ public class PayoneerPayService extends BasePayService im * 刷卡付,pos主动扫码付款(条码付) * * @param order 发起支付的订单信息 - * * @return 返回支付结果 */ @Override public Map microPay(PayOrder order) { + order.setTransactionType(PayoneerTransactionType.CHARGE); + HttpStringEntity entity = new HttpStringEntity(JSON.toJSONString(orderInfo(order)), ContentType.APPLICATION_JSON); //设置 base atuh entity.setHeaders(authHeader()); @@ -270,7 +281,7 @@ public class PayoneerPayService extends BasePayService im if (response != null) { return response; } - throw new PayErrorException(new PayException("fail", "Payoneer申请收款失败,原因:未有返回值" )); + throw new PayErrorException(new PayException("fail", "Payoneer申请收款失败,原因:未有返回值")); } /** @@ -278,7 +289,6 @@ public class PayoneerPayService extends BasePayService im * * @param tradeNo 支付平台订单号 * @param outTradeNo 商户单号 - * * @return 返回查询回来的结果集,支付方原值返回 */ @Override @@ -286,13 +296,23 @@ public class PayoneerPayService extends BasePayService im return secondaryInterface(tradeNo, outTradeNo, PayoneerTransactionType.CHARGE_STATUS); } + /** + * 交易查询接口 + * + * @param assistOrder 查询条件 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(AssistOrder assistOrder) { + return secondaryInterface(assistOrder.getTradeNo(), assistOrder.getOutTradeNo(), PayoneerTransactionType.CHARGE_STATUS); + } + /** * 交易关闭接口 * * @param tradeNo 支付平台订单号 * @param outTradeNo 商户单号 - * * @return 返回支付方交易关闭后的结果 */ @Override @@ -301,32 +321,26 @@ public class PayoneerPayService extends BasePayService im } /** - * 交易交易撤销 + * 交易关闭接口 * - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * @return 返回支付方交易撤销后的结果 + * @param assistOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 */ @Override - public Map cancel(String tradeNo, String outTradeNo) { - return secondaryInterface(tradeNo, outTradeNo, PayoneerTransactionType.CHARGE_CANCEL); + public Map close(AssistOrder assistOrder) { + return secondaryInterface(assistOrder.getTradeNo(), assistOrder.getOutTradeNo(), PayoneerTransactionType.CHARGE_CANCEL); } /** - * 申请退款接口 - * 废弃 - * - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * @param refundAmount 退款金额 - * @param totalAmount 总金额 + * 交易交易撤销 * - * @return 返回支付方申请退款后的结果 - * @see #refund(RefundOrder) + * @param tradeNo 支付平台订单号 + * @param outTradeNo 商户单号 + * @return 返回支付方交易撤销后的结果 */ @Override - public Map refund(String tradeNo, String outTradeNo, BigDecimal refundAmount, BigDecimal totalAmount) { - return close(tradeNo, outTradeNo); + public Map cancel(String tradeNo, String outTradeNo) { + return secondaryInterface(tradeNo, outTradeNo, PayoneerTransactionType.CHARGE_CANCEL); } @@ -334,27 +348,59 @@ public class PayoneerPayService extends BasePayService im * 申请退款接口 * * @param refundOrder 退款订单信息 - * * @return 返回支付方申请退款后的结果 */ @Override - public Map refund(RefundOrder refundOrder) { - return close(refundOrder.getTradeNo(), refundOrder.getOutTradeNo()); - } + public RefundResult refund(RefundOrder refundOrder) { + return new BaseRefundResult(close(refundOrder.getTradeNo(), refundOrder.getOutTradeNo())) { + @Override + public String getCode() { + return getAttrString(CODE); + } - /** - * 查询退款 - * - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * - * @return 返回支付方查询退款后的结果 - */ - @Override - public Map refundquery(String tradeNo, String outTradeNo) { - return Collections.emptyMap(); + @Override + public String getMsg() { + return null; + } + + @Override + public String getResultCode() { + return null; + } + + @Override + public String getResultMsg() { + return null; + } + + @Override + public BigDecimal getRefundFee() { + return null; + } + + @Override + public CurType getRefundCurrency() { + return null; + } + + @Override + public String getTradeNo() { + return null; + } + + @Override + public String getOutTradeNo() { + return null; + } + + @Override + public String getRefundNo() { + return null; + } + }; } + /** * 查询退款 * @@ -366,17 +412,16 @@ public class PayoneerPayService extends BasePayService im return Collections.emptyMap(); } + /** * 下载对账单 * * @param billDate 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 * @param billType 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; - * - * @return 返回支付方下载对账单的结果 + * @return 空 */ @Override - public Map downloadbill(Date billDate, String billType) { - + public Map downloadBill(Date billDate, BillType billType) { return Collections.emptyMap(); } @@ -385,18 +430,17 @@ public class PayoneerPayService extends BasePayService im * 类型为{@link String }或者 {@link Date },类型须强制限制,类型不对应则抛出异常{@link PayErrorException} * @param outTradeNoBillType 商户单号或者 账单类型 * @param transactionType 交易类型 - * * @return 返回支付方对应接口的结果 */ - @Override public Map secondaryInterface(Object tradeNoOrBillDate, String outTradeNoBillType, TransactionType transactionType) { MethodType methodType = null; if (transactionType == PayoneerTransactionType.CHARGE_CANCEL) { // 退款 methodType = MethodType.POST; - }else { + } + else { methodType = MethodType.GET; } - JSONObject result = getHttpRequestTemplate().doExecute(UriVariables.getUri(getReqUrl(transactionType), outTradeNoBillType), authHeader() ,JSONObject.class, methodType); + JSONObject result = getHttpRequestTemplate().doExecute(UriVariables.getUri(getReqUrl(transactionType), outTradeNoBillType), authHeader(), JSONObject.class, methodType); return result; } @@ -404,7 +448,6 @@ public class PayoneerPayService extends BasePayService im * 转账 * * @param order 转账订单 - * * @return 对应的转账结果 */ @Override @@ -425,12 +468,22 @@ public class PayoneerPayService extends BasePayService im return response; } + /** + * 转账查询 + * + * @param assistOrder 辅助交易订单 + * @return 对应的转账订单 + */ + @Override + public Map transferQuery(AssistOrder assistOrder) { + return secondaryInterface(assistOrder.getTradeNo(), assistOrder.getOutTradeNo(), PayoneerTransactionType.PAYOUT_STATUS); + } + /** * 转账 * * @param outNo 商户转账订单号 * @param tradeNo 支付平台转账订单号 - * * @return 对应的转账订单 */ @Override @@ -443,6 +496,7 @@ public class PayoneerPayService extends BasePayService im * * @return 请求地址 */ + @Override public String getReqUrl(TransactionType type) { return (payConfigStorage.isTest() ? SANDBOX_DOMAIN : RELEASE_DOMAIN) + payConfigStorage.getPid() + "/" + type.getMethod(); } diff --git a/pay-java-payoneer/test/java/PayTest.java b/pay-java-payoneer/test/java/PayTest.java index e19aa4205910154ffbce28bb6f0168354219c2d2..ddf2691d7d0c9c74a53fc392bac6909ef9e5d863 100644 --- a/pay-java-payoneer/test/java/PayTest.java +++ b/pay-java-payoneer/test/java/PayTest.java @@ -27,8 +27,8 @@ import java.util.UUID; * * payoneer支付测试 * @author Actinia - * @email hayesfu@qq.com - * @date 2018/1/18 0018 16:49 + * email hayesfu@qq.com + * date 2018/1/18 0018 16:49 */ public class PayTest { @@ -36,7 +36,7 @@ public class PayTest { if (1==1){ - String auth = Base64.encode("Huodull6190:12BkDT8152Zj".getBytes()); + String auth = Base64.encode("egan6190:12BkDT8152Zj".getBytes()); System.out.println(auth); return; } @@ -49,7 +49,7 @@ public class PayTest { CredentialsProvider credsProvider = new BasicCredentialsProvider(); credsProvider.setCredentials( new AuthScope(uri.getHost(), uri.getPort()), - new UsernamePasswordCredentials("Huodull6190", "12BkDT8152Zj")); + new UsernamePasswordCredentials("egan6190", "12BkDT8152Zj")); CloseableHttpClient httpclient = HttpClients.custom() .setDefaultCredentialsProvider(credsProvider) @@ -73,7 +73,7 @@ public class PayTest { // PayoneerRequestBean bean = new PayoneerRequestBean("666"); String referenceId = UUID.randomUUID().toString().replace("-", ""); - PayoneerRequestBean bean = new PayoneerRequestBean("8a2950f959043699015904453b330057","1.01", referenceId, CurType.USD,"huodull order"); + PayoneerRequestBean bean = new PayoneerRequestBean("8a2950f959043699015904453b330057","1.01", referenceId, CurType.USD,"egan order"); // PayoneerRequestBean bean = JSON.parseObject("{\"amount\":\"1.00\",\"client_reference_id\":\""+ System.nanoTime()+"\",\"currency\":\"USD\",\"description\":\"aaabb\",\"payee_id\":\"asdfg13\"}", PayoneerRequestBean.class); System.out.println(JSON.toJSONString(bean)); StringEntity entity = new StringEntity(JSON.toJSONString(bean), ContentType.APPLICATION_JSON); diff --git a/pay-java-paypal/README.md b/pay-java-paypal/README.md index 590af71b08f85fc7c9cadc9f3e2c18aabb2f6f39..5ca93d06f7474c5207bb8ba2a9cb2c5066c02cef 100644 --- a/pay-java-paypal/README.md +++ b/pay-java-paypal/README.md @@ -94,7 +94,7 @@ order.setCurType(CurType.USD); order.setDescription(" description "); order.setTradeNo("paypal 平台的单号"); - order.setRefundAmount(new BigDecimal(0.01)); - Map result = service.refund(order); + order.setRefundAmount(BigDecimal.valueOf(0.01)); + RefundResult result = service.refund(order); ``` diff --git a/pay-java-paypal/pom.xml b/pay-java-paypal/pom.xml index 2a2f2b3cbad7a74ce45a5098dba67645e7e195d0..d818075f211e73b33cf356f1dc7cc66eb787b213 100644 --- a/pay-java-paypal/pom.xml +++ b/pay-java-paypal/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.12.6-SNAPSHOT + 2.14.7 4.0.0 diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/api/PayPalConfigStorage.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/api/PayPalConfigStorage.java index ae836b05ad381f82cd7d3a12d159b9dcadfac23c..69c5a899c3928e007f26a59883b4c3aef52eb278 100644 --- a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/api/PayPalConfigStorage.java +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/api/PayPalConfigStorage.java @@ -1,68 +1,114 @@ package com.egzosn.pay.paypal.api; +import java.util.concurrent.locks.ReentrantLock; + import com.egzosn.pay.common.api.BasePayConfigStorage; /** * 贝宝支付配置存储 - * @author egan * + * @author egan + *

* email egzosn@gmail.com * date 2018-4-8 22:11:42 */ public class PayPalConfigStorage extends BasePayConfigStorage { - private volatile String clientID; + private String clientId; + + /** + * 回调验签使用 + */ + private String webHookId; + @Override + @Deprecated public String getAppid() { - return clientID; + return clientId; + } + + /** + * 应用id + * 纠正名称 + * + * @return 应用id + */ + @Override + public String getAppId() { + return clientId; } @Override public String getPid() { - return clientID; + return clientId; } @Override public String getSeller() { - return clientID; + return clientId; } public String getClientID() { - return clientID; + return clientId; + } + + public void setClientID(String clientId) { + this.clientId = clientId; } - public void setClientID(String clientID) { - this.clientID = clientID; + public String getClientId() { + return clientId; } + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getClientSecret() { return getKeyPrivate(); } public void setClientSecret(String clientSecret) { - setKeyPrivate(clientSecret); + setKeyPrivate(clientSecret); } - @Override - public boolean isAccessTokenExpired() { - if (getExpiresTime() == 0){ - return true; - } - return (getExpiresTime() - System.currentTimeMillis() / 1000) <= 0; - } /** * 设置取消页面的url + *

+     * 注意:这里不是异步回调的通知
+     * IPN 地址设置的路径:https://developer.paypal.com/developer/ipnSimulator/
+     * 
+ * * @param cancelUrl 取消页面的url */ - public void setCancelUrl(String cancelUrl){ + public void setCancelUrl(String cancelUrl) { setNotifyUrl(cancelUrl); } + /** * 获取取消页面的url + *
+     * 注意:这里不是异步回调的通知
+     * 
+ * + * @return 取消页面的url */ - public String getCancelUrl(){ - return getNotifyUrl(); + public String getCancelUrl() { + return getNotifyUrl(); + } + + public PayPalConfigStorage() { + setAccessTokenLock(new ReentrantLock()); + } + + public String getWebHookId() { + return webHookId; + } + + public void setWebHookId(String webHookId) { + this.webHookId = webHookId; } } diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/api/PayPalPayService.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/api/PayPalPayService.java index 19c233214e9254f383a4206cb44098285dc1d7b1..d375df69e16f156e45535e1bd65a16224cd18e7f 100644 --- a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/api/PayPalPayService.java +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/api/PayPalPayService.java @@ -1,10 +1,37 @@ package com.egzosn.pay.paypal.api; +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.locks.Lock; + +import org.apache.http.Header; +import org.apache.http.entity.ContentType; +import org.apache.http.message.BasicHeader; + import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.common.api.BasePayService; -import com.egzosn.pay.common.bean.*; +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.common.bean.BaseRefundResult; +import com.egzosn.pay.common.bean.BillType; +import com.egzosn.pay.common.bean.CurType; +import com.egzosn.pay.common.bean.DefaultCurType; +import com.egzosn.pay.common.bean.MethodType; +import com.egzosn.pay.common.bean.NoticeParams; +import com.egzosn.pay.common.bean.PayMessage; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.PayOutMessage; +import com.egzosn.pay.common.bean.RefundOrder; +import com.egzosn.pay.common.bean.RefundResult; +import com.egzosn.pay.common.bean.TransactionType; import com.egzosn.pay.common.bean.result.PayException; import com.egzosn.pay.common.exception.PayErrorException; import com.egzosn.pay.common.http.HttpHeader; @@ -12,24 +39,22 @@ import com.egzosn.pay.common.http.HttpStringEntity; import com.egzosn.pay.common.util.Util; import com.egzosn.pay.common.util.str.StringUtils; import com.egzosn.pay.paypal.bean.PayPalTransactionType; -import com.egzosn.pay.paypal.bean.order.*; -import org.apache.http.Header; -import org.apache.http.entity.ContentType; -import org.apache.http.message.BasicHeader; -import java.awt.image.BufferedImage; -import java.io.UnsupportedEncodingException; -import java.math.BigDecimal; -import java.util.*; -import java.util.concurrent.locks.Lock; +import com.egzosn.pay.paypal.bean.order.Amount; +import com.egzosn.pay.paypal.bean.order.Links; +import com.egzosn.pay.paypal.bean.order.Payer; +import com.egzosn.pay.paypal.bean.order.Payment; +import com.egzosn.pay.paypal.bean.order.RedirectUrls; +import com.egzosn.pay.paypal.bean.order.Transaction; /** * 贝宝支付配置存储 - * @author egan * + * @author egan + *

* email egzosn@gmail.com * date 2018-4-8 ‏‎22:15:09 */ -public class PayPalPayService extends BasePayService{ +public class PayPalPayService extends BasePayService { /** * 沙箱环境 @@ -42,9 +67,11 @@ public class PayPalPayService extends BasePayService{ /** * 获取对应的请求地址 + * * @return 请求地址 */ - public String getReqUrl(TransactionType transactionType){ + @Override + public String getReqUrl(TransactionType transactionType) { return (payConfigStorage.isTest() ? SANDBOX_REQ_URL : REQ_URL) + transactionType.getMethod(); } @@ -54,21 +81,23 @@ public class PayPalPayService extends BasePayService{ } - /** * 获取请求token + * * @return 授权令牌 */ - public String getAccessToken() { + public String getAccessToken() { try { return getAccessToken(false); - } catch (PayErrorException e) { + } + catch (PayErrorException e) { throw e; } } /** - * 获取授权令牌 + * 获取授权令牌 + * * @param forceRefresh 是否重新获取, true重新获取 * @return 新的授权令牌 * @throws PayErrorException 支付异常 @@ -83,54 +112,53 @@ public class PayPalPayService extends BasePayService{ } if (payConfigStorage.isAccessTokenExpired()) { - if (null == payConfigStorage.getAccessToken()){ - Map header = new HashMap<>(); - header.put("Authorization", "Basic " + authorizationString(getPayConfigStorage().getAppid(), getPayConfigStorage().getKeyPrivate())); - header.put("Accept", "application/json"); - header.put("Content-Type", "application/x-www-form-urlencoded"); - try { - HttpStringEntity entity = new HttpStringEntity("grant_type=client_credentials", header); - JSONObject resp = getHttpRequestTemplate().postForObject(getReqUrl(PayPalTransactionType.AUTHORIZE), entity, JSONObject.class); - payConfigStorage.updateAccessToken(String.format("%s %s", resp.getString("token_type" ), resp.getString("access_token" )), resp.getLongValue("expires_in" )); - - } catch (UnsupportedEncodingException e) { - throw new PayErrorException(new PayException("failure", e.getMessage())); - } + Map header = new HashMap<>(); + header.put("Authorization", "Basic " + authorizationString(getPayConfigStorage().getAppId(), getPayConfigStorage().getKeyPrivate())); + header.put("Accept", "application/json"); + header.put("Content-Type", "application/x-www-form-urlencoded"); + try { + HttpStringEntity entity = new HttpStringEntity("grant_type=client_credentials", header); + JSONObject resp = getHttpRequestTemplate().postForObject(getReqUrl(PayPalTransactionType.AUTHORIZE), entity, JSONObject.class); + payConfigStorage.updateAccessToken(String.format("%s %s", resp.getString("token_type"), resp.getString("access_token")), resp.getIntValue("expires_in")); + + } + catch (UnsupportedEncodingException e) { + throw new PayErrorException(new PayException("failure", e.getMessage())); } return payConfigStorage.getAccessToken(); } - } finally { + } + finally { lock.unlock(); } return payConfigStorage.getAccessToken(); } + @Deprecated @Override public boolean verify(Map params) { - HttpStringEntity httpEntity = new HttpStringEntity("{\"payer_id\":\""+(String)params.get("PayerID")+"\"}", ContentType.APPLICATION_JSON); - httpEntity.setHeaders(authHeader()); - JSONObject resp = getHttpRequestTemplate().postForObject(getReqUrl(PayPalTransactionType.EXECUTE), httpEntity, JSONObject.class, (String) params.get("paymentId")); - return "approved".equals(resp.getString("state")); + return verify(new NoticeParams(params)); } @Override - public boolean signVerify(Map params, String sign) { - return true; - } + public boolean verify(NoticeParams noticeParams) { + final Map params = noticeParams.getBody(); + HttpStringEntity httpEntity = new HttpStringEntity("{\"payer_id\":\"" + (String) params.get("PayerID") + "\"}", ContentType.APPLICATION_JSON); + httpEntity.setHeaders(authHeader()); + JSONObject resp = getHttpRequestTemplate().postForObject(getReqUrl(PayPalTransactionType.EXECUTE), httpEntity, JSONObject.class, (String) params.get("paymentId")); + return "approved".equals(resp.getString("state")); - @Override - public boolean verifySource(String id) { - return true; } /** * 获取授权请求头 + * * @return 授权请求头 */ - private HttpHeader authHeader(){ + private HttpHeader authHeader() { List

headers = new ArrayList<>(); headers.add(new BasicHeader("Authorization", getAccessToken())); @@ -138,6 +166,7 @@ public class PayPalPayService extends BasePayService{ return new HttpHeader(headers); } + /** * 返回创建的订单信息 * @@ -147,21 +176,26 @@ public class PayPalPayService extends BasePayService{ */ @Override public Map orderInfo(PayOrder order) { + if (null == order.getTransactionType()) { + order.setTransactionType(PayPalTransactionType.sale); + } + Amount amount = new Amount(); - if (null == order.getCurType()){ - order.setCurType(CurType.USD); + if (null == order.getCurType()) { + order.setCurType(DefaultCurType.USD); } - amount.setCurrency(order.getCurType().name()); + amount.setCurrency(order.getCurType().getType()); amount.setTotal(Util.conversionAmount(order.getPrice()).toString()); Transaction transaction = new Transaction(); - if (!StringUtils.isEmpty(order.getSubject())){ + if (!StringUtils.isEmpty(order.getSubject())) { transaction.setDescription(order.getSubject()); - }else { + } + else { transaction.setDescription(order.getBody()); } transaction.setAmount(amount); - + transaction.setCustom(order.getOutTradeNo()); List transactions = new ArrayList<>(); transactions.add(transaction); @@ -178,10 +212,13 @@ public class PayPalPayService extends BasePayService{ //发起付款后的页面转跳地址 redirectUrls.setReturnUrl(payConfigStorage.getReturnUrl()); payment.setRedirectUrls(redirectUrls); - HttpStringEntity entity = new HttpStringEntity(JSON.toJSONString(payment), ContentType.APPLICATION_JSON); + HttpStringEntity entity = new HttpStringEntity(JSON.toJSONString(payment), ContentType.APPLICATION_JSON); entity.setHeaders(authHeader()); JSONObject resp = getHttpRequestTemplate().postForObject(getReqUrl(order.getTransactionType()), entity, JSONObject.class); - return resp; + if ("created".equals(resp.getString("state")) && StringUtils.isNotEmpty(resp.getString("id"))) { + order.setTradeNo(resp.getString("id")); + } + return preOrderHandler(resp, order); } @Override @@ -196,19 +233,19 @@ public class PayPalPayService extends BasePayService{ @Override public String buildRequest(Map orderInfo, MethodType method) { - if (orderInfo instanceof JSONObject){ + if (orderInfo instanceof JSONObject) { Payment payment = ((JSONObject) orderInfo).toJavaObject(Payment.class); - for(Links links : payment.getLinks()){ - if(links.getRel().equals("approval_url")){ - return String.format("",links.getHref() ); + for (Links links : payment.getLinks()) { + if (links.getRel().equals("approval_url")) { + return String.format("", links.getHref()); } } } - return "" ; + return ""; } @Override - public BufferedImage genQrPay(PayOrder order) { + public String getQrPay(PayOrder order) { return null; } @@ -216,6 +253,7 @@ public class PayPalPayService extends BasePayService{ public Map microPay(PayOrder order) { return null; } + /** * 交易查询接口 * @@ -225,66 +263,108 @@ public class PayPalPayService extends BasePayService{ */ @Override public Map query(String tradeNo, String outTradeNo) { - JSONObject resp = getHttpRequestTemplate().getForObject(getReqUrl(PayPalTransactionType.ORDERS), authHeader(), JSONObject.class, tradeNo); - return resp; + + return query(new AssistOrder(tradeNo, outTradeNo)); + } + + /** + * 交易查询接口 + * + * @param assistOrder 查询条件 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(AssistOrder assistOrder) { + return getHttpRequestTemplate().getForObject(getReqUrl(PayPalTransactionType.ORDERS), authHeader(), JSONObject.class, assistOrder.getTradeNo()); } @Override public Map close(String tradeNo, String outTradeNo) { return null; } + /** - * 申请退款接口 - * 废弃 - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * @param refundAmount 退款金额 - * @param totalAmount 总金额 - * @return 返回支付方申请退款后的结果 - * @see #refund(RefundOrder) - * @deprecated {@link #refund(RefundOrder)} + * 交易关闭接口 + * + * @param assistOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 */ - @Deprecated @Override - public Map refund(String tradeNo, String outTradeNo, BigDecimal refundAmount, BigDecimal totalAmount) { - return refund(new RefundOrder( tradeNo, outTradeNo, refundAmount, totalAmount)); + public Map close(AssistOrder assistOrder) { + throw new UnsupportedOperationException("不支持该操作"); } - /** * 申请退款接口 * - * @param refundOrder 退款订单信息 + * @param refundOrder 退款订单信息 * @return 返回支付方申请退款后的结果 */ @Override - public Map refund(RefundOrder refundOrder) { - JSONObject request = new JSONObject(); + public RefundResult refund(RefundOrder refundOrder) { + JSONObject request = new JSONObject(); - if (null != refundOrder.getRefundAmount() && BigDecimal.ZERO.compareTo( refundOrder.getRefundAmount()) > 0){ + if (null != refundOrder.getRefundAmount() && BigDecimal.ZERO.compareTo(refundOrder.getRefundAmount()) == -1) { Amount amount = new Amount(); - amount.setCurrency(refundOrder.getCurType().name()); + if (null == refundOrder.getCurType()) { + refundOrder.setCurType(DefaultCurType.USD); + } + + amount.setCurrency(refundOrder.getCurType().getType()); amount.setTotal(Util.conversionAmount(refundOrder.getRefundAmount()).toString()); request.put("amount", amount); request.put("description", refundOrder.getDescription()); } - HttpStringEntity httpEntity = new HttpStringEntity(request, ContentType.APPLICATION_JSON); + HttpStringEntity httpEntity = new HttpStringEntity(request.toJSONString(), ContentType.APPLICATION_JSON); httpEntity.setHeaders(authHeader()); - JSONObject resp = getHttpRequestTemplate().postForObject(getReqUrl(PayPalTransactionType.REFUND), httpEntity, JSONObject.class, refundOrder.getTradeNo()); - return resp; - } - /** - * 查询退款 - * - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * @return 返回支付方查询退款后的结果 - */ - @Override - public Map refundquery(String tradeNo, String outTradeNo) { - JSONObject resp = getHttpRequestTemplate().getForObject(getReqUrl(PayPalTransactionType.REFUND_QUERY), authHeader(), JSONObject.class, tradeNo); - return resp; + JSONObject resp = getHttpRequestTemplate().postForObject(getReqUrl(PayPalTransactionType.REFUND), httpEntity, JSONObject.class, refundOrder.getTradeNo()); + return new BaseRefundResult(resp) { + @Override + public String getCode() { + return getAttrString("state"); + } + + @Override + public String getMsg() { + return null; + } + + @Override + public String getResultCode() { + return null; + } + + @Override + public String getResultMsg() { + return null; + } + + @Override + public BigDecimal getRefundFee() { + return null; + } + + @Override + public CurType getRefundCurrency() { + return null; + } + + @Override + public String getTradeNo() { + return null; + } + + @Override + public String getOutTradeNo() { + return null; + } + + @Override + public String getRefundNo() { + return null; + } + }; } /** @@ -295,16 +375,14 @@ public class PayPalPayService extends BasePayService{ */ @Override public Map refundquery(RefundOrder refundOrder) { - return refundquery(refundOrder.getTradeNo(), refundOrder.getOutTradeNo()); + return getHttpRequestTemplate().getForObject(getReqUrl(PayPalTransactionType.REFUND_QUERY), authHeader(), JSONObject.class, refundOrder.getTradeNo()); } - @Override - public Map downloadbill(Date billDate, String billType) { - return Collections.emptyMap(); - } @Override - public Map secondaryInterface(Object tradeNoOrBillDate, String outTradeNoBillType, TransactionType transactionType) { + public Map downloadBill(Date billDate, BillType billType) { return Collections.emptyMap(); } + + } diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Amount.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Amount.java index 34d645bbde8ba68d03169943093ca86213b4f52c..9d1ebb302de47836b9a58ded14d8b2915e04c3d8 100644 --- a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Amount.java +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Amount.java @@ -22,6 +22,9 @@ public class Amount { /** * Parameterized Constructor + * + * @param currency 类型 + * @param total 金额 */ public Amount(String currency, String total) { this.currency = currency; diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/CartBase.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/CartBase.java index 077acebe4e0437f8dad5d10ec46224929e39336a..060b1142dd304207b0d84aa1b174f34d37797a11 100644 --- a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/CartBase.java +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/CartBase.java @@ -56,6 +56,7 @@ public class CartBase { /** * Parameterized Constructor + * @param amount 金额 */ public CartBase(Amount amount) { this.amount = amount; @@ -63,72 +64,72 @@ public class CartBase { /** * Merchant identifier to the purchase unit. Optional parameter + * @return identifier */ - @SuppressWarnings("all") public String getReferenceId() { return this.referenceId; } /** * Amount being collected. + * @return amount 金额 */ - @SuppressWarnings("all") public Amount getAmount() { return this.amount; } /** * Recipient of the funds in this transaction. + * @return Recipient */ - @SuppressWarnings("all") public Payee getPayee() { return this.payee; } /** * Description of what is being paid for. + * @return Description */ - @SuppressWarnings("all") public String getDescription() { return this.description; } /** * Note to the recipient of the funds in this transaction. + * @return Note */ - @SuppressWarnings("all") public String getNoteToPayee() { return this.noteToPayee; } /** * free-form field for the use of clients + * @return custom */ - @SuppressWarnings("all") public String getCustom() { return this.custom; } /** * invoice number to track this payment + * @return invoice number */ - @SuppressWarnings("all") public String getInvoiceNumber() { return this.invoiceNumber; } /** * Soft descriptor used when charging this funding source. If length exceeds max length, the value will be truncated + * @return SoftDescriptor */ - @SuppressWarnings("all") public String getSoftDescriptor() { return this.softDescriptor; } /** * Soft descriptor city used when charging this funding source. If length exceeds max length, the value will be truncated. Only supported when the `payment_method` is set to `credit_card` + * @return Soft descriptor */ - @SuppressWarnings("all") public String getSoftDescriptorCity() { return this.softDescriptorCity; } @@ -136,25 +137,25 @@ public class CartBase { /** * URL to send payment notifications + * @return URL */ - @SuppressWarnings("all") public String getNotifyUrl() { return this.notifyUrl; } /** * Url on merchant site pertaining to this payment. + * @return URL */ - @SuppressWarnings("all") public String getOrderUrl() { return this.orderUrl; } /** * Merchant identifier to the purchase unit. Optional parameter + * @param referenceId identifier * @return this */ - @SuppressWarnings("all") public CartBase setReferenceId(final String referenceId) { this.referenceId = referenceId; return this; @@ -162,9 +163,9 @@ public class CartBase { /** * Amount being collected. + * @param amount 金额 * @return this */ - @SuppressWarnings("all") public CartBase setAmount(final Amount amount) { this.amount = amount; return this; @@ -172,9 +173,9 @@ public class CartBase { /** * Recipient of the funds in this transaction. + * @param payee Recipient * @return this */ - @SuppressWarnings("all") public CartBase setPayee(final Payee payee) { this.payee = payee; return this; @@ -182,9 +183,9 @@ public class CartBase { /** * Description of what is being paid for. + * @param description description * @return this */ - @SuppressWarnings("all") public CartBase setDescription(final String description) { this.description = description; return this; @@ -192,9 +193,9 @@ public class CartBase { /** * Note to the recipient of the funds in this transaction. + * @param noteToPayee noteToPayee * @return this */ - @SuppressWarnings("all") public CartBase setNoteToPayee(final String noteToPayee) { this.noteToPayee = noteToPayee; return this; @@ -202,9 +203,9 @@ public class CartBase { /** * free-form field for the use of clients + * @param custom custom * @return this */ - @SuppressWarnings("all") public CartBase setCustom(final String custom) { this.custom = custom; return this; @@ -212,9 +213,9 @@ public class CartBase { /** * invoice number to track this payment + * @param invoiceNumber invoiceNumber * @return this */ - @SuppressWarnings("all") public CartBase setInvoiceNumber(final String invoiceNumber) { this.invoiceNumber = invoiceNumber; return this; @@ -222,9 +223,9 @@ public class CartBase { /** * Soft descriptor used when charging this funding source. If length exceeds max length, the value will be truncated + * @param softDescriptor softDescriptor * @return this */ - @SuppressWarnings("all") public CartBase setSoftDescriptor(final String softDescriptor) { this.softDescriptor = softDescriptor; return this; @@ -232,9 +233,9 @@ public class CartBase { /** * Soft descriptor city used when charging this funding source. If length exceeds max length, the value will be truncated. Only supported when the `payment_method` is set to `credit_card` + * @param softDescriptorCity softDescriptorCity * @return this */ - @SuppressWarnings("all") public CartBase setSoftDescriptorCity(final String softDescriptorCity) { this.softDescriptorCity = softDescriptorCity; return this; @@ -244,9 +245,9 @@ public class CartBase { /** * URL to send payment notifications + * @param notifyUrl notifyUrl * @return this */ - @SuppressWarnings("all") public CartBase setNotifyUrl(final String notifyUrl) { this.notifyUrl = notifyUrl; return this; @@ -254,9 +255,9 @@ public class CartBase { /** * Url on merchant site pertaining to this payment. + * @param orderUrl orderUrl * @return this */ - @SuppressWarnings("all") public CartBase setOrderUrl(final String orderUrl) { this.orderUrl = orderUrl; return this; diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Error.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Error.java index 3d9069ff37204ea2c7073ee4a479991f6c14d9d1..1207b884a5c25367cbd3d86ee864239ba9d80034 100644 --- a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Error.java +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Error.java @@ -4,170 +4,119 @@ package com.egzosn.pay.paypal.bean.order; import java.util.List; -public class Error { - /** - * Human readable, unique name of the error. - */ - private String name; - /** - * Message describing the error. - */ - private String message; - /** - * Additional details of the error - */ - private List details; - /** - * URI for detailed information related to this error for the developer. - */ - private String informationLink; - /** - * PayPal internal identifier used for correlation purposes. - */ - private String debugId; - - /** - * @deprecated This property is not available publicly - * PayPal internal error code. - */ - @Deprecated - private String code; - - - /** - * Default Constructor - */ - public Error() { - } - - /** - * Parameterized Constructor - */ - public Error(String name, String message, String informationLink, String debugId) { - this.name = name; - this.message = message; - this.informationLink = informationLink; - this.debugId = debugId; - } - - public String toString() { - return "name: " + this.name + "\tmessage: " + this.message + "\tdetails: " + this.details + "\tdebug-id: " + this.debugId + "\tinformation-link: " + this.informationLink; - } - - /** - * Human readable, unique name of the error. - */ - @SuppressWarnings("all") - public String getName() { - return this.name; - } - - /** - * Message describing the error. - */ - @SuppressWarnings("all") - public String getMessage() { - return this.message; - } - - /** - * Additional details of the error - */ - @SuppressWarnings("all") - public List getDetails() { - return this.details; - } - - /** - * URI for detailed information related to this error for the developer. - */ - @SuppressWarnings("all") - public String getInformationLink() { - return this.informationLink; - } - - /** - * PayPal internal identifier used for correlation purposes. - */ - @SuppressWarnings("all") - public String getDebugId() { - return this.debugId; - } - - /** - * @deprecated This property is not available publicly - * PayPal internal error code. - */ - @Deprecated - @SuppressWarnings("all") - public String getCode() { - return this.code; - } - - - - /** - * Human readable, unique name of the error. - * @return this - */ - @SuppressWarnings("all") - public Error setName(final String name) { - this.name = name; - return this; - } - - /** - * Message describing the error. - * @return this - */ - @SuppressWarnings("all") - public Error setMessage(final String message) { - this.message = message; - return this; - } - - /** - * Additional details of the error - * @return this - */ - @SuppressWarnings("all") - public Error setDetails(final List details) { - this.details = details; - return this; - } - - /** - * URI for detailed information related to this error for the developer. - * @return this - */ - @SuppressWarnings("all") - public Error setInformationLink(final String informationLink) { - this.informationLink = informationLink; - return this; - } - - /** - * PayPal internal identifier used for correlation purposes. - * @return this - */ - @SuppressWarnings("all") - public Error setDebugId(final String debugId) { - this.debugId = debugId; - return this; - } - - - /** - * @deprecated This property is not available publicly - * PayPal internal error code. - * @return this - */ - @Deprecated - @SuppressWarnings("all") - public Error setCode(final String code) { - this.code = code; - return this; - } +public class Error { + /** + * Human readable, unique name of the error. + */ + private String name; + /** + * Message describing the error. + */ + private String message; + /** + * Additional details of the error + */ + private List details; + /** + * URI for detailed information related to this error for the developer. + */ + private String informationLink; + /** + * PayPal internal identifier used for correlation purposes. + */ + private String debugId; + + /** + * @deprecated This property is not available publicly + * PayPal internal error code. + */ + @Deprecated + private String code; + + + /** + * Default Constructor + */ + public Error() { + } + + + public Error(String name, String message, String informationLink, String debugId) { + this.name = name; + this.message = message; + this.informationLink = informationLink; + this.debugId = debugId; + } + + public String toString() { + return "name: " + this.name + "\tmessage: " + this.message + "\tdetails: " + this.details + "\tdebug-id: " + this.debugId + "\tinformation-link: " + this.informationLink; + } + + + public String getName() { + return this.name; + } + + + public String getMessage() { + return this.message; + } + + + public List getDetails() { + return this.details; + } + + + public String getInformationLink() { + return this.informationLink; + } + + + public String getDebugId() { + return this.debugId; + } + + + public String getCode() { + return this.code; + } + + + public Error setName(final String name) { + this.name = name; + return this; + } + + + public Error setMessage(final String message) { + this.message = message; + return this; + } + + + public Error setDetails(final List details) { + this.details = details; + return this; + } + + + public Error setInformationLink(final String informationLink) { + this.informationLink = informationLink; + return this; + } + + + public Error setDebugId(final String debugId) { + this.debugId = debugId; + return this; + } + + + public Error setCode(final String code) { + this.code = code; + return this; + } } diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/ErrorDetails.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/ErrorDetails.java index d054eb1ebd00ce27d3f3e8a80309d67c529ab91a..31c64868828c6091fabef9096879a9cc4f4a3710 100644 --- a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/ErrorDetails.java +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/ErrorDetails.java @@ -19,46 +19,30 @@ public class ErrorDetails { public ErrorDetails() { } - /** - * Parameterized Constructor - */ + public ErrorDetails(String field, String issue) { this.field = field; this.issue = issue; } - /** - * Name of the field that caused the error. - */ - @SuppressWarnings("all") + public String getField() { return this.field; } - /** - * Reason for the error. - */ - @SuppressWarnings("all") + public String getIssue() { return this.issue; } - /** - * Name of the field that caused the error. - * @return this - */ - @SuppressWarnings("all") + public ErrorDetails setField(final String field) { this.field = field; return this; } - /** - * Reason for the error. - * @return this - */ - @SuppressWarnings("all") + public ErrorDetails setIssue(final String issue) { this.issue = issue; return this; diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/FmfDetails.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/FmfDetails.java index 2ef402f4c35366b5227d694cf349191846ef69ce..0341cc49fe459013728b6b30ecdc26a43a31eabb 100644 --- a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/FmfDetails.java +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/FmfDetails.java @@ -19,71 +19,46 @@ public class FmfDetails{ */ private String description; - /** - * Type of filter. - */ + public String getFilterType() { return this.filterType; } - /** - * Filter Identifier. - */ - + public String getFilterId() { return this.filterId; } - /** - * Name of the filter - */ - public String getName() { return this.name; } - /** - * Description of the filter. - */ + public String getDescription() { return this.description; } - /** - * Type of filter. - * @return this - */ - + public FmfDetails setFilterType(final String filterType) { this.filterType = filterType; return this; } - /** - * Filter Identifier. - * @return this - */ - + public FmfDetails setFilterId(final String filterId) { this.filterId = filterId; return this; } - /** - * Name of the filter - * @return this - */ + public FmfDetails setName(final String name) { this.name = name; return this; } - /** - * Description of the filter. - * @return this - */ + public FmfDetails setDescription(final String description) { this.description = description; @@ -119,7 +94,6 @@ public class FmfDetails{ } @Override - public int hashCode() { final int PRIME = 59; int result = 1; diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Links.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Links.java index 55d66654f09356c2399a675a0528ed70eb761be6..ebb7376c88b740a2ab1ccf4d306ba6a22ee50471 100644 --- a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Links.java +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Links.java @@ -23,9 +23,7 @@ public class Links { public Links() { } - /** - * Parameterized Constructor - */ + public Links(String href, String rel) { this.href = href; this.rel = rel; diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Order.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Order.java index a9ecd54672be1fe729c5b54cd3a654cd373ec810..afb681a52b28e120431416e14c5c8ac2b10eb1ca 100644 --- a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Order.java +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Order.java @@ -65,25 +65,16 @@ public class Order { public Order() { } - /** - * Parameterized Constructor - */ + public Order(Amount amount) { this.amount = amount; } - /** - * Identifier of the order transaction. - */ - public String getId() { return this.id; } - /** - * Identifier to the purchase unit associated with this object. Obsolete. Use one in cart_base. - */ public String getPurchaseUnitReferenceId() { return this.purchaseUnitReferenceId; @@ -91,109 +82,73 @@ public class Order { - /** - * Amount being collected. - */ - + public Amount getAmount() { return this.amount; } - /** - * specifies payment mode of the transaction - */ + public String getPaymentMode() { return this.paymentMode; } - /** - * State of the order transaction. - */ - + public String getState() { return this.state; } - /** - * Reason code for the transaction state being Pending or Reversed. This field will replace pending_reason field eventually. Only supported when the `payment_method` is set to `paypal`. - */ - + public String getReasonCode() { return this.reasonCode; } - /** - * The level of seller protection in force for the transaction. - */ - + public String getProtectionEligibility() { return this.protectionEligibility; } - /** - * The kind of seller protection in force for the transaction. This property is returned only when the `protection_eligibility` property is set to `ELIGIBLE`or `PARTIALLY_ELIGIBLE`. Only supported when the `payment_method` is set to `paypal`. Allowed values:
`ITEM_NOT_RECEIVED_ELIGIBLE`- Sellers are protected against claims for items not received.
`UNAUTHORIZED_PAYMENT_ELIGIBLE`- Sellers are protected against claims for unauthorized payments.
One or both of the allowed values can be returned. - */ + public String getProtectionEligibilityType() { return this.protectionEligibilityType; } - /** - * ID of the Payment resource that this transaction is based on. - */ - + public String getParentPayment() { return this.parentPayment; } - /** - * Fraud Management Filter (FMF) details applied for the payment that could result in accept/deny/pending action. - */ - + public FmfDetails getFmfDetails() { return this.fmfDetails; } - /** - * Time the resource was created in UTC ISO8601 format. - */ - + public String getCreateTime() { return this.createTime; } - /** - * Time the resource was last updated in UTC ISO8601 format. - */ public String getUpdateTime() { return this.updateTime; } - /** - */ - + public List getLinks() { return this.links; } - /** - * Identifier of the order transaction. - * @return this - */ + public Order setId(final String id) { this.id = id; return this; } - /** - * Identifier to the purchase unit associated with this object. Obsolete. Use one in cart_base. - * @return this - */ + public Order setPurchaseUnitReferenceId(final String purchaseUnitReferenceId) { this.purchaseUnitReferenceId = purchaseUnitReferenceId; @@ -202,112 +157,70 @@ public class Order { - /** - * Amount being collected. - * @return this - */ - public Order setAmount(final Amount amount) { this.amount = amount; return this; } - /** - * specifies payment mode of the transaction - * @return this - */ - + public Order setPaymentMode(final String paymentMode) { this.paymentMode = paymentMode; return this; } - /** - * State of the order transaction. - * @return this - */ - + public Order setState(final String state) { this.state = state; return this; } - /** - * Reason code for the transaction state being Pending or Reversed. This field will replace pending_reason field eventually. Only supported when the `payment_method` is set to `paypal`. - * @return this - */ - + public Order setReasonCode(final String reasonCode) { this.reasonCode = reasonCode; return this; } - /** - * The level of seller protection in force for the transaction. - * @return this - */ - + public Order setProtectionEligibility(final String protectionEligibility) { this.protectionEligibility = protectionEligibility; return this; } - /** - * The kind of seller protection in force for the transaction. This property is returned only when the `protection_eligibility` property is set to `ELIGIBLE`or `PARTIALLY_ELIGIBLE`. Only supported when the `payment_method` is set to `paypal`. Allowed values:
`ITEM_NOT_RECEIVED_ELIGIBLE`- Sellers are protected against claims for items not received.
`UNAUTHORIZED_PAYMENT_ELIGIBLE`- Sellers are protected against claims for unauthorized payments.
One or both of the allowed values can be returned. - * @return this - */ + public Order setProtectionEligibilityType(final String protectionEligibilityType) { this.protectionEligibilityType = protectionEligibilityType; return this; } - /** - * ID of the Payment resource that this transaction is based on. - * @return this - */ - + public Order setParentPayment(final String parentPayment) { this.parentPayment = parentPayment; return this; } - /** - * Fraud Management Filter (FMF) details applied for the payment that could result in accept/deny/pending action. - * @return this - */ + public Order setFmfDetails(final FmfDetails fmfDetails) { this.fmfDetails = fmfDetails; return this; } - /** - * Time the resource was created in UTC ISO8601 format. - * @return this - */ + public Order setCreateTime(final String createTime) { this.createTime = createTime; return this; } - /** - * Time the resource was last updated in UTC ISO8601 format. - * @return this - */ public Order setUpdateTime(final String updateTime) { this.updateTime = updateTime; return this; } - /** - * - * @return this - */ - + public Order setLinks(final List links) { this.links = links; return this; diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Payee.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Payee.java index 9c09ebd420416d36c62c54084d1e2ff6d5d91cb0..10e3b8f4790ef88ba75d1de1c9f5cf12261bd1c9 100644 --- a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Payee.java +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Payee.java @@ -40,116 +40,73 @@ public class Payee { public Payee() { } - /** - * Email Address associated with the Payee's PayPal Account. If the provided email address is not associated with any PayPal Account, the payee can only receive PayPal Wallet Payments. Direct Credit Card Payments will be denied due to card compliance requirements. - */ - @SuppressWarnings("all") + public String getEmail() { return this.email; } - /** - * Encrypted PayPal account identifier for the Payee. - */ - @SuppressWarnings("all") + public String getMerchantId() { return this.merchantId; } - /** - * First Name of the Payee. - */ - @SuppressWarnings("all") + public String getFirstName() { return this.firstName; } - /** - * Last Name of the Payee. - */ - @SuppressWarnings("all") + public String getLastName() { return this.lastName; } - /** - * Unencrypted PayPal account Number of the Payee - */ - @SuppressWarnings("all") + public String getAccountNumber() { return this.accountNumber; } - /** - * Information related to the Payee. - */ - @SuppressWarnings("all") + public Phone getPhone() { return this.phone; } - /** - * Email Address associated with the Payee's PayPal Account. If the provided email address is not associated with any PayPal Account, the payee can only receive PayPal Wallet Payments. Direct Credit Card Payments will be denied due to card compliance requirements. - * @return this - */ - @SuppressWarnings("all") + public Payee setEmail(final String email) { this.email = email; return this; } - /** - * Encrypted PayPal account identifier for the Payee. - * @return this - */ - @SuppressWarnings("all") + public Payee setMerchantId(final String merchantId) { this.merchantId = merchantId; return this; } - /** - * First Name of the Payee. - * @return this - */ - @SuppressWarnings("all") + public Payee setFirstName(final String firstName) { this.firstName = firstName; return this; } - /** - * Last Name of the Payee. - * @return this - */ - @SuppressWarnings("all") + public Payee setLastName(final String lastName) { this.lastName = lastName; return this; } - /** - * Unencrypted PayPal account Number of the Payee - * @return this - */ - @SuppressWarnings("all") + public Payee setAccountNumber(final String accountNumber) { this.accountNumber = accountNumber; return this; } - /** - * Information related to the Payee. - * @return this - */ - @SuppressWarnings("all") + public Payee setPhone(final Phone phone) { this.phone = phone; return this; } - @Override - @SuppressWarnings("all") + public boolean equals(final Object o) { if (o == this) return true; if (!(o instanceof Payee)) return false; @@ -177,13 +134,12 @@ public class Payee { return true; } - @SuppressWarnings("all") + protected boolean canEqual(final Object other) { return other instanceof Payee; } @Override - @SuppressWarnings("all") public int hashCode() { final int PRIME = 59; int result = 1; diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Phone.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Phone.java index b820b1b8c806f333bba826376060a246ddf15968..8276fe312394945050fa643469e6dce0cb15b283 100644 --- a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Phone.java +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Phone.java @@ -26,70 +26,46 @@ public class Phone{ public Phone() { } - /** - * Parameterized Constructor - */ + public Phone(String countryCode, String nationalNumber) { this.countryCode = countryCode; this.nationalNumber = nationalNumber; } - /** - * Country code (from in E.164 format) - */ - @SuppressWarnings("all") + public String getCountryCode() { return this.countryCode; } - /** - * In-country phone number (from in E.164 format) - */ - @SuppressWarnings("all") + public String getNationalNumber() { return this.nationalNumber; } - /** - * Phone extension - */ - @SuppressWarnings("all") + public String getExtension() { return this.extension; } - /** - * Country code (from in E.164 format) - * @return this - */ - @SuppressWarnings("all") + public Phone setCountryCode(final String countryCode) { this.countryCode = countryCode; return this; } - /** - * In-country phone number (from in E.164 format) - * @return this - */ - @SuppressWarnings("all") + public Phone setNationalNumber(final String nationalNumber) { this.nationalNumber = nationalNumber; return this; } - /** - * Phone extension - * @return this - */ - @SuppressWarnings("all") + public Phone setExtension(final String extension) { this.extension = extension; return this; } @Override - @SuppressWarnings("all") public boolean equals(final Object o) { if (o == this) return true; if (!(o instanceof Phone)) return false; @@ -108,13 +84,11 @@ public class Phone{ return true; } - @SuppressWarnings("all") protected boolean canEqual(final Object other) { return other instanceof Phone; } @Override - @SuppressWarnings("all") public int hashCode() { final int PRIME = 59; int result = 1; diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/RedirectUrls.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/RedirectUrls.java index a98b49803b15b822e912cf1aa80271cd8cc90a22..05ea8421eb48eed36f702120e89c2b200c66cfba 100644 --- a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/RedirectUrls.java +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/RedirectUrls.java @@ -22,44 +22,30 @@ public class RedirectUrls{ public RedirectUrls() { } - /** - * Url where the payer would be redirected to after approving the payment. **Required for PayPal account payments.** - */ - @SuppressWarnings("all") + public String getReturnUrl() { return this.returnUrl; } - /** - * Url where the payer would be redirected to after canceling the payment. **Required for PayPal account payments.** - */ - @SuppressWarnings("all") + public String getCancelUrl() { return this.cancelUrl; } - /** - * Url where the payer would be redirected to after approving the payment. **Required for PayPal account payments.** - * @return this - */ - @SuppressWarnings("all") + public RedirectUrls setReturnUrl(final String returnUrl) { this.returnUrl = returnUrl; return this; } - /** - * Url where the payer would be redirected to after canceling the payment. **Required for PayPal account payments.** - * @return this - */ - @SuppressWarnings("all") + public RedirectUrls setCancelUrl(final String cancelUrl) { this.cancelUrl = cancelUrl; return this; } @Override - @SuppressWarnings("all") + public boolean equals(final Object o) { if (o == this) return true; if (!(o instanceof RedirectUrls)) return false; @@ -75,13 +61,11 @@ public class RedirectUrls{ return true; } - @SuppressWarnings("all") protected boolean canEqual(final Object other) { return other instanceof RedirectUrls; } @Override - @SuppressWarnings("all") public int hashCode() { final int PRIME = 59; int result = 1; diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Refund.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Refund.java index fa8fefebee7c51161e827489694d10833f19576e..72bc77532439b328ea2c15676fab3a0812a4a60e 100644 --- a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Refund.java +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Refund.java @@ -64,233 +64,152 @@ public class Refund { } - /** - * ID of the refund transaction. 17 characters max. - */ - + public String getId() { return this.id; } - /** - * Details including both refunded amount (to payer) and refunded fee (to payee). 10 characters max. - */ - + public Amount getAmount() { return this.amount; } - /** - * State of the refund. - */ - + public String getState() { return this.state; } - /** - * Reason description for the Sale transaction being refunded. - */ - public String getReason() { return this.reason; } - /** - * Your own invoice or tracking ID number. Character length and limitations: 127 single-byte alphanumeric characters. - */ - + public String getInvoiceNumber() { return this.invoiceNumber; } - /** - * ID of the Sale transaction being refunded. - */ + public String getSaleId() { return this.saleId; } - /** - * ID of the sale transaction being refunded. - */ public String getCaptureId() { return this.captureId; } - /** - * ID of the payment resource on which this transaction is based. - */ + public String getParentPayment() { return this.parentPayment; } - /** - * Description of what is being refunded for. - */ + public String getDescription() { return this.description; } - /** - * Time of refund as defined in [RFC 3339 Section 5.6](http://tools.ietf.org/html/rfc3339#section-5.6). - */ + public String getCreateTime() { return this.createTime; } - /** - * Time that the resource was last updated. - */ public String getUpdateTime() { return this.updateTime; } - /** - * The reason code for the refund state being pending - */ - + public String getReasonCode() { return this.reasonCode; } - /** - */ + public List getLinks() { return this.links; } - /** - * ID of the refund transaction. 17 characters max. - * @return this - */ - public Refund setId(final String id) { this.id = id; return this; } - /** - * Details including both refunded amount (to payer) and refunded fee (to payee). 10 characters max. - * @return this - */ - + public Refund setAmount(final Amount amount) { this.amount = amount; return this; } - /** - * State of the refund. - * @return this - */ + public Refund setState(final String state) { this.state = state; return this; } - /** - * Reason description for the Sale transaction being refunded. - * @return this - */ + public Refund setReason(final String reason) { this.reason = reason; return this; } - /** - * Your own invoice or tracking ID number. Character length and limitations: 127 single-byte alphanumeric characters. - * @return this - */ + public Refund setInvoiceNumber(final String invoiceNumber) { this.invoiceNumber = invoiceNumber; return this; } - /** - * ID of the Sale transaction being refunded. - * @return this - */ public Refund setSaleId(final String saleId) { this.saleId = saleId; return this; } - /** - * ID of the sale transaction being refunded. - * @return this - */ - + public Refund setCaptureId(final String captureId) { this.captureId = captureId; return this; } - /** - * ID of the payment resource on which this transaction is based. - * @return this - */ - + public Refund setParentPayment(final String parentPayment) { this.parentPayment = parentPayment; return this; } - /** - * Description of what is being refunded for. - * @return this - */ + public Refund setDescription(final String description) { this.description = description; return this; } - /** - * Time of refund as defined in [RFC 3339 Section 5.6](http://tools.ietf.org/html/rfc3339#section-5.6). - * @return this - */ + public Refund setCreateTime(final String createTime) { this.createTime = createTime; return this; } - /** - * Time that the resource was last updated. - * @return this - */ - + public Refund setUpdateTime(final String updateTime) { this.updateTime = updateTime; return this; } - /** - * The reason code for the refund state being pending - * @return this - */ public Refund setReasonCode(final String reasonCode) { this.reasonCode = reasonCode; return this; } - /** - * - * @return this - */ + public Refund setLinks(final List links) { this.links = links; diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/api/PayPalPayService.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/api/PayPalPayService.java new file mode 100644 index 0000000000000000000000000000000000000000..e6cce200bc51e77d89ae166b7fadd544915e0ce1 --- /dev/null +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/api/PayPalPayService.java @@ -0,0 +1,606 @@ +package com.egzosn.pay.paypal.v2.api; + + +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.locks.Lock; + +import org.apache.http.Header; +import org.apache.http.entity.ContentType; +import org.apache.http.message.BasicHeader; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.egzosn.pay.common.api.BasePayService; +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.common.bean.BillType; +import com.egzosn.pay.common.bean.CurType; +import com.egzosn.pay.common.bean.DefaultCurType; +import com.egzosn.pay.common.bean.MethodType; +import com.egzosn.pay.common.bean.NoticeParams; +import com.egzosn.pay.common.bean.NoticeRequest; +import com.egzosn.pay.common.bean.PayMessage; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.PayOutMessage; +import com.egzosn.pay.common.bean.RefundOrder; +import com.egzosn.pay.common.bean.RefundResult; +import com.egzosn.pay.common.bean.TransactionType; +import com.egzosn.pay.common.bean.result.PayException; +import com.egzosn.pay.common.exception.PayErrorException; +import com.egzosn.pay.common.http.HttpHeader; +import com.egzosn.pay.common.http.HttpStringEntity; +import com.egzosn.pay.common.http.ResponseEntity; +import com.egzosn.pay.common.http.UriVariables; +import com.egzosn.pay.common.util.IOUtils; +import com.egzosn.pay.common.util.Util; +import com.egzosn.pay.common.util.str.StringUtils; +import com.egzosn.pay.paypal.api.PayPalConfigStorage; +import com.egzosn.pay.paypal.v2.bean.Constants; +import com.egzosn.pay.paypal.v2.bean.PayPalRefundResult; +import com.egzosn.pay.paypal.v2.bean.PayPalTransactionType; +import com.egzosn.pay.paypal.v2.bean.order.ApplicationContext; +import com.egzosn.pay.paypal.v2.bean.order.Money; +import com.egzosn.pay.paypal.v2.bean.order.OrderRequest; +import com.egzosn.pay.paypal.v2.bean.order.PurchaseUnitRequest; +import com.egzosn.pay.paypal.v2.bean.order.ShippingDetail; +import com.egzosn.pay.paypal.v2.utils.PayPalUtil; + + +/** + * 贝宝支付配置存储 + * + * @author egan + *

+ * email egzosn@gmail.com + * date 2021-1-16 ‏‎22:15:09 + */ +public class PayPalPayService extends BasePayService implements PayPalPayServiceInf { + + + /** + * 沙箱环境 + */ + private static final String SANDBOX_REQ_URL = "https://api.sandbox.paypal.com/"; + /** + * 正式测试环境 + */ + private static final String REQ_URL = "https://api.paypal.com/"; + + private static final String NOTIFY_VALIDATE_URL = "https://ipnpb.paypal.com/cgi-bin/webscr?cmd=_notify-validate&"; + private static final String SANDBOX_NOTIFY_VALIDATE_URL = "https://ipnpb.sandbox.paypal.com/cgi-bin/webscr?cmd=_notify-validate&"; + + /** + * 获取对应的请求地址 + * + * @return 请求地址 + */ + @Override + public String getReqUrl(TransactionType transactionType) { + return (payConfigStorage.isTest() ? SANDBOX_REQ_URL : REQ_URL) + transactionType.getMethod(); + } + + + /** + * 获取通知校验对应的请求地址 + * + * @param params 回调参数 + * @return 请求地址 + */ + public String getNotifyReqUrl(Map params) { + return (payConfigStorage.isTest() ? SANDBOX_NOTIFY_VALIDATE_URL : NOTIFY_VALIDATE_URL) + UriVariables.getMapToParameters(params); + } + + + public PayPalPayService(PayPalConfigStorage payConfigStorage) { + super(payConfigStorage); + } + + + /** + * 获取请求token + * + * @return 授权令牌 + */ + public String getAccessToken() { + return getAccessToken(false); + } + + /** + * 获取授权令牌 + * + * @param forceRefresh 是否重新获取, true重新获取 + * @return 新的授权令牌 + * @throws PayErrorException 支付异常 + */ + public String getAccessToken(boolean forceRefresh) throws PayErrorException { + Lock lock = payConfigStorage.getAccessTokenLock(); + try { + lock.lock(); + if (forceRefresh) { + payConfigStorage.expireAccessToken(); + } + if (payConfigStorage.isAccessTokenExpired()) { + Map header = new HashMap<>(); + header.put("Authorization", "Basic " + authorizationString(getPayConfigStorage().getAppId(), getPayConfigStorage().getKeyPrivate())); + header.put("Accept", "application/json"); + header.put("Content-Type", "application/x-www-form-urlencoded"); + try { + HttpStringEntity entity = new HttpStringEntity("grant_type=client_credentials", header); + JSONObject resp = getHttpRequestTemplate().postForObject(getReqUrl(PayPalTransactionType.AUTHORIZE), entity, JSONObject.class); + payConfigStorage.updateAccessToken(String.format("%s %s", resp.getString("token_type"), resp.getString("access_token")), resp.getIntValue("expires_in")); + + } + catch (UnsupportedEncodingException e) { + throw new PayErrorException(new PayException("failure", e.getMessage())); + } + return payConfigStorage.getAccessToken(); + } + } + finally { + lock.unlock(); + } + return payConfigStorage.getAccessToken(); + } + + + /** + * IPN 地址设置的路径:https://developer.paypal.com/developer/ipnSimulator/ + * 参数解析与校验 https://developer.paypal.com/docs/api-basics/notifications/ipn/IPNIntro/#id08CKFJ00JYK + * 1.Check that the payment_status is Completed. + * 2.If the payment_status is Completed, check the txn_id against the previous PayPal transaction that you processed to ensure the IPN message is not a duplicate. + * 3.Check that the receiver_email is an email address registered in your PayPal account. + * 4.Check that the price (carried in mc_gross) and the currency (carried in mc_currency) are correct for the item (carried in item_name or item_number). + * + * @param params 回调回来的参数集 + * @return 是否成功 true成功 + */ + @Deprecated + @Override + public boolean verify(Map params) { + + throw new PayErrorException(new PayException("failure", "payPal V2版本不支持此校验方式")); + + } + + + /** + * 保留IPN的校验方式 + * + * @param noticeParams 参数 + * @return 结果 + */ + public boolean verifyIpn(NoticeParams noticeParams) { + final Map params = noticeParams.getBody(); + Object paymentStatus = params.get("payment_status"); + if (!"Completed".equals(paymentStatus)) { + LOG.warn("状态未完成:" + paymentStatus); + return false; + } + String resp = getHttpRequestTemplate().getForObject(getNotifyReqUrl(params), authHeader(), String.class); + return "VERIFIED".equals(resp); + + } + + @Override + public boolean verify(NoticeParams noticeParams) { + + final Map> headers = noticeParams.getHeaders(); + if (null == headers || headers.isEmpty()) { + throw new PayErrorException(new PayException("failure", "校验失败,请求头不能为空")); + } + + + String clientCertificateLocation = noticeParams.getHeader(Constants.PAYPAL_HEADER_CERT_URL); + ResponseEntity clientCertificateResponseEntity = requestTemplate.getForObjectEntity(clientCertificateLocation, InputStream.class); + if (clientCertificateResponseEntity.getStatusCode() > 400) { + LOG.error("获取证书信息失败,无法进行webHook校验:{}", clientCertificateLocation); + return false; + } + InputStream inputStream = clientCertificateResponseEntity.getBody(); + Collection clientCerts = PayPalUtil.getCertificateFromStream(inputStream); + String webHookId = payConfigStorage.getWebHookId(); + String actualSignatureEncoded = noticeParams.getHeader(Constants.PAYPAL_HEADER_TRANSMISSION_SIG); + String authAlgo = noticeParams.getHeader(Constants.PAYPAL_HEADER_AUTH_ALGO); + String transmissionId = noticeParams.getHeader(Constants.PAYPAL_HEADER_TRANSMISSION_ID); + String transmissionTime = noticeParams.getHeader(Constants.PAYPAL_HEADER_TRANSMISSION_TIME); + String requestBody = noticeParams.getBodyStr(); + String expectedSignature = String.format("%s|%s|%s|%s", transmissionId, transmissionTime, webHookId, PayPalUtil.crc32(requestBody)); + boolean isDataValid = PayPalUtil.validateData(clientCerts, authAlgo, actualSignatureEncoded, expectedSignature); + LOG.debug("数据校验结果: {}", isDataValid); + return isDataValid; + + } + + /** + * 将请求参数或者请求流转化为 Map + * + * @param request 通知请求 + * @return 获得回调的请求参数 + */ + @Override + public NoticeParams getNoticeParams(NoticeRequest request) { + NoticeParams noticeParams = new NoticeParams(); + try (InputStream is = request.getInputStream()) { + String body = IOUtils.toString(is); + noticeParams.setBodyStr(body); + noticeParams.setBody(JSON.parseObject(body)); + } + catch (IOException e) { + throw new PayErrorException(new PayException("failure", "获取回调参数异常"), e); + } + Map> headers = new HashMap<>(); + Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String name = headerNames.nextElement(); + headers.put(name, Collections.list(request.getHeaders(name))); + } + noticeParams.setHeaders(headers); + return noticeParams; + } + + /** + * 获取授权请求头 + * + * @return 授权请求头 + */ + private HttpHeader authHeader() { + + List

headers = new ArrayList<>(); + headers.add(new BasicHeader("Authorization", getAccessToken())); + headers.add(new BasicHeader("PayPal-Request-Id", UUID.randomUUID().toString())); + + return new HttpHeader(headers); + } + + + /** + * 页面转跳支付, 返回对应页面重定向信息 + * + * @param order 订单信息 + * @return 对应页面重定向信息 + */ + @Override + public String toPay(PayOrder order) { + order.setTransactionType(PayPalTransactionType.CHECKOUT); + return super.toPay(order); + } + + private ApplicationContext initUrl(ApplicationContext applicationContext, PayOrder order) { + String cancelUrl = (String) order.getAttr("cancelUrl"); + if (StringUtils.isEmpty(cancelUrl)) { + cancelUrl = payConfigStorage.getCancelUrl(); + } + + String returnUrl = (String) order.getAttr("returnUrl"); + if (StringUtils.isEmpty(returnUrl)) { + returnUrl = payConfigStorage.getReturnUrl(); + } + applicationContext + .cancelUrl(cancelUrl) + .returnUrl(returnUrl); + + + return applicationContext; + } + + private ApplicationContext createApplicationContext(PayOrder order) { + ApplicationContext applicationContext = new ApplicationContext(); + initUrl(applicationContext, order); + String brandName = (String) order.getAttr("brandName"); + if (StringUtils.isEmpty(brandName)) { + applicationContext.setBrandName(brandName); + } + + String landingPage = (String) order.getAttr("landingPage"); + if (StringUtils.isEmpty(landingPage)) { + applicationContext.setLandingPage(landingPage); + } + + String shippingPreference = (String) order.getAttr("shippingPreference"); + if (StringUtils.isEmpty(shippingPreference)) { + applicationContext.setShippingPreference(shippingPreference); + } + + String userAction = (String) order.getAttr("userAction"); + if (StringUtils.isEmpty(userAction)) { + applicationContext.setUserAction(userAction); + } + + + return applicationContext; + } + + /** + * 返回创建的订单信息 + * 订单信息与接口地址 https://developer.paypal.com/docs/api/orders/v2 + * + * @param order 支付订单 + * @return 订单信息 + * @see PayOrder 支付订单信息 + */ + @Override + public Map orderInfo(PayOrder order) { + if (null == order.getTransactionType()) { + order.setTransactionType(PayPalTransactionType.CHECKOUT); + } + OrderRequest orderRequest = new OrderRequest(); + orderRequest.setCheckoutPaymentIntent("CAPTURE"); + + orderRequest.setApplicationContext(createApplicationContext(order)); + + List purchaseUnitRequests = new ArrayList(); + CurType curType = order.getCurType(); + if (null == curType) { + curType = DefaultCurType.USD; + } + PurchaseUnitRequest purchaseUnitRequest = new PurchaseUnitRequest() + .description(order.getSubject()) + .invoiceId((String) order.getAttr("invoiceId")) + .customId(order.getOutTradeNo()) + .money(new Money() + .currencyCode(curType.getType()) + .value(Util.conversionAmount(order.getPrice()).toString())); + + Object shippingDetail = order.getAttr("shippingDetail"); + if (shippingDetail instanceof ShippingDetail) { + purchaseUnitRequest.setShippingDetail((ShippingDetail) shippingDetail); + } + else { + ShippingDetail shippingDetail1 = JSON.parseObject(JSON.toJSONString(shippingDetail), ShippingDetail.class); + purchaseUnitRequest.setShippingDetail(shippingDetail1); + } + + purchaseUnitRequests.add(purchaseUnitRequest); + orderRequest.setPurchaseUnits(purchaseUnitRequests); + + HttpStringEntity entity = new HttpStringEntity(JSON.toJSONString(orderRequest), ContentType.APPLICATION_JSON); + HttpHeader header = authHeader(); + header.addHeader(new BasicHeader("prefer", "return=representation")); + + entity.setHeaders(header); + JSONObject resp = getHttpRequestTemplate().postForObject(getReqUrl(order.getTransactionType()), entity, JSONObject.class); + if ("created".equalsIgnoreCase(resp.getString("status")) && StringUtils.isNotEmpty(resp.getString("id"))) { + order.setTradeNo(resp.getString("id")); + } + return preOrderHandler(resp, order); + } + + @Override + public PayOutMessage getPayOutMessage(String code, String message) { + + return PayOutMessage.TEXT().content(code).build(); + } + + @Override + public PayOutMessage successPayOutMessage(PayMessage payMessage) { + + return PayOutMessage.TEXT().content("200").build(); + } + + @Override + public String buildRequest(Map orderInfo, MethodType method) { + if (orderInfo instanceof JSONObject) { + JSONObject resp = (JSONObject) orderInfo; + JSONArray links = resp.getJSONArray("links"); + for (int i = 0; i < links.size(); i++) { + JSONObject link = links.getJSONObject(i); + if ("approve".equals(link.getString("rel"))) { + return String.format("", link.getString("href")); + } + } + } + return ""; + } + + @Override + public String getQrPay(PayOrder order) { + return null; + } + + @Override + public Map microPay(PayOrder order) { + return null; + } + + /** + * 交易查询接口 + * + * @param tradeNo 支付平台订单号 + * @param outTradeNo 商户单号 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(String tradeNo, String outTradeNo) { + return getHttpRequestTemplate().getForObject(getReqUrl(PayPalTransactionType.ORDERS_GET), authHeader(), JSONObject.class, tradeNo); + } + + /** + * 交易查询接口 + * + * @param assistOrder 查询条件 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(AssistOrder assistOrder) { + return getHttpRequestTemplate().getForObject(getReqUrl(PayPalTransactionType.ORDERS_GET), authHeader(), JSONObject.class, assistOrder.getTradeNo()); + } + + @Override + public Map close(String tradeNo, String outTradeNo) { + return null; + } + + /** + * 交易关闭接口 + * + * @param assistOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 + */ + @Override + public Map close(AssistOrder assistOrder) { + throw new UnsupportedOperationException("不支持该操作"); + } + + /** + * 注意:最好在付款成功之后回调时进行调用 + * 确认订单并返回确认后订单信息 + * 注意:此方法一个订单只能调用一次, 建议在支付回调时进行调用 + * 这里主要用来获取captureId使用,后续退款,查订单等等使用,用来替换下单返回的id + * 详情: https://developer.paypal.com/docs/api/orders/v2/#orders_capture + * + * @param tradeNo paypal下单成功之后返回的订单号 + * @return 确认后订单信息 + * 获取captureId + */ + @Override + public Map ordersCapture(String tradeNo) { + final HttpHeader header = authHeader(); + header.addHeader(new BasicHeader("Content-Type", "application/json")); + JSONObject ordersCaptureInfo = getHttpRequestTemplate().postForObject(getReqUrl(PayPalTransactionType.ORDERS_CAPTURE), header, JSONObject.class, tradeNo); +// String captureId = ordersCaptureInfo.getJSONArray("purchaseUnits").getJSONObject(0).getJSONObject("payments").getJSONArray("captures").getJSONObject(0).getString("id"); + return ordersCaptureInfo; + } + + /** + * 确认订单之后获取订单信息 + * 详情: https://developer.paypal.com/docs/api/payments/v2/#captures_get + * + * @param captureId 确认付款订单之后生成的id + * @return 确认付款订单详情 + *
+     *     {
+     *   "id": "2GG279541U471931P",
+     *   "status": "COMPLETED",
+     *   "status_details": {},
+     *   "amount": {
+     *     "total": "10.99",
+     *     "currency": "USD"
+     *   },
+     *   "final_capture": true,
+     *   "seller_protection": {
+     *     "status": "ELIGIBLE",
+     *     "dispute_categories": [
+     *       "ITEM_NOT_RECEIVED",
+     *       "UNAUTHORIZED_TRANSACTION"
+     *     ]
+     *   },
+     *   "seller_receivable_breakdown": {
+     *     "gross_amount": {
+     *       "total": "10.99",
+     *       "currency": "USD"
+     *     },
+     *     "paypal_fee": {
+     *       "value": "0.33",
+     *       "currency": "USD"
+     *     },
+     *     "net_amount": {
+     *       "value": "10.66",
+     *       "currency": "USD"
+     *     },
+     *     "receivable_amount": {
+     *       "currency_code": "CNY",
+     *       "value": "59.26"
+     *     },
+     *     "paypal_fee_in_receivable_currency": {
+     *       "currency_code": "CNY",
+     *       "value": "1.13"
+     *     },
+     *     "exchange_rate": {
+     *       "source_currency": "USD",
+     *       "target_currency": "CNY",
+     *       "value": "5.9483297432325"
+     *     }
+     *   },
+     *   "invoice_id": "INVOICE-123",
+     *   "create_time": "2017-09-11T23:24:01Z",
+     *   "update_time": "2017-09-11T23:24:01Z",
+     *   "links": [
+     *     {
+     *       "href": "https://api-m.paypal.com/v2/payments/captures/2GG279541U471931P",
+     *       "rel": "self",
+     *       "method": "GET"
+     *     },
+     *     {
+     *       "href": "https://api-m.paypal.com/v2/payments/captures/2GG279541U471931P/refund",
+     *       "rel": "refund",
+     *       "method": "POST"
+     *     },
+     *     {
+     *       "href": "https://api-m.paypal.com/v2/payments/authorizations/0VF52814937998046",
+     *       "rel": "up",
+     *       "method": "GET"
+     *     }
+     *   ]
+     * }
+     *
+     * 
+ */ + @Override + public Map getCapture(String captureId) { + JSONObject ordersCaptureInfo = getHttpRequestTemplate().getForObject(getReqUrl(PayPalTransactionType.GET_CAPTURE), authHeader(), JSONObject.class, captureId); + return ordersCaptureInfo; + } + + /** + * 申请退款接口 + * 通过captureId发起退款 详情: https://developer.paypal.com/docs/api/payments/v2/#captures_refund + * captureId 详情{@link #ordersCapture(String)} + * + * @param refundOrder 退款订单信息 + * @return 返回支付方申请退款后的结果 + */ + @Override + public RefundResult refund(RefundOrder refundOrder) { + JSONObject request = new JSONObject(); + Money amount = new Money(); + if (null == refundOrder.getCurType()) { + refundOrder.setCurType(DefaultCurType.USD); + } + amount.setCurrencyCode(refundOrder.getCurType().getType()); + amount.value(Util.conversionAmount(refundOrder.getRefundAmount()).toString()); + request.put("amount", amount); + request.put("note_to_payer", refundOrder.getDescription()); + request.put("invoiceId", refundOrder.getOutTradeNo()); + + HttpStringEntity httpEntity = new HttpStringEntity(request.toJSONString(), ContentType.APPLICATION_JSON); + httpEntity.setHeaders(authHeader()); + //TODO: 这里TradeNo为{@link #ordersCapture} 确认订单之后的captureId + String captureId = refundOrder.getTradeNo(); + JSONObject resp = getHttpRequestTemplate().postForObject(getReqUrl(PayPalTransactionType.REFUND), httpEntity, JSONObject.class, captureId); + PayPalRefundResult payPalRefundResult = new PayPalRefundResult(resp, refundOrder.getTradeNo()); + refundOrder.setRefundNo(payPalRefundResult.getRefundNo()); + return payPalRefundResult; + } + + /** + * 查询退款 + * 通过退款id获取退款信息 详情:https://developer.paypal.com/docs/api/payments/v2/#refunds + * + * @param refundOrder 退款订单单号信息 + * @return 返回支付方查询退款后的结果 + */ + @Override + public Map refundquery(RefundOrder refundOrder) { + JSONObject resp = getHttpRequestTemplate().getForObject(getReqUrl(PayPalTransactionType.REFUND_GET), authHeader(), JSONObject.class, refundOrder.getRefundNo()); + return resp; + } + + @Override + public Map downloadBill(Date billDate, BillType billType) { + return Collections.emptyMap(); + } + + +} diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/api/PayPalPayServiceInf.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/api/PayPalPayServiceInf.java new file mode 100644 index 0000000000000000000000000000000000000000..01435abb4d22135ca6d80ffb9799e4830c267a44 --- /dev/null +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/api/PayPalPayServiceInf.java @@ -0,0 +1,103 @@ +package com.egzosn.pay.paypal.v2.api; + +import java.util.Map; + +/** + * PayPal 支付接口 + * + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2021/2/20
+ * 
+ */ +public interface PayPalPayServiceInf { + + /** + * 注意:最好在付款成功之后回调时进行调用 + * 确认付款订单并返回确认后订单信息 + * 注意:此方法一个订单只能调用一次, 建议在支付回调时进行调用 + * 这里主要用来获取captureId使用,后续退款,查订单等等使用,用来替换下单返回的id + * 详情: https://developer.paypal.com/docs/api/orders/v2/#orders_capture + * + * @param tradeNo paypal下单成功之后返回的订单号 + * @return 确认付款后订单信息 + */ + Map ordersCapture(String tradeNo); + + /** + * 确认订单之后获取订单信息 + * 详情: https://developer.paypal.com/docs/api/payments/v2/#captures_get + * + * @param captureId 确认付款订单之后生成的id + * @return 确认付款订单详情 + *
+     *     {
+     *   "id": "2GG279541U471931P",
+     *   "status": "COMPLETED",
+     *   "status_details": {},
+     *   "amount": {
+     *     "total": "10.99",
+     *     "currency": "USD"
+     *   },
+     *   "final_capture": true,
+     *   "seller_protection": {
+     *     "status": "ELIGIBLE",
+     *     "dispute_categories": [
+     *       "ITEM_NOT_RECEIVED",
+     *       "UNAUTHORIZED_TRANSACTION"
+     *     ]
+     *   },
+     *   "seller_receivable_breakdown": {
+     *     "gross_amount": {
+     *       "total": "10.99",
+     *       "currency": "USD"
+     *     },
+     *     "paypal_fee": {
+     *       "value": "0.33",
+     *       "currency": "USD"
+     *     },
+     *     "net_amount": {
+     *       "value": "10.66",
+     *       "currency": "USD"
+     *     },
+     *     "receivable_amount": {
+     *       "currency_code": "CNY",
+     *       "value": "59.26"
+     *     },
+     *     "paypal_fee_in_receivable_currency": {
+     *       "currency_code": "CNY",
+     *       "value": "1.13"
+     *     },
+     *     "exchange_rate": {
+     *       "source_currency": "USD",
+     *       "target_currency": "CNY",
+     *       "value": "5.9483297432325"
+     *     }
+     *   },
+     *   "invoice_id": "INVOICE-123",
+     *   "create_time": "2017-09-11T23:24:01Z",
+     *   "update_time": "2017-09-11T23:24:01Z",
+     *   "links": [
+     *     {
+     *       "href": "https://api-m.paypal.com/v2/payments/captures/2GG279541U471931P",
+     *       "rel": "self",
+     *       "method": "GET"
+     *     },
+     *     {
+     *       "href": "https://api-m.paypal.com/v2/payments/captures/2GG279541U471931P/refund",
+     *       "rel": "refund",
+     *       "method": "POST"
+     *     },
+     *     {
+     *       "href": "https://api-m.paypal.com/v2/payments/authorizations/0VF52814937998046",
+     *       "rel": "up",
+     *       "method": "GET"
+     *     }
+     *   ]
+     * }
+     *
+     * 
+ */ + Map getCapture(String captureId); +} diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/Constants.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/Constants.java new file mode 100644 index 0000000000000000000000000000000000000000..3f8fc2061e53148c13f0efb4c209c94d7a1ddccb --- /dev/null +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/Constants.java @@ -0,0 +1,59 @@ +package com.egzosn.pay.paypal.v2.bean; + +/** + * @author Egan + * email egan@egzosn.com + * date 2023/9/12 + */ +public final class Constants { + private Constants() { + } + + /** + * PayPal webhook transmission ID HTTP request header + */ + public static final String PAYPAL_HEADER_TRANSMISSION_ID = "PAYPAL-TRANSMISSION-ID"; + + /** + * PayPal webhook transmission time HTTP request header + */ + public static final String PAYPAL_HEADER_TRANSMISSION_TIME = "PAYPAL-TRANSMISSION-TIME"; + + /** + * PayPal webhook transmission signature HTTP request header + */ + public static final String PAYPAL_HEADER_TRANSMISSION_SIG = "PAYPAL-TRANSMISSION-SIG"; + /** + * PayPal webhook certificate URL HTTP request header + */ + public static final String PAYPAL_HEADER_CERT_URL = "PAYPAL-CERT-URL"; + + /** + * PayPal webhook authentication algorithm HTTP request header + */ + public static final String PAYPAL_HEADER_AUTH_ALGO = "PAYPAL-AUTH-ALGO"; + + /** + * Trust Certificate Location to be used to validate webhook certificates + */ + public static final String PAYPAL_TRUST_CERT_URL = "webhook.trustCert"; + + + /** + * Default Trust Certificate that comes packaged with SDK. + */ + public static final String PAYPAL_TRUST_DEFAULT_CERT = "DigiCertSHA2ExtendedValidationServerCA.crt"; + + /** + * Webhook Id to be set for validation purposes + */ + public static final String PAYPAL_WEBHOOK_ID = "webhook.id"; + + /** + * Webhook Id to be set for validation purposes + */ + public static final String PAYPAL_WEBHOOK_CERTIFICATE_AUTHTYPE = "webhook.authType"; + public static final String ID = "id"; + + +} diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/PayPalOrder.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/PayPalOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..24093e1a872c002e1a493b19b5ed6531746c5899 --- /dev/null +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/PayPalOrder.java @@ -0,0 +1,189 @@ +package com.egzosn.pay.paypal.v2.bean; + +import com.egzosn.pay.common.bean.CurType; +import com.egzosn.pay.common.bean.DefaultCurType; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.paypal.v2.bean.order.ShippingDetail; + +/** + * PayPal付款订单 + * + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2021/1/12
+ * 
+ */ +public class PayPalOrder extends PayOrder { + + /** + * 该标签将覆盖PayPal网站上PayPal帐户中的公司名称 + */ + private String brandName; + /** + * 支付成功之后回调的页面 + */ + private String returnUrl; + + /** + * 取消支付的页面 + *
+     * 注意:这里不是异步回调的通知
+     * IPN 地址设置的路径:https://developer.paypal.com/developer/ipnSimulator/
+     * 
+ */ + private String cancelUrl; + /** + * LOGIN。当客户单击PayPal Checkout时,客户将被重定向到页面以登录PayPal并批准付款。 + * BILLING。当客户单击PayPal Checkout时,客户将被重定向到一个页面,以输入信用卡或借记卡以及完成购买所需的其他相关账单信息 + * NO_PREFERENCE。当客户单击“ PayPal Checkout”时,将根据其先前的交互方式将其重定向到页面以登录PayPal并批准付款,或重定向至页面以输入信用卡或借记卡以及完成购买所需的其他相关账单信息使用PayPal。 + * 默认值:NO_PREFERENCE + */ + private String landingPage = "NO_PREFERENCE"; + + /** + * GET_FROM_FILE。使用贝宝网站上客户提供的送货地址。 + * NO_SHIPPING。从PayPal网站编辑送货地址。推荐用于数字商品 + * SET_PROVIDED_ADDRESS。使用商家提供的地址。客户无法在PayPal网站上更改此地址 + */ + private String shippingPreference = "NO_SHIPPING"; + /** + * CONTINUE。将客户重定向到PayPal付款页面后,将出现“ 继续”按钮。当结帐流程启动时最终金额未知时,请使用此选项,并且您想将客户重定向到商家页面而不处理付款。 + * PAY_NOW。将客户重定向到PayPal付款页面后,出现“ 立即付款”按钮。当启动结帐时知道最终金额并且您要在客户单击“ 立即付款”时立即处理付款时,请使用此选项。 + */ + private String userAction = "CONTINUE"; + + private ShippingDetail shippingDetail; + /** + * API调用者为购买单元提供的外部ID。当您必须通过“补丁”更新订单时,需要多个购买单位。如果忽略该值,且订单只包含一个购买单元,PayPal将该值设置为' default '。 + */ + private String referenceId; + /** + * API调用者为该订单提供的外部发票号码。出现在付款人的交易历史记录和付款人收到的电子邮件中 + */ + private String invoiceId; + + + /** + * API调用者提供的外部ID。用于协调客户端交易与PayPal交易。出现在交易和结算报告中,但付款人不可见。 + * + * @return 外部ID + */ + public String getCustomId() { + return super.getOutTradeNo(); + } + + /** + * /** + * API调用者提供的外部ID。用于协调客户端交易与PayPal交易。出现在交易和结算报告中,但付款人不可见。 + * + * @param customId 外部ID + */ + public void setCustomId(String customId) { + super.setOutTradeNo(customId); + } + + + public String getDescription() { + return super.getSubject(); + } + + public void setDescription(String description) { + super.setSubject(description); + } + + + public String getCurrencyCode() { + CurType curType = super.getCurType(); + if (null == curType) { + curType = DefaultCurType.USD; + } + return curType.getType(); + } + + public void setCurrencyCode(CurType currencyCode) { + super.setCurType(currencyCode); + } + + public String getBrandName() { + return brandName; + } + + public void setBrandName(String brandName) { + super.addAttr("brandName", brandName); + this.brandName = brandName; + } + + public String getReturnUrl() { + return returnUrl; + } + + public void setReturnUrl(String returnUrl) { + super.addAttr("returnUrl", returnUrl); + this.returnUrl = returnUrl; + } + + public String getCancelUrl() { + return cancelUrl; + } + + public void setCancelUrl(String cancelUrl) { + super.addAttr("cancelUrl", cancelUrl); + this.cancelUrl = cancelUrl; + } + + public String getLandingPage() { + return landingPage; + } + + public void setLandingPage(String landingPage) { + super.addAttr("landingPage", landingPage); + this.landingPage = landingPage; + } + + public String getShippingPreference() { + return shippingPreference; + } + + public void setShippingPreference(String shippingPreference) { + super.addAttr("shippingPreference", shippingPreference); + this.shippingPreference = shippingPreference; + } + + public String getUserAction() { + + return userAction; + } + + public void setUserAction(String userAction) { + super.addAttr("userAction", userAction); + this.userAction = userAction; + } + + public ShippingDetail getShippingDetail() { + return shippingDetail; + } + + public void setShippingDetail(ShippingDetail shippingDetail) { + super.addAttr("shippingDetail", shippingDetail); + this.shippingDetail = shippingDetail; + } + + public String getReferenceId() { + return referenceId; + } + + public void setReferenceId(String referenceId) { + super.addAttr("referenceId", referenceId); + this.referenceId = referenceId; + } + + public String getInvoiceId() { + return invoiceId; + } + + public void setInvoiceId(String invoiceId) { + super.addAttr("invoiceId", referenceId); + this.invoiceId = invoiceId; + } +} diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/PayPalRefundResult.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/PayPalRefundResult.java new file mode 100644 index 0000000000000000000000000000000000000000..551b8cab7f5a411458d3e7178ff7d303df0053e6 --- /dev/null +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/PayPalRefundResult.java @@ -0,0 +1,119 @@ +package com.egzosn.pay.paypal.v2.bean; + +import java.math.BigDecimal; +import java.util.Map; + +import com.egzosn.pay.common.bean.BaseRefundResult; +import com.egzosn.pay.common.bean.CurType; + +/** + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2021/1/16
+ * 
+ */ +public class PayPalRefundResult extends BaseRefundResult { + + /** + * 支付平台订单号,交易号 + */ + private String tradeNo; + + public PayPalRefundResult(Map attrs, String tradeNo) { + super(attrs); + this.tradeNo = tradeNo; + } + + /** + * 获取退款请求结果状态码 + * + * @return 状态码 + */ + @Override + public String getCode() { + return getAttrString("state"); + } + + /** + * 获取退款请求结果状态提示信息 + * + * @return 提示信息 + */ + @Override + public String getMsg() { + return null; + } + + /** + * 返回业务结果状态码 + * + * @return 业务结果状态码 + */ + @Override + public String getResultCode() { + return getAttrString("state"); + } + + /** + * 返回业务结果状态提示信息 + * + * @return 业务结果状态提示信息 + */ + @Override + public String getResultMsg() { + return null; + } + + /** + * 退款金额 + * + * @return 退款金额 + */ + @Override + public BigDecimal getRefundFee() { + return null; + } + + /** + * 退款币种信息 + * + * @return 币种信息 + */ + @Override + public CurType getRefundCurrency() { + return null; + } + + /** + * 支付平台交易号 + * 发起支付时 支付平台(如支付宝)返回的交易订单号 + * + * @return 支付平台交易号 + */ + @Override + public String getTradeNo() { + return tradeNo; + } + + /** + * 支付订单号 + * 发起支付时,用户系统的订单号 + * + * @return 支付订单号 + */ + @Override + public String getOutTradeNo() { + return null; + } + + /** + * 商户退款单号 + * + * @return 商户退款单号 + */ + @Override + public String getRefundNo() { + return getAttrString("id"); + } +} diff --git a/pay-java-ali/src/main/java/com/egzosn/pay/ali/before/bean/AliTransactionType.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/PayPalTransactionType.java similarity index 41% rename from pay-java-ali/src/main/java/com/egzosn/pay/ali/before/bean/AliTransactionType.java rename to pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/PayPalTransactionType.java index 6dcf1d117ccdbdbd234dcb6d6bbc6779a11371b1..2cde37de6f22a87cc260594793e33f35b5762fb5 100644 --- a/pay-java-ali/src/main/java/com/egzosn/pay/ali/before/bean/AliTransactionType.java +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/PayPalTransactionType.java @@ -1,70 +1,58 @@ -package com.egzosn.pay.ali.before.bean; +package com.egzosn.pay.paypal.v2.bean; import com.egzosn.pay.common.bean.TransactionType; /** - * 阿里交易类型 + * 贝宝交易类型 *
  * 说明交易类型主要用于支付接口调用参数所需
- * {@link #APP 新版app支付}
  *
  *
  *
  * 
* * @author egan - * + *

* email egzosn@gmail.com - * date 2016/10/19 22:58 - * - * @see com.egzosn.pay.ali.bean.AliTransactionType + * date 2018/04/28 11:10 */ -@Deprecated -public enum AliTransactionType implements TransactionType { - /** - * 即时到帐 - */ - DIRECT("create_direct_pay_by_user"), +public enum PayPalTransactionType implements TransactionType { /** - * 移动支付 + * 获取token */ - APP("mobile.securitypay.pay"), + AUTHORIZE("v1/oauth2/token"), /** - * 手机网站支付 + * 付款 网页支付 */ - WAP("alipay.wap.create.direct.pay.by.user"), - - //交易辅助接口,以下属于新版接口 - + CHECKOUT("v2/checkout/orders"), /** - * 交易订单查询 + * 获取订单信息 */ - QUERY("alipay.trade.query"), + ORDERS_GET("/v2/checkout/orders/{order_id}"), /** - * 交易订单关闭 + * 确认订单并返回确认后订单信息 */ - CLOSE("alipay.trade.close"), + ORDERS_CAPTURE("/v2/checkout/orders/{order_id}/capture"), /** - * 交易订单撤销 + * 获取确认后订单信息 */ - CANCEL("alipay.trade.cancel "), + GET_CAPTURE("/v2/payments/captures/{capture_id}"), /** * 退款 */ - REFUND("alipay.trade.refund"), + REFUND("/v2/payments/captures/{capture_id}/refund"), + /** * 退款查询 */ - REFUNDQUERY("alipay.trade.fastpay.refund.query"), - /** - * 下载对账单 - */ - DOWNLOADBILL("alipay.data.dataservice.bill.downloadurl.query") + REFUND_GET("/v2/payments/refunds/{refund_id}"), + ; + private String method; - private AliTransactionType(String method) { + private PayPalTransactionType(String method) { this.method = method; } @@ -75,10 +63,12 @@ public enum AliTransactionType implements TransactionType { /** * 获取接口名称 + * * @return 接口名称 */ @Override public String getMethod() { return this.method; } + } diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/AddressPortable.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/AddressPortable.java new file mode 100644 index 0000000000000000000000000000000000000000..3abf4210021c567e5e81dddc9929863b2e807548 --- /dev/null +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/AddressPortable.java @@ -0,0 +1,220 @@ +package com.egzosn.pay.paypal.v2.bean.order; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * The portable international postal address. Maps to [AddressValidationMetadata](https://github.com/googlei18n/libaddressinput/wiki/AddressValidationMetadata) and HTML 5.1 [Autofilling form controls: the autocomplete attribute](https://www.w3.org/TR/html51/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + */ +public class AddressPortable { + + public AddressPortable() { + } + + /** + * The first line of the address. For example, number or street. For example, `173 Drury Lane`. Required for data entry and compliance and risk checks. Must contain the full address. + */ + @JSONField(name = "address_line_1") + private String addressLine1; + + public String addressLine1() { + return addressLine1; + } + + public AddressPortable addressLine1(String addressLine1) { + this.addressLine1 = addressLine1; + return this; + } + + /** + * The second line of the address. For example, suite or apartment number. + */ + @JSONField(name = "address_line_2") + private String addressLine2; + + public String addressLine2() { + return addressLine2; + } + + public AddressPortable addressLine2(String addressLine2) { + this.addressLine2 = addressLine2; + return this; + } + + /** + * The third line of the address, if needed. For example, a street complement for Brazil, direction text, such as `next to Walmart`, or a landmark in an Indian address. + */ + @JSONField(name = "address_line_3") + private String addressLine3; + + public String addressLine3() { + return addressLine3; + } + + public AddressPortable addressLine3(String addressLine3) { + this.addressLine3 = addressLine3; + return this; + } + + /** + * The highest level sub-division in a country, which is usually a province, state, or ISO-3166-2 subdivision. Format for postal delivery. For example, `CA` and not `California`. Value, by country, is:

  • UK. A county.
  • US. A state.
  • Canada. A province.
  • Japan. A prefecture.
  • Switzerland. A kanton.
+ */ + @JSONField(name = "admin_area_1") + private String adminArea1; + + public String adminArea1() { + return adminArea1; + } + + public AddressPortable adminArea1(String adminArea1) { + this.adminArea1 = adminArea1; + return this; + } + + /** + * A city, town, or village. Smaller than `admin_area_level_1`. + */ + @JSONField(name = "admin_area_2") + private String adminArea2; + + public String adminArea2() { + return adminArea2; + } + + public AddressPortable adminArea2(String adminArea2) { + this.adminArea2 = adminArea2; + return this; + } + + /** + * A sub-locality, suburb, neighborhood, or district. Smaller than `admin_area_level_2`. Value is:
  • Brazil. Suburb, bairro, or neighborhood.
  • India. Sub-locality or district. Street name information is not always available but a sub-locality or district can be a very small area.
+ */ + @JSONField(name = "admin_area_3") + private String adminArea3; + + public String adminArea3() { + return adminArea3; + } + + public AddressPortable adminArea3(String adminArea3) { + this.adminArea3 = adminArea3; + return this; + } + + /** + * The neighborhood, ward, or district. Smaller than `admin_area_level_3` or `sub_locality`. Value is:
  • The postal sorting code for Guernsey and many French territories, such as French Guiana.
  • The fine-grained administrative levels in China.
+ */ + @JSONField(name = "admin_area_4") + private String adminArea4; + + public String adminArea4() { + return adminArea4; + } + + public AddressPortable adminArea4(String adminArea4) { + this.adminArea4 = adminArea4; + return this; + } + + /** + * REQUIRED + * The [two-character ISO 3166-1 code](/docs/integration/direct/rest/country-codes/) that identifies the country or region.
Note: The country code for Great Britain is GB and not UK as used in the top-level domain names for that country. Use the `C2` country code for China worldwide for comparable uncontrolled price (CUP) method, bank card, and cross-border transactions.
+ */ + @JSONField(name = "country_code") + private String countryCode; + + public String countryCode() { + return countryCode; + } + + public AddressPortable countryCode(String countryCode) { + this.countryCode = countryCode; + return this; + } + + /** + * The postal code, which is the zip code or equivalent. Typically required for countries with a postal code or an equivalent. See [postal code](https://en.wikipedia.org/wiki/Postal_code). + */ + @JSONField(name = "postal_code") + private String postalCode; + + public String postalCode() { + return postalCode; + } + + public AddressPortable postalCode(String postalCode) { + this.postalCode = postalCode; + return this; + } + + public String getAddressLine1() { + return addressLine1; + } + + public void setAddressLine1(String addressLine1) { + this.addressLine1 = addressLine1; + } + + public String getAddressLine2() { + return addressLine2; + } + + public void setAddressLine2(String addressLine2) { + this.addressLine2 = addressLine2; + } + + public String getAddressLine3() { + return addressLine3; + } + + public void setAddressLine3(String addressLine3) { + this.addressLine3 = addressLine3; + } + + public String getAdminArea1() { + return adminArea1; + } + + public void setAdminArea1(String adminArea1) { + this.adminArea1 = adminArea1; + } + + public String getAdminArea2() { + return adminArea2; + } + + public void setAdminArea2(String adminArea2) { + this.adminArea2 = adminArea2; + } + + public String getAdminArea3() { + return adminArea3; + } + + public void setAdminArea3(String adminArea3) { + this.adminArea3 = adminArea3; + } + + public String getAdminArea4() { + return adminArea4; + } + + public void setAdminArea4(String adminArea4) { + this.adminArea4 = adminArea4; + } + + public String getCountryCode() { + return countryCode; + } + + public void setCountryCode(String countryCode) { + this.countryCode = countryCode; + } + + public String getPostalCode() { + return postalCode; + } + + public void setPostalCode(String postalCode) { + this.postalCode = postalCode; + } +} diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/ApplicationContext.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/ApplicationContext.java new file mode 100644 index 0000000000000000000000000000000000000000..53118d744db6cddc0284368d71ab59b0b97fc10a --- /dev/null +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/ApplicationContext.java @@ -0,0 +1,147 @@ +package com.egzosn.pay.paypal.v2.bean.order; + + +import com.alibaba.fastjson.annotation.JSONField; + +public class ApplicationContext { + + /** + * 该标签将覆盖PayPal网站上PayPal帐户中的公司名称 + */ + @JSONField(name = "brand_name") + private String brandName; + + @JSONField(name = "cancel_url") + private String cancelUrl; + /** + * LOGIN。当客户单击PayPal Checkout时,客户将被重定向到页面以登录PayPal并批准付款。 + * BILLING。当客户单击PayPal Checkout时,客户将被重定向到一个页面,以输入信用卡或借记卡以及完成购买所需的其他相关账单信息 + * NO_PREFERENCE。当客户单击“ PayPal Checkout”时,将根据其先前的交互方式将其重定向到页面以登录PayPal并批准付款,或重定向至页面以输入信用卡或借记卡以及完成购买所需的其他相关账单信息使用PayPal。 + * 默认值:NO_PREFERENCE + */ + @JSONField(name = "landing_page") + private String landingPage = "NO_PREFERENCE"; + + + @JSONField(name = "return_url") + private String returnUrl; + /** + * GET_FROM_FILE。使用贝宝网站上客户提供的送货地址。 + * NO_SHIPPING。从PayPal网站编辑送货地址。推荐用于数字商品 + * SET_PROVIDED_ADDRESS。使用商家提供的地址。客户无法在PayPal网站上更改此地址 + */ + @JSONField(name = "shipping_preference") + private String shippingPreference = "NO_SHIPPING"; + /** + * CONTINUE。将客户重定向到PayPal付款页面后,将出现“ 继续”按钮。当结帐流程启动时最终金额未知时,请使用此选项,并且您想将客户重定向到商家页面而不处理付款。 + * PAY_NOW。将客户重定向到PayPal付款页面后,出现“ 立即付款”按钮。当启动结帐时知道最终金额并且您要在客户单击“ 立即付款”时立即处理付款时,请使用此选项。 + */ + @JSONField(name = "user_action") + private String userAction = "CONTINUE"; + + public ApplicationContext() { + } + + + public String brandName() { + return brandName; + } + + public ApplicationContext brandName(String brandName) { + this.brandName = brandName; + return this; + } + + public String cancelUrl() { + return this.cancelUrl; + } + + public ApplicationContext cancelUrl(String cancelUrl) { + this.cancelUrl = cancelUrl; + return this; + } + + public String landingPage() { + return this.landingPage; + } + + public ApplicationContext landingPage(String landingPage) { + this.landingPage = landingPage; + return this; + } + + public String returnUrl() { + return this.returnUrl; + } + + public ApplicationContext returnUrl(String returnUrl) { + this.returnUrl = returnUrl; + return this; + } + + public String shippingPreference() { + return this.shippingPreference; + } + + public ApplicationContext shippingPreference(String shippingPreference) { + this.shippingPreference = shippingPreference; + return this; + } + + public String userAction() { + return this.userAction; + } + + public ApplicationContext userAction(String userAction) { + this.userAction = userAction; + return this; + } + + public String getBrandName() { + return brandName; + } + + public void setBrandName(String brandName) { + this.brandName = brandName; + } + + public String getCancelUrl() { + return cancelUrl; + } + + public void setCancelUrl(String cancelUrl) { + this.cancelUrl = cancelUrl; + } + + public String getLandingPage() { + return landingPage; + } + + public void setLandingPage(String landingPage) { + this.landingPage = landingPage; + } + + public String getReturnUrl() { + return returnUrl; + } + + public void setReturnUrl(String returnUrl) { + this.returnUrl = returnUrl; + } + + public String getShippingPreference() { + return shippingPreference; + } + + public void setShippingPreference(String shippingPreference) { + this.shippingPreference = shippingPreference; + } + + public String getUserAction() { + return userAction; + } + + public void setUserAction(String userAction) { + this.userAction = userAction; + } +} diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/Money.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/Money.java new file mode 100644 index 0000000000000000000000000000000000000000..1ed4274085fc76bbf21675d492c959c6e05b15f1 --- /dev/null +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/Money.java @@ -0,0 +1,37 @@ +package com.egzosn.pay.paypal.v2.bean.order; + +import com.alibaba.fastjson.annotation.JSONField; + +public class Money { + @JSONField(name = "currency_code") + private String currencyCode; + @JSONField(name = "value") + private String value; + + public String getCurrencyCode() { + return currencyCode; + } + + public void setCurrencyCode(String currencyCode) { + this.currencyCode = currencyCode; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public Money currencyCode(String currencyCode) { + this.currencyCode = currencyCode; + return this; + } + + + public Money value(String value) { + this.value = value; + return this; + } +} \ No newline at end of file diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/Name.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/Name.java new file mode 100644 index 0000000000000000000000000000000000000000..f86d543203d15bd0b2d4ed5b4f3bf0a54c989da3 --- /dev/null +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/Name.java @@ -0,0 +1,174 @@ +package com.egzosn.pay.paypal.v2.bean.order; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * The name of the party. + */ +public class Name { + + // Required default constructor + public Name() { + } + + /** + * DEPRECATED. The party's alternate name. Can be a business name, nickname, or any other name that cannot be split into first, last name. Required when the party is a business. + */ + @JSONField(name = "alternate_full_name") + private String alternateFullName; + + public String alternateFullName() { + return alternateFullName; + } + + public Name alternateFullName(String alternateFullName) { + this.alternateFullName = alternateFullName; + return this; + } + + /** + * When the party is a person, the party's full name. + */ + @JSONField(name = "full_name") + private String fullName; + + public String fullName() { + return fullName; + } + + public Name fullName(String fullName) { + this.fullName = fullName; + return this; + } + + /** + * When the party is a person, the party's given, or first, name. + */ + @JSONField(name = "given_name") + private String givenName; + + public String givenName() { + return givenName; + } + + public Name givenName(String givenName) { + this.givenName = givenName; + return this; + } + + /** + * When the party is a person, the party's middle name. Use also to store multiple middle names including the patronymic, or father's, middle name. + */ + @JSONField(name = "middle_name") + private String middleName; + + public String middleName() { + return middleName; + } + + public Name middleName(String middleName) { + this.middleName = middleName; + return this; + } + + /** + * The prefix, or title, to the party's name. + */ + @JSONField(name = "prefix") + private String prefix; + + public String prefix() { + return prefix; + } + + public Name prefix(String prefix) { + this.prefix = prefix; + return this; + } + + /** + * The suffix for the party's name. + */ + @JSONField(name = "suffix") + private String suffix; + + public String suffix() { + return suffix; + } + + public Name suffix(String suffix) { + this.suffix = suffix; + return this; + } + + /** + * When the party is a person, the party's surname or family name. Also known as the last name. Required when the party is a person. Use also to store multiple surnames including the matronymic, or mother's, surname. + */ + @JSONField(name = "surname") + private String surname; + + public String surname() { + return surname; + } + + public Name surname(String surname) { + this.surname = surname; + return this; + } + + public String getAlternateFullName() { + return alternateFullName; + } + + public void setAlternateFullName(String alternateFullName) { + this.alternateFullName = alternateFullName; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public String getGivenName() { + return givenName; + } + + public void setGivenName(String givenName) { + this.givenName = givenName; + } + + public String getMiddleName() { + return middleName; + } + + public void setMiddleName(String middleName) { + this.middleName = middleName; + } + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public String getSuffix() { + return suffix; + } + + public void setSuffix(String suffix) { + this.suffix = suffix; + } + + public String getSurname() { + return surname; + } + + public void setSurname(String surname) { + this.surname = surname; + } +} diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/OrderRequest.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/OrderRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..7b754927d8c7a12c1575933a57d95108c362bc6b --- /dev/null +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/OrderRequest.java @@ -0,0 +1,43 @@ +package com.egzosn.pay.paypal.v2.bean.order; + +import java.util.List; + +import com.alibaba.fastjson.annotation.JSONField; + +public class OrderRequest { + @JSONField(name = "application_context") + private ApplicationContext applicationContext; + @JSONField(name = "intent") + private String checkoutPaymentIntent; + + @JSONField(name = + "purchase_units" + ) + private List purchaseUnits; + + + public ApplicationContext getApplicationContext() { + return applicationContext; + } + + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + public String getCheckoutPaymentIntent() { + return checkoutPaymentIntent; + } + + public void setCheckoutPaymentIntent(String checkoutPaymentIntent) { + this.checkoutPaymentIntent = checkoutPaymentIntent; + } + + + public List getPurchaseUnits() { + return purchaseUnits; + } + + public void setPurchaseUnits(List purchaseUnits) { + this.purchaseUnits = purchaseUnits; + } +} \ No newline at end of file diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/PurchaseUnitRequest.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/PurchaseUnitRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..591e884091e284f79f64e81e19c0e4def90c712c --- /dev/null +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/PurchaseUnitRequest.java @@ -0,0 +1,141 @@ +package com.egzosn.pay.paypal.v2.bean.order; + +import com.alibaba.fastjson.annotation.JSONField; + +public class PurchaseUnitRequest { + @JSONField(name = "amount") + private Money money; + @JSONField(name = "custom_id") + private String customId; + @JSONField(name = "description") + private String description; + @JSONField(name = "invoice_id") + private String invoiceId; + @JSONField(name = "reference_id") + private String referenceId; + + @JSONField(name = "soft_descriptor") + private String softDescriptor; + /** + * The shipping details. + */ + @JSONField(name = "shipping") + private ShippingDetail shippingDetail; + + public Money money() { + return this.money; + } + + public PurchaseUnitRequest money(Money money) { + this.money = money; + return this; + } + + public String customId() { + return this.customId; + } + + public PurchaseUnitRequest customId(String customId) { + this.customId = customId; + return this; + } + + public String description() { + return this.description; + } + + public PurchaseUnitRequest description(String description) { + this.description = description; + return this; + } + + public String invoiceId() { + return this.invoiceId; + } + + public PurchaseUnitRequest invoiceId(String invoiceId) { + this.invoiceId = invoiceId; + return this; + } + + + public String referenceId() { + return this.referenceId; + } + + public PurchaseUnitRequest referenceId(String referenceId) { + this.referenceId = referenceId; + return this; + } + + + public String softDescriptor() { + return this.softDescriptor; + } + + public PurchaseUnitRequest softDescriptor(String softDescriptor) { + this.softDescriptor = softDescriptor; + return this; + } + + public PurchaseUnitRequest shippingDetail(ShippingDetail shippingDetail) { + this.shippingDetail = shippingDetail; + return this; + } + + public Money getMoney() { + return money; + } + + public void setMoney(Money money) { + this.money = money; + } + + public String getCustomId() { + return customId; + } + + public void setCustomId(String customId) { + this.customId = customId; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getInvoiceId() { + return invoiceId; + } + + public void setInvoiceId(String invoiceId) { + this.invoiceId = invoiceId; + } + + public String getReferenceId() { + return referenceId; + } + + public void setReferenceId(String referenceId) { + this.referenceId = referenceId; + } + + public String getSoftDescriptor() { + return softDescriptor; + } + + public void setSoftDescriptor(String softDescriptor) { + this.softDescriptor = softDescriptor; + } + + public ShippingDetail getShippingDetail() { + return shippingDetail; + } + + public void setShippingDetail(ShippingDetail shippingDetail) { + this.shippingDetail = shippingDetail; + } +} \ No newline at end of file diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/ShippingDetail.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/ShippingDetail.java new file mode 100644 index 0000000000000000000000000000000000000000..6561fc19adcd8aaceeecdc2398d48c4238a98e13 --- /dev/null +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/ShippingDetail.java @@ -0,0 +1,59 @@ +package com.egzosn.pay.paypal.v2.bean.order; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * The shipping details. + */ +public class ShippingDetail { + + // Required default constructor + public ShippingDetail() { + } + + /** + * The portable international postal address. Maps to [AddressValidationMetadata](https://github.com/googlei18n/libaddressinput/wiki/AddressValidationMetadata) and HTML 5.1 [Autofilling form controls: the autocomplete attribute](https://www.w3.org/TR/html51/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute). + */ + @JSONField(name = "address") + private AddressPortable addressPortable; + + public AddressPortable addressPortable() { + return addressPortable; + } + + public ShippingDetail addressPortable(AddressPortable addressPortable) { + this.addressPortable = addressPortable; + return this; + } + + /** + * The name of the party. + */ + @JSONField(name = "name") + private Name name; + + public Name name() { + return name; + } + + public ShippingDetail name(Name name) { + this.name = name; + return this; + } + + public AddressPortable getAddressPortable() { + return addressPortable; + } + + public void setAddressPortable(AddressPortable addressPortable) { + this.addressPortable = addressPortable; + } + + public Name getName() { + return name; + } + + public void setName(Name name) { + this.name = name; + } +} diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/utils/PayPalUtil.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/utils/PayPalUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..cc8c0d911a44cf32383466167298feee0283210e --- /dev/null +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/utils/PayPalUtil.java @@ -0,0 +1,93 @@ +package com.egzosn.pay.paypal.v2.utils; + +import java.io.InputStream; +import java.nio.charset.Charset; +import java.security.GeneralSecurityException; +import java.security.Signature; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.zip.CRC32; +import java.util.zip.Checksum; + +import org.apache.commons.codec.binary.Base64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.egzosn.pay.common.bean.result.PayException; +import com.egzosn.pay.common.exception.PayErrorException; + +/** + * @author Egan + * email egan@egzosn.com + * date 2023/9/12 + */ +public final class PayPalUtil { + private static final Logger LOG = LoggerFactory.getLogger(PayPalUtil.class); + + private PayPalUtil() { + } + + public static Collection getCertificateFromStream(InputStream stream) { + if (stream == null) { + throw new PayErrorException(new PayException("failure", "未找到证书")); + } + try { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + + return (Collection) cf.generateCertificates(stream); + } + catch (CertificateException ex) { + throw new PayErrorException(new PayException("failure", "证书加载异常"), ex); + } + + } + + /** + * 生成字符串传递的CRC 32值 + * + * @param data 字符 + * @return 返回长crc32输入值。-1如果string为null + */ + public static long crc32(String data) { + if (data == null) { + return -1; + } + byte[] bytes = data.getBytes(Charset.forName("utf-8")); + Checksum checksum = new CRC32(); + checksum.update(bytes, 0, bytes.length); + return checksum.getValue(); + + } + + + /** + * 基于https://developer.paypal.com/docs/integration/direct/rest-webhooks-overview/#event-signature验证Webhook签名验证,如果签名有效则返回true + * + * @param clientCerts 客户端证书 + * @param algo 服务器生成签名时使用的算法 + * @param actualSignatureEncoded Paypal-Transmission-Sig服务器传递的报头值 + * @param expectedSignature 用请求体的CRC32值格式化数据生成的签名 + * @return true 校验通过 + */ + public static boolean validateData(Collection clientCerts, String algo, String actualSignatureEncoded, String expectedSignature) { + // 从paypal-auth-algorithm HTTP头中获取signatureAlgorithm + Signature signatureAlgorithm = null; + try { + signatureAlgorithm = Signature.getInstance(algo); + //从HTTP头中提供的URL中获取certData并缓存它 + X509Certificate[] clientChain = clientCerts.toArray(new X509Certificate[0]); + signatureAlgorithm.initVerify(clientChain[0].getPublicKey()); + signatureAlgorithm.update(expectedSignature.getBytes()); + // 实际的签名是base 64编码的,可以在HTTP头中找到 + byte[] actualSignature = Base64.decodeBase64(actualSignatureEncoded.getBytes()); + return signatureAlgorithm.verify(actualSignature); + } + catch (GeneralSecurityException e) { + LOG.error("校验异常", e); + return false; + } + + } +} diff --git a/pay-java-union/README.md b/pay-java-union/README.md index 779c9b96bd81293573503f7c6892ee9241855a79..7d604a80d7021ca89487b591c77804fd11a64fed 100644 --- a/pay-java-union/README.md +++ b/pay-java-union/README.md @@ -8,22 +8,23 @@ UnionPayConfigStorage unionPayConfigStorage = new UnionPayConfigStorage(); unionPayConfigStorage.setMerId("700000000000001"); - //设置CertSign必须在设置证书前 + //是否为证书签名 unionPayConfigStorage.setCertSign(true); - //公钥,验签证书链格式: 中级证书路径;根证书路径 -// unionPayConfigStorage.setKeyPublic("D:/certs/acp_test_middle.cer;D:/certs/acp_test_root.cer"); + //中级证书路径 - unionPayConfigStorage.setAcpMiddleCert("D:/certs/acp_test_middle.cer"); + unionPayConfigStorage.setAcpMiddleCert("证书文件流,证书字符串信息或证书绝对地址"); //根证书路径 - unionPayConfigStorage.setAcpRootCert("D:/certs/acp_test_root.cer"); - - //私钥, 私钥证书格式: 私钥证书路径;私钥证书对应的密码 -// unionPayConfigStorage.setKeyPrivate("D:/certs/acp_test_sign.pfx;000000"); + unionPayConfigStorage.setAcpRootCert("证书文件流,证书字符串信息或证书绝对地址"); // 私钥证书路径 - unionPayConfigStorage.setKeyPrivateCert("D:/certs/acp_test_sign.pfx"); + unionPayConfigStorage.setKeyPrivateCert("证书文件流,证书字符串信息或证书绝对地址"); //私钥证书对应的密码 - unionPayConfigStorage.setKeyPrivateCertPwd("000000"); - + unionPayConfigStorage.setKeyPrivateCertPwd("私钥证书对应的密码"); + //设置证书对应的存储方式,这里默认为文件地址 + httpConfigStorage.setCertStoreType(CertStoreType.PATH); + + + + unionPayConfigStorage.setNotifyUrl("http://www.pay.egzosn.com/payBack.json"); // 无需同步回调可不填 app填这个就可以 unionPayConfigStorage.setReturnUrl("http://www.pay.egzosn.com/payBack.json"); @@ -48,9 +49,9 @@ //代理端口 httpConfigStorage.setHttpProxyPort(3308); //代理用户名 - httpConfigStorage.setHttpProxyUsername("user"); + httpConfigStorage.setAuthUsername("user"); //代理密码 - httpConfigStorage.setHttpProxyPassword("password"); + httpConfigStorage.setAuthPassword("password"); /* /网路代理配置 根据需求进行设置**/ /* /网络请求连接池**/ @@ -79,7 +80,7 @@ ```java //支付订单基础信息 - PayOrder payOrder = new PayOrder("订单title", "摘要", new BigDecimal(0.01) , UUID.randomUUID().toString().replace("-", "")); + PayOrder payOrder = new PayOrder("订单title", "摘要", BigDecimal.valueOf(0.01) , UUID.randomUUID().toString().replace("-", "")); ``` @@ -92,6 +93,7 @@ /*-----------扫码付-------------------*/ payOrder.setTransactionType(UnionTransactionType.APPLY_QR_CODE); //获取扫码付的二维码 +// String image = service.getQrPay(payOrder); BufferedImage image = service.genQrPay(payOrder); /*-----------/扫码付-------------------*/ @@ -178,7 +180,7 @@ ```java - Map result = service..query(null, "我方系统单号"); + Map result = service.query(null, "我方系统单号"); ``` @@ -188,6 +190,6 @@ RefundOrder order = new RefundOrder(null, "原交易查询流水号", "退款金额", "订单总金额"); order.setRefundNo("退款单号") - Map result = service.refund(order); + UnionRefundResult result = service.refund(order); ``` diff --git a/pay-java-union/pom.xml b/pay-java-union/pom.xml index 919403a5f20edd155dfb04bfdd3babed42762702..a49a64b0f36060d53e95b768dd8992e35ab55c56 100644 --- a/pay-java-union/pom.xml +++ b/pay-java-union/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.12.6-SNAPSHOT + 2.14.7 4.0.0 diff --git a/pay-java-union/src/main/java/com/egzosn/pay/union/api/UnionPayConfigStorage.java b/pay-java-union/src/main/java/com/egzosn/pay/union/api/UnionPayConfigStorage.java index 56ce4205544d83d31680a67dd51ce90e5eeaa1d6..010ed7d1dda37c5cca8bc3ef8740a567e2058a6f 100644 --- a/pay-java-union/src/main/java/com/egzosn/pay/union/api/UnionPayConfigStorage.java +++ b/pay-java-union/src/main/java/com/egzosn/pay/union/api/UnionPayConfigStorage.java @@ -1,15 +1,18 @@ package com.egzosn.pay.union.api; +import java.io.IOException; +import java.io.InputStream; + import com.egzosn.pay.common.api.BasePayConfigStorage; +import com.egzosn.pay.common.bean.CertStoreType; /** * @author Actinia - * - *
- * email hayesfu@qq.com
- *   create 2017 2017/11/4 0004
- * 
+ *
+ *         email hayesfu@qq.com
+ *           create 2017 2017/11/4 0004
+ *         
*/ public class UnionPayConfigStorage extends BasePayConfigStorage { @@ -17,113 +20,133 @@ public class UnionPayConfigStorage extends BasePayConfigStorage { /** * 商户号 */ - private volatile String merId; + private String merId; /** * 商户收款账号 */ - private volatile String seller; + private String seller; - private volatile String version = "5.1.0"; + private String version = "5.1.0"; /** * 0:普通商户直连接入 * 1: 收单机构 * 2:平台类商户接入 */ - private volatile String accessType = "0"; + private String accessType = "0"; + /** - * 中级证书路径 + * 应用私钥证书 */ - private String acpMiddleCert; + private Object keyPrivateCert; /** - * 根证书路径 + * 应用私钥证书,rsa_private pkcs8格式 生成签名时使用 */ - private String acpRootCert; + private String keyPrivateCertPwd; /** - * 私钥证书是否已经初始化 - * 默认没有 + * 中级证书 + */ + private Object acpMiddleCert; + /** + * 根证书 */ - private boolean keyPrivateInit = false; + private Object acpRootCert; /** - * 公钥证书是否已经初始化 - * 默认没有 + * 证书存储类型 */ - private boolean keyPublicInit = false; + private CertStoreType certStoreType; + /** + * 设置私钥证书 + * + * @param certificate 私钥证书地址 或者证书内容字符串 + * 私钥证书密码 {@link #setKeyPrivateCertPwd(String)} + */ + public void setKeyPrivateCert(String certificate) { + super.setKeyPrivate(certificate); + this.keyPrivateCert = certificate; + } /** * 设置私钥证书 - * @param certificatePath 私钥证书地址 - * 私钥证书密码 {@link #setKeyPrivateCertPwd(String)} + * + * @param keyPrivateCert 私钥证书信息流 + * 私钥证书密码 {@link #setKeyPrivateCertPwd(String)} + */ + public void setKeyPrivateCert(InputStream keyPrivateCert) { + this.keyPrivateCert = keyPrivateCert; + } + + public InputStream getKeyPrivateCertInputStream() throws IOException { + return certStoreType.getInputStream(keyPrivateCert); + } + + /** + * 设置中级证书 + * + * @param acpMiddleCert 证书信息或者证书路径 */ - public void setKeyPrivateCert(String certificatePath){ - super.setKeyPrivate(certificatePath); + public void setAcpMiddleCert(String acpMiddleCert) { + this.acpMiddleCert = acpMiddleCert; } /** * 设置中级证书 - * @param certificatePath 证书地址 + * + * @param acpMiddleCert 证书文件 */ - public void setAcpMiddleCert(String certificatePath){ - this.acpMiddleCert = certificatePath; + public void setAcpMiddleCert(InputStream acpMiddleCert) { + this.acpMiddleCert = acpMiddleCert; } + /** - * 设置根证书路径 - * @param certificatePath 证书路径 + * 设置根证书 + * + * @param acpRootCert 证书路径或者证书信息字符串 */ - public void setAcpRootCert(String certificatePath){ - this.acpRootCert = certificatePath; + public void setAcpRootCert(String acpRootCert) { + this.acpRootCert = acpRootCert; + } + + /** + * 设置根证书 + * + * @param acpRootCert 证书文件流 + */ + public void setAcpRootCert(InputStream acpRootCert) { + this.acpRootCert = acpRootCert; } public String getAcpMiddleCert() { - return acpMiddleCert; + return (String) acpMiddleCert; } public String getAcpRootCert() { - return acpRootCert; + return (String) acpRootCert; + } + + public InputStream getAcpMiddleCertInputStream() throws IOException { + return certStoreType.getInputStream(acpMiddleCert); + } + + public InputStream getAcpRootCertInputStream() throws IOException { + return certStoreType.getInputStream(acpRootCert); } /** + * 获取私钥证书密码 * - * 设置私钥证书与证书密码 - * @param keyPrivate 私钥证书与证书对应的密码 格式: D:/certs/acp_test_sign.pfx;000000 - * 替代方法 - * {@link #setKeyPrivateCert(String)} - * {@link #setKeyPrivateCertPwd(String)} + * @return 私钥证书密码 */ - @Deprecated - @Override - public void setKeyPrivate(String keyPrivate) { - super.setKeyPrivate(keyPrivate); - if (isCertSign() && keyPrivate.length() < 1024 && keyPrivate.contains(";")){ - String[] split = keyPrivate.split(";"); - super.setKeyPrivateCertPwd( split[1]); - super.setKeyPrivate(split[0]); - getCertDescriptor().initPrivateSignCert(getKeyPrivate(), getKeyPrivateCertPwd(), "PKCS12"); - keyPrivateInit = true; - } + public String getKeyPrivateCertPwd() { + return keyPrivateCertPwd; } - /** - * 设置中级证书与根证书 格式:D:/certs/acp_test_middle.cer;D:/certs/acp_test_root.cer - * @param keyPublic 中级证书与根证书 - * 替代方法 - * {@link #setAcpRootCert(String)} - * {@link #setAcpMiddleCert(String)} - */ - @Deprecated - @Override - public void setKeyPublic(String keyPublic) { - super.setKeyPublic(keyPublic); - if (isCertSign() && keyPublic.length() < 1024 ){ - String[] split = keyPublic.split(";"); - getCertDescriptor().initPublicCert(split[0]); - getCertDescriptor().initRootCert(split[1]); - keyPublicInit = true; - } + public void setKeyPrivateCertPwd(String keyPrivateCertPwd) { + this.keyPrivateCertPwd = keyPrivateCertPwd; } @Override @@ -131,6 +154,17 @@ public class UnionPayConfigStorage extends BasePayConfigStorage { return null; } + /** + * 应用id + * 纠正名称 + * + * @return 应用id + */ + @Override + public String getAppId() { + return null; + } + /** * @return 合作者id * @see #getPid() @@ -157,15 +191,16 @@ public class UnionPayConfigStorage extends BasePayConfigStorage { return merId; } - public void setPid (String pid) { + public void setPid(String pid) { this.merId = pid; } + @Override public String getSeller() { return seller; } - public void setSeller (String seller) { + public void setSeller(String seller) { this.seller = seller; } @@ -173,7 +208,7 @@ public class UnionPayConfigStorage extends BasePayConfigStorage { return merId; } - public void setMerId (String merId) { + public void setMerId(String merId) { this.merId = merId; } @@ -193,11 +228,16 @@ public class UnionPayConfigStorage extends BasePayConfigStorage { this.accessType = accessType; } - public boolean isKeyPrivateInit() { - return keyPrivateInit; + /** + * 证书存储类型 + * + * @return 证书存储类型 + */ + public CertStoreType getCertStoreType() { + return certStoreType; } - public boolean isKeyPublicInit() { - return keyPublicInit; + public void setCertStoreType(CertStoreType certStoreType) { + this.certStoreType = certStoreType; } } diff --git a/pay-java-union/src/main/java/com/egzosn/pay/union/api/UnionPayService.java b/pay-java-union/src/main/java/com/egzosn/pay/union/api/UnionPayService.java index dab9c79f62688de9492147a1c36ccfc6ae7ee36a..97c913d9b52ff1cf668ef4490814de95e53b1450 100644 --- a/pay-java-union/src/main/java/com/egzosn/pay/union/api/UnionPayService.java +++ b/pay-java-union/src/main/java/com/egzosn/pay/union/api/UnionPayService.java @@ -1,37 +1,64 @@ package com.egzosn.pay.union.api; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.security.GeneralSecurityException; +import java.security.cert.CertPathBuilder; +import java.security.cert.CertStore; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateFactory; +import java.security.cert.CollectionCertStoreParameters; +import java.security.cert.PKIXBuilderParameters; +import java.security.cert.TrustAnchor; +import java.security.cert.X509CertSelector; +import java.security.cert.X509Certificate; +import java.sql.Timestamp; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.common.api.BasePayService; -import com.egzosn.pay.common.bean.*; +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.common.bean.BillType; + +import com.egzosn.pay.common.bean.MethodType; +import com.egzosn.pay.common.bean.NoticeParams; +import com.egzosn.pay.common.bean.OrderParaStructure; +import com.egzosn.pay.common.bean.PayMessage; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.PayOutMessage; +import com.egzosn.pay.common.bean.RefundOrder; +import com.egzosn.pay.common.bean.TransactionType; import com.egzosn.pay.common.bean.outbuilder.PayTextOutMessage; import com.egzosn.pay.common.bean.result.PayException; import com.egzosn.pay.common.exception.PayErrorException; import com.egzosn.pay.common.http.HttpConfigStorage; import com.egzosn.pay.common.http.UriVariables; import com.egzosn.pay.common.util.DateUtils; -import com.egzosn.pay.common.util.MatrixToImageWriter; import com.egzosn.pay.common.util.Util; import com.egzosn.pay.common.util.sign.CertDescriptor; +import com.egzosn.pay.common.util.sign.SignTextUtils; import com.egzosn.pay.common.util.sign.SignUtils; import com.egzosn.pay.common.util.sign.encrypt.RSA; import com.egzosn.pay.common.util.sign.encrypt.RSA2; import com.egzosn.pay.common.util.str.StringUtils; import com.egzosn.pay.union.bean.SDKConstants; +import com.egzosn.pay.union.bean.UnionPayBillType; +import com.egzosn.pay.union.bean.UnionPayMessage; +import com.egzosn.pay.union.bean.UnionRefundResult; import com.egzosn.pay.union.bean.UnionTransactionType; -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.math.BigDecimal; -import java.security.cert.*; -import java.sql.Timestamp; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.*; - /** * @author Actinia - *
+ * 
  *         email hayesfu@qq.com
  *         create 2017 2017/11/5
  *         
@@ -55,6 +82,10 @@ public class UnionPayService extends BasePayService { private static final String FILE_TRANS_URL = "https://filedownload.%s/"; private static final String APP_TRANS_URL = "https://gateway.%s/gateway/api/appTransReq.do"; private static final String CARD_TRANS_URL = "https://gateway.%s/gateway/api/cardTransReq.do"; + /** + * 证书解释器 + */ + private volatile CertDescriptor certDescriptor; /** * 构造函数 @@ -62,7 +93,7 @@ public class UnionPayService extends BasePayService { * @param payConfigStorage 支付配置 */ public UnionPayService(UnionPayConfigStorage payConfigStorage) { - super(payConfigStorage); + this(payConfigStorage, null); } public UnionPayService(UnionPayConfigStorage payConfigStorage, HttpConfigStorage configStorage) { @@ -77,29 +108,42 @@ public class UnionPayService extends BasePayService { */ @Override public UnionPayService setPayConfigStorage(UnionPayConfigStorage payConfigStorage) { - super.setPayConfigStorage(payConfigStorage); - if (!payConfigStorage.isCertSign()) { + this.payConfigStorage = payConfigStorage; + if (null != certDescriptor) { return this; } - CertDescriptor certDescriptor = payConfigStorage.getCertDescriptor(); - if (!payConfigStorage.isKeyPrivateInit()) { - certDescriptor.initPrivateSignCert(payConfigStorage.getKeyPrivate(), payConfigStorage.getKeyPrivateCertPwd(), "PKCS12"); + try { + certDescriptor = new CertDescriptor(); + certDescriptor.initPrivateSignCert(payConfigStorage.getKeyPrivateCertInputStream(), payConfigStorage.getKeyPrivateCertPwd(), "PKCS12"); + certDescriptor.initPublicCert(payConfigStorage.getAcpMiddleCertInputStream()); + certDescriptor.initRootCert(payConfigStorage.getAcpRootCertInputStream()); } - if (!payConfigStorage.isKeyPublicInit()) { - certDescriptor.initPublicCert(payConfigStorage.getAcpMiddleCert()); - certDescriptor.initRootCert(payConfigStorage.getAcpRootCert()); + catch (IOException e) { + LOG.error("", e); } + return this; } + /** + * 获取支付请求地址 + * + * @param transactionType 交易类型 + * @return 请求地址 + */ + @Override + public String getReqUrl(TransactionType transactionType) { + return (payConfigStorage.isTest() ? TEST_BASE_DOMAIN : RELEASE_BASE_DOMAIN); + } + /** * 根据是否为沙箱环境进行获取请求地址 * * @return 请求地址 */ public String getReqUrl() { - return (payConfigStorage.isTest() ? TEST_BASE_DOMAIN : RELEASE_BASE_DOMAIN); + return getReqUrl(null); } public String getFrontTransUrl() { @@ -110,6 +154,10 @@ public class UnionPayService extends BasePayService { return String.format(BACK_TRANS_URL, getReqUrl()); } + public String getAppTransUrl() { + return String.format(APP_TRANS_URL, getReqUrl()); + } + public String getSingleQueryUrl() { return String.format(SINGLE_QUERY_URL, getReqUrl()); } @@ -119,6 +167,21 @@ public class UnionPayService extends BasePayService { return String.format(FILE_TRANS_URL, getReqUrl()); } + /** + * 后台通知地址 + * + * @param parameters 预订单信息 + * @param order 订单 + * @return 预订单信息 + */ + private Map initNotifyUrl(Map parameters, AssistOrder order) { + //后台通知地址 + OrderParaStructure.loadParameters(parameters, SDKConstants.param_backUrl, payConfigStorage.getNotifyUrl()); + OrderParaStructure.loadParameters(parameters, SDKConstants.param_backUrl, order.getNotifyUrl()); + OrderParaStructure.loadParameters(parameters, SDKConstants.param_backUrl, order); + return parameters; + } + /** * 银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改 @@ -127,7 +190,7 @@ public class UnionPayService extends BasePayService { */ private Map getCommonParam() { Map params = new TreeMap<>(); - UnionPayConfigStorage configStorage = (UnionPayConfigStorage) payConfigStorage; + UnionPayConfigStorage configStorage = payConfigStorage; //银联接口版本 params.put(SDKConstants.param_version, configStorage.getVersion()); //编码方式 @@ -135,11 +198,11 @@ public class UnionPayService extends BasePayService { //商户代码 params.put(SDKConstants.param_merId, payConfigStorage.getPid()); - DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss"); //订单发送时间 - params.put(SDKConstants.param_txnTime, df.format(System.currentTimeMillis())); + params.put(SDKConstants.param_txnTime, DateUtils.formatDate(new Date(), DateUtils.YYYYMMDDHHMMSS)); //后台通知地址 params.put(SDKConstants.param_backUrl, payConfigStorage.getNotifyUrl()); + //交易币种 params.put(SDKConstants.param_currencyCode, "156"); //接入类型,商户接入填0 ,不需修改(0:直连商户, 1: 收单机构 2:平台商户) @@ -154,9 +217,23 @@ public class UnionPayService extends BasePayService { * @param result 回调回来的参数集 * @return 签名校验 true通过 */ + @Deprecated @Override public boolean verify(Map result) { + + return verify(new NoticeParams(result)); + } + + /** + * 回调校验 + * + * @param noticeParams 回调回来的参数集 + * @return 签名校验 true通过 + */ + @Override + public boolean verify(NoticeParams noticeParams) { + final Map result = noticeParams.getBody(); if (null == result || result.get(SDKConstants.param_signature) == null) { LOG.debug("银联支付验签异常:params:" + result); return false; @@ -171,11 +248,10 @@ public class UnionPayService extends BasePayService { * @param sign 签名原文 * @return 签名校验 true通过 */ - @Override public boolean signVerify(Map params, String sign) { SignUtils signUtils = SignUtils.valueOf(payConfigStorage.getSignType()); - String data = SignUtils.parameterText(params, "&", "signature"); + String data = SignTextUtils.parameterText(params, "&", "signature"); switch (signUtils) { case RSA: data = SignUtils.SHA1.createSign(data, "", payConfigStorage.getInputCharset()); @@ -193,23 +269,13 @@ public class UnionPayService extends BasePayService { } } - /** - * 支付宝需要,微信是否也需要再次校验来源,进行订单查询 - * 校验数据来源 - * - * @param id 业务id, 数据的真实性. - * @return true通过 - */ - @Override - public boolean verifySource(String id) { - return false; - } /** * 订单超时时间。 * 超过此时间后,除网银交易外,其他交易银联系统会拒绝受理,提示超时。 跳转银行网银交易如果超时后交易成功,会自动退款,大约5个工作日金额返还到持卡人账户。 * 此时间建议取支付时的北京时间加15分钟。 * 超过超时时间调查询接口应答origRespCode不是A6或者00的就可以判断为失败。 + * * @param expirationTime 超时时间 * @return 具体的时间字符串 */ @@ -220,6 +286,7 @@ public class UnionPayService extends BasePayService { } return DateUtils.formatDate(new Timestamp(System.currentTimeMillis() + 30 * 60 * 1000), DateUtils.YYYYMMDDHHMMSS); } + /** * 返回创建的订单信息 * @@ -230,20 +297,16 @@ public class UnionPayService extends BasePayService { @Override public Map orderInfo(PayOrder order) { Map params = this.getCommonParam(); -// if(order instanceof UnionPayOrder){ -// UnionPayOrder unionPayOrder = (UnionPayOrder)order; -// //todo 其他参数 -//// params.put(); -// } - UnionTransactionType type = (UnionTransactionType) order.getTransactionType(); + UnionTransactionType type = (UnionTransactionType) order.getTransactionType(); + initNotifyUrl(params, order); //设置交易类型相关的参数 type.convertMap(params); params.put(SDKConstants.param_orderId, order.getOutTradeNo()); - if (StringUtils.isNotEmpty(order.getAddition())){ + if (StringUtils.isNotEmpty(order.getAddition())) { params.put(SDKConstants.param_reqReserved, order.getAddition()); } switch (type) { @@ -272,7 +335,8 @@ public class UnionPayService extends BasePayService { params.put(SDKConstants.param_payTimeout, getPayTimeout(order.getExpirationTime())); params.put("orderDesc", order.getSubject()); } - + params.putAll(order.getAttrs()); + params = preOrderHandler(params, order); return setSign(params); } @@ -291,21 +355,21 @@ public class UnionPayService extends BasePayService { switch (signUtils) { case RSA: parameters.put(SDKConstants.param_signMethod, SDKConstants.SIGNMETHOD_RSA); - parameters.put(SDKConstants.param_certId, payConfigStorage.getCertDescriptor().getSignCertId()); - signStr = SignUtils.SHA1.createSign(SignUtils.parameterText(parameters, "&", "signature"), "", payConfigStorage.getInputCharset()); - parameters.put(SDKConstants.param_signature, RSA.sign(signStr, payConfigStorage.getCertDescriptor().getSignCertPrivateKey(payConfigStorage.getKeyPrivateCertPwd()), payConfigStorage.getInputCharset())); + parameters.put(SDKConstants.param_certId, certDescriptor.getSignCertId()); + signStr = SignUtils.SHA1.createSign(SignTextUtils.parameterText(parameters, "&", "signature"), "", payConfigStorage.getInputCharset()); + parameters.put(SDKConstants.param_signature, RSA.sign(signStr, certDescriptor.getSignCertPrivateKey(payConfigStorage.getKeyPrivateCertPwd()), payConfigStorage.getInputCharset())); break; case RSA2: parameters.put(SDKConstants.param_signMethod, SDKConstants.SIGNMETHOD_RSA); - parameters.put(SDKConstants.param_certId, payConfigStorage.getCertDescriptor().getSignCertId()); - signStr = SignUtils.SHA256.createSign(SignUtils.parameterText(parameters, "&", "signature"), "", payConfigStorage.getInputCharset()); - parameters.put(SDKConstants.param_signature, RSA2.sign(signStr, payConfigStorage.getCertDescriptor().getSignCertPrivateKey(payConfigStorage.getKeyPrivateCertPwd()), payConfigStorage.getInputCharset())); + parameters.put(SDKConstants.param_certId, certDescriptor.getSignCertId()); + signStr = SignUtils.SHA256.createSign(SignTextUtils.parameterText(parameters, "&", "signature"), "", payConfigStorage.getInputCharset()); + parameters.put(SDKConstants.param_signature, RSA2.sign(signStr, certDescriptor.getSignCertPrivateKey(payConfigStorage.getKeyPrivateCertPwd()), payConfigStorage.getInputCharset())); break; case SHA1: case SHA256: case SM3: String key = payConfigStorage.getKeyPrivate(); - signStr = SignUtils.parameterText(parameters, "&", "signature"); + signStr = SignTextUtils.parameterText(parameters, "&", "signature"); key = signUtils.createSign(key, "", payConfigStorage.getInputCharset()) + "&"; parameters.put(SDKConstants.param_signature, signUtils.createSign(signStr, key, payConfigStorage.getInputCharset())); break; @@ -326,8 +390,8 @@ public class UnionPayService extends BasePayService { private X509Certificate verifyCertificate(X509Certificate cert) { try { cert.checkValidity();//验证有效期 - X509Certificate middleCert = payConfigStorage.getCertDescriptor().getPublicCert(); - X509Certificate rootCert = payConfigStorage.getCertDescriptor().getRootCert(); + X509Certificate middleCert = certDescriptor.getPublicCert(); + X509Certificate rootCert = certDescriptor.getRootCert(); X509CertSelector selector = new X509CertSelector(); selector.setCertificate(cert); @@ -348,43 +412,70 @@ public class UnionPayService extends BasePayService { CertPathBuilder builder = CertPathBuilder.getInstance("PKIX"); - @SuppressWarnings("unused") - PKIXCertPathBuilderResult result = (PKIXCertPathBuilderResult) builder.build(pkixParams); + /*PKIXCertPathBuilderResult result = (PKIXCertPathBuilderResult)*/ + builder.build(pkixParams); return cert; - } catch (java.security.cert.CertPathBuilderException e) { + } + catch (java.security.cert.CertPathBuilderException e) { LOG.error("verify certificate chain fail.", e); - } catch (CertificateExpiredException e) { - LOG.error(e); - } catch (CertificateNotYetValidException e) { - LOG.error(e); - } catch (Exception e) { - LOG.error(e); + } + catch (CertificateExpiredException e) { + LOG.error("", e); + } + catch (GeneralSecurityException e) { + LOG.error("", e); } return null; } /** - * 获取输出二维码,用户返回给支付端, + * 发送订单 * * @param order 发起支付的订单信息 - * @return 返回图片信息,支付时需要的 + * @return 返回支付结果 */ - @Override - public BufferedImage genQrPay(PayOrder order) { + + public JSONObject postOrder(PayOrder order, String url) { Map params = orderInfo(order); - String responseStr = getHttpRequestTemplate().postForObject(this.getBackTransUrl(), params, String.class); - Map response = UriVariables.getParametersToMap(responseStr); + String responseStr = getHttpRequestTemplate().postForObject(url, params, String.class); + JSONObject response = UriVariables.getParametersToMap(responseStr); if (response.isEmpty()) { throw new PayErrorException(new PayException("failure", "响应内容有误!", responseStr)); } + return response; + } + + @Override + public String toPay(PayOrder order) { + + if (null == order.getTransactionType()) { + order.setTransactionType(UnionTransactionType.WEB); + } + else if (UnionTransactionType.WEB != order.getTransactionType() && UnionTransactionType.WAP != order.getTransactionType() && UnionTransactionType.B2B != order.getTransactionType()) { + throw new PayErrorException(new PayException("-1", "错误的交易类型:" + order.getTransactionType())); + } + + return super.toPay(order); + } + + /** + * 获取输出二维码,用户返回给支付端, + * + * @param order 发起支付的订单信息 + * @return 返回图片信息,支付时需要的 + */ + @Override + public String getQrPay(PayOrder order) { + order.setTransactionType(UnionTransactionType.APPLY_QR_CODE); + JSONObject response = postOrder(order, getBackTransUrl()); if (this.verify(response)) { if (SDKConstants.OK_RESP_CODE.equals(response.get(SDKConstants.param_respCode))) { //成功 - return MatrixToImageWriter.writeInfoToJpgBuff((String) response.get(SDKConstants.param_qrCode)); + return (String) response.get(SDKConstants.param_qrCode); } - throw new PayErrorException(new PayException((String) response.get(SDKConstants.param_respCode), (String) response.get(SDKConstants.param_respMsg), responseStr)); + throw new PayErrorException(new PayException((String) response.get(SDKConstants.param_respCode), (String) response.get(SDKConstants.param_respMsg), response.toJSONString())); } - throw new PayErrorException(new PayException("failure", "验证签名失败", responseStr)); + throw new PayErrorException(new PayException("failure", "验证签名失败", response.toJSONString())); } /** @@ -395,9 +486,9 @@ public class UnionPayService extends BasePayService { */ @Override public Map microPay(PayOrder order) { - Map params = orderInfo(order); - String responseStr = getHttpRequestTemplate().postForObject(this.getBackTransUrl(), params, String.class); - return UriVariables.getParametersToMap(responseStr); + order.setTransactionType(UnionTransactionType.CONSUME); + JSONObject response = postOrder(order, getBackTransUrl()); + return response; } @@ -413,7 +504,8 @@ public class UnionPayService extends BasePayService { CertificateFactory cf = CertificateFactory.getInstance("X.509"); InputStream tIn = new ByteArrayInputStream(x509CertString.getBytes("ISO-8859-1")); x509Cert = (X509Certificate) cf.generateCertificate(tIn); - } catch (Exception e) { + } + catch (Exception e) { throw new PayErrorException(new PayException("证书加载失败", "gen certificate error:" + e.getLocalizedMessage())); } return x509Cert; @@ -474,17 +566,17 @@ public class UnionPayService extends BasePayService { /** * 功能:将订单信息进行签名并提交请求 - * 业务范围:手机控件支付产品(WAP), - * @param order 订单信息 - * @return 成功:返回支付结果 失败:返回 + * 业务范围:手机支付控件(含安卓Pay) + * + * @param order 订单信息 + * @return 成功:返回支付结果 失败:返回 */ - public Map sendHttpRequest(PayOrder order){ - Map params = orderInfo(order); - String responseStr = getHttpRequestTemplate().postForObject(this.getBackTransUrl(), params, String.class); - Map response = UriVariables.getParametersToMap(responseStr); - if (response.isEmpty()) { - throw new PayErrorException(new PayException("failure", "响应内容有误!", responseStr)); + @Override + public Map app(PayOrder order) { + if (null == order.getTransactionType()) { + order.setTransactionType(UnionTransactionType.APP); } + JSONObject response = postOrder(order, getAppTransUrl()); if (this.verify(response)) { if (SDKConstants.OK_RESP_CODE.equals(response.get(SDKConstants.param_respCode))) { // //成功,获取tn号 @@ -492,9 +584,9 @@ public class UnionPayService extends BasePayService { // //TODO return response; } - throw new PayErrorException(new PayException((String) response.get(SDKConstants.param_respCode), (String) response.get(SDKConstants.param_respMsg), responseStr)); + throw new PayErrorException(new PayException((String) response.get(SDKConstants.param_respCode), (String) response.get(SDKConstants.param_respMsg), response.toJSONString())); } - throw new PayErrorException(new PayException("failure", "验证签名失败", responseStr)); + throw new PayErrorException(new PayException("failure", "验证签名失败", response.toJSONString())); } /** @@ -506,25 +598,35 @@ public class UnionPayService extends BasePayService { */ @Override public Map query(String tradeNo, String outTradeNo) { + return query(new AssistOrder(tradeNo, outTradeNo)); + + } + + /** + * 交易查询接口 + * + * @param assistOrder 查询条件 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(AssistOrder assistOrder) { Map params = this.getCommonParam(); UnionTransactionType.QUERY.convertMap(params); - params.put(SDKConstants.param_orderId, outTradeNo); + params.put(SDKConstants.param_orderId, assistOrder.getOutTradeNo()); this.setSign(params); String responseStr = getHttpRequestTemplate().postForObject(this.getSingleQueryUrl(), params, String.class); JSONObject response = UriVariables.getParametersToMap(responseStr); - if (this.verify(response)) { + if (this.verify(new NoticeParams(response))) { if (SDKConstants.OK_RESP_CODE.equals(response.getString(SDKConstants.param_respCode))) { String origRespCode = response.getString(SDKConstants.param_origRespCode); if ((SDKConstants.OK_RESP_CODE).equals(origRespCode)) { //交易成功,更新商户订单状态 - //TODO return response; } } throw new PayErrorException(new PayException(response.getString(SDKConstants.param_respCode), response.getString(SDKConstants.param_respMsg), response.toJSONString())); } throw new PayErrorException(new PayException("failure", "验证签名失败", response.toJSONString())); - } @@ -537,7 +639,7 @@ public class UnionPayService extends BasePayService { * @param type UnionTransactionType.REFUND 或者UnionTransactionType.CONSUME_UNDO * @return 返回支付方申请退款后的结果 */ - public Map unionRefundOrConsumeUndo(String origQryId, String orderId, BigDecimal refundAmount, UnionTransactionType type) { + public UnionRefundResult unionRefundOrConsumeUndo(String origQryId, String orderId, BigDecimal refundAmount, UnionTransactionType type) { return unionRefundOrConsumeUndo(new RefundOrder(orderId, origQryId, refundAmount), type); } @@ -549,21 +651,21 @@ public class UnionPayService extends BasePayService { * @param type UnionTransactionType.REFUND 或者UnionTransactionType.CONSUME_UNDO * @return 返回支付方申请退款后的结果 */ - public Map unionRefundOrConsumeUndo(RefundOrder refundOrder, UnionTransactionType type) { + public UnionRefundResult unionRefundOrConsumeUndo(RefundOrder refundOrder, UnionTransactionType type) { Map params = this.getCommonParam(); type.convertMap(params); params.put(SDKConstants.param_orderId, refundOrder.getRefundNo()); params.put(SDKConstants.param_txnAmt, Util.conversionCentAmount(refundOrder.getRefundAmount())); params.put(SDKConstants.param_origQryId, refundOrder.getTradeNo()); + params.putAll(refundOrder.getAttrs()); this.setSign(params); String responseStr = getHttpRequestTemplate().postForObject(this.getBackTransUrl(), params, String.class); JSONObject response = UriVariables.getParametersToMap(responseStr); - if (this.verify(response)) { - if (SDKConstants.OK_RESP_CODE.equals(response.getString(SDKConstants.param_respCode))) { -// String origRespCode = response.getString(SDKConstants.param_origRespCode); - //交易成功,更新商户订单状态 - //TODO - return response; + + if (this.verify(new NoticeParams(response))) { + final UnionRefundResult refundResult = UnionRefundResult.create(response); + if (SDKConstants.OK_RESP_CODE.equals(refundResult.getRespCode())) { + return refundResult; } throw new PayErrorException(new PayException(response.getString(SDKConstants.param_respCode), response.getString(SDKConstants.param_respMsg), response.toJSONString())); @@ -584,49 +686,43 @@ public class UnionPayService extends BasePayService { } /** - * 申请退款接口 + * 交易关闭接口 * - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 - * @param refundAmount 退款金额 - * @param totalAmount 总金额 - * @return 返回支付方申请退款后的结果 - * @see #refund(RefundOrder) + * @param assistOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 */ - @Deprecated @Override - public Map refund(String tradeNo, String outTradeNo, BigDecimal refundAmount, BigDecimal totalAmount) { - return refund(new RefundOrder(tradeNo, outTradeNo, refundAmount, totalAmount)); + public Map close(AssistOrder assistOrder) { + return Collections.emptyMap(); } - @Override - public Map refund(RefundOrder refundOrder) { + public UnionRefundResult refund(RefundOrder refundOrder) { return unionRefundOrConsumeUndo(refundOrder, UnionTransactionType.REFUND); } + /** * 查询退款 * - * @param tradeNo 支付平台订单号 - * @param outTradeNo 商户单号 + * @param refundOrder 退款订单单号信息 * @return 返回支付方查询退款后的结果 */ @Override - public Map refundquery(String tradeNo, String outTradeNo) { + public Map refundquery(RefundOrder refundOrder) { return Collections.emptyMap(); } - /** - * 查询退款 + * 下载对账单 * - * @param refundOrder 退款订单单号信息 - * @return 返回支付方查询退款后的结果 + * @param billDate 账单时间 + * @param fileType 文件类型 文件类型,一般商户填写00即可 + * @return 返回fileContent 请自行将数据落地 */ @Override - public Map refundquery(RefundOrder refundOrder) { - return Collections.emptyMap(); + public Map downloadBill(Date billDate, String fileType) { + return downloadBill(billDate, new UnionPayBillType(fileType)); } /** @@ -637,12 +733,13 @@ public class UnionPayService extends BasePayService { * @return 返回fileContent 请自行将数据落地 */ @Override - public Map downloadbill(Date billDate, String billType) { + public Map downloadBill(Date billDate, BillType billType) { + Map params = this.getCommonParam(); UnionTransactionType.FILE_TRANSFER.convertMap(params); params.put(SDKConstants.param_settleDate, DateUtils.formatDate(billDate, DateUtils.MMDD)); - params.put(SDKConstants.param_fileType, billType); + params.put(SDKConstants.param_fileType, billType.getFileType()); params.remove(SDKConstants.param_backUrl); params.remove(SDKConstants.param_currencyCode); this.setSign(params); @@ -661,16 +758,13 @@ public class UnionPayService extends BasePayService { /** - * @param tradeNoOrBillDate 支付平台订单号或者账单类型, 具体请 - * 类型为{@link String }或者 {@link Date },类型须强制限制,类型不对应则抛出异常{@link PayErrorException} - * @param outTradeNoBillType 商户单号或者 账单类型 - * @param transactionType 交易类型 - * @return 返回支付方对应接口的结果 + * 创建消息 + * + * @param message 支付平台返回的消息 + * @return 支付消息对象 */ @Override - public Map secondaryInterface(Object tradeNoOrBillDate, String outTradeNoBillType, TransactionType transactionType) { - return Collections.emptyMap(); + public PayMessage createMessage(Map message) { + return UnionPayMessage.create(message); } - - } diff --git a/pay-java-union/src/main/java/com/egzosn/pay/union/bean/UnionPayBillType.java b/pay-java-union/src/main/java/com/egzosn/pay/union/bean/UnionPayBillType.java new file mode 100644 index 0000000000000000000000000000000000000000..c83368517c32d8848de5e1cf2a43ac55a56d9343 --- /dev/null +++ b/pay-java-union/src/main/java/com/egzosn/pay/union/bean/UnionPayBillType.java @@ -0,0 +1,71 @@ +package com.egzosn.pay.union.bean; + +import com.egzosn.pay.common.bean.BillType; +import com.egzosn.pay.common.util.str.StringUtils; + +/** + * 银联账单类型 + * + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2021/2/23
+ * 
+ */ +public class UnionPayBillType implements BillType { + + private String fileType = "00"; + + /** + * 获取类型名称 + * + * @return 类型 + */ + @Override + public String getType() { + return null; + } + + /** + * 获取类型对应的日期格式化表达式 + * + * @return 日期格式化表达式 + */ + @Override + public String getDatePattern() { + return null; + } + + /** + * 获取文件类型 + * + * @return 文件类型 + */ + @Override + public String getFileType() { + return fileType; + } + + public void setFileType(String fileType) { + this.fileType = fileType; + } + + /** + * 自定义属性 + * + * @return 自定义属性 + */ + @Override + public String getCustom() { + return null; + } + + public UnionPayBillType() { + } + + public UnionPayBillType(String fileType) { + if (StringUtils.isNotEmpty(fileType)) { + this.fileType = fileType; + } + } +} diff --git a/pay-java-union/src/main/java/com/egzosn/pay/union/bean/UnionPayMessage.java b/pay-java-union/src/main/java/com/egzosn/pay/union/bean/UnionPayMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..0ef0d8dc821012c519e6a375919c5d07d431c0ab --- /dev/null +++ b/pay-java-union/src/main/java/com/egzosn/pay/union/bean/UnionPayMessage.java @@ -0,0 +1,354 @@ +package com.egzosn.pay.union.bean; + +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.annotation.JSONField; +import com.egzosn.pay.common.bean.PayMessage; + +import java.math.BigDecimal; +import java.util.Map; + +/** + * 银联回调信息 + * + * @author egan + *
+ *                         email egzosn@gmail.com
+ *                         date 2019/7/3.22:07
+ *                 
+ */ + +public class UnionPayMessage extends PayMessage { +// 查询流水号 queryId AN20..21 M-必填 消费交易的流水号,供后续查询用 + private String queryId; + // 交易币种 currencyCode AN3 M-必填 默认为156 + private String currencyCode; + // 交易传输时间 traceTime MMDDhhmmss M-必填 + @JSONField(name = "traceTime", format = "MMDDhhmmss") + private String traceTime; + // 签名 signature ANS1..1024 M-必填 + private String signature; + // 签名方法 signMethod N2 M-必填 + private String signMethod; + // 清算币种 settleCurrencyCode AN3 M-必填 + private String settleCurrencyCode; + // 清算金额 settleAmt N1..12 M-必填 + private BigDecimal settleAmt; + // 清算日期 settleDate MMDD M-必填 + @JSONField(name = "settleDate", format = "MMDD") + private String settleDate; + // 系统跟踪号 traceNo N6 M-必填 + private String traceNo; + // 应答码 respCode AN2 M-必填 + private String respCode; + // 应答信息 respMsg ANS1..256 M-必填 + private String respMsg; + // 兑换日期 exchangeDate MMDD C-按条件必填 交易成功,交易币种和清算币种不一致的时候返回 + @JSONField(name = "exchangeDate", format = "MMDD") + private String exchangeDate; + // 签名公钥证书 signPubKeyCert AN1..2048 C-按条件必填 使用RSA签名方式时必选,此域填写银联签名公钥证书。 + private String signPubKeyCert; + // 清算汇率 exchangeRate N8 C-按条件必填 交易成功,交易币种和清算币种不一致的时候返回 + private String exchangeRate; + // 账号 accNo AN1..1024 C-按条件必填 根据商户配置返回 + private String accNo; + // 支付方式 payType N4 C-按条件必填 根据商户配置返回 + private String payType; + // 支付卡标识 payCardNo ANS1..19 C-按条件必填 移动支付交易时,根据商户配置返回 + private String payCardNo; + // 支付卡类型 payCardType N2 C-按条件必填 根据商户配置返回 + private String payCardType; + // 支付卡名称 payCardIssueName ANS1..64 C-按条件必填 移动支付交易时,根据商户配置返回 + private String payCardIssueName; + // 版本号 version NS5 R-需要返回 + private String version; + // 绑定标识号 bindId ANS1..128 R-需要返回 绑定支付时,根据商户配置返回 + private String bindId; + // 编码方式 encoding ANS1..20 R-需要返回 + private String encoding; + // 产品类型 bizType N6 R-需要返回 + private String bizType; + // 订单发送时间 txnTime YYYYMMDDhhmmss R-需要返回 + @JSONField(name = "txnTime", format = "YYYYMMDDhhmmss") + private String txnTime; + // 交易金额 txnAmt N1..12 R-需要返回 + private BigDecimal txnAmt; + // 交易类型 txnType N2 R-需要返回 + private String txnType; + // 交易子类 txnSubType N2 R-需要返回 + private String txnSubType; + // 接入类型 accessType N1 R-需要返回 0:商户直连接入 1:收单机构接入2:平台商户接入 + private String accessType; + // 请求方保留域 reqReserved ANS1..1024 R-需要返回 + private String reqReserved; + // 商户代码 merId AN15 R-需要返回 + private String merId; + // 商户订单号 orderId AN8..40 R-需要返回 商户订单号,不能含“-”或“_”; 商户自定义,同一交易日期内不可重复; 商户代码merId、商户订单号orderId、订单发送时间txnTime三要素唯一确定一笔交易。 保留域 reserved ANS1..2048O- 选填 查看详情 + private String orderId; + // 分账域 accSplitData ANS1..512O- 选填 查看详情 + private String accSplitData; + + public String getQueryId() { + return queryId; + } + + public void setQueryId(String queryId) { + this.queryId = queryId; + } + + public String getCurrencyCode() { + return currencyCode; + } + + public void setCurrencyCode(String currencyCode) { + this.currencyCode = currencyCode; + } + + public String getTraceTime() { + return traceTime; + } + + public void setTraceTime(String traceTime) { + this.traceTime = traceTime; + } + + public String getSignature() { + return signature; + } + + public void setSignature(String signature) { + this.signature = signature; + } + + public String getSignMethod() { + return signMethod; + } + + public void setSignMethod(String signMethod) { + this.signMethod = signMethod; + } + + public String getSettleCurrencyCode() { + return settleCurrencyCode; + } + + public void setSettleCurrencyCode(String settleCurrencyCode) { + this.settleCurrencyCode = settleCurrencyCode; + } + + public BigDecimal getSettleAmt() { + return settleAmt; + } + + public void setSettleAmt(BigDecimal settleAmt) { + this.settleAmt = settleAmt; + } + + public String getSettleDate() { + return settleDate; + } + + public void setSettleDate(String settleDate) { + this.settleDate = settleDate; + } + + public String getTraceNo() { + return traceNo; + } + + public void setTraceNo(String traceNo) { + this.traceNo = traceNo; + } + + public String getRespCode() { + return respCode; + } + + public void setRespCode(String respCode) { + this.respCode = respCode; + } + + public String getRespMsg() { + return respMsg; + } + + public void setRespMsg(String respMsg) { + this.respMsg = respMsg; + } + + public String getExchangeDate() { + return exchangeDate; + } + + public void setExchangeDate(String exchangeDate) { + this.exchangeDate = exchangeDate; + } + + public String getSignPubKeyCert() { + return signPubKeyCert; + } + + public void setSignPubKeyCert(String signPubKeyCert) { + this.signPubKeyCert = signPubKeyCert; + } + + public String getExchangeRate() { + return exchangeRate; + } + + public void setExchangeRate(String exchangeRate) { + this.exchangeRate = exchangeRate; + } + + public String getAccNo() { + return accNo; + } + + public void setAccNo(String accNo) { + this.accNo = accNo; + } + + @Override + public String getPayType() { + return payType; + } + + @Override + public void setPayType(String payType) { + this.payType = payType; + } + + public String getPayCardNo() { + return payCardNo; + } + + public void setPayCardNo(String payCardNo) { + this.payCardNo = payCardNo; + } + + public String getPayCardType() { + return payCardType; + } + + public void setPayCardType(String payCardType) { + this.payCardType = payCardType; + } + + public String getPayCardIssueName() { + return payCardIssueName; + } + + public void setPayCardIssueName(String payCardIssueName) { + this.payCardIssueName = payCardIssueName; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getBindId() { + return bindId; + } + + public void setBindId(String bindId) { + this.bindId = bindId; + } + + public String getEncoding() { + return encoding; + } + + public void setEncoding(String encoding) { + this.encoding = encoding; + } + + public String getBizType() { + return bizType; + } + + public void setBizType(String bizType) { + this.bizType = bizType; + } + + public String getTxnTime() { + return txnTime; + } + + public void setTxnTime(String txnTime) { + this.txnTime = txnTime; + } + + public BigDecimal getTxnAmt() { + return txnAmt; + } + + public void setTxnAmt(BigDecimal txnAmt) { + this.txnAmt = txnAmt; + } + + public String getTxnType() { + return txnType; + } + + public void setTxnType(String txnType) { + this.txnType = txnType; + } + + public String getTxnSubType() { + return txnSubType; + } + + public void setTxnSubType(String txnSubType) { + this.txnSubType = txnSubType; + } + + public String getAccessType() { + return accessType; + } + + public void setAccessType(String accessType) { + this.accessType = accessType; + } + + public String getReqReserved() { + return reqReserved; + } + + public void setReqReserved(String reqReserved) { + this.reqReserved = reqReserved; + } + + public String getMerId() { + return merId; + } + + public void setMerId(String merId) { + this.merId = merId; + } + + public String getOrderId() { + return orderId; + } + + public void setOrderId(String orderId) { + this.orderId = orderId; + } + + public String getAccSplitData() { + return accSplitData; + } + + public void setAccSplitData(String accSplitData) { + this.accSplitData = accSplitData; + } + + public static final UnionPayMessage create(Map message) { + UnionPayMessage payMessage = new JSONObject(message).toJavaObject(UnionPayMessage.class); + payMessage.setPayMessage(message); + return payMessage; + } + +} diff --git a/pay-java-union/src/main/java/com/egzosn/pay/union/bean/UnionPayOrder.java b/pay-java-union/src/main/java/com/egzosn/pay/union/bean/UnionPayOrder.java deleted file mode 100644 index 153570c2e0fb03fbd09a175534c043b479625897..0000000000000000000000000000000000000000 --- a/pay-java-union/src/main/java/com/egzosn/pay/union/bean/UnionPayOrder.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.egzosn.pay.union.bean; - -import com.egzosn.pay.common.bean.PayOrder; -import com.egzosn.pay.common.bean.TransactionType; - -import java.math.BigDecimal; - -/** - * 银联订单实体 - * @author Actinia - * @create 2019-02-13 23:39 - */ -public class UnionPayOrder extends PayOrder { - - //请求方保留域(透传字段) - private String reqReserved; - //风控信息域 - private String riskRateInfo; - - public UnionPayOrder(String subject, String body, BigDecimal price, String outTradeNo, TransactionType transactionType) { - setSubject(subject); - setBody(body); - setPrice(price); - setOutTradeNo(outTradeNo); - setTransactionType(transactionType); - } - public UnionPayOrder(String subject, String body, BigDecimal price, String outTradeNo) { - setSubject(subject); - setBody(body); - setPrice(price); - setOutTradeNo(outTradeNo); - } - - public String getReqReserved() { - return reqReserved; - } - - public void setReqReserved(String reqReserved) { - this.reqReserved = reqReserved; - } - - public String getRiskRateInfo() { - return riskRateInfo; - } - - public void setRiskRateInfo(String riskRateInfo) { - this.riskRateInfo = riskRateInfo; - } -} diff --git a/pay-java-union/src/main/java/com/egzosn/pay/union/bean/UnionRefundResult.java b/pay-java-union/src/main/java/com/egzosn/pay/union/bean/UnionRefundResult.java new file mode 100644 index 0000000000000000000000000000000000000000..36990cac52fd5060c03112b8c2a96e7d0e3a2494 --- /dev/null +++ b/pay-java-union/src/main/java/com/egzosn/pay/union/bean/UnionRefundResult.java @@ -0,0 +1,326 @@ +package com.egzosn.pay.union.bean; + +import java.math.BigDecimal; +import java.util.Map; + +import com.alibaba.fastjson.JSONObject; +import com.egzosn.pay.common.bean.BaseRefundResult; +import com.egzosn.pay.common.bean.CurType; + +/** + * 银联退款结果 + * + * @author Egan + * email egzosn@gmail.com + * date 2020/8/16 22:15 + */ +public class UnionRefundResult extends BaseRefundResult { + + /** + * 二维码数据 + */ + private String qrCode; + /** + * 签名 + */ + private String signature; + /** + * 签名方法 + */ + private String signMethod; + /** + * 应答码 + */ + private String respCode; + /** + * 应答信息 + */ + private String respMsg; + /** + * 签名公钥证书 + */ + private String signPubKeyCert; + /** + * 版本号 + */ + private String version; + /** + * 编码方式 + */ + private String encoding; + /** + * 产品类型 + */ + private String bizType; + /** + * 订单发送时间 + */ + private String txnTime; + + /** + * 交易类型 + */ + private String txnType; + /** + * 交易子类 + */ + private String txnSubType; + /** + * 接入类型 + * 0:商户直连接入 + * 1:收单机构接入 + * 2:平台商户接入 + */ + private String accessType; + /** + * 请求方保留域 + */ + private String reqReserved; + /** + * 商户代码 + */ + private String merId; + /** + * 商户订单号 + */ + private String orderId; + /** + * 保留域 + */ + private String reserved; + + + /** + * 获取退款请求结果状态码 + * + * @return 状态码 + */ + @Override + public String getCode() { + return respCode; + } + + /** + * 获取退款请求结果状态提示信息 + * + * @return 提示信息 + */ + @Override + public String getMsg() { + return respMsg; + } + + /** + * 返回业务结果状态码 + * + * @return 业务结果状态码 + */ + @Override + public String getResultCode() { + return null; + } + + /** + * 返回业务结果状态提示信息 + * + * @return 业务结果状态提示信息 + */ + @Override + public String getResultMsg() { + return null; + } + + /** + * 退款金额 + * + * @return 退款金额 + */ + @Override + public BigDecimal getRefundFee() { + return null; + } + + /** + * 退款币种信息 + * + * @return 币种信息 + */ + @Override + public CurType getRefundCurrency() { + return null; + } + + /** + * 支付平台交易号 + * 发起支付时 支付平台(如支付宝)返回的交易订单号 + * + * @return 支付平台交易号 + */ + @Override + public String getTradeNo() { + return null; + } + + /** + * 支付订单号 + * 发起支付时,用户系统的订单号 + * + * @return 支付订单号 + */ + @Override + public String getOutTradeNo() { + return orderId; + } + + /** + * 商户退款单号 + * + * @return 商户退款单号 + */ + @Override + public String getRefundNo() { + return null; + } + + public String getQrCode() { + return qrCode; + } + + public void setQrCode(String qrCode) { + this.qrCode = qrCode; + } + + public String getSignature() { + return signature; + } + + public void setSignature(String signature) { + this.signature = signature; + } + + public String getSignMethod() { + return signMethod; + } + + public void setSignMethod(String signMethod) { + this.signMethod = signMethod; + } + + public String getRespCode() { + return respCode; + } + + public void setRespCode(String respCode) { + this.respCode = respCode; + } + + public String getRespMsg() { + return respMsg; + } + + public void setRespMsg(String respMsg) { + this.respMsg = respMsg; + } + + public String getSignPubKeyCert() { + return signPubKeyCert; + } + + public void setSignPubKeyCert(String signPubKeyCert) { + this.signPubKeyCert = signPubKeyCert; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getEncoding() { + return encoding; + } + + public void setEncoding(String encoding) { + this.encoding = encoding; + } + + public String getBizType() { + return bizType; + } + + public void setBizType(String bizType) { + this.bizType = bizType; + } + + public String getTxnTime() { + return txnTime; + } + + public void setTxnTime(String txnTime) { + this.txnTime = txnTime; + } + + public String getTxnType() { + return txnType; + } + + public void setTxnType(String txnType) { + this.txnType = txnType; + } + + public String getTxnSubType() { + return txnSubType; + } + + public void setTxnSubType(String txnSubType) { + this.txnSubType = txnSubType; + } + + public String getAccessType() { + return accessType; + } + + public void setAccessType(String accessType) { + this.accessType = accessType; + } + + public String getReqReserved() { + return reqReserved; + } + + public void setReqReserved(String reqReserved) { + this.reqReserved = reqReserved; + } + + public String getMerId() { + return merId; + } + + public void setMerId(String merId) { + this.merId = merId; + } + + public String getOrderId() { + return orderId; + } + + public void setOrderId(String orderId) { + this.orderId = orderId; + } + + public String getReserved() { + return reserved; + } + + public void setReserved(String reserved) { + this.reserved = reserved; + } + + public static final UnionRefundResult create(Map result){ + UnionRefundResult refundResult = new JSONObject(result).toJavaObject(UnionRefundResult.class); + refundResult.setAttrs(result); + return refundResult; + } +} diff --git a/pay-java-union/src/test/java/PayTest.java b/pay-java-union/src/test/java/PayTest.java index f64b70b02ac99dd6af28d1a4607eab2277026a14..93bda1250ab918b2d259ff2b05a6b6d86125262d 100644 --- a/pay-java-union/src/test/java/PayTest.java +++ b/pay-java-union/src/test/java/PayTest.java @@ -1,18 +1,18 @@ +import java.awt.image.BufferedImage; +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; + import com.egzosn.pay.common.bean.MethodType; import com.egzosn.pay.common.bean.PayOrder; import com.egzosn.pay.common.bean.RefundOrder; import com.egzosn.pay.union.api.UnionPayConfigStorage; import com.egzosn.pay.union.api.UnionPayService; +import com.egzosn.pay.union.bean.UnionRefundResult; import com.egzosn.pay.union.bean.UnionTransactionType; -import java.awt.image.BufferedImage; -import java.math.BigDecimal; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Map; - /** - * * Descrption:银联支付测试 * Author:Actinia * Date:2017/12/19 21:12 @@ -52,7 +52,7 @@ public class PayTest { //支付服务 UnionPayService service = new UnionPayService(unionPayConfigStorage); //支付订单基础信息 - PayOrder payOrder = new PayOrder("订单title", "摘要", new BigDecimal(0.01) , new SimpleDateFormat("yyyyMMddHHmmss").format(System.currentTimeMillis())); + PayOrder payOrder = new PayOrder("订单title", "摘要", BigDecimal.valueOf(0.01), new SimpleDateFormat("yyyyMMddHHmmss").format(System.currentTimeMillis())); /*----------- 网页支付-------------------*/ @@ -79,34 +79,34 @@ public class PayTest { /*-----------消费(被扫场景)待定------------------------------*/ payOrder.setTransactionType(UnionTransactionType.CONSUME); payOrder.setAuthCode("C2B码(条码号),1-20位数字"); - params = service.microPay(payOrder); + params = service.microPay(payOrder); /*-----------消费(被扫场景)------------------------------*/ // /*-----------消费撤销------------------------------*/ - params = service.unionRefundOrConsumeUndo(new RefundOrder( "订单号", "原交易查询流水号", new BigDecimal("退款金额" )),UnionTransactionType.CONSUME_UNDO); + UnionRefundResult refundResult = service.unionRefundOrConsumeUndo(new RefundOrder("订单号", "原交易查询流水号", new BigDecimal("退款金额")), UnionTransactionType.CONSUME_UNDO); // /*-----------消费撤销------------------------------*/ /*-----------交易状态查询交易:只有同步应答------------------------------*/ payOrder.setTransactionType(UnionTransactionType.QUERY); - params = service.query(null,"商户单号"); + params = service.query(null, "商户单号"); /*-----------交易状态查询交易:只有同步应答------------------------------*/ /*-----------退货交易:后台资金类交易,有同步应答和后台通知应答------------------------------*/ payOrder.setTransactionType(UnionTransactionType.REFUND); - params = service.refund("原交易查询流水号", "订单号", null,new BigDecimal("退款金额" )); + refundResult = service.refund(new RefundOrder("原交易查询流水号", "订单号", null, new BigDecimal("退款金额"))); /*-----------退货交易:后台资金类交易,有同步应答和后台通知应答------------------------------*/ /*-----------文件传输类接口:后台获取对账文件交易,只有同步应答 ------------------------------*/ - Map fileConten = service.downloadbill(new Date(),"文件类型,一般商户填写00即可"); /*-----------退货交易:后台资金类交易,有同步应答和后台通知应答------------------------------*/ + Map fileConten = service.downloadBill(new Date(), "文件类型,一般商户填写00即可"); /*-----------退货交易:后台资金类交易,有同步应答和后台通知应答------------------------------*/ - /*-----------回调处理-------------------*/ + /*-----------回调处理-------------------*/ // HttpServletRequest request // params = service.getParameter2Map(request.getParameterMap(), request.getInputStream()); - if (service.verify(params)){ + if (service.verify(params)) { System.out.println("支付成功"); return; } diff --git a/pay-java-web-support/pom.xml b/pay-java-web-support/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..a69e2c7a1ab1595c64ce13918e41707fc66829f9 --- /dev/null +++ b/pay-java-web-support/pom.xml @@ -0,0 +1,34 @@ + + + + pay-java-parent + com.egzosn + 2.14.7 + + 4.0.0 + jar + pay-java-web-support + Pay Java Web 相关支持 + + + 4.0.1 + + + + + com.egzosn + pay-java-common + + + javax.servlet + javax.servlet-api + provided + ${servlet-api.version} + + + + + + \ No newline at end of file diff --git a/pay-java-web-support/src/main/java/com/egzosn/pay/web/support/HttpRequestNoticeParams.java b/pay-java-web-support/src/main/java/com/egzosn/pay/web/support/HttpRequestNoticeParams.java new file mode 100644 index 0000000000000000000000000000000000000000..acf0753a0c0443ac1cfe2bbbf66341087bcb8257 --- /dev/null +++ b/pay-java-web-support/src/main/java/com/egzosn/pay/web/support/HttpRequestNoticeParams.java @@ -0,0 +1,82 @@ +package com.egzosn.pay.web.support; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import com.egzosn.pay.common.bean.NoticeRequest; + +/** + * web 相关请求支持 + * + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2021/8/16
+ * 
+ */ +public class HttpRequestNoticeParams implements NoticeRequest { + + + private final HttpServletRequest httpServletRequest; + + public HttpRequestNoticeParams(HttpServletRequest httpServletRequest) { + this.httpServletRequest = httpServletRequest; + } + + /** + * 根据请求头名称获取请求头信息 + * + * @param name 名称 + * @return 请求头值 + */ + @Override + public String getHeader(String name) { + return httpServletRequest.getHeader(name); + } + + /** + * 根据请求头名称获取请求头信息 + * + * @param name 名称 + * @return 请求头值 + */ + @Override + public Enumeration getHeaders(String name) { + return httpServletRequest.getHeaders(name); + } + + /** + * 获取所有的请求头名称 + * + * @return 请求头名称 + */ + @Override + public Enumeration getHeaderNames() { + return httpServletRequest.getHeaderNames(); + } + + /** + * 输入流 + * + * @return 输入流 + * @throws IOException IOException + */ + @Override + public InputStream getInputStream() throws IOException { + return httpServletRequest.getInputStream(); + } + + /** + * 获取所有的请求参数 + * + * @return 请求参数 + */ + @Override + public Map getParameterMap() { + return httpServletRequest.getParameterMap(); + } +} diff --git a/pay-java-wx-youdian/README.md b/pay-java-wx-youdian/README.md index b5afadf9aeb25686b1b607d5e0c15f9e3341efe2..e7f855c6ef1ad5fccb3b2a97de87dbed997ea38b 100644 --- a/pay-java-wx-youdian/README.md +++ b/pay-java-wx-youdian/README.md @@ -50,7 +50,7 @@ ```java //支付订单基础信息 - PayOrder payOrder = new PayOrder("订单title", "摘要", new BigDecimal(0.01) , UUID.randomUUID().toString().replace("-", "")); + PayOrder payOrder = new PayOrder("订单title", "摘要", BigDecimal.valueOf(0.01) , UUID.randomUUID().toString().replace("-", "")); ``` @@ -62,6 +62,7 @@ /*-----------扫码付-------------------*/ payOrder.setTransactionType(YoudianTransactionType.NATIVE); //获取扫码付的二维码 +// String image = service.getQrPay(payOrder); BufferedImage image = service.genQrPay(payOrder); /*-----------/扫码付-------------------*/ diff --git a/pay-java-wx-youdian/pom.xml b/pay-java-wx-youdian/pom.xml index c4862a8ecd334a46658770a9c26ce3afe2845d4d..01f935ba899a507782e850e0e56b1a0efce88774 100644 --- a/pay-java-wx-youdian/pom.xml +++ b/pay-java-wx-youdian/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.12.6-SNAPSHOT + 2.14.7 4.0.0 pay-java-wx-youdian diff --git a/pay-java-wx-youdian/src/main/java/com/egzosn/pay/wx/youdian/api/WxYouDianPayConfigStorage.java b/pay-java-wx-youdian/src/main/java/com/egzosn/pay/wx/youdian/api/WxYouDianPayConfigStorage.java index 8523223fa35ef12a4dc8e8efac534801aebe41fe..8888db6121b8d2551b0eab4400614d9e800d2636 100644 --- a/pay-java-wx-youdian/src/main/java/com/egzosn/pay/wx/youdian/api/WxYouDianPayConfigStorage.java +++ b/pay-java-wx-youdian/src/main/java/com/egzosn/pay/wx/youdian/api/WxYouDianPayConfigStorage.java @@ -1,11 +1,14 @@ package com.egzosn.pay.wx.youdian.api; +import java.util.concurrent.locks.ReentrantLock; + import com.egzosn.pay.common.api.BasePayConfigStorage; /** * 支付客户端配置存储 - * @author egan * + * @author egan + *

* email egzosn@gmail.com * date 2017/01/12 22:58 */ @@ -15,11 +18,7 @@ public class WxYouDianPayConfigStorage extends BasePayConfigStorage { /** * 账号 */ - public volatile String seller; - - - - + public String seller; @Override @@ -27,6 +26,17 @@ public class WxYouDianPayConfigStorage extends BasePayConfigStorage { return null; } + /** + * 应用id + * 纠正名称 + * + * @return 应用id + */ + @Override + public String getAppId() { + return null; + } + @Override public String getPid() { @@ -43,9 +53,8 @@ public class WxYouDianPayConfigStorage extends BasePayConfigStorage { } - public void setToken(String accessToken) { - setAccessToken(accessToken); + setAccessToken(accessToken); } @Override @@ -53,9 +62,7 @@ public class WxYouDianPayConfigStorage extends BasePayConfigStorage { return getAccessToken(); } - - - - - + public WxYouDianPayConfigStorage() { + setAccessTokenLock(new ReentrantLock()); + } } diff --git a/pay-java-wx-youdian/src/main/java/com/egzosn/pay/wx/youdian/api/WxYouDianPayService.java b/pay-java-wx-youdian/src/main/java/com/egzosn/pay/wx/youdian/api/WxYouDianPayService.java index 701b7343aab1004d5a670de275e3756b7a1ed899..d16cdd8281b1bfb123779812d22b44072ef8cec2 100644 --- a/pay-java-wx-youdian/src/main/java/com/egzosn/pay/wx/youdian/api/WxYouDianPayService.java +++ b/pay-java-wx-youdian/src/main/java/com/egzosn/pay/wx/youdian/api/WxYouDianPayService.java @@ -1,55 +1,71 @@ package com.egzosn.pay.wx.youdian.api; +import java.math.BigDecimal; +import java.util.Collections; +import java.util.Date; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.locks.Lock; + import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.common.api.BasePayService; -import com.egzosn.pay.common.bean.*; +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.common.bean.BaseRefundResult; +import com.egzosn.pay.common.bean.BillType; +import com.egzosn.pay.common.bean.CurType; +import com.egzosn.pay.common.bean.MethodType; +import com.egzosn.pay.common.bean.NoticeParams; +import com.egzosn.pay.common.bean.PayMessage; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.PayOutMessage; +import com.egzosn.pay.common.bean.RefundOrder; +import com.egzosn.pay.common.bean.RefundResult; +import com.egzosn.pay.common.bean.TransactionType; import com.egzosn.pay.common.bean.result.PayError; import com.egzosn.pay.common.exception.PayErrorException; import com.egzosn.pay.common.http.HttpConfigStorage; -import com.egzosn.pay.common.util.MatrixToImageWriter; import com.egzosn.pay.common.util.Util; +import com.egzosn.pay.common.util.sign.SignTextUtils; import com.egzosn.pay.common.util.sign.SignUtils; import com.egzosn.pay.common.util.str.StringUtils; +import com.egzosn.pay.wx.youdian.bean.WxYoudianPayMessage; import com.egzosn.pay.wx.youdian.bean.YdPayError; import com.egzosn.pay.wx.youdian.bean.YoudianTransactionType; -import java.awt.image.BufferedImage; -import java.io.InputStream; -import java.math.BigDecimal; -import java.util.*; -import java.util.concurrent.locks.Lock; /** - * 友店支付服务 - * @author egan + * 友店支付服务 * + * @author egan + *

* email egzosn@gmail.com * date 2017/01/12 22:58 */ public class WxYouDianPayService extends BasePayService { - private final static String URL = "http://life.51youdian.com/Api/CheckoutCounter/"; - + private static final String URL = "http://life.51youdian.com/Api/CheckoutCounter/"; + private static final String ACCESS_TOKEN = "access_token"; + private static final String RETURN_CODE = "return_code"; + private static final String ERROR_CODE = "errorcode"; + private static final String ORDER_SN = "order_sn"; /** * 获取请求token + * * @return 授权令牌 */ - public String getAccessToken() { - try { - return getAccessToken(false); - } catch (PayErrorException e) { - throw e; - } + private String getAccessToken() { + return getAccessToken(false); } /** - * 获取授权令牌 + * 获取授权令牌 + * * @param forceRefresh 是否重新获取, true重新获取 * @return 新的授权令牌 * @throws PayErrorException 支付异常 */ - public String getAccessToken(boolean forceRefresh) throws PayErrorException { + private String getAccessToken(boolean forceRefresh) throws PayErrorException { Lock lock = payConfigStorage.getAccessTokenLock(); try { lock.lock(); @@ -59,23 +75,25 @@ public class WxYouDianPayService extends BasePayService data = new TreeMap<>(); - data.put("username", payConfigStorage.getSeller()); - data.put("password", payConfigStorage.getKeyPrivate()); - String apbNonce = SignUtils.randomStr(); + private JSONObject login() throws PayErrorException { + TreeMap data = new TreeMap<>(); + data.put("username", payConfigStorage.getSeller()); + data.put("password", payConfigStorage.getKeyPrivate()); + String apbNonce = SignTextUtils.randomStr(); // 1、确定请求主体为用户登录,即需要传登录的用户名username和密码password并且要生成唯一的随机数命名为apb_nonce,长度为32位 // 2、将所有的参数集进行key排序 // 3、将排序后的数组从起始位置拼接成字符串如:password=XXXXXXXusername=XXXXX // 4、将拼接出来的字符串连接上apb_nonce的值即AAAAAAAAAA。再连接 password=XXXXXXXusername=XXXXXAAAAAAAAAA - String sign = createSign(SignUtils.parameterText(data, "") + apbNonce, payConfigStorage.getInputCharset()); - String queryParam = SignUtils.parameterText(data) + "&apb_nonce=" + apbNonce + "&sign=" + sign; - - JSONObject json = execute(getUrl(YoudianTransactionType.LOGIN) + "?" + queryParam, MethodType.GET, null); - payConfigStorage.updateAccessToken(json.getString("access_token"), json.getLongValue("viptime")); - return json; - } - + String sign = createSign(SignTextUtils.parameterText(data, "") + apbNonce, payConfigStorage.getInputCharset()); + String queryParam = SignTextUtils.parameterText(data) + "&apb_nonce=" + apbNonce + "&sign=" + sign; + JSONObject json = execute(getReqUrl(YoudianTransactionType.LOGIN) + "?" + queryParam, MethodType.GET, null); + payConfigStorage.updateAccessToken(json.getString(ACCESS_TOKEN), json.getLongValue("viptime")); + return json; + } /** @@ -113,51 +130,62 @@ public class WxYouDianPayService extends BasePayService params) { - if (!"SUCCESS".equals(params.get("return_code"))){ - LOG.debug(String.format("友店微信支付异常:return_code=%s,参数集=%s", params.get("return_code"), params)); + + return verify(new NoticeParams(params)); + } + + /** + * 回调校验 + * + * @param noticeParams 回调回来的参数集 + * @return 签名校验 true通过 + */ + @Override + public boolean verify(NoticeParams noticeParams) { + final Map params = noticeParams.getBody(); + + if (!"SUCCESS".equals(params.get(RETURN_CODE))) { + LOG.debug("友店微信支付异常:return_code={},参数集={}", params.get(RETURN_CODE), params); return false; } - if(params.get("sign") == null) {LOG.debug("友店微信支付异常:签名为空!out_trade_no=" + params.get("out_trade_no"));} - - try { - return signVerify(params, (String) params.get("sign")) && verifySource((String)params.get("out_trade_no")); - } catch (PayErrorException e) { - LOG.error(e.getMessage()); + if (params.get("sign") == null) { + LOG.debug("友店微信支付异常:签名为空!out_trade_no={}", params.get("out_trade_no")); } - return false; - } + return signVerify(params, (String) params.get("sign")) && verifySource((String) params.get("out_trade_no")); + } /** * 根据反馈回来的信息,生成签名结果 + * * @param params 通知返回来的参数数组 - * @param sign 比对的签名结果 + * @param sign 比对的签名结果 * @return 生成的签名结果 */ - @Override - public boolean signVerify(Map params, String sign) { + private boolean signVerify(Map params, String sign) { return SignUtils.valueOf(payConfigStorage.getSignType()).verify(params, sign, "&key=" + payConfigStorage.getKeyPublic(), payConfigStorage.getInputCharset()); } - /** * 验证链接来源是否有效 * 校验数据来源 - * @param id id 商户订单号(扫码收款返回的order_sn) + * + * @param id id 商户订单号(扫码收款返回的order_sn) * @return true通过 */ - @Override - public boolean verifySource(String id) { + private boolean verifySource(String id) { try { - JSONObject jsonObject = (JSONObject)query(id, null); + JSONObject jsonObject = (JSONObject) query(id, null); - return 0 == jsonObject.getIntValue("errorcode"); - }catch (PayErrorException e){ - if (Integer.parseInt(e.getPayError().getErrorCode()) >= 400){ + return 0 == jsonObject.getIntValue(ERROR_CODE); + } + catch (PayErrorException e) { + if (Integer.parseInt(e.getPayError().getErrorCode()) >= 400) { throw e; } return false; @@ -166,46 +194,47 @@ public class WxYouDianPayService extends BasePayService data = new TreeMap<>(); - data.put("access_token", getAccessToken()); + Map data = new TreeMap<>(); + data.put(ACCESS_TOKEN, getAccessToken()); data.put("paymoney", Util.conversionAmount(order.getPrice()).toString()); - String apbNonce = SignUtils.randomStr(); - String sign = createSign(SignUtils.parameterText(data, "") + apbNonce, payConfigStorage.getInputCharset()); + data.putAll(order.getAttrs()); + data = preOrderHandler(data, order); + String apbNonce = SignTextUtils.randomStr(); + String sign = createSign(SignTextUtils.parameterText(data, "") + apbNonce, payConfigStorage.getInputCharset()); data.put("PayMoney", data.remove("paymoney")); - String params = SignUtils.parameterText(data) + "&apb_nonce=" + apbNonce + "&sign=" + sign; - try { - JSONObject json = execute(getUrl(order.getTransactionType())+ "?" + params, MethodType.GET, null); - //友店比较特殊,需要在下完预订单后,自己存储 order_sn 对应 微信官方文档 out_trade_no - order.setOutTradeNo(json.getString("order_sn")); - return json; - } catch (PayErrorException e) { - throw e; - } + String params = SignTextUtils.parameterText(data) + "&apb_nonce=" + apbNonce + "&sign=" + sign; + JSONObject json = execute(getReqUrl(order.getTransactionType()) + "?" + params, MethodType.GET, null); + //友店比较特殊,需要在下完预订单后,自己存储 order_sn 对应 微信官方文档 out_trade_no + order.setTradeNo(json.getString(ORDER_SN)); + return json; } - /** * 签名 - * @param content 需要签名的内容 - * @param characterEncoding 字符编码 * - * 1、确定请求主体为用户登录,即需要传登录的用户名username和密码password并且要生成唯一的随机数命名为apb_nonce,长度为32位 - * 2、将所有的参数集进行key排序 - * 3、将排序后的数组从起始位置拼接成字符串如:password=XXXXXXXusername=XXXXX - * 4、将拼接出来的字符串连接上apb_nonce的值即AAAAAAAAAA。再连接 password=XXXXXXXusername=XXXXXAAAAAAAAAA + * @param content 需要签名的内容 + * @param characterEncoding 字符编码 + *

+ * 1、确定请求主体为用户登录,即需要传登录的用户名username和密码password并且要生成唯一的随机数命名为apb_nonce,长度为32位 + * 2、将所有的参数集进行key排序 + * 3、将排序后的数组从起始位置拼接成字符串如:password=XXXXXXXusername=XXXXX + * 4、将拼接出来的字符串连接上apb_nonce的值即AAAAAAAAAA。再连接 password=XXXXXXXusername=XXXXXAAAAAAAAAA * @return 签名结果 */ @Override public String createSign(String content, String characterEncoding) { - return SignUtils.valueOf(payConfigStorage.getSignType().toUpperCase()).createSign(content, "&source=http://life.51youdian.com", characterEncoding); + return SignUtils.valueOf(payConfigStorage.getSignType().toUpperCase()).createSign(content, "&source=http://life.51youdian.com", characterEncoding); } - /** - * 将请求参数或者请求流转化为 Map - * - * @param parameterMap 请求参数 - * @param is 请求流 - * @return 获得回调的请求参数 - */ - @Override - public Map getParameter2Map(Map parameterMap, InputStream is) { - Map params = new TreeMap(); - for (Iterator iter = parameterMap.keySet().iterator(); iter.hasNext();) { - String name = (String) iter.next(); - String[] values = parameterMap.get(name); - String valueStr = ""; - for (int i = 0; i < values.length; i++) { - valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ","; - } - params.put(name, valueStr.trim()); - } - - return params; - - } /** * 具体需要返回的数据为 @@ -293,36 +296,37 @@ public class WxYouDianPayService extends BasePayService builder = new TreeMap<>(); - builder.put("return_code", code.toUpperCase()); + builder.put(RETURN_CODE, code.toUpperCase()); builder.put("return_msg", message); - builder.put("nonce_str", SignUtils.randomStr()); + builder.put("nonce_str", SignTextUtils.randomStr()); String sgin = SignUtils.valueOf(payConfigStorage.getSignType()).sign(builder, "&key=" + payConfigStorage.getKeyPrivate(), payConfigStorage.getInputCharset()); - return PayOutMessage.TEXT().content("{\"return_code\":\""+builder.get("return_code")+"\",\"return_msg\":\""+builder.get("return_msg")+"\",\"nonce_str\":\""+builder.get("nonce_str")+"\",\"sign\":\""+ sgin +"\"}").build(); + return PayOutMessage.TEXT().content("{\"return_code\":\"" + builder.get(RETURN_CODE) + "\",\"return_msg\":\"" + builder.get("return_msg") + "\",\"nonce_str\":\"" + builder.get("nonce_str") + "\",\"sign\":\"" + sgin + "\"}").build(); } /** * 获取成功输出消息,用户返回给支付端 * 主要用于拦截器中返回 + * * @param payMessage 支付回调消息 * @return 返回输出消息 */ @Override public PayOutMessage successPayOutMessage(PayMessage payMessage) { - return PayOutMessage.TEXT().content(JSON.toJSONString(payMessage.getPayMessage())).build(); + return PayOutMessage.TEXT().content(JSON.toJSONString(payMessage.getPayMessage())).build(); } /** * 针对web端的即时付款 - * 暂未实现或无此功能 + * 暂未实现或无此功能 + * * @param orderInfo 发起支付的订单信息 - * @param method 请求方式 "post" "get", + * @param method 请求方式 "post" "get", * @return 获取输出消息,用户返回给支付端, 针对于web端 * @see MethodType 请求类型 */ @@ -333,20 +337,22 @@ public class WxYouDianPayService extends BasePayService microPay(PayOrder order) { - JSONObject orderInfo = orderInfo(order); - return orderInfo; + order.setTransactionType(YoudianTransactionType.MICROPAY); + return orderInfo(order); } /** @@ -358,19 +364,30 @@ public class WxYouDianPayService extends BasePayService query(String tradeNo, String outTradeNo) { - String apbNonce = SignUtils.randomStr(); + return query(new AssistOrder(tradeNo, outTradeNo)); + } + + /** + * 交易查询接口 + * + * @param assistOrder 查询条件 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(AssistOrder assistOrder) { + String apbNonce = SignTextUtils.randomStr(); TreeMap data = new TreeMap<>(); - data.put("access_token", payConfigStorage.getAccessToken()); + data.put(ACCESS_TOKEN, payConfigStorage.getAccessToken()); - if (StringUtils.isEmpty(tradeNo)){ - data.put("order_sn", outTradeNo); - }else { - data.put("order_sn", tradeNo); + if (StringUtils.isEmpty(assistOrder.getTradeNo())) { + data.put(ORDER_SN, assistOrder.getOutTradeNo()); + } + else { + data.put(ORDER_SN, assistOrder.getTradeNo()); } - String sign = createSign(SignUtils.parameterText(data, "") + apbNonce, payConfigStorage.getInputCharset()); - String queryParam = SignUtils.parameterText(data) + "&apb_nonce=" + apbNonce + "&sign=" + sign; - JSONObject jsonObject = execute(getUrl(YoudianTransactionType.NATIVE_STATUS) + "?" + queryParam, MethodType.GET, null); - return jsonObject; + String sign = createSign(SignTextUtils.parameterText(data, "") + apbNonce, payConfigStorage.getInputCharset()); + String queryParam = SignTextUtils.parameterText(data) + "&apb_nonce=" + apbNonce + "&sign=" + sign; + return execute(getReqUrl(YoudianTransactionType.NATIVE_STATUS) + "?" + queryParam, MethodType.GET, null); } @@ -379,40 +396,90 @@ public class WxYouDianPayService extends BasePayService refund(String tradeNo, String outTradeNo, BigDecimal refundAmount, BigDecimal totalAmount) { - return refund(new RefundOrder(tradeNo, outTradeNo,refundAmount, totalAmount)); + public Map close(AssistOrder assistOrder) { + return Collections.emptyMap(); } - - + /** + * 申请退款接口 + * + * @param refundOrder 退款订单信息 + * @return 返回支付方申请退款后的结果 + */ @Override - public Map refund(RefundOrder refundOrder) { - String apbNonce = SignUtils.randomStr(); + public RefundResult refund(RefundOrder refundOrder) { + String apbNonce = SignTextUtils.randomStr(); TreeMap data = new TreeMap<>(); - data.put("access_token", payConfigStorage.getAccessToken()); + data.put(ACCESS_TOKEN, payConfigStorage.getAccessToken()); - if (StringUtils.isEmpty(refundOrder.getOutTradeNo())){ - data.put("order_sn", refundOrder.getOutTradeNo()); - }else { - data.put("order_sn", refundOrder.getTradeNo()); + if (StringUtils.isEmpty(refundOrder.getOutTradeNo())) { + data.put(ORDER_SN, refundOrder.getOutTradeNo()); + } + else { + data.put(ORDER_SN, refundOrder.getTradeNo()); } //支付类型刷卡为3扫码为4 data.put("type", "4"); data.put("refund_fee", refundOrder.getRefundAmount().setScale(2, BigDecimal.ROUND_HALF_UP).toString()); - String sign = createSign(SignUtils.parameterText(data, "") + apbNonce, payConfigStorage.getInputCharset()); - String queryParam = SignUtils.parameterText(data) + "&apb_nonce=" + apbNonce + "&sign=" + sign; - JSONObject jsonObject = execute(getUrl(YoudianTransactionType.NATIVE_STATUS) + "?" + queryParam, MethodType.GET, null); - return jsonObject; - } + String sign = createSign(SignTextUtils.parameterText(data, "") + apbNonce, payConfigStorage.getInputCharset()); + String queryParam = SignTextUtils.parameterText(data) + "&apb_nonce=" + apbNonce + "&sign=" + sign; + JSONObject jsonObject = execute(getReqUrl(YoudianTransactionType.REFUND) + "?" + queryParam, MethodType.GET, null); + return new BaseRefundResult(jsonObject) { + @Override + public String getCode() { + return getAttrString(ERROR_CODE); + } + @Override + public String getMsg() { + return getAttrString("msg"); + } - @Override - public Map refundquery(String tradeNo, String outTradeNo) { - return Collections.emptyMap(); + @Override + public String getResultCode() { + return null; + } + + @Override + public String getResultMsg() { + return null; + } + + @Override + public BigDecimal getRefundFee() { + return null; + } + + @Override + public CurType getRefundCurrency() { + return null; + } + + @Override + public String getTradeNo() { + return null; + } + + @Override + public String getOutTradeNo() { + return null; + } + + @Override + public String getRefundNo() { + return null; + } + }; } + /** * 查询退款 * @@ -426,26 +493,11 @@ public class WxYouDianPayService extends BasePayService downloadbill(Date billDate, String billType) { - return Collections.emptyMap(); - } - - - /** - * @param tradeNoOrBillDate 支付平台订单号或者账单类型, 具体请 - * 类型为{@link String }或者 {@link Date },类型须强制限制,类型不对应则抛出异常{@link PayErrorException} - * @param outTradeNoBillType 商户单号或者 账单类型 - * @param transactionType 交易类型 - * - * @return 返回支付方对应接口的结果 - */ - @Override - public Map secondaryInterface(Object tradeNoOrBillDate, String outTradeNoBillType, TransactionType transactionType) { + public Map downloadBill(Date billDate, BillType billType) { return Collections.emptyMap(); } - public WxYouDianPayService(WxYouDianPayConfigStorage payConfigStorage) { super(payConfigStorage); } @@ -456,11 +508,24 @@ public class WxYouDianPayService extends BasePayService message) { + return WxYoudianPayMessage.create(message); + } } diff --git a/pay-java-wx-youdian/src/main/java/com/egzosn/pay/wx/youdian/bean/WxYoudianPayMessage.java b/pay-java-wx-youdian/src/main/java/com/egzosn/pay/wx/youdian/bean/WxYoudianPayMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..c909f4d7cfa9665ed7c4330b694e21616be56d99 --- /dev/null +++ b/pay-java-wx-youdian/src/main/java/com/egzosn/pay/wx/youdian/bean/WxYoudianPayMessage.java @@ -0,0 +1,327 @@ +package com.egzosn.pay.wx.youdian.bean; + +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.annotation.JSONField; +import com.egzosn.pay.common.bean.PayMessage; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.Map; + +/** + * 友店回调信息 + * + * @author egan + * email egzosn@gmail.com + * date 2019/7/3.20:25 + */ + +public class WxYoudianPayMessage extends PayMessage { + // 公众账号ID appid 是 String(32) wx8888888888888888 微信分配的公众账号ID(企业号corpid即为此appId) + @JSONField(name = "appid") + private String appid; + // 商户号 mch_id 是 String(32) 1900000109 微信支付分配的商户号 + @JSONField(name = "mch_id") + private String mchId; + // 设备号 device_info 否 String(32) 013467007045764 微信支付分配的终端设备号, + @JSONField(name = "device_info") + private String deviceInfo; + // 随机字符串 nonce_str 是 String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,不长于32位 + @JSONField(name = "nonce_str") + private String nonceStr; + // 签名 sign 是 String(32) C380BEC2BFD727A4B6845133519F3AD6 签名,详见签名算法 + @JSONField(name = "sign") + private String sign; + // 签名类型 sign_type 否 String(32) HMAC-SHA256 签名类型,目前支持HMAC-SHA256和MD5,默认为MD5 + @JSONField(name = "sign_type") + private String signType; + // 业务结果 result_code 是 String(16) SUCCESS SUCCESS/FAIL + @JSONField(name = "result_code") + private String resultCode; + // 错误代码 err_code 否 String(32) SYSTEMERROR 错误返回的信息描述 + @JSONField(name = "err_code") + private String errCode; + // 错误代码描述 err_code_des 否 String(128) 系统错误 错误返回的信息描述 + @JSONField(name = "err_code_des") + private String errCodeDes; + // 用户标识 openid 是 String(128) wxd930ea5d5a258f4f 用户在商户appid下的唯一标识 + @JSONField(name = "openid") + private String openid; + // 是否关注公众账号 is_subscribe 是 String(1) Y 用户是否关注公众账号,Y-关注,N-未关注 + @JSONField(name = "is_subscribe") + private String isSubscribe; + // 交易类型 trade_type 是 String(16) JSAPI JSAPI、NATIVE、APP + @JSONField(name = "trade_type") + private String tradeType; + // 付款银行 bank_type 是 String(16) CMC 银行类型,采用字符串类型的银行标识,银行类型见银行列表 + @JSONField(name = "bank_type") + private String bankType; + // 订单金额 total_fee 是 Int 100 订单总金额,单位为分 + @JSONField(name = "total_fee") + private BigDecimal totalFee; + // 应结订单金额 settlement_total_fee 否 Int 100 应结订单金额=订单金额-非充值代金券金额,应结订单金额<=订单金额。 + @JSONField(name = "settlement_total_fee") + private BigDecimal settlementTotalFee; + // 货币种类 fee_type 否 String(8) CNY 货币类型,符合ISO4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型 + @JSONField(name = "fee_type") + private String feeType; + // 现金支付金额 cash_fee 是 Int 100 现金支付金额订单现金支付金额,详见支付金额 + @JSONField(name = "cash_fee") + private BigDecimal cashFee; + // 现金支付货币类型 cash_fee_type 否 String(16) CNY 货币类型,符合ISO4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型 + @JSONField(name = "cash_fee_type") + private String cashFeeType; + // 总代金券金额 coupon_fee 否 Int 10 代金券金额<=订单金额,订单金额-代金券金额=现金支付金额,详见支付金额 + @JSONField(name = "coupon_fee") + private BigDecimal couponFee; + // 代金券使用数量 coupon_count 否 Int 1 代金券使用数量 + @JSONField(name = "coupon_count") + private Integer couponCount; + // 代金券类型 coupon_type_$n 否 String CASH CASH--充值代金券 NO_CASH---非充值代金券 并且订单使用了免充值券后有返回(取值:CASH、NO_CASH)。$n为下标,从0开始编号,举例:coupon_type_0 + @JSONField(name = "coupon_type_$0") + private String couponType0; + // 代金券ID coupon_id_$n 否 String(20) 10000 代金券ID,$n为下标,从0开始编号 + @JSONField(name = "coupon_id_$0") + private String couponId0; + // 单个代金券支付金额 coupon_fee_$n 否 Int 100 单个代金券支付金额,$n为下标,从0开始编号 + @JSONField(name = "coupon_fee_$0") + private Integer couponFee0; + // 微信支付订单号 transaction_id 是 String(32) 1217752501201407033233368018 微信支付订单号 + @JSONField(name = "transaction_id") + private String transactionId; + // 商户订单号 out_trade_no 是 String(32) 1212321211201407033568112322 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。 + @JSONField(name = "out_trade_no") + private String outTradeNo; + // 商家数据包 attach 否 String(128) 123456 商家数据包,原样返回 + @JSONField(name = "attach") + private String attach; + // 支付完成时间 time_end 是 String(14) 20141030133525 支付完成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则 + @JSONField(name = "time_end", format="yyyyMMddHHmmss") + private Date timeEnd; + + public String getAppid() { + return appid; + } + + public void setAppid(String appid) { + this.appid = appid; + } + + public String getMchId() { + return mchId; + } + + public void setMchId(String mchId) { + this.mchId = mchId; + } + + public String getDeviceInfo() { + return deviceInfo; + } + + public void setDeviceInfo(String deviceInfo) { + this.deviceInfo = deviceInfo; + } + + public String getNonceStr() { + return nonceStr; + } + + public void setNonceStr(String nonceStr) { + this.nonceStr = nonceStr; + } + + @Override + public String getSign() { + return sign; + } + + public void setSign(String sign) { + this.sign = sign; + } + + public String getSignType() { + return signType; + } + + public void setSignType(String signType) { + this.signType = signType; + } + + public String getResultCode() { + return resultCode; + } + + public void setResultCode(String resultCode) { + this.resultCode = resultCode; + } + + public String getErrCode() { + return errCode; + } + + public void setErrCode(String errCode) { + this.errCode = errCode; + } + + public String getErrCodeDes() { + return errCodeDes; + } + + public void setErrCodeDes(String errCodeDes) { + this.errCodeDes = errCodeDes; + } + + public String getOpenid() { + return openid; + } + + public void setOpenid(String openid) { + this.openid = openid; + } + + public String getIsSubscribe() { + return isSubscribe; + } + + public void setIsSubscribe(String isSubscribe) { + this.isSubscribe = isSubscribe; + } + + public String getTradeType() { + return tradeType; + } + + public void setTradeType(String tradeType) { + this.tradeType = tradeType; + } + + public String getBankType() { + return bankType; + } + + public void setBankType(String bankType) { + this.bankType = bankType; + } + + @Override + public BigDecimal getTotalFee() { + return totalFee; + } + + public void setTotalFee(BigDecimal totalFee) { + this.totalFee = totalFee; + } + + public BigDecimal getSettlementTotalFee() { + return settlementTotalFee; + } + + public void setSettlementTotalFee(BigDecimal settlementTotalFee) { + this.settlementTotalFee = settlementTotalFee; + } + + public String getFeeType() { + return feeType; + } + + public void setFeeType(String feeType) { + this.feeType = feeType; + } + + public BigDecimal getCashFee() { + return cashFee; + } + + public void setCashFee(BigDecimal cashFee) { + this.cashFee = cashFee; + } + + public String getCashFeeType() { + return cashFeeType; + } + + public void setCashFeeType(String cashFeeType) { + this.cashFeeType = cashFeeType; + } + + public BigDecimal getCouponFee() { + return couponFee; + } + + public void setCouponFee(BigDecimal couponFee) { + this.couponFee = couponFee; + } + + public Integer getCouponCount() { + return couponCount; + } + + public void setCouponCount(Integer couponCount) { + this.couponCount = couponCount; + } + + public String getCouponType0() { + return couponType0; + } + + public void setCouponType0(String couponType0) { + this.couponType0 = couponType0; + } + + public String getCouponId0() { + return couponId0; + } + + public void setCouponId0(String couponId0) { + this.couponId0 = couponId0; + } + + public Integer getCouponFee0() { + return couponFee0; + } + + public void setCouponFee0(Integer couponFee0) { + this.couponFee0 = couponFee0; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } + + @Override + public String getOutTradeNo() { + return outTradeNo; + } + + public void setOutTradeNo(String outTradeNo) { + this.outTradeNo = outTradeNo; + } + + public String getAttach() { + return attach; + } + + public void setAttach(String attach) { + this.attach = attach; + } + + public Date getTimeEnd() { + return timeEnd; + } + + public void setTimeEnd(Date timeEnd) { + this.timeEnd = timeEnd; + } + + public static final WxYoudianPayMessage create(Map message) { + WxYoudianPayMessage payMessage = new JSONObject(message).toJavaObject(WxYoudianPayMessage.class); + payMessage.setPayMessage(message); + return payMessage; + } + +} diff --git a/pay-java-wx-youdian/src/main/java/com/egzosn/pay/wx/youdian/bean/YdPayError.java b/pay-java-wx-youdian/src/main/java/com/egzosn/pay/wx/youdian/bean/YdPayError.java index 49cd9cf3f1bda5384245759dc1a5c45622c5883f..9aed2479c452eca1ed9eb82a4364b248abcb90ac 100644 --- a/pay-java-wx-youdian/src/main/java/com/egzosn/pay/wx/youdian/bean/YdPayError.java +++ b/pay-java-wx-youdian/src/main/java/com/egzosn/pay/wx/youdian/bean/YdPayError.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original huodull or egan. + * Copyright 2002-2017 the original egan or egan. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ package com.egzosn.pay.wx.youdian.bean; import com.egzosn.pay.common.bean.result.PayError; /** - * @author: egan + * @author egan * * email egzosn@gmail.com * date 2017/3/6 19:41 diff --git a/pay-java-wx-youdian/src/test/java/PayTest.java b/pay-java-wx-youdian/src/test/java/PayTest.java index 069cca4690247a387d5b6b96e79db1dd77869706..26d342fb380144ad2308d264053e5ffbff84b223 100644 --- a/pay-java-wx-youdian/src/test/java/PayTest.java +++ b/pay-java-wx-youdian/src/test/java/PayTest.java @@ -16,8 +16,8 @@ import java.util.UUID; * * 友店微信 * @author egan - * @email egzosn@gmail.com - * @date 2017/8/18 + * email egzosn@gmail.com + * date 2017/8/18 */ public class PayTest { @@ -37,7 +37,7 @@ public class PayTest { //支付服务 PayService service = new WxYouDianPayService(wxPayConfigStorage); //支付订单基础信息 - PayOrder payOrder = new PayOrder("订单title", "摘要", new BigDecimal(0.01) , UUID.randomUUID().toString().replace("-", "")); + PayOrder payOrder = new PayOrder("订单title", "摘要", BigDecimal.valueOf(0.01) , UUID.randomUUID().toString().replace("-", "")); /*-----------扫码付-------------------*/ payOrder.setTransactionType(YoudianTransactionType.NATIVE); //获取扫码付的二维码 diff --git a/pay-java-wx/README.md b/pay-java-wx/README.md index 7ac3b850d37ec24ac6965e5ffedd5537f38baa38..2689e118dd320c7b0adecece238ee3ea31dc340f 100644 --- a/pay-java-wx/README.md +++ b/pay-java-wx/README.md @@ -8,7 +8,7 @@ WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage(); wxPayConfigStorage.setMchId("合作者id(商户号)"); - wxPayConfigStorage.setAppid("应用id"); + wxPayConfigStorage.setAppId("应用id"); wxPayConfigStorage.setKeyPublic("转账公钥,转账时必填"); wxPayConfigStorage.setSecretKey("密钥"); wxPayConfigStorage.setNotifyUrl("异步回调地址"); @@ -29,9 +29,9 @@ //代理端口 httpConfigStorage.setHttpProxyPort(3308); //代理用户名 - httpConfigStorage.setHttpProxyUsername("user"); + httpConfigStorage.setAuthUsername("user"); //代理密码 - httpConfigStorage.setHttpProxyPassword("password"); + httpConfigStorage.setAuthPassword("password"); /* /网路代理配置 根据需求进行设置**/ //退款使用 @@ -39,9 +39,12 @@ //设置ssl证书路径 //TODO 这里也支持输入流的入参。 // httpConfigStorage.setKeystore(this.getClass()..getResourceAsStream("/证书文件")); - httpConfigStorage.setKeystorePath("证书绝对路径"); + //设置ssl证书路径 跟着setCertStoreType 进行对应 + httpConfigStorage.setKeystore("证书文件流,证书字符串信息或证书绝对地址"); //设置ssl证书对应的密码 httpConfigStorage.setStorePassword("证书对应的密码"); + //设置ssl证书对应的存储方式 + httpConfigStorage.setCertStoreType(CertStoreType.PATH); /* /网络请求ssl证书**/ /* /网络请求连接池**/ //最大连接数 @@ -78,7 +81,7 @@ ```java //支付订单基础信息 - PayOrder payOrder = new PayOrder("订单title", "摘要", new BigDecimal(0.01) , UUID.randomUUID().toString().replace("-", "")); + PayOrder payOrder = new PayOrder("订单title", "摘要", BigDecimal.valueOf(0.01) , UUID.randomUUID().toString().replace("-", "")); ``` @@ -90,6 +93,7 @@ /*-----------扫码付-------------------*/ payOrder.setTransactionType(WxTransactionType.NATIVE); //获取扫码付的二维码 +// String image = service.getQrPay(payOrder); BufferedImage image = service.genQrPay(payOrder); /*-----------/扫码付-------------------*/ @@ -108,6 +112,21 @@ ``` + +#### JSAPI支付 + +```java + + /*-----------JSAPI-------------------*/ + //公众号支付 + payOrder.setTransactionType(WxTransactionType.JSAPI); + //微信公众号对应微信付款用户的唯一标识 + payOrder.setOpenid(openid); + Map appOrderInfo = service.orderInfo(payOrder); + /*-----------/JSAPI-------------------*/ + +``` + #### 网页支付 ```java @@ -135,6 +154,23 @@ /*-----------/条码付 刷卡付-------------------*/ +``` +#### 刷脸付 + +```java + + /*-----------刷脸付-------------------*/ + //获取对应的支付账户操作工具(可根据账户id) + PayOrder order = new PayOrder("egan order", "egan order", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), WxTransactionType.FACEPAY); + //设置人脸凭证 + order.setAuthCode(authCode); + // 用户在商户 appid下的唯一标识 + order.setOpenid(openid); + //支付结果 + Map params = service.microPay(order); + + /*-----------/刷脸付-------------------*/ + ``` #### 回调处理 @@ -173,7 +209,7 @@ ```java - Map result = service..query("微信单号", "我方系统单号"); + Map result = service.query("微信单号", "我方系统单号"); ``` @@ -181,7 +217,7 @@ #### 交易关闭接口 ```java - Map result = service..close("微信单号", "我方系统单号"); + Map result = service.close("微信单号", "我方系统单号"); ``` @@ -194,7 +230,7 @@ RefundOrder order = new RefundOrder("微信单号", "我方系统单号", "退款金额", "订单总金额"); //可用于多次退款 order.setRefundNo("退款单号") - Map result = service.refund(order); + WxRefundResult result = service.refund(order); ``` diff --git a/pay-java-wx/pom.xml b/pay-java-wx/pom.xml index be3ef7f53e212fcec68d87c947c5c089885419db..7a41e7472432d9b6890631eb4c0a63e3a098b668 100644 --- a/pay-java-wx/pom.xml +++ b/pay-java-wx/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.12.6-SNAPSHOT + 2.14.7 4.0.0 pay-java-wx @@ -20,7 +20,11 @@ + + org.bouncycastle + bcprov-jdk15on + diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/api/WxBillService.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/api/WxBillService.java new file mode 100644 index 0000000000000000000000000000000000000000..09232dfa974b19dfd9015a28dbb4b9e22af59bef --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/api/WxBillService.java @@ -0,0 +1,20 @@ +package com.egzosn.pay.wx.api; + +import java.util.Date; +import java.util.Map; + +import com.egzosn.pay.common.bean.BillType; + +/** + * 账单接口 + * + * @author faymanwang + * email: 1057438332@qq.com + * time: 2020/7/31 11:21 + */ +public interface WxBillService { + + @Deprecated + Map downloadbill(Date billDate, String billType, String path); + +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/api/WxConst.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/api/WxConst.java new file mode 100644 index 0000000000000000000000000000000000000000..07a290b14e6a53c8ddd753b49132758e30926c5c --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/api/WxConst.java @@ -0,0 +1,40 @@ +package com.egzosn.pay.wx.api; + +/** + * 常量 + * @author egan + *

+ * email egzosn@gmail.com
+ * date 2020/3/10 21:22
+ * 
+ */ +public interface WxConst { + /** + * 微信请求地址 + */ + String URI = "https://api.mch.weixin.qq.com/"; + /** + * 沙箱 + */ + String SANDBOXNEW = "xdc/apiv2sandbox/"; + + String SUCCESS = "SUCCESS"; + String FAIL = "FAIL"; + String RETURN_CODE = "return_code"; + String SIGN = "sign"; + String CIPHER_ALGORITHM = "RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING"; + String FAILURE = "failure"; + String APPID = "appid"; + String HMAC_SHA256 = "HMAC-SHA256"; + String HMACSHA256 = "HMACSHA256"; + String RETURN_MSG_CODE = "return_msg"; + String RESULT_CODE = "result_code"; + String MCH_ID = "mch_id"; + String NONCE_STR = "nonce_str"; + String OUT_TRADE_NO = "out_trade_no"; + String GZIP = "GZIP"; + String BILL_DATE = "bill_date"; + String REQ_INFO = "req_info"; + String NOTIFY_URL = "notify_url"; + +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/api/WxPayConfigStorage.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/api/WxPayConfigStorage.java index 15d039a639cb995025ac9120020ff9b1f4aeaae1..11000b581248622ca290b1a008d41cd6ab2f6a61 100644 --- a/pay-java-wx/src/main/java/com/egzosn/pay/wx/api/WxPayConfigStorage.java +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/api/WxPayConfigStorage.java @@ -4,7 +4,8 @@ import com.egzosn.pay.common.api.BasePayConfigStorage; /** * 微信配置存储 - * @author egan + * + * @author egan * *
  * email egzosn@gmail.com
@@ -17,34 +18,32 @@ public class WxPayConfigStorage extends BasePayConfigStorage {
     /**
      * 微信分配的公众账号ID
      */
-    private String appid ;
+    private String appId;
     /**
      * 微信分配的子商户公众账号ID
      */
-    private String subAppid ;
+    private String subAppId;
     /**
-     *  微信支付分配的商户号 合作者id
+     * 微信支付分配的商户号 合作者id
      */
     private String mchId;
     /**
-     *  微信支付分配的子商户号,开发者模式下必填 合作者id
+     * 微信支付分配的子商户号,开发者模式下必填 合作者id
      */
     private String subMchId;
 
 
-
-
-
+    @Deprecated
     @Override
     public String getAppid() {
-        return appid;
-    }
-
-    public void setAppid(String appid) {
-        this.appid = appid;
+        return appId;
     }
 
 
+    @Deprecated
+    public void setAppid(String appId) {
+        this.appId = appId;
+    }
 
 
     /**
@@ -56,8 +55,6 @@ public class WxPayConfigStorage extends BasePayConfigStorage {
     }
 
 
-
-
     @Override
     public String getSeller() {
         return null;
@@ -73,7 +70,8 @@ public class WxPayConfigStorage extends BasePayConfigStorage {
     }
 
     /**
-     *  为商户平台设置的密钥key
+     * 为商户平台设置的密钥key
+     *
      * @return 微信密钥
      */
     public String getSecretKey() {
@@ -81,17 +79,50 @@ public class WxPayConfigStorage extends BasePayConfigStorage {
     }
 
     public void setSecretKey(String secretKey) {
-         setKeyPrivate(secretKey);
+        setKeyPrivate(secretKey);
     }
 
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
+
+    /**
+     * 应用id
+     * 纠正名称
+     *
+     * @return 应用id
+     */
+    @Override
+    public String getAppId() {
+        return appId;
+    }
+
+    public String getSubAppId() {
+        return subAppId;
+    }
+
+    public void setSubAppId(String subAppId) {
+        this.subAppId = subAppId;
+    }
+
+    /**
+     * 应用id
+     * 纠正名称
+     *
+     * @return 应用id
+     * @see #getSubAppId()
+     */
+    @Deprecated
     public String getSubAppid() {
-        return subAppid;
+        return subAppId;
     }
 
+    @Deprecated
     public void setSubAppid(String subAppid) {
-        this.subAppid = subAppid;
+        this.subAppId = subAppid;
     }
 
+
     public String getSubMchId() {
         return subMchId;
     }
diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/api/WxPayService.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/api/WxPayService.java
index a57162065d8dc1f7f052a347b3f1dd0821c472a2..fad36ae1cf469b0d94b17a6587b6ccbf4cb533c4 100644
--- a/pay-java-wx/src/main/java/com/egzosn/pay/wx/api/WxPayService.java
+++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/api/WxPayService.java
@@ -1,103 +1,137 @@
 package com.egzosn.pay.wx.api;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.security.GeneralSecurityException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.zip.GZIPInputStream;
+
+import static com.egzosn.pay.wx.api.WxConst.APPID;
+import static com.egzosn.pay.wx.api.WxConst.CIPHER_ALGORITHM;
+import static com.egzosn.pay.wx.api.WxConst.FAIL;
+import static com.egzosn.pay.wx.api.WxConst.FAILURE;
+import static com.egzosn.pay.wx.api.WxConst.HMACSHA256;
+import static com.egzosn.pay.wx.api.WxConst.HMAC_SHA256;
+import static com.egzosn.pay.wx.api.WxConst.MCH_ID;
+import static com.egzosn.pay.wx.api.WxConst.NONCE_STR;
+import static com.egzosn.pay.wx.api.WxConst.OUT_TRADE_NO;
+import static com.egzosn.pay.wx.api.WxConst.REQ_INFO;
+import static com.egzosn.pay.wx.api.WxConst.RESULT_CODE;
+import static com.egzosn.pay.wx.api.WxConst.RETURN_CODE;
+import static com.egzosn.pay.wx.api.WxConst.RETURN_MSG_CODE;
+import static com.egzosn.pay.wx.api.WxConst.SANDBOXNEW;
+import static com.egzosn.pay.wx.api.WxConst.SIGN;
+import static com.egzosn.pay.wx.api.WxConst.SUCCESS;
+import static com.egzosn.pay.wx.api.WxConst.URI;
+import static com.egzosn.pay.wx.bean.WxTransferType.GETTRANSFERINFO;
+import static com.egzosn.pay.wx.bean.WxTransferType.QUERY_BANK;
+import static com.egzosn.pay.wx.bean.WxTransferType.TRANSFERS;
+
 import com.alibaba.fastjson.JSONObject;
 import com.egzosn.pay.common.api.BasePayService;
-import com.egzosn.pay.common.api.Callback;
-import com.egzosn.pay.common.bean.*;
+import com.egzosn.pay.common.api.TransferService;
+import com.egzosn.pay.common.bean.AssistOrder;
+import com.egzosn.pay.common.bean.BillType;
+import com.egzosn.pay.common.bean.MethodType;
+import com.egzosn.pay.common.bean.NoticeParams;
+import com.egzosn.pay.common.bean.NoticeRequest;
+import com.egzosn.pay.common.bean.OrderParaStructure;
+import com.egzosn.pay.common.bean.PayMessage;
+import com.egzosn.pay.common.bean.PayOrder;
+import com.egzosn.pay.common.bean.PayOutMessage;
+import com.egzosn.pay.common.bean.RefundOrder;
+import com.egzosn.pay.common.bean.SignType;
+import com.egzosn.pay.common.bean.TransactionType;
+import com.egzosn.pay.common.bean.TransferOrder;
+import com.egzosn.pay.common.bean.TransferType;
 import com.egzosn.pay.common.bean.result.PayException;
 import com.egzosn.pay.common.exception.PayErrorException;
+import com.egzosn.pay.common.http.ClientHttpRequest;
 import com.egzosn.pay.common.http.HttpConfigStorage;
+import com.egzosn.pay.common.http.HttpStringEntity;
+import com.egzosn.pay.common.http.UriVariables;
 import com.egzosn.pay.common.util.DateUtils;
-import com.egzosn.pay.common.util.MatrixToImageWriter;
 import com.egzosn.pay.common.util.Util;
+import com.egzosn.pay.common.util.XML;
+import com.egzosn.pay.common.util.sign.SignTextUtils;
 import com.egzosn.pay.common.util.sign.SignUtils;
+import com.egzosn.pay.common.util.sign.encrypt.AES;
 import com.egzosn.pay.common.util.sign.encrypt.RSA2;
 import com.egzosn.pay.common.util.str.StringUtils;
+import com.egzosn.pay.wx.bean.RedpackOrder;
+import com.egzosn.pay.wx.bean.WxPayBillType;
 import com.egzosn.pay.wx.bean.WxPayError;
+import com.egzosn.pay.wx.bean.WxPayMessage;
+import com.egzosn.pay.wx.bean.WxRefundResult;
+import com.egzosn.pay.wx.bean.WxSendredpackType;
 import com.egzosn.pay.wx.bean.WxTransactionType;
-import com.egzosn.pay.common.util.XML;
 import com.egzosn.pay.wx.bean.WxTransferType;
-import java.awt.image.BufferedImage;
-import java.io.IOException;
-import java.io.InputStream;
-import java.math.BigDecimal;
-import java.net.URLEncoder;
-import java.util.*;
-
-import static com.egzosn.pay.wx.bean.WxTransferType.*;
 
 /**
  * 微信支付服务
  *
  * @author egan
- *         
- *         email egzosn@gmail.com
- *         date 2016-5-18 14:09:01
- *         
+ *
+ * email egzosn@gmail.com
+ * date 2016-5-18 14:09:01
+ * 
*/ -public class WxPayService extends BasePayService { - - - /** - * 微信请求地址 - */ - public static final String URI = "https://api.mch.weixin.qq.com/"; - /** - * 沙箱 - */ - public static final String SANDBOXNEW = "sandboxnew/"; - - public static final String SUCCESS = "SUCCESS"; - public static final String RETURN_CODE = "return_code"; - public static final String SIGN = "sign"; - public static final String CIPHER_ALGORITHM = "RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING"; - public static final String FAILURE = "failure"; - public static final String APPID = "appid"; - private static final String HMAC_SHA256 = "HMAC-SHA256"; - private static final String HMACSHA256 = "HMACSHA256"; - private static final String RETURN_MSG_CODE = "return_msg"; - private static final String RESULT_CODE = "result_code"; - - - +public class WxPayService extends BasePayService implements WxRedPackService, WxBillService, TransferService { /** * 创建支付服务 + * * @param payConfigStorage 微信对应的支付配置 */ public WxPayService(WxPayConfigStorage payConfigStorage) { super(payConfigStorage); } + /** * 创建支付服务 + * * @param payConfigStorage 微信对应的支付配置 - * @param configStorage 微信对应的网络配置,包含代理配置、ssl证书配置 + * @param configStorage 微信对应的网络配置,包含代理配置、ssl证书配置 */ public WxPayService(WxPayConfigStorage payConfigStorage, HttpConfigStorage configStorage) { super(payConfigStorage, configStorage); } + /** * 设置支付配置 + * * @param payConfigStorage 支付配置 */ @Override public BasePayService setPayConfigStorage(WxPayConfigStorage payConfigStorage) { String signType = payConfigStorage.getSignType(); - if (HMAC_SHA256.equals(signType)){ + if (HMAC_SHA256.equals(signType)) { payConfigStorage.setSignType(HMACSHA256); } this.payConfigStorage = payConfigStorage; return this; } + /** * 根据交易类型获取url * * @param transactionType 交易类型 - * * @return 请求url */ - private String getUrl(TransactionType transactionType) { + @Override + public String getReqUrl(TransactionType transactionType) { return URI + (payConfigStorage.isTest() ? SANDBOXNEW : "") + transactionType.getMethod(); } @@ -108,37 +142,37 @@ public class WxPayService extends BasePayService { * @param params 回调回来的参数集 * @return 签名校验 true通过 */ + @Deprecated @Override public boolean verify(Map params) { - if (!(SUCCESS.equals(params.get(RETURN_CODE)) && SUCCESS.equals(params.get(RESULT_CODE)))){ - LOG.debug(String.format("微信支付异常:return_code=%s,参数集=%s", params.get(RETURN_CODE), params)); - return false; - } - - if(null == params.get(SIGN)) { - LOG.debug("微信支付异常:签名为空!out_trade_no=" + params.get("out_trade_no")); - return false; - } - - try { - return signVerify(params, (String) params.get(SIGN)) && verifySource((String) params.get("out_trade_no")); - } catch (PayErrorException e) { - LOG.error(e); - } - return false; + return verify(new NoticeParams(params)); } /** - * 微信是否也需要再次校验来源,进行订单查询 + * 回调校验 * - * @param id 商户单号 - * @return true通过 + * @param noticeParams 回调回来的参数集 + * @return 签名校验 true通过 */ @Override - public boolean verifySource(String id) { - return true; + public boolean verify(NoticeParams noticeParams) { + final Map params = noticeParams.getBody(); + //如果为退款不需要校验, 直接返回, + if (params.containsKey(REQ_INFO)) { + return true; + } + + if (Objects.isNull(params.get(SIGN)) || !(SUCCESS.equals(params.get(RETURN_CODE)) && SUCCESS.equals(params.get(RESULT_CODE)))) { + if (LOG.isErrorEnabled()) { + LOG.error(String.format("微信支付异常:return_code=%s,参数集=%s", params.get(RETURN_CODE), params)); + } + return false; + } + + return signVerify(params, (String) params.get(SIGN)); + } @@ -149,11 +183,18 @@ public class WxPayService extends BasePayService { * @param sign 比对的签名结果 * @return 生成的签名结果 */ - @Override - public boolean signVerify(Map params, String sign) { + private boolean signVerify(Map params, String sign) { + return signVerify(params, sign, payConfigStorage.isTest()); + } + + private boolean signVerify(Map params, String sign, boolean isTest) { SignUtils signUtils = SignUtils.valueOf(payConfigStorage.getSignType()); - String content = SignUtils.parameterText(params, "&", SIGN, "appId") + "&key=" + (signUtils == SignUtils.MD5 ? "" : payConfigStorage.getKeyPrivate()); - return signUtils.verify(content, sign, payConfigStorage.getKeyPrivate(), payConfigStorage.getInputCharset()); + String keyPrivate = payConfigStorage.getKeyPrivate(); + if (isTest) { + keyPrivate = getKeyPrivate(); + } + String content = SignTextUtils.parameterText(params, "&", SIGN, "appId") + "&key=" + (signUtils == SignUtils.MD5 ? "" : keyPrivate); + return signUtils.verify(content, sign, keyPrivate, payConfigStorage.getInputCharset()); } /** @@ -163,15 +204,13 @@ public class WxPayService extends BasePayService { */ private Map getPublicParameters() { - Map parameters = new TreeMap(); - parameters.put(APPID, payConfigStorage.getAppid()); - parameters.put("mch_id", payConfigStorage.getMchId()); + Map parameters = new TreeMap<>(); + parameters.put(APPID, payConfigStorage.getAppId()); + parameters.put(MCH_ID, payConfigStorage.getMchId()); //判断如果是服务商模式信息则加入 - if (!StringUtils.isEmpty(payConfigStorage.getSubAppid()) && !StringUtils.isEmpty(payConfigStorage.getSubMchId())){ - parameters.put("sub_appid", payConfigStorage.getSubAppid()); - parameters.put("sub_mch_id", payConfigStorage.getSubMchId()); - } - parameters.put("nonce_str", SignUtils.randomStr()); + OrderParaStructure.loadParameters(parameters, "sub_mch_id", payConfigStorage.getSubMchId()); + OrderParaStructure.loadParameters(parameters, "sub_appid", payConfigStorage.getSubAppId()); + parameters.put(NONCE_STR, SignTextUtils.randomStr()); return parameters; @@ -191,32 +230,47 @@ public class WxPayService extends BasePayService { // 购买支付信息 parameters.put("body", order.getSubject()); // 购买支付信息 -// parameters.put("detail", order.getBody()); + OrderParaStructure.loadParameters(parameters, "detail", order); // 订单号 - parameters.put("out_trade_no", order.getOutTradeNo()); - parameters.put("spbill_create_ip", StringUtils.isEmpty(order.getSpbillCreateIp()) ? "192.168.1.150" : order.getSpbillCreateIp() ); + parameters.put(OUT_TRADE_NO, order.getOutTradeNo()); + parameters.put("spbill_create_ip", StringUtils.isEmpty(order.getSpbillCreateIp()) ? "192.168.1.150" : order.getSpbillCreateIp()); // 总金额单位为分 - parameters.put("total_fee", Util.conversionCentAmount( order.getPrice())); - if (StringUtils.isNotEmpty(order.getAddition())){ - parameters.put("attach", order.getAddition()); - } - parameters.put("notify_url", payConfigStorage.getNotifyUrl()); + parameters.put("total_fee", Util.conversionCentAmount(order.getPrice())); + OrderParaStructure.loadParameters(parameters, "attach", order.getAddition()); + initNotifyUrl(parameters, order); parameters.put("trade_type", order.getTransactionType().getType()); - if (null != order.getExpirationTime()){ + if (null != order.getExpirationTime()) { parameters.put("time_start", DateUtils.formatDate(new Date(), DateUtils.YYYYMMDDHHMMSS)); - parameters.put("time_expire", DateUtils.formatDate(order.getExpirationTime(), DateUtils.YYYYMMDDHHMMSS)); + parameters.put("time_expire", DateUtils.formatDate(order.getExpirationTime(), DateUtils.YYYYMMDDHHMMSS)); } - ((WxTransactionType) order.getTransactionType()).setAttribute(parameters, order); - setSign(parameters); + if (null != order.getCurType()) { + parameters.put("fee_type", order.getCurType().getType()); + } + + ((WxTransactionType) order.getTransactionType()).setAttribute(parameters, order); + //可覆盖参数 +/* OrderParaStructure.loadParameters(parameters, NOTIFY_URL, order); + OrderParaStructure.loadParameters(parameters, "goods_tag", order); + OrderParaStructure.loadParameters(parameters, "limit_pay", order); + OrderParaStructure.loadParameters(parameters, "receipt", order); + OrderParaStructure.loadParameters(parameters, "product_id", order);*/ + parameters.putAll(order.getAttrs()); + parameters = preOrderHandler(parameters, order); + setSign(parameters); String requestXML = XML.getMap2Xml(parameters); - LOG.debug("requestXML:" + requestXML); + if (LOG.isDebugEnabled()) { + LOG.debug("requestXML:" + requestXML); + } + + HttpStringEntity entity = new HttpStringEntity(requestXML, ClientHttpRequest.APPLICATION_XML_UTF_8); + //调起支付的参数列表 - JSONObject result = requestTemplate.postForObject(getUrl(order.getTransactionType()), requestXML, JSONObject.class); + JSONObject result = requestTemplate.postForObject(getReqUrl(order.getTransactionType()), entity, JSONObject.class); - if (!SUCCESS.equals(result.get(RETURN_CODE))) { - throw new PayErrorException(new WxPayError(result.getString(RETURN_CODE), result.getString(RETURN_MSG_CODE), result.toJSONString())); + if (!SUCCESS.equals(result.get(RETURN_CODE)) || !SUCCESS.equals(result.get(RESULT_CODE))) { + throw new PayErrorException(new WxPayError(result.getString(RESULT_CODE), result.getString(RETURN_MSG_CODE), result.toJSONString())); } return result; } @@ -234,32 +288,32 @@ public class WxPayService extends BasePayService { ////统一下单 JSONObject result = unifiedOrder(order); - // 对微信返回的数据进行校验 - if (verify(result)) { + if (verify(new NoticeParams(preOrderHandler(result, order)))) { //如果是扫码支付或者刷卡付无需处理,直接返回 - if (((WxTransactionType)order.getTransactionType()).isReturn()) { + if (((WxTransactionType) order.getTransactionType()).isReturn()) { return result; } - SortedMap params = new TreeMap(); + Map params = new TreeMap<>(); if (WxTransactionType.JSAPI == order.getTransactionType()) { params.put("signType", payConfigStorage.getSignType()); - params.put("appId", payConfigStorage.getAppid()); - params.put("timeStamp", System.currentTimeMillis() / 1000); - params.put("nonceStr", result.get("nonce_str")); + params.put("appId", payConfigStorage.getAppId()); + params.put("timeStamp", System.currentTimeMillis() / 1000 + ""); + params.put("nonceStr", result.get(NONCE_STR)); params.put("package", "prepay_id=" + result.get("prepay_id")); - } else if (WxTransactionType.APP == order.getTransactionType()) { + } + else if (WxTransactionType.APP == order.getTransactionType()) { params.put("partnerid", payConfigStorage.getPid()); - params.put(APPID, payConfigStorage.getAppid()); + params.put(APPID, payConfigStorage.getAppId()); params.put("prepayid", result.get("prepay_id")); params.put("timestamp", System.currentTimeMillis() / 1000); - params.put("noncestr", result.get("nonce_str")); + params.put("noncestr", result.get(NONCE_STR)); params.put("package", "Sign=WXPay"); } - String paySign = createSign(SignUtils.parameterText(params), payConfigStorage.getInputCharset()); - params.put(SIGN, paySign); + String paySign = createSign(SignTextUtils.parameterText(params), payConfigStorage.getInputCharset()); + params.put(WxTransactionType.JSAPI.equals(order.getTransactionType()) ? "paySign" : SIGN, paySign); return params; } throw new PayErrorException(new WxPayError(result.getString(RETURN_CODE), result.getString(RETURN_MSG_CODE), "Invalid sign value")); @@ -273,16 +327,43 @@ public class WxPayService extends BasePayService { * @return 请求参数 */ private Map setSign(Map parameters) { - String signType = payConfigStorage.getSignType(); - if (HMACSHA256.equals(signType)){ - signType = HMAC_SHA256; + + String signTypeStr = payConfigStorage.getSignType(); + if (HMACSHA256.equals(signTypeStr)) { + signTypeStr = SignUtils.HMACSHA256.getName(); } - parameters.put("sign_type", signType); - String sign = createSign(SignUtils.parameterText(parameters, "&", SIGN, "appId"), payConfigStorage.getInputCharset()); + parameters.put("sign_type", signTypeStr); + String sign = createSign(SignTextUtils.parameterText(parameters, "&", SIGN, "appId"), payConfigStorage.getInputCharset()); parameters.put(SIGN, sign); return parameters; } + + /** + * 获取验签秘钥 + * + * @return 验签秘钥 + */ + private String getKeyPrivate() { + if (!payConfigStorage.isTest()) { + return payConfigStorage.getKeyPrivate(); + } + SortedMap parameters = new TreeMap(); + parameters.put(MCH_ID, payConfigStorage.getMchId()); + parameters.put(NONCE_STR, SignTextUtils.randomStr()); + + String sign = createSign(SignTextUtils.parameterText(parameters, "&", SIGN, "appId"), payConfigStorage.getInputCharset(), false); + parameters.put(SIGN, sign); + + HttpStringEntity entity = new HttpStringEntity(XML.getMap2Xml(parameters), ClientHttpRequest.APPLICATION_XML_UTF_8); + JSONObject result = requestTemplate.postForObject(getReqUrl(WxTransactionType.GETSIGNKEY), entity, JSONObject.class); + if (SUCCESS.equals(result.get(RETURN_CODE))) { + return result.getString("sandbox_signkey"); + } + LOG.error("获取sandbox_signkey失败", new PayErrorException(new PayException(result.getString(RETURN_CODE), result.getString(RETURN_MSG_CODE), result.toJSONString()))); + return null; + } + /** * 签名 * @@ -292,23 +373,67 @@ public class WxPayService extends BasePayService { */ @Override public String createSign(String content, String characterEncoding) { - SignUtils signUtils = SignUtils.valueOf(payConfigStorage.getSignType().toUpperCase()); - return signUtils.createSign(content + "&key=" + (signUtils == SignUtils.MD5 ? "" : payConfigStorage.getKeyPrivate()) , payConfigStorage.getKeyPrivate(), characterEncoding).toUpperCase(); + + return createSign(content, characterEncoding, payConfigStorage.isTest()); + } + + /** + * 签名 + * + * @param content 需要签名的内容 不包含key + * @param characterEncoding 字符编码 + * @param test 是否为沙箱环境 + * @return 签名结果 + */ + public String createSign(String content, String characterEncoding, boolean test) { + SignType signType = SignUtils.valueOf(payConfigStorage.getSignType().toUpperCase()); + String keyPrivate = payConfigStorage.getKeyPrivate(); + if (test) { + keyPrivate = getKeyPrivate(); + } + return signType.createSign(content + "&key=" + (signType == SignUtils.MD5 ? "" : keyPrivate), keyPrivate, characterEncoding).toUpperCase(); } + /** * 将请求参数或者请求流转化为 Map * - * @param parameterMap 请求参数 - * @param is 请求流 + * @param body 通知请求` + * @return 获得回调的请求参数 + */ + public Map getRefundNoticeParams(Map body) { + String reqInfo = (String) body.get(REQ_INFO); + if (StringUtils.isEmpty(reqInfo)) { + return body; + } + try { + String decrypt = AES.decrypt(reqInfo, payConfigStorage.getSecretKey(), payConfigStorage.getInputCharset()); + JSONObject data = XML.toJSONObject(decrypt); + body.putAll(data); + return body; + } + catch (GeneralSecurityException | IOException e) { + throw new PayErrorException(new WxPayError(FAIL, e.getMessage()), e); + } + } + + + /** + * 将请求参数或者请求流转化为 Map + * + * @param request 通知请求 * @return 获得回调的请求参数 */ @Override - public Map getParameter2Map(Map parameterMap, InputStream is) { + public NoticeParams getNoticeParams(NoticeRequest request) { + TreeMap map = new TreeMap(); try { - return XML.inputStream2Map(is, map); - } catch (IOException e) { + Map body = XML.inputStream2Map(request.getInputStream(), map); + body = getRefundNoticeParams(body); + return new NoticeParams(body); + } + catch (IOException e) { throw new PayErrorException(new PayException("IOException", e.getMessage())); } @@ -336,7 +461,7 @@ public class WxPayService extends BasePayService { */ @Override public PayOutMessage successPayOutMessage(PayMessage payMessage) { - return PayOutMessage.XML().code("Success").content("成功").build(); + return PayOutMessage.XML().code("SUCCESS").content("成功").build(); } @@ -354,28 +479,27 @@ public class WxPayService extends BasePayService { throw new PayErrorException(new WxPayError((String) orderInfo.get(RETURN_CODE), (String) orderInfo.get(RETURN_MSG_CODE))); } if (WxTransactionType.MWEB.name().equals(orderInfo.get("trade_type"))) { - return String.format("",orderInfo.get("mweb_url"), StringUtils.isEmpty(payConfigStorage.getReturnUrl()) ? "" : "&redirect_url=" + URLEncoder.encode(payConfigStorage.getReturnUrl())); + return String.format("", orderInfo.get("mweb_url"), StringUtils.isEmpty(payConfigStorage.getReturnUrl()) ? "" : "&redirect_url=" + URLEncoder.encode(payConfigStorage.getReturnUrl())); } throw new UnsupportedOperationException(); } /** - * 获取输出二维码,用户返回给支付端, + * 获取输出二维码信息, * * @param order 发起支付的订单信息 - * @return 返回图片信息,支付时需要的 + * @return 返回二维码信息,,支付时需要的 */ @Override - public BufferedImage genQrPay(PayOrder order) { + public String getQrPay(PayOrder order) { + order.setTransactionType(WxTransactionType.NATIVE); Map orderInfo = orderInfo(order); //获取对应的支付账户操作工具(可根据账户id) if (!SUCCESS.equals(orderInfo.get(RESULT_CODE))) { - throw new PayErrorException(new WxPayError("-1", (String) orderInfo.get("err_code"))); + throw new PayErrorException(new WxPayError((String) orderInfo.get("err_code"), orderInfo.toString())); } - - - return MatrixToImageWriter.writeInfoToJpgBuff((String) orderInfo.get("code_url")); + return (String) orderInfo.get("code_url"); } /** @@ -386,7 +510,13 @@ public class WxPayService extends BasePayService { */ @Override public Map microPay(PayOrder order) { + if (null == order.getTransactionType()) { + order.setTransactionType(WxTransactionType.MICROPAY); + } + else if (WxTransactionType.MICROPAY != order.getTransactionType() && WxTransactionType.FACEPAY != order.getTransactionType()) { + throw new PayErrorException(new PayException("-1", "错误的交易类型:" + order.getTransactionType())); + } return orderInfo(order); } @@ -403,6 +533,16 @@ public class WxPayService extends BasePayService { return secondaryInterface(transactionId, outTradeNo, WxTransactionType.QUERY); } + /** + * 交易查询接口 + * + * @param assistOrder 查询条件 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(AssistOrder assistOrder) { + return secondaryInterface(assistOrder.getTradeNo(), assistOrder.getOutTradeNo(), WxTransactionType.QUERY); + } /** @@ -419,81 +559,66 @@ public class WxPayService extends BasePayService { } /** - * 交易交易撤销 + * 交易关闭接口 * - * @param transactionId 支付平台订单号 - * @param outTradeNo 商户单号 - * @return 返回支付方交易撤销后的结果 + * @param assistOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 */ @Override - public Map cancel(String transactionId, String outTradeNo) { - return secondaryInterface(transactionId, outTradeNo, WxTransactionType.REVERSE); + public Map close(AssistOrder assistOrder) { + return secondaryInterface(assistOrder.getTradeNo(), assistOrder.getOutTradeNo(), WxTransactionType.CLOSE); } + /** - * 退款 + * 交易交易撤销 * - * @param transactionId 微信订单号 + * @param transactionId 支付平台订单号 * @param outTradeNo 商户单号 - * @param refundAmount 退款金额 - * @param totalAmount 总金额 - * @return 返回支付方申请退款后的结果 - * @see #refund(RefundOrder, Callback) + * @return 返回支付方交易撤销后的结果 */ - @Deprecated @Override - public Map refund(String transactionId, String outTradeNo, BigDecimal refundAmount, BigDecimal totalAmount) { - - return refund(new RefundOrder(transactionId, outTradeNo, refundAmount, totalAmount)); + public Map cancel(String transactionId, String outTradeNo) { + return secondaryInterface(transactionId, outTradeNo, WxTransactionType.REVERSE); } - private Map setParameters(Map parameters, String key, String value){ - if (!StringUtils.isEmpty(value)){ - parameters.put(key, value); - } + private Map initNotifyUrl(Map parameters, AssistOrder order) { + OrderParaStructure.loadParameters(parameters, WxConst.NOTIFY_URL, payConfigStorage.getNotifyUrl()); + OrderParaStructure.loadParameters(parameters, WxConst.NOTIFY_URL, order.getNotifyUrl()); + OrderParaStructure.loadParameters(parameters, WxConst.NOTIFY_URL, order); return parameters; } /** * 申请退款接口 * - * @param refundOrder 退款订单信息 + * @param refundOrder 退款订单信息 * @return 返回支付方申请退款后的结果 */ @Override - public Map refund(RefundOrder refundOrder) { + public WxRefundResult refund(RefundOrder refundOrder) { //获取公共参数 Map parameters = getPublicParameters(); - setParameters(parameters, "transaction_id", refundOrder.getTradeNo()); - setParameters(parameters, "out_trade_no", refundOrder.getOutTradeNo()); - setParameters(parameters, "out_refund_no", refundOrder.getRefundNo()); + OrderParaStructure.loadParameters(parameters, "transaction_id", refundOrder.getTradeNo()); + OrderParaStructure.loadParameters(parameters, OUT_TRADE_NO, refundOrder.getOutTradeNo()); + OrderParaStructure.loadParameters(parameters, "out_refund_no", refundOrder.getRefundNo()); parameters.put("total_fee", Util.conversionCentAmount(refundOrder.getTotalAmount())); parameters.put("refund_fee", Util.conversionCentAmount(refundOrder.getRefundAmount())); - parameters.put("op_user_id", payConfigStorage.getPid()); - + initNotifyUrl(parameters, refundOrder); + if (null != refundOrder.getCurType()) { + parameters.put("refund_fee_type", refundOrder.getCurType().getType()); + } + OrderParaStructure.loadParameters(parameters, "refund_desc", refundOrder.getDescription()); + //附加参数,这里可进行覆盖前面所有参数 + parameters.putAll(refundOrder.getAttrs()); //设置签名 setSign(parameters); - return requestTemplate.postForObject(getUrl(WxTransactionType.REFUND), XML.getMap2Xml(parameters), JSONObject.class); + return WxRefundResult.create(requestTemplate.postForObject(getReqUrl(WxTransactionType.REFUND), XML.getMap2Xml(parameters), JSONObject.class)); } - - - - /** - * 查询退款 - * - * @param transactionId 支付平台订单号 - * @param outTradeNo 商户单号 - * @return 返回支付方查询退款后的结果 - */ - @Override - public Map refundquery(String transactionId, String outTradeNo) { - return secondaryInterface(transactionId, outTradeNo, WxTransactionType.REFUNDQUERY); - } - /** * 查询退款 * @@ -505,141 +630,260 @@ public class WxPayService extends BasePayService { //获取公共参数 Map parameters = getPublicParameters(); - setParameters(parameters, "transaction_id", refundOrder.getTradeNo()); - setParameters(parameters, "out_trade_no", refundOrder.getOutTradeNo()); - setParameters(parameters, "out_refund_no", refundOrder.getRefundNo()); + OrderParaStructure.loadParameters(parameters, "transaction_id", refundOrder.getTradeNo()); + OrderParaStructure.loadParameters(parameters, OUT_TRADE_NO, refundOrder.getOutTradeNo()); + OrderParaStructure.loadParameters(parameters, "out_refund_no", refundOrder.getRefundNo()); //设置签名 setSign(parameters); - return requestTemplate.postForObject(getUrl( WxTransactionType.REFUNDQUERY), XML.getMap2Xml(parameters) , JSONObject.class); + return requestTemplate.postForObject(getReqUrl(WxTransactionType.REFUNDQUERY), XML.getMap2Xml(parameters), JSONObject.class); } /** * 目前只支持日账单 * - * @param billDate 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; - * @param billType 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 + * @param billDate 下载对账单的日期,格式:20140603 + * @param billType 账单类型 + * ALL(默认值),返回当日所有订单信息(不含充值退款订单) + * SUCCESS,返回当日成功支付的订单(不含充值退款订单) + * REFUND,返回当日退款订单(不含充值退款订单) + * RECHARGE_REFUND,返回当日充值退款订单 * @return 返回支付方下载对账单的结果 */ @Override - public Map downloadbill(Date billDate, String billType) { + public Map downloadBill(Date billDate, String billType) { + return downloadBill(billDate, WxPayBillType.valueOf(billType)); + } + /** + * 目前只支持日账单 + * + * @param billDate 下载对账单的日期,格式:20140603 + * @param billType 账单类型 + * ALL(默认值),返回当日所有订单信息(不含充值退款订单) + * SUCCESS,返回当日成功支付的订单(不含充值退款订单) + * REFUND,返回当日退款订单(不含充值退款订单) + * RECHARGE_REFUND,返回当日充值退款订单 + * @return 返回支付方下载对账单的结果, 如果【账单类型】为gzip的话则返回值中key为data值为gzip的输入流 + */ + @Override + public Map downloadBill(Date billDate, BillType billType) { //获取公共参数 Map parameters = getPublicParameters(); - - parameters.put("bill_type", billType); + parameters.put("bill_type", billType.getType()); //目前只支持日账单 + parameters.put(WxConst.BILL_DATE, DateUtils.formatDate(billDate, DateUtils.YYYYMMDD)); + String fileType = billType.getFileType(); + OrderParaStructure.loadParameters(parameters, "tar_type", fileType); + //设置签名 + setSign(parameters); + Map ret = new HashMap(3); + ret.put(RETURN_CODE, SUCCESS); + ret.put(RETURN_MSG_CODE, "ok"); + if (StringUtils.isEmpty(fileType)) { + String respStr = requestTemplate.postForObject(getReqUrl(WxTransactionType.DOWNLOADBILL), XML.getMap2Xml(parameters), String.class); + if (respStr.indexOf("<") == 0) { + return XML.toJSONObject(respStr); + } + ret.put("data", respStr); + return ret; + } + InputStream respStream = requestTemplate.postForObject(getReqUrl(WxTransactionType.DOWNLOADBILL), XML.getMap2Xml(parameters), InputStream.class); + ret.put("data", respStream); + return ret; + } - parameters.put("bill_date", DateUtils.formatDate(billDate, DateUtils.YYYYMMDD)); + /** + * 目前只支持日账单,增加账单返回格式 + * + * @param billDate 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; + * @param billType 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 + * @param path 账单返回格式 账单存储的基础路径,按月切割 + * @return 返回支付方下载对账单的结果 + */ + @Deprecated + @Override + public Map downloadbill(Date billDate, String billType, String path) { + Map parameters = getDownloadBillParam(billDate, billType, true); //设置签名 setSign(parameters); - String respStr = requestTemplate.postForObject(getUrl(WxTransactionType.DOWNLOADBILL), XML.getMap2Xml(parameters), String.class); - if (respStr.indexOf("<") == 0) { - return XML.toJSONObject(respStr); + InputStream inputStream = requestTemplate.postForObject(getReqUrl(WxTransactionType.DOWNLOADBILL), XML.getMap2Xml(parameters), InputStream.class); + //解压流 + try (InputStream fileIs = uncompress(inputStream);) { + writeToLocal(path + DateUtils.formatDate(new Date(), DateUtils.YYYYMM) + "/" + DateUtils.formatDate(new Date(), DateUtils.YYYYMMDDHHMMSS) + ".txt", fileIs); + Map ret = new HashMap<>(3); + ret.put(RETURN_CODE, SUCCESS); + ret.put(RETURN_MSG_CODE, "ok"); + ret.put("data", path); + return ret; + } + catch (IOException e) { + throw new PayErrorException(new WxPayError(FAIL, e.getMessage()), e); } - Map ret = new HashMap(); - ret.put(RETURN_CODE, SUCCESS); - ret.put(RETURN_MSG_CODE, "ok"); - ret.put("data", respStr); - return ret; + } + + /** + * GZIP解压缩 + * + * @param input 输入流账单 + * @return 解压后输入流 + * @throws IOException IOException + */ + @Deprecated + public static InputStream uncompress(InputStream input) throws IOException { + try (GZIPInputStream ungZip = new GZIPInputStream(input); ByteArrayOutputStream out = new ByteArrayOutputStream()) { + byte[] buffer = new byte[1024]; + int n; + while ((n = ungZip.read(buffer)) >= 0) { + out.write(buffer, 0, n); + } + return new ByteArrayInputStream(out.toByteArray()); + } + + } + + /** + * 将InputStream写入本地文件 + * + * @param destination 写入本地目录 + * @param inputStream 输入流 + * @throws IOException IOException + */ + @Deprecated + private void writeToLocal(String destination, InputStream inputStream) throws IOException { + + // 判断字节大小 + if (inputStream.available() != 0) { + LOG.debug("结果大小:{}", inputStream.available()); + File file = new File(destination); + if (!file.getParentFile().exists()) { + boolean result = file.getParentFile().mkdirs(); + if (!result) { + LOG.warn("创建失败"); + } + } + try (OutputStream out = new FileOutputStream(file)) { + int size = 0; + int len = 0; + byte[] buf = new byte[1024]; + while ((size = inputStream.read(buf)) != -1) { + len += size; + out.write(buf, 0, size); + } + LOG.debug("最终写入字节数大小:{}", len); + } + + } } + /** + * 下载账单公共参数 + * + * @param billDate 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; + * @param billType 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 + * @param tarType 账单返回格式 默认返回流false ,gzip 时候true + * @return + */ + @Deprecated + private Map getDownloadBillParam(Date billDate, String billType, boolean tarType) { + //获取公共参数 + Map parameters = getPublicParameters(); + parameters.put("bill_type", billType); + //目前只支持日账单 + parameters.put(WxConst.BILL_DATE, DateUtils.formatDate(billDate, DateUtils.YYYYMMDD)); + if (tarType) { + parameters.put("tar_type", "GZIP"); + } + return parameters; + } /** - * @param transactionIdOrBillDate 支付平台订单号或者账单类型, 具体请 类型为{@link String }或者 {@link Date },类型须强制限制,类型不对应则抛出异常{@link PayErrorException} + * @param transactionIdOrBillDate 支付平台订单号或者账单日期, 具体请 类型为{@link String }或者 {@link Date },类型须强制限制,类型不对应则抛出异常{@link PayErrorException} * @param outTradeNoBillType 商户单号或者 账单类型 * @param transactionType 交易类型 * @return 返回支付方对应接口的结果 */ - @Override - public Map secondaryInterface(Object transactionIdOrBillDate, String outTradeNoBillType, TransactionType transactionType) { + private Map secondaryInterface(Object transactionIdOrBillDate, String outTradeNoBillType, TransactionType transactionType) { if (transactionType == WxTransactionType.REFUND) { throw new PayErrorException(new PayException(FAILURE, "通用接口不支持:" + transactionType)); } - if (transactionType == WxTransactionType.DOWNLOADBILL){ - if (transactionIdOrBillDate instanceof Date){ - return downloadbill((Date) transactionIdOrBillDate, outTradeNoBillType); + if (transactionType == WxTransactionType.DOWNLOADBILL) { + if (transactionIdOrBillDate instanceof Date) { + return downloadBill((Date) transactionIdOrBillDate, WxPayBillType.forType(outTradeNoBillType)); } throw new PayErrorException(new PayException(FAILURE, "非法类型异常:" + transactionIdOrBillDate.getClass())); } - if (!(null == transactionIdOrBillDate || transactionIdOrBillDate instanceof String)){ + if (!(null == transactionIdOrBillDate || transactionIdOrBillDate instanceof String)) { throw new PayErrorException(new PayException(FAILURE, "非法类型异常:" + transactionIdOrBillDate.getClass())); } //获取公共参数 Map parameters = getPublicParameters(); - if (StringUtils.isEmpty((String)transactionIdOrBillDate)){ - parameters.put("out_trade_no", outTradeNoBillType); - }else { - parameters.put("transaction_id", transactionIdOrBillDate); - } + OrderParaStructure.loadParameters(parameters, OUT_TRADE_NO, outTradeNoBillType); + OrderParaStructure.loadParameters(parameters, "transaction_id", (String) transactionIdOrBillDate); //设置签名 setSign(parameters); - return requestTemplate.postForObject(getUrl(transactionType), XML.getMap2Xml(parameters) , JSONObject.class); + return requestTemplate.postForObject(getReqUrl(transactionType), XML.getMap2Xml(parameters), JSONObject.class); } /** * 转账 * * @param order 转账订单 - *
-     *
-     * 注意事项:
-     * ◆ 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。
-     * ◆ XML具有可扩展性,因此返回参数可能会有新增,而且顺序可能不完全遵循此文档规范,如果在解析回包的时候发生错误,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新回包字段,会更新到此API文档中。
-     * ◆ 因为错误代码字段err_code的值后续可能会增加,所以商户如果遇到回包返回新的错误码,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新的错误码,会更新到此API文档中。
-     * ◆ 错误代码描述字段err_code_des只供人工定位问题时做参考,系统实现时请不要依赖这个字段来做自动化处理。
-     *
-     *
- * * @return 对应的转账结果 */ @Override public Map transfer(TransferOrder order) { - Map parameters = new TreeMap(); + Map parameters = new TreeMap<>(); + - parameters.put("mch_id", payConfigStorage.getPid()); parameters.put("partner_trade_no", order.getOutNo()); parameters.put("amount", Util.conversionCentAmount(order.getAmount())); - if (!StringUtils.isEmpty(order.getRemark())){ + if (!StringUtils.isEmpty(order.getRemark())) { parameters.put("desc", order.getRemark()); } - parameters.put("nonce_str", SignUtils.randomStr()); - if (null != order.getTransferType() && TRANSFERS == order.getTransferType()){ + parameters.put(NONCE_STR, SignTextUtils.randomStr()); + if (null != order.getTransferType() && TRANSFERS == order.getTransferType()) { transfers(parameters, order); - }else { + parameters.put("mchid", payConfigStorage.getPid()); + } + else { + parameters.put(MCH_ID, payConfigStorage.getPid()); order.setTransferType(WxTransferType.PAY_BANK); payBank(parameters, order); } - parameters.put(SIGN, createSign(SignUtils.parameterText(parameters, "&", SIGN), payConfigStorage.getInputCharset())); + parameters.put(SIGN, createSign(SignTextUtils.parameterText(parameters, "&", SIGN), payConfigStorage.getInputCharset())); - return getHttpRequestTemplate().postForObject(getUrl(order.getTransferType()), XML.getMap2Xml(parameters), JSONObject.class); + return getHttpRequestTemplate().postForObject(getReqUrl(order.getTransferType()), XML.getMap2Xml(parameters), JSONObject.class); } + /** * 转账到余额所需要参数 + * * @param parameters 参数信息 - * @param order 转账订单 + * @param order 转账订单 * @return 包装后参数信息 *

- * 企业付款到零钱 - * 商户企业付款到银行卡 + * 企业付款到零钱 + * 商户企业付款到银行卡 *

*/ - public Map transfers(Map parameters, TransferOrder order){ + private Map transfers(Map parameters, TransferOrder order) { //转账到余额, 申请商户号的appid或商户号绑定的appid - parameters.put("mch_appid", payConfigStorage.getAppid()); + parameters.put("mch_appid", payConfigStorage.getAppId()); parameters.put("openid", order.getPayeeAccount()); + parameters.put("spbill_create_ip", StringUtils.isEmpty(order.getIp()) ? "192.168.1.150" : order.getIp()); //默认不校验真实姓名 parameters.put("check_name", "NO_CHECK"); //当存在时候 校验收款用户真实姓名 - if (!StringUtils.isEmpty(order.getPayeeName())){ + if (!StringUtils.isEmpty(order.getPayeeName())) { parameters.put("check_name", "FORCE_CHECK"); parameters.put("re_user_name", order.getPayeeName()); } @@ -648,11 +892,12 @@ public class WxPayService extends BasePayService { /** * 转账到银行卡所需要参数 + * * @param parameters 参数信息 - * @param order 转账订单 + * @param order 转账订单 * @return 包装后参数信息 */ - public Map payBank(Map parameters, TransferOrder order){ + private Map payBank(Map parameters, TransferOrder order) { parameters.put("enc_bank_no", keyPublic(order.getPayeeAccount())); parameters.put("enc_true_name", keyPublic(order.getPayeeName())); @@ -664,43 +909,179 @@ public class WxPayService extends BasePayService { /** * 转账查询 * - * @param outNo 商户转账订单号 + * @param outNo 商户转账订单号 * @param wxTransferType 微信转账类型,.....这里没办法了只能这样写(┬_┬),请见谅 {@link com.egzosn.pay.wx.bean.WxTransferType} - * - *

- * 企业付款到零钱 - * 商户企业付款到银行卡 - *

+ *

+ * 企业付款到零钱 + * 商户企业付款到银行卡 + *

* @return 对应的转账订单 + * @deprecated {@link #transferQuery(AssistOrder)} */ + @Deprecated @Override public Map transferQuery(String outNo, String wxTransferType) { + if (StringUtils.isEmpty(wxTransferType)) { + throw new PayErrorException(new WxPayError(FAILURE, "微信转账类型必填,详情com.egzosn.pay.wx.bean.WxTransferType")); + } + AssistOrder assistOrder = new AssistOrder(outNo); + + assistOrder.setTransactionType(WxTransferType.valueOf(wxTransferType)); + return transferQuery(assistOrder); + } + + /** + * 转账查询 + * + * @param assistOrder 辅助交易订单 + * @return 对应的转账订单 + */ + @Override + public Map transferQuery(AssistOrder assistOrder) { Map parameters = new TreeMap(); - parameters.put("mch_id", payConfigStorage.getPid()); - parameters.put("partner_trade_no", outNo); - parameters.put("nonce_str", SignUtils.randomStr()); - parameters.put(SIGN, createSign(SignUtils.parameterText(parameters, "&", SIGN), payConfigStorage.getInputCharset())); - if (StringUtils.isEmpty(wxTransferType)){ - throw new PayErrorException(new WxPayError(FAILURE, "微信转账类型 #transferQuery(String outNo, String wxTransferType) 必填,详情com.egzosn.pay.wx.bean.WxTransferType")); + parameters.put(MCH_ID, payConfigStorage.getPid()); + parameters.put("partner_trade_no", assistOrder.getOutTradeNo()); + parameters.put(NONCE_STR, SignTextUtils.randomStr()); + if (null == assistOrder.getTransactionType()) { + throw new PayErrorException(new WxPayError(FAILURE, "微信转账类型必填,详情com.egzosn.pay.wx.bean.WxTransferType")); } //如果类型为余额方式 - if (TRANSFERS.getType().equals(wxTransferType) || GETTRANSFERINFO.getType().equals(wxTransferType)){ - return getHttpRequestTemplate().postForObject(getUrl(GETTRANSFERINFO), XML.getMap2Xml(parameters), JSONObject.class); + if (TRANSFERS == assistOrder.getTransactionType() || GETTRANSFERINFO == assistOrder.getTransactionType()) { + parameters.put(APPID, payConfigStorage.getAppId()); + parameters.put(SIGN, createSign(SignTextUtils.parameterText(parameters, "&", SIGN), payConfigStorage.getInputCharset())); + return getHttpRequestTemplate().postForObject(getReqUrl(GETTRANSFERINFO), XML.getMap2Xml(parameters), JSONObject.class); } + parameters.put(SIGN, createSign(SignTextUtils.parameterText(parameters, "&", SIGN), payConfigStorage.getInputCharset())); //默认查询银行卡的记录 - return getHttpRequestTemplate().postForObject(getUrl(QUERY_BANK), XML.getMap2Xml(parameters), JSONObject.class); + return getHttpRequestTemplate().postForObject(getReqUrl(QUERY_BANK), XML.getMap2Xml(parameters), JSONObject.class); + } + + private String keyPublic(String content) { + try { + return RSA2.encrypt(content, payConfigStorage.getKeyPublic(), CIPHER_ALGORITHM, payConfigStorage.getInputCharset()); + } + catch (GeneralSecurityException | IOException e) { + throw new PayErrorException(new WxPayError(FAILURE, e.getLocalizedMessage())); + } + } + + /** + * 创建消息 + * + * @param message 支付平台返回的消息 + * @return 支付消息对象 + */ + @Override + public PayMessage createMessage(Map message) { + return WxPayMessage.create(message); } + /** + * 微信发红包 + * + * @param redpackOrder 红包实体 + * @return 返回发红包实体后的结果 + * @author faymanwang 1057438332@qq.com + */ + @Override + public Map sendredpack(RedpackOrder redpackOrder) { + return sendRedPack(redpackOrder); + } + /** + * 微信发红包 + * + * @param redpackOrder 红包实体 + * @return 返回发红包实体后的结果 + * @author faymanwang 1057438332@qq.com + */ + @Override + public Map sendRedPack(RedpackOrder redpackOrder) { + Map parameters = new TreeMap<>(); + redPackParam(redpackOrder, parameters); + final TransferType transferType = redpackOrder.getTransferType(); + if (WxSendredpackType.SENDGROUPREDPACK == transferType) { + //现金红包,小程序红包默认传1.裂变红包取传入值,且需要大于3 + parameters.put("total_num", Math.max(redpackOrder.getTotalNum(), 3)); + parameters.put("amt_type", "ALL_RAND"); + } + else if (WxSendredpackType.SENDMINIPROGRAMHB == transferType) { + parameters.put("notify_way", "MINI_PROGRAM_JSAPI"); + } - public String keyPublic(String content){ - try { - return RSA2.encrypt(content, payConfigStorage.getKeyPublic(), CIPHER_ALGORITHM, payConfigStorage.getInputCharset()); - } catch (Exception e) { - throw new PayErrorException(new WxPayError(FAILURE, e.getLocalizedMessage())); + parameters.put(SIGN, createSign(SignTextUtils.parameterText(parameters, "&", SIGN), payConfigStorage.getInputCharset())); + final JSONObject resp = requestTemplate.postForObject(getReqUrl(redpackOrder.getTransferType()), XML.getMap2Xml(parameters), JSONObject.class); + if (WxSendredpackType.SENDMINIPROGRAMHB != transferType || FAIL.equals(resp.getString(RESULT_CODE))) { + return resp; } + Map params = new TreeMap<>(); + params.put("appId", payConfigStorage.getAppId()); + params.put("timeStamp", System.currentTimeMillis() / 1000 + ""); + params.put("nonceStr", parameters.get(NONCE_STR)); + params.put("package", UriVariables.urlEncoder(resp.getString("package"))); + String paySign = createSign(SignTextUtils.parameterText(params), payConfigStorage.getInputCharset()); + params.put("signType", payConfigStorage.getSignType()); + params.put("paySign", paySign); + return params; } + /** + * 查询红包记录 + * 用于商户对已发放的红包进行查询红包的具体信息,可支持普通红包和裂变包 + * 查询红包记录API只支持查询30天内的红包订单,30天之前的红包订单请登录商户平台查询。 + * + * @param mchBillno 商户发放红包的商户订单号 + * @return 返回查询结果 + * @author faymanwang 1057438332@qq.com + */ + @Override + public Map gethbinfo(String mchBillno) { + return getHbInfo(mchBillno); + } + /** + * 查询红包记录 + * 用于商户对已发放的红包进行查询红包的具体信息,可支持普通红包和裂变包 + * 查询红包记录API只支持查询30天内的红包订单,30天之前的红包订单请登录商户平台查询。 + * + * @param mchBillNo 商户发放红包的商户订单号 + * @return 返回查询结果 + * @author faymanwang 1057438332@qq.com + */ + @Override + public Map getHbInfo(String mchBillNo) { + Map parameters = this.getPublicParameters(); + parameters.put("mch_billno", mchBillNo); + parameters.put("bill_type", "MCHT"); + parameters.put(SIGN, createSign(SignTextUtils.parameterText(parameters, "&", SIGN), payConfigStorage.getInputCharset())); + return requestTemplate.postForObject(getReqUrl(WxSendredpackType.GETHBINFO), XML.getMap2Xml(parameters), JSONObject.class); + } + + /** + * 微信红包构造参数方法 + * + * @param redpackOrder 红包实体 + * @param parameters 接收参数 + */ + private void redPackParam(RedpackOrder redpackOrder, Map parameters) { + parameters.put(NONCE_STR, SignTextUtils.randomStr()); + parameters.put(MCH_ID, payConfigStorage.getPid()); + if (StringUtils.isEmpty(redpackOrder.getWxAppId())) { + throw new PayErrorException(new WxPayError(FAIL, "RedpackOrder#getWxAppId()公众账号appid 必填")); + } + parameters.put("wxappid", redpackOrder.getWxAppId()); + parameters.put("send_name", redpackOrder.getSendName()); + parameters.put("re_openid", redpackOrder.getReOpenid()); + parameters.put("mch_billno", redpackOrder.getMchBillNo()); + parameters.put("total_amount", Util.conversionCentAmount(redpackOrder.getTotalAmount())); + parameters.put("total_num", 1); + parameters.put("wishing", redpackOrder.getWishing()); + parameters.put("client_ip", StringUtils.isNotEmpty(redpackOrder.getIp()) ? redpackOrder.getIp() : "192.168.0.1"); + parameters.put("act_name", redpackOrder.getActName()); + parameters.put("remark", redpackOrder.getRemark()); + if (StringUtils.isNotEmpty(redpackOrder.getSceneId())) { + parameters.put("scene_id", redpackOrder.getSceneId()); + } + } } diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/api/WxRedPackService.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/api/WxRedPackService.java new file mode 100644 index 0000000000000000000000000000000000000000..a18cff5500575edbd30a995f99d91fc9b177b0c0 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/api/WxRedPackService.java @@ -0,0 +1,53 @@ +package com.egzosn.pay.wx.api; + +import com.egzosn.pay.wx.bean.RedpackOrder; + +import java.util.Map; + +/** + * 微信红包服务 + * @author egan + *
+ * email egzosn@gmail.com
+ * date 2020/5/17 22:24
+ * 
+ */ +public interface WxRedPackService { + /** + * 微信发红包 + * + * @param redpackOrder 红包实体 + * @return 返回发红包实体后的结果 + * @see #sendRedPack(RedpackOrder) + */ + @Deprecated + Map sendredpack(RedpackOrder redpackOrder); + /** + * 微信发红包 + * + * @param redpackOrder 红包实体 + * @return 返回发红包实体后的结果 + */ + Map sendRedPack(RedpackOrder redpackOrder); + + /** + * 查询红包记录 + * 用于商户对已发放的红包进行查询红包的具体信息,可支持普通红包和裂变包 + * 查询红包记录API只支持查询30天内的红包订单,30天之前的红包订单请登录商户平台查询。 + * + * @param mchBillno 商户发放红包的商户订单号 + * @return 返回查询结果 + * @see #getHbInfo(String) + */ + @Deprecated + Map gethbinfo(String mchBillno); + /** + * 查询红包记录 + * 用于商户对已发放的红包进行查询红包的具体信息,可支持普通红包和裂变包 + * 查询红包记录API只支持查询30天内的红包订单,30天之前的红包订单请登录商户平台查询。 + * + * @param mchBillNo 商户发放红包的商户订单号 + * @return 返回查询结果 + */ + Map getHbInfo(String mchBillNo); +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/RedpackOrder.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/RedpackOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..2587431ea7f35d526b58bce1c61ef92d0c4dbfac --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/RedpackOrder.java @@ -0,0 +1,169 @@ +package com.egzosn.pay.wx.bean; + +import java.math.BigDecimal; + +import com.egzosn.pay.common.bean.TransferOrder; + +/** + * 发红包订单 + * + * @author 保网 faymanwang 1057438332@qq.com + * 2020/5/15 12:40 + */ +public class RedpackOrder extends TransferOrder { + /** + * 微信为发放红包商户分配的公众账号ID,接口传入的appid应该为公众号的appid或小程序的appid(在mp.weixin.qq.com申请的)或APP的appid(在open.weixin.qq.com申请的)。 + * 校验规则: + * 1、该appid需要与接口传入中的re_openid有对应关系; + * 2、该appid需要与发放红包商户号有绑定关系,若未绑定,可参考该指引完成绑定(商家商户号与AppID账号关联管理) + */ + private String wxAppId; + /** + * 商户订单号(每个订单号必须唯一。取值范围:0~9,a~z,A~Z) + * 接口根据商户订单号支持重入,如出现超时可再调用。 + */ + private String mchBillNo; + + /** + * 商户订单号(每个订单号必须唯一。取值范围:0~9,a~z,A~Z) + * 接口根据商户订单号支持重入,如出现超时可再调用 + * + * @return 商户订单号 + */ + @Deprecated + public String getMchBillno() { + return getMchBillNo(); + } + + @Deprecated + public void setMchBillno(String mchBillno) { + setMchBillNo(mchBillno); + } + + public String getMchBillNo() { + return mchBillNo; + } + + public void setMchBillNo(String mchBillNo) { + setOutNo(mchBillNo); + this.mchBillNo = mchBillNo; + } + + /** + * 商户名称:红包发送者名称 + * + * @return 红包发送者名称 + */ + public String getSendName() { + return getPayerName(); + } + + public void setSendName(String sendName) { + super.setPayerName(sendName); + } + + /** + * 用户openid + * + * @return 用户openid + */ + public String getReOpenid() { + return getPayeeAccount(); + } + + public void setReOpenid(String reOpenid) { + super.setPayeeAccount(reOpenid); + } + + /** + * 付款金额 每个红包金额必须在默认额度内(默认大于1元,小于200元,可在产品设置中自行申请调高额度) + * + * @return 付款金额 + */ + public BigDecimal getTotalAmount() { + return getAmount(); + } + + public void setTotalAmount(BigDecimal totalAmount) { + super.setAmount(totalAmount); + } + + /** + * 红包发放总人数 + * 普通红包:1 + * 裂变:必须介于(包括)3到20之间 + * + * @return 红包发放总人数 + */ + public int getTotalNum() { + Object totalNum = getAttr("total_num"); + return null == totalNum ? 1 : (Integer) totalNum; + } + + public void setTotalNum(int totalNum) { + addAttr("total_num", totalNum); + } + + /** + * 红包祝福语 + * + * @return 红包祝福语 + */ + public String getWishing() { + return (String) getAttr("wishing"); + + } + + public void setWishing(String wishing) { + addAttr("wishing", wishing); + } + + + /** + * 活动名称 + * + * @return 活动名称 + */ + public String getActName() { + return (String) getAttr("act_name"); + } + + public void setActName(String actName) { + addAttr("act_name", actName); + } + + public String getSceneId() { + return (String) getAttr("scene_id"); + } + + /** + * 发放红包使用场景,红包金额大于200或者小于1元时必传 + * PRODUCT_1:商品促销 + * PRODUCT_2:抽奖 + * PRODUCT_3:虚拟物品兑奖 + * PRODUCT_4:企业内部福利 + * PRODUCT_5:渠道分润 + * PRODUCT_6:保险回馈 + * PRODUCT_7:彩票派奖 + * PRODUCT_8:税务刮奖 + * + * @param sceneId 红包使用场景 + */ + public void setSceneId(String sceneId) { + addAttr("scene_id", sceneId); + } + + + public void setTransferType(WxSendredpackType transferType) { + super.setTransferType(transferType); + } + + public String getWxAppId() { + return wxAppId; + } + + public void setWxAppId(String wxAppId) { + addAttr("wxappid", wxAppId); + this.wxAppId = wxAppId; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxPayBillType.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxPayBillType.java new file mode 100644 index 0000000000000000000000000000000000000000..b4fabfb3ddeac92160fd26e7a4deb0162dddfd10 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxPayBillType.java @@ -0,0 +1,123 @@ +package com.egzosn.pay.wx.bean; + +import com.egzosn.pay.common.bean.BillType; +import com.egzosn.pay.common.util.str.StringUtils; +import com.egzosn.pay.wx.api.WxConst; + +/** + * 支付宝账单类型 + * + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2021/2/22
+ * 
+ */ +public enum WxPayBillType implements BillType { + /** + * 商户基于支付宝交易收单的业务账单;每日账单 + */ + ALL("ALL"), + /** + * 商户基于支付宝交易收单的业务账单;每日账单 + */ + ALL_GZIP("ALL", WxConst.GZIP), + /** + * 商户基于支付宝交易收单的业务账单;每月账单 + */ + SUCCESS(WxConst.SUCCESS), + /** + * 商户基于支付宝交易收单的业务账单;每月账单 + */ + SUCCESS_GZIP(WxConst.SUCCESS, WxConst.GZIP), + /** + * 基于商户支付宝余额收入及支出等资金变动的帐务账单;每日账单 + */ + REFUND("REFUND"), + /** + * 基于商户支付宝余额收入及支出等资金变动的帐务账单;每日账单 + */ + REFUND_GZIP("REFUND", WxConst.GZIP), + /** + * 基于商户支付宝余额收入及支出等资金变动的帐务账单;每月账单 + */ + RECHARGE_REFUND("RECHARGE_REFUND"), + /** + * 基于商户支付宝余额收入及支出等资金变动的帐务账单;每月账单 + */ + RECHARGE_REFUND_GZIP("RECHARGE_REFUND", WxConst.GZIP); + + /** + * 账单类型 + */ + private String type; + /** + * 日期格式化表达式 + */ + private String tarType; + + WxPayBillType(String type) { + this.type = type; + } + + + WxPayBillType(String type, String tarType) { + this.type = type; + this.tarType = tarType; + } + + /** + * 获取类型名称 + * + * @return 类型 + */ + @Override + public String getType() { + return type; + } + + /** + * 获取类型对应的日期格式化表达式 + * + * @return 日期格式化表达式 + */ + @Override + public String getDatePattern() { + return null; + } + + /** + * 获取文件类型 + * + * @return 文件类型 + */ + @Override + public String getFileType() { + return tarType; + } + + + + + + + /** + * 自定义属性 + * + * @return 自定义属性 + */ + @Override + public String getCustom() { + return null; + } + + public static WxPayBillType forType(String type){ + for (WxPayBillType wxPayBillType : WxPayBillType.values()){ + if (wxPayBillType.getType().equals(type) && StringUtils.isEmpty(wxPayBillType.getFileType())){ + return wxPayBillType; + } + } + return WxPayBillType.ALL; + } + +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxPayError.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxPayError.java index b3655d4bb8f0c7fcd6256ebda40e1eda636619c2..f079de1e5a105099416d5ef911b53b9f32948786 100644 --- a/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxPayError.java +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxPayError.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original huodull or egan. + * Copyright 2002-2017 the original egan or egan. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import com.egzosn.pay.common.bean.result.PayError; /** * 微信支付异常 - * @author: egan + * @author egan *
  *
  * email egzosn@gmail.com
diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxPayMessage.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxPayMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..3c7e5b64d0871c9c7f434c8e67e7cd0672b89bd0
--- /dev/null
+++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxPayMessage.java
@@ -0,0 +1,327 @@
+package com.egzosn.pay.wx.bean;
+
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.annotation.JSONField;
+import com.egzosn.pay.common.bean.PayMessage;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * 微信回调信息
+ *
+ * @author egan
+ *         email egzosn@gmail.com
+ *         date 2019/7/3.20:25
+ */
+
+public class WxPayMessage extends PayMessage {
+    //    公众账号ID 	appid 	是 	String(32) 	wx8888888888888888 	微信分配的公众账号ID(企业号corpid即为此appId)
+    @JSONField(name = "appid")
+    private String appid;
+    //    商户号 	mch_id 	是 	String(32) 	1900000109 	微信支付分配的商户号
+    @JSONField(name = "mch_id")
+    private String mchId;
+    //    设备号 	device_info 	否 	String(32) 	013467007045764 	微信支付分配的终端设备号,
+    @JSONField(name = "device_info")
+    private String deviceInfo;
+    //    随机字符串 	nonce_str 	是 	String(32) 	5K8264ILTKCH16CQ2502SI8ZNMTM67VS 	随机字符串,不长于32位
+    @JSONField(name = "nonce_str")
+    private String nonceStr;
+    //    签名 	sign 	是 	String(32) 	C380BEC2BFD727A4B6845133519F3AD6 	签名,详见签名算法
+    @JSONField(name = "sign")
+    private String sign;
+    //    签名类型 	sign_type 	否 	String(32) 	HMAC-SHA256 	签名类型,目前支持HMAC-SHA256和MD5,默认为MD5
+    @JSONField(name = "sign_type")
+    private String signType;
+    //    业务结果 	result_code 	是 	String(16) 	SUCCESS 	SUCCESS/FAIL
+    @JSONField(name = "result_code")
+    private String resultCode;
+    //    错误代码 	err_code 	否 	String(32) 	SYSTEMERROR 	错误返回的信息描述
+    @JSONField(name = "err_code")
+    private String errCode;
+    //    错误代码描述 	err_code_des 	否 	String(128) 	系统错误 	错误返回的信息描述
+    @JSONField(name = "err_code_des")
+    private String errCodeDes;
+    //    用户标识 	openid 	是 	String(128) 	wxd930ea5d5a258f4f 	用户在商户appid下的唯一标识
+    @JSONField(name = "openid")
+    private String openid;
+    //    是否关注公众账号 	is_subscribe 	是 	String(1) 	Y 	用户是否关注公众账号,Y-关注,N-未关注
+    @JSONField(name = "is_subscribe")
+    private String isSubscribe;
+    //    交易类型 	trade_type 	是 	String(16) 	JSAPI 	JSAPI、NATIVE、APP
+    @JSONField(name = "trade_type")
+    private String tradeType;
+    //    付款银行 	bank_type 	是 	String(16) 	CMC 	银行类型,采用字符串类型的银行标识,银行类型见银行列表
+    @JSONField(name = "bank_type")
+    private String bankType;
+    //    订单金额 	total_fee 	是 	Int 	100 	订单总金额,单位为分
+    @JSONField(name = "total_fee")
+    private BigDecimal totalFee;
+    //    应结订单金额 	settlement_total_fee 	否 	Int 	100 	应结订单金额=订单金额-非充值代金券金额,应结订单金额<=订单金额。
+    @JSONField(name = "settlement_total_fee")
+    private BigDecimal settlementTotalFee;
+    //    货币种类 	fee_type 	否 	String(8) 	CNY 	货币类型,符合ISO4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型
+    @JSONField(name = "fee_type")
+    private String feeType;
+    //    现金支付金额 	cash_fee 	是 	Int 	100 	现金支付金额订单现金支付金额,详见支付金额
+    @JSONField(name = "cash_fee")
+    private BigDecimal cashFee;
+    //    现金支付货币类型 	cash_fee_type 	否 	String(16) 	CNY 	货币类型,符合ISO4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型
+    @JSONField(name = "cash_fee_type")
+    private String cashFeeType;
+    //    总代金券金额 	coupon_fee 	否 	Int 	10 	代金券金额<=订单金额,订单金额-代金券金额=现金支付金额,详见支付金额
+    @JSONField(name = "coupon_fee")
+    private BigDecimal couponFee;
+    //    代金券使用数量 	coupon_count 	否 	Int 	1 	代金券使用数量
+    @JSONField(name = "coupon_count")
+    private Integer couponCount;
+    //    代金券类型 	coupon_type_$n 	否 	String 	CASH    CASH--充值代金券    NO_CASH---非充值代金券    并且订单使用了免充值券后有返回(取值:CASH、NO_CASH)。$n为下标,从0开始编号,举例:coupon_type_0
+    @JSONField(name = "coupon_type_$0")
+    private String couponType0;
+    //    代金券ID 	coupon_id_$n 	否 	String(20) 	10000 	代金券ID,$n为下标,从0开始编号
+    @JSONField(name = "coupon_id_$0")
+    private String couponId0;
+    //    单个代金券支付金额 	coupon_fee_$n 	否 	Int 	100 	单个代金券支付金额,$n为下标,从0开始编号
+    @JSONField(name = "coupon_fee_$0")
+    private Integer couponFee0;
+    //    微信支付订单号 	transaction_id 	是 	String(32) 	1217752501201407033233368018 	微信支付订单号
+    @JSONField(name = "transaction_id")
+    private String transactionId;
+    //    商户订单号 	out_trade_no 	是 	String(32) 	1212321211201407033568112322 	商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。
+    @JSONField(name = "out_trade_no")
+    private String outTradeNo;
+    //    商家数据包 	attach 	否 	String(128) 	123456 	商家数据包,原样返回
+    @JSONField(name = "attach")
+    private String attach;
+    //    支付完成时间 	time_end 	是 	String(14) 	20141030133525 	支付完成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则
+    @JSONField(name = "time_end", format="yyyyMMddHHmmss")
+    private Date timeEnd;
+
+    public String getAppid() {
+        return appid;
+    }
+
+    public void setAppid(String appid) {
+        this.appid = appid;
+    }
+
+    public String getMchId() {
+        return mchId;
+    }
+
+    public void setMchId(String mchId) {
+        this.mchId = mchId;
+    }
+
+    public String getDeviceInfo() {
+        return deviceInfo;
+    }
+
+    public void setDeviceInfo(String deviceInfo) {
+        this.deviceInfo = deviceInfo;
+    }
+
+    public String getNonceStr() {
+        return nonceStr;
+    }
+
+    public void setNonceStr(String nonceStr) {
+        this.nonceStr = nonceStr;
+    }
+
+    @Override
+    public String getSign() {
+        return sign;
+    }
+
+    public void setSign(String sign) {
+        this.sign = sign;
+    }
+
+    public String getSignType() {
+        return signType;
+    }
+
+    public void setSignType(String signType) {
+        this.signType = signType;
+    }
+
+    public String getResultCode() {
+        return resultCode;
+    }
+
+    public void setResultCode(String resultCode) {
+        this.resultCode = resultCode;
+    }
+
+    public String getErrCode() {
+        return errCode;
+    }
+
+    public void setErrCode(String errCode) {
+        this.errCode = errCode;
+    }
+
+    public String getErrCodeDes() {
+        return errCodeDes;
+    }
+
+    public void setErrCodeDes(String errCodeDes) {
+        this.errCodeDes = errCodeDes;
+    }
+
+    public String getOpenid() {
+        return openid;
+    }
+
+    public void setOpenid(String openid) {
+        this.openid = openid;
+    }
+
+    public String getIsSubscribe() {
+        return isSubscribe;
+    }
+
+    public void setIsSubscribe(String isSubscribe) {
+        this.isSubscribe = isSubscribe;
+    }
+
+    public String getTradeType() {
+        return tradeType;
+    }
+
+    public void setTradeType(String tradeType) {
+        this.tradeType = tradeType;
+    }
+
+    public String getBankType() {
+        return bankType;
+    }
+
+    public void setBankType(String bankType) {
+        this.bankType = bankType;
+    }
+
+    @Override
+    public BigDecimal getTotalFee() {
+        return totalFee;
+    }
+
+    public void setTotalFee(BigDecimal totalFee) {
+        this.totalFee = totalFee;
+    }
+
+    public BigDecimal getSettlementTotalFee() {
+        return settlementTotalFee;
+    }
+
+    public void setSettlementTotalFee(BigDecimal settlementTotalFee) {
+        this.settlementTotalFee = settlementTotalFee;
+    }
+
+    public String getFeeType() {
+        return feeType;
+    }
+
+    public void setFeeType(String feeType) {
+        this.feeType = feeType;
+    }
+
+    public BigDecimal getCashFee() {
+        return cashFee;
+    }
+
+    public void setCashFee(BigDecimal cashFee) {
+        this.cashFee = cashFee;
+    }
+
+    public String getCashFeeType() {
+        return cashFeeType;
+    }
+
+    public void setCashFeeType(String cashFeeType) {
+        this.cashFeeType = cashFeeType;
+    }
+
+    public BigDecimal getCouponFee() {
+        return couponFee;
+    }
+
+    public void setCouponFee(BigDecimal couponFee) {
+        this.couponFee = couponFee;
+    }
+
+    public Integer getCouponCount() {
+        return couponCount;
+    }
+
+    public void setCouponCount(Integer couponCount) {
+        this.couponCount = couponCount;
+    }
+
+    public String getCouponType0() {
+        return couponType0;
+    }
+
+    public void setCouponType0(String couponType0) {
+        this.couponType0 = couponType0;
+    }
+
+    public String getCouponId0() {
+        return couponId0;
+    }
+
+    public void setCouponId0(String couponId0) {
+        this.couponId0 = couponId0;
+    }
+
+    public Integer getCouponFee0() {
+        return couponFee0;
+    }
+
+    public void setCouponFee0(Integer couponFee0) {
+        this.couponFee0 = couponFee0;
+    }
+
+    public String getTransactionId() {
+        return transactionId;
+    }
+
+    public void setTransactionId(String transactionId) {
+        this.transactionId = transactionId;
+    }
+
+    @Override
+    public String getOutTradeNo() {
+        return outTradeNo;
+    }
+
+    public void setOutTradeNo(String outTradeNo) {
+        this.outTradeNo = outTradeNo;
+    }
+
+    public String getAttach() {
+        return attach;
+    }
+
+    public void setAttach(String attach) {
+        this.attach = attach;
+    }
+
+    public Date getTimeEnd() {
+        return timeEnd;
+    }
+
+    public void setTimeEnd(Date timeEnd) {
+        this.timeEnd = timeEnd;
+    }
+
+    public static final WxPayMessage create(Map message) {
+        WxPayMessage payMessage = new JSONObject(message).toJavaObject(WxPayMessage.class);
+        payMessage.setPayMessage(message);
+        return payMessage;
+    }
+
+}
diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxRefundResult.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxRefundResult.java
new file mode 100644
index 0000000000000000000000000000000000000000..7fadc2e2332030a2f9ab246b8c6cabacd69c85f6
--- /dev/null
+++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxRefundResult.java
@@ -0,0 +1,497 @@
+package com.egzosn.pay.wx.bean;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.annotation.JSONField;
+import com.egzosn.pay.common.bean.BaseRefundResult;
+import com.egzosn.pay.common.bean.CurType;
+
+/**
+ * 微信退款结果
+ * @author Egan
+ * 
+ * email egzosn@gmail.com
+ * date 2020/8/16 21:29
+ * 
+ */ +public class WxRefundResult extends BaseRefundResult { + + /** + * 返回状态码 + * SUCCESS/FAIL + * 此字段是通信标识,表示接口层的请求结果,并非退款状态。 + */ + @JSONField(name = "return_code") + private String returnCode; + /** + * 返回信息 + * 当return_code为FAIL时返回信息为错误原因 ,例如 + * 签名失败 + * 参数格式校验错误 + */ + @JSONField(name = "return_msg") + private String returnMsg; + /** + * 业务结果 + * SUCCESS/FAIL + * SUCCESS退款申请接收成功,结果通过退款查询接口查询 + * FAIL 提交业务失败 + */ + @JSONField(name = "result_code") + private String resultCode; + /** + * 错误代码 + * 名称 描述 原因 解决方案 + * SYSTEMERROR 接口返回错误 系统超时等 请不要更换商户退款单号,请使用相同参数再次调用API。 + * BIZERR_NEED_RETRY 退款业务流程错误,需要商户触发重试来解决 并发情况下,业务被拒绝,商户重试即可解决 请不要更换商户退款单号,请使用相同参数再次调用API。 + * TRADE_OVERDUE 订单已经超过退款期限 订单已经超过可退款的最大期限(支付后一年内可退款) 请选择其他方式自行退款 + * ERROR 业务错误 申请退款业务发生错误 该错误都会返回具体的错误原因,请根据实际返回做相应处理。 + * USER_ACCOUNT_ABNORMAL 退款请求失败 用户帐号注销 此状态代表退款申请失败,商户可自行处理退款。 + * INVALID_REQ_TOO_MUCH 无效请求过多 连续错误请求数过多被系统短暂屏蔽 请检查业务是否正常,确认业务正常后请在1分钟后再来重试 + * NOTENOUGH 余额不足 商户可用退款余额不足 此状态代表退款申请失败,商户可根据具体的错误提示做相应的处理。 + * INVALID_TRANSACTIONID 无效transaction_id 请求参数未按指引进行填写 请求参数错误,检查原交易号是否存在或发起支付交易接口返回失败 + * PARAM_ERROR 参数错误 请求参数未按指引进行填写 请求参数错误,请重新检查再调用退款申请 + * APPID_NOT_EXIST APPID不存在 参数中缺少APPID 请检查APPID是否正确 + * MCHID_NOT_EXIST MCHID不存在 参数中缺少MCHID 请检查MCHID是否正确 + * ORDERNOTEXIST 订单号不存在 缺少有效的订单号 请检查你的订单号是否正确且是否已支付,未支付的订单不能发起退款 + * REQUIRE_POST_METHOD 请使用post方法 未使用post传递参数 请检查请求参数是否通过post方法提交 + * SIGNERROR 签名错误 参数签名结果不正确 请检查签名参数和方法是否都符合签名算法要求 + * XML_FORMAT_ERROR XML格式错误 XML格式错误 请检查XML参数格式是否正确 + * FREQUENCY_LIMITED 频率限制 2个月之前的订单申请退款有频率限制 该笔退款未受理,请降低频率后重试 + * NOAUTH 异常IP请求不予受理 请求ip异常 如果是动态ip,请登录商户平台后台关闭ip安全配置; + * 如果是静态ip,请确认商户平台配置的请求ip 在不在配的ip列表里 + * CERT_ERROR 证书校验错误 请检查证书是否正确,证书是否过期或作废。 请检查证书是否正确,证书是否过期或作废。 + * REFUND_FEE_MISMATCH 订单金额或退款金额与之前请求不一致,请核实后再试 订单金额或退款金额与之前请求不一致,请核实后再试 订单金额或退款金额与之前请求不一致,请核实后再试 + * INVALID_REQUEST 请求参数符合参数格式,但不符合业务规则 此状态代表退款申请失败,商户可根据具体的错误提示做相应的处理。 此状态代表退款申请失败,商户可根据具体的错误提示做相应的处理。 + */ + @JSONField(name = "err_code") + private String errCode; + /** + * 错误代码描述 + */ + @JSONField(name = "err_code_des") + private String errCodeDes; + /** + * 应用id + * 公众账号ID + * 微信分配的公众账号ID + */ + @JSONField(name = "appid") + private String appid; + /** + * 商户号 + * 微信支付分配的商户号 + */ + @JSONField(name = "mch_id") + private String mchId; + /** + * 随机字符串,不长于32位 + */ + @JSONField(name = "nonce_str") + private String nonceStr; + /** + * 签名 + */ + @JSONField(name = "sign") + private String sign; + /** + * 微信订单号 + */ + @JSONField(name = "transaction_id") + private String transactionId; + /** + * 商户订单号 + */ + @JSONField(name = "out_trade_no") + private String outTradeNo; + /** + * 商户退款单号 + */ + @JSONField(name = "out_refund_no") + private String outRefundNo; + /** + * 微信退款单号 + */ + @JSONField(name = "refund_id") + private String refundId; + /** + * 退款金额 + * 退款总金额,单位为分,可以做部分退款 + */ + @JSONField(name = "refund_fee") + private BigDecimal refundFee; + /** + * 应结退款金额 + * 去掉非充值代金券退款金额后的退款金额,退款金额=申请退款金额-非充值代金券退款金额,退款金额<=申请退款金额 + */ + @JSONField(name = "settlement_refund_fee") + private BigDecimal settlementRefundFee; + /** + * 标价金额 + * 订单总金额,单位为分,只能为整数,详见支付金额 + */ + @JSONField(name = "total_fee") + private BigDecimal totalFee; + /** + * 应结订单金额 + * 去掉非充值代金券金额后的订单总金额,应结订单金额=订单金额-非充值代金券金额,应结订单金额<=订单金额。 + */ + @JSONField(name = "settlement_total_fee") + private BigDecimal settlementTotalFee; + /** + * 标价币种 + * 订单金额货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型 + */ + @JSONField(name = "fee_type") + private CurType feeType; + /** + * 现金支付金额 + * 现金支付金额,单位为分,只能为整数,详见支付金额 + */ + @JSONField(name = "cash_fee") + private BigDecimal cashFee; + /** + * 现金支付币种 + * 货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型 + */ + @JSONField(name = "cash_fee_type") + private CurType cashFeeType; + /** + * 现金退款金额 + * 现金退款金额,单位为分,只能为整数,详见支付金额 + */ + @JSONField(name = "cash_refund_fee") + private BigDecimal cashRefundFee; + /** + * 代金券类型 + * CASH--充值代金券 + * + * NO_CASH---非充值代金券 + * + * 订单使用代金券时有返回(取值:CASH、NO_CASH)。$n为下标,从0开始编号,举例:coupon_type_0 + * 这里只接收0的,其余请自行获取 + */ + @JSONField(name = "coupon_type_0") + private String couponType0; + /** + * 代金券退款总金额 + * 代金券退款金额<=退款金额,退款金额-代金券或立减优惠退款金额为现金,说明详见代金券或立减优惠 + */ + @JSONField(name = "coupon_refund_fee") + private BigDecimal couponRefundFee; + /** + * 单个代金券退款金额 + * 代金券退款金额<=退款金额,退款金额-代金券或立减优惠退款金额为现金,说明详见代金券或立减优惠 + * 这里只接收0的,其余请自行获取 + */ + @JSONField(name = "coupon_refund_fee_0") + private BigDecimal couponRefundFee0; + @JSONField(name = "coupon_refund_count") + /** + * 退款代金券使用数量 + * 退款代金券使用数量 + */ + private Integer couponRefundCount; + /** + * 退款代金券ID, $n为下标,从0开始编号 + * 这里只接收0的,其余请自行获取 + */ + @JSONField(name = "coupon_refund_id_0") + private String couponRefundId0; + /** + * 获取退款请求结果状态码 + * + * @return 状态码 + */ + @Override + public String getCode() { + return returnCode; + } + + /** + * 获取退款请求结果状态提示信息 + * + * @return 提示信息 + */ + @Override + public String getMsg() { + return returnMsg; + } + + /** + * 返回业务结果状态码 + * + * @return 业务结果状态码 + */ + @Override + public String getResultCode() { + return resultCode; + } + + /** + * 返回业务结果状态提示信息 + * + * @return 业务结果状态提示信息 + */ + @Override + public String getResultMsg() { + return errCodeDes; + } + + /** + * 退款金额 + * + * @return 退款金额 + */ + @Override + public BigDecimal getRefundFee() { + return refundFee; + } + + /** + * 退款币种信息 + * + * @return 币种信息 + */ + @Override + public CurType getRefundCurrency() { + return feeType; + } + + /** + * 支付平台交易号 + * 发起支付时 支付平台(如支付宝)返回的交易订单号 + * + * @return 支付平台交易号 + */ + @Override + public String getTradeNo() { + return transactionId; + } + + /** + * 支付订单号 + * 发起支付时,用户系统的订单号 + * + * @return 支付订单号 + */ + @Override + public String getOutTradeNo() { + return outTradeNo; + } + + /** + * 商户退款单号 + * + * @return 商户退款单号 + */ + @Override + public String getRefundNo() { + return outRefundNo; + } + + public String getReturnCode() { + return returnCode; + } + + public void setReturnCode(String returnCode) { + this.returnCode = returnCode; + } + + public String getReturnMsg() { + return returnMsg; + } + + public void setReturnMsg(String returnMsg) { + this.returnMsg = returnMsg; + } + + public void setResultCode(String resultCode) { + this.resultCode = resultCode; + } + + public String getErrCode() { + return errCode; + } + + public void setErrCode(String errCode) { + this.errCode = errCode; + } + + public String getErrCodeDes() { + return errCodeDes; + } + + public void setErrCodeDes(String errCodeDes) { + this.errCodeDes = errCodeDes; + } + + public String getAppid() { + return appid; + } + + public void setAppid(String appid) { + this.appid = appid; + } + + public String getMchId() { + return mchId; + } + + public void setMchId(String mchId) { + this.mchId = mchId; + } + + public String getNonceStr() { + return nonceStr; + } + + public void setNonceStr(String nonceStr) { + this.nonceStr = nonceStr; + } + + public String getSign() { + return sign; + } + + public void setSign(String sign) { + this.sign = sign; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } + + public void setOutTradeNo(String outTradeNo) { + this.outTradeNo = outTradeNo; + } + + public String getOutRefundNo() { + return outRefundNo; + } + + public void setOutRefundNo(String outRefundNo) { + this.outRefundNo = outRefundNo; + } + + public String getRefundId() { + return refundId; + } + + public void setRefundId(String refundId) { + this.refundId = refundId; + } + + public void setRefundFee(BigDecimal refundFee) { + this.refundFee = refundFee; + } + + public BigDecimal getSettlementRefundFee() { + return settlementRefundFee; + } + + public void setSettlementRefundFee(BigDecimal settlementRefundFee) { + this.settlementRefundFee = settlementRefundFee; + } + + public BigDecimal getTotalFee() { + return totalFee; + } + + public void setTotalFee(BigDecimal totalFee) { + this.totalFee = totalFee; + } + + public BigDecimal getSettlementTotalFee() { + return settlementTotalFee; + } + + public void setSettlementTotalFee(BigDecimal settlementTotalFee) { + this.settlementTotalFee = settlementTotalFee; + } + + public CurType getFeeType() { + return feeType; + } + + public void setFeeType(CurType feeType) { + this.feeType = feeType; + } + + public BigDecimal getCashFee() { + return cashFee; + } + + public void setCashFee(BigDecimal cashFee) { + this.cashFee = cashFee; + } + + public CurType getCashFeeType() { + return cashFeeType; + } + + public void setCashFeeType(CurType cashFeeType) { + this.cashFeeType = cashFeeType; + } + + public BigDecimal getCashRefundFee() { + return cashRefundFee; + } + + public void setCashRefundFee(BigDecimal cashRefundFee) { + this.cashRefundFee = cashRefundFee; + } + + public String getCouponType0() { + return couponType0; + } + + public void setCouponType0(String couponType0) { + this.couponType0 = couponType0; + } + + public BigDecimal getCouponRefundFee() { + return couponRefundFee; + } + + public void setCouponRefundFee(BigDecimal couponRefundFee) { + this.couponRefundFee = couponRefundFee; + } + + public BigDecimal getCouponRefundFee0() { + return couponRefundFee0; + } + + public void setCouponRefundFee0(BigDecimal couponRefundFee0) { + this.couponRefundFee0 = couponRefundFee0; + } + + public Integer getCouponRefundCount() { + return couponRefundCount; + } + + public void setCouponRefundCount(Integer couponRefundCount) { + this.couponRefundCount = couponRefundCount; + } + + public String getCouponRefundId0() { + return couponRefundId0; + } + + public void setCouponRefundId0(String couponRefundId0) { + this.couponRefundId0 = couponRefundId0; + } + + + public static final WxRefundResult create(Map result){ + WxRefundResult refundResult = new JSONObject(result).toJavaObject(WxRefundResult.class); + refundResult.setAttrs(result); + return refundResult; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxSendredpackType.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxSendredpackType.java new file mode 100644 index 0000000000000000000000000000000000000000..27b1bae38402ba90c6cc0ac84eb977ca1399bf09 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxSendredpackType.java @@ -0,0 +1,51 @@ +package com.egzosn.pay.wx.bean; + +import com.egzosn.pay.common.bean.TransferOrder; +import com.egzosn.pay.common.bean.TransferType; + +import java.util.Map; + +/** + * 红包交易类型 + * @author faymanwang + * 2020/5/14 20:11 + */ +public enum WxSendredpackType implements TransferType { + /** + * 现金红包-发放红包接口 + */ + SENDREDPACK("mmpaymkttransfers/sendredpack"), + /** + * 现金红包-发放裂变红包 + */ + SENDGROUPREDPACK("mmpaymkttransfers/sendgroupredpack"), + /** + * 现金红包-查询红包记录 + */ + GETHBINFO ("mmpaymkttransfers/gethbinfo"), + /** + * 小程序 + */ + SENDMINIPROGRAMHB ("mmpaymkttransfers/sendminiprogramhb") + + ; + + WxSendredpackType(String method) { + this.method = method; + } + private String method; + + @Override + public String getType() { + return this.name(); + } + @Override + public String getMethod() { + return this.method; + } + + @Override + public Map setAttr(Map attr, TransferOrder order) { + return attr; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxTransactionType.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxTransactionType.java index 2ce5aaa3b5be18855a91a2833daf5aac18b2bee4..a0497546ec8cf3cd51661b09ce13e0a5cc1ecc30 100644 --- a/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxTransactionType.java +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxTransactionType.java @@ -9,12 +9,13 @@ import java.util.Map; /** * 微信交易类型 - * @author egan * + * @author egan + *

* email egzosn@gmail.com * date 2016/10/19 22:58 */ -public enum WxTransactionType implements TransactionType { +public enum WxTransactionType implements TransactionType { /** * 公众号支付 */ @@ -47,14 +48,24 @@ public enum WxTransactionType implements TransactionType { * 移动支付 */ APP("pay/unifiedorder"), + /** + * 刷脸支付 + */ + FACEPAY("pay/facepay") { + @Override + public void setAttribute(Map parameters, PayOrder order) { + parameters.put("openid", order.getOpenid()); + parameters.put("face_code", order.getAuthCode()); + } + }, /** * H5支付 */ - MWEB("pay/unifiedorder"){ + MWEB("pay/unifiedorder") { @Override public void setAttribute(Map parameters, PayOrder order) { //H5支付专用 - LinkedHashMap value = new LinkedHashMap(); + LinkedHashMap value = new LinkedHashMap(6); value.put("type", "Wap"); //WAP网站URL地址 value.put("wap_url", order.getWapUrl()); @@ -64,6 +75,7 @@ public enum WxTransactionType implements TransactionType { sceneInfo.put("h5_info", value); parameters.put("scene_info", sceneInfo.toJSONString()); } + /** * 是否直接返回 * @@ -77,13 +89,14 @@ public enum WxTransactionType implements TransactionType { /** * 刷卡付 */ - MICROPAY("pay/micropay"){ + MICROPAY("pay/micropay") { @Override public void setAttribute(Map parameters, PayOrder order) { parameters.put("auth_code", order.getAuthCode()); parameters.remove("notify_url"); parameters.remove("trade_type"); } + /** * 是否直接返回 * @@ -120,15 +133,10 @@ public enum WxTransactionType implements TransactionType { */ DOWNLOADBILL("pay/downloadbill"), /** - * 银行卡转账 + * 获取验签秘钥,沙箱使用 */ - @Deprecated - BANK("mmpaysptrans/pay_bank"), - /** - * 转账查询 - */ - @Deprecated - QUERY_BANK("mmpaysptrans/query_bank") + GETSIGNKEY("pay/getsignkey"), + ; WxTransactionType(String method) { @@ -141,6 +149,7 @@ public enum WxTransactionType implements TransactionType { public String getType() { return this.name(); } + @Override public String getMethod() { return this.method; @@ -148,13 +157,14 @@ public enum WxTransactionType implements TransactionType { /** * 是否直接返回 + * * @return 是否直接返回 */ - public boolean isReturn(){ + public boolean isReturn() { return false; } - public void setAttribute(Map parameters, PayOrder order){ + public void setAttribute(Map parameters, PayOrder order) { } } diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxTransferType.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxTransferType.java index e0f2760bafa5f69a874c24872ef61b4e55f2da96..1983c55de04c86c1ee593aa5a2d21bf0e7674252 100644 --- a/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxTransferType.java +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxTransferType.java @@ -1,7 +1,10 @@ package com.egzosn.pay.wx.bean; +import com.egzosn.pay.common.bean.TransferOrder; import com.egzosn.pay.common.bean.TransferType; +import java.util.Map; + /** * 微信转账类型 * @author egan @@ -41,4 +44,10 @@ public enum WxTransferType implements TransferType{ public String getMethod() { return this.method; } + + + @Override + public Map setAttr(Map attr, TransferOrder order) { + return attr; + } } diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/DefaultWxPayAssistService.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/DefaultWxPayAssistService.java new file mode 100644 index 0000000000000000000000000000000000000000..54897ffd96037ab2901fcde3cd7bb4529e821f98 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/DefaultWxPayAssistService.java @@ -0,0 +1,215 @@ +package com.egzosn.pay.wx.v3.api; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.security.cert.Certificate; +import java.util.Map; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.entity.ContentType; +import org.apache.http.message.BasicHeader; + +import static org.apache.http.entity.ContentType.APPLICATION_JSON; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.egzosn.pay.common.bean.MethodType; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.TransactionType; +import com.egzosn.pay.common.exception.PayErrorException; +import com.egzosn.pay.common.http.HttpRequestTemplate; +import com.egzosn.pay.common.http.HttpStringEntity; +import com.egzosn.pay.common.http.ResponseEntity; +import com.egzosn.pay.common.http.UriVariables; +import com.egzosn.pay.common.util.DateUtils; +import com.egzosn.pay.common.util.sign.SignTextUtils; +import com.egzosn.pay.common.util.str.StringUtils; +import com.egzosn.pay.wx.bean.WxPayError; +import com.egzosn.pay.wx.v3.bean.WxTransactionType; +import com.egzosn.pay.wx.v3.utils.AntCertificationUtil; +import com.egzosn.pay.wx.v3.utils.WxConst; + +/** + * 默认的微信支付辅助服务 + * + * @author Egan + * email egzosn@gmail.com + * date 2021/8/7 + */ +public class DefaultWxPayAssistService implements WxPayAssistService { + private WxPayConfigStorage payConfigStorage; + + private HttpRequestTemplate requestTemplate; + + private WxPayServiceInf wxPayService; + + + public DefaultWxPayAssistService(WxPayServiceInf wxPayService) { + this.wxPayService = wxPayService; + payConfigStorage = wxPayService.getPayConfigStorage(); + requestTemplate = wxPayService.getHttpRequestTemplate(); + + } + + + /** + * 发起请求 + * + * @param parameters 支付参数 + * @param transactionType 交易类型 + * @return 响应内容体 + */ + @Override + public JSONObject doExecute(Map parameters, TransactionType transactionType) { +// String requestBody = JSON.toJSONString(parameters, SerializerFeature.WriteMapNullValue); + String requestBody = JSON.toJSONString(parameters); + return doExecute(requestBody, transactionType); + } + + + /** + * 发起请求 + * + * @param body 请求内容 + * @param transactionType 交易类型 + * @param uriVariables 用于匹配表达式 + * @return 响应内容体 + */ + @Override + public ResponseEntity doExecuteEntity(String body, TransactionType transactionType, Object... uriVariables) { + String reqUrl = UriVariables.getUri(wxPayService.getReqUrl(transactionType), uriVariables); + MethodType method = MethodType.valueOf(transactionType.getMethod()); + if (MethodType.GET == method && StringUtils.isNotEmpty(body)) { + reqUrl += UriVariables.QUESTION.concat(body); + body = ""; + } + HttpEntity entity = buildHttpEntity(reqUrl, body, transactionType.getMethod()); + ResponseEntity responseEntity = requestTemplate.doExecuteEntity(reqUrl, entity, JSONObject.class, method); + return responseEntity; + } + + /** + * 发起请求 + * + * @param body 请求内容 + * @param transactionType 交易类型 + * @param uriVariables 用于匹配表达式 + * @return 响应内容体 + */ + @Override + public JSONObject doExecute(String body, TransactionType transactionType, Object... uriVariables) { + final ResponseEntity responseEntity = doExecuteEntity(body, transactionType, uriVariables); + int statusCode = responseEntity.getStatusCode(); + JSONObject responseBody = responseEntity.getBody(); + if (statusCode >= 400) { + throw new PayErrorException(new WxPayError(responseBody.getString(WxConst.CODE), responseBody.getString(WxConst.MESSAGE), responseBody.toJSONString())); + } + Header[] headers = responseEntity.getHeaders(); + if (headers == null) { + return responseBody; + } + for (Header header : headers) { + if (WxConst.WECHATPAY_SERIAL.equals(header.getName())) { + // 更新平台证书的序列号,需要每次都更新,因为这个可能会改变 + payConfigStorage.getCertEnvironment().setPlatformSerialNumber(header.getValue()); + break; + } + } + return responseBody; + } + + /** + * 发起请求 + * + * @param parameters 支付参数 + * @param order 订单 + * @return 请求响应 + */ + @Override + public JSONObject doExecute(Map parameters, PayOrder order) { + TransactionType transactionType = order.getTransactionType(); + return doExecute(parameters, transactionType); + } + + + /** + * 构建请求实体 + * 这里也做签名处理 + * + * @param url url + * @param body 请求内容体 + * @param method 请求方法 + * @return 请求实体 + */ + @Override + public HttpEntity buildHttpEntity(String url, String body, String method) { + String nonceStr = SignTextUtils.randomStr(); + long timestamp = DateUtils.toEpochSecond(); + String canonicalUrl = UriVariables.getCanonicalUrl(url); + //签名信息 + String signText = StringUtils.joining("\n", method, canonicalUrl, String.valueOf(timestamp), nonceStr, body); + String sign = wxPayService.createSign(signText, payConfigStorage.getInputCharset()); + String serialNumber = payConfigStorage.getCertEnvironment().getSerialNumber(); + // 生成token + String token = String.format(WxConst.TOKEN_PATTERN, payConfigStorage.getMchId(), nonceStr, timestamp, serialNumber, sign); + HttpStringEntity entity = new HttpStringEntity(body, ContentType.APPLICATION_JSON); + entity.addHeader(new BasicHeader("Authorization", WxConst.SCHEMA.concat(token))); + entity.addHeader(new BasicHeader("User-Agent", "Pay-Java-Parent")); + entity.addHeader(new BasicHeader("Accept", APPLICATION_JSON.getMimeType())); + + + return wxPayService.hookHttpEntity(entity); + } + + + /** + * 当缓存中平台证书不存在事进行刷新重新获取平台证书 + * 调用/v3/certificates + */ + @Override + public void refreshCertificate() { + JSONObject responseEntity = doExecute("", WxTransactionType.CERT); + + if (null == responseEntity) { + throw new PayErrorException(new WxPayError(WxConst.FAILURE, "获取证书失败")); + } + JSONArray certificates = responseEntity.getJSONArray("data"); + if (null == certificates) { + return; + } + + for (int i = 0; i < certificates.size(); i++) { + JSONObject certificate = certificates.getJSONObject(i); + JSONObject encryptCertificate = certificate.getJSONObject("encrypt_certificate"); + String associatedData = encryptCertificate.getString("associated_data"); + String nonce = encryptCertificate.getString("nonce"); + String ciphertext = encryptCertificate.getString("ciphertext"); + String publicKey = AntCertificationUtil.decryptToString(associatedData, nonce, ciphertext, payConfigStorage.getV3ApiKey(), payConfigStorage.getInputCharset()); + ByteArrayInputStream inputStream = new ByteArrayInputStream(publicKey.getBytes(StandardCharsets.UTF_8)); + AntCertificationUtil.loadCertificate(certificate.getString("serial_no"), inputStream); + } + + } + + /** + * 通过证书序列获取平台证书 + * + * @param serialNo 证书序列 + * @return 平台证书 + */ + @Override + public Certificate getCertificate(String serialNo) { + Certificate certificate = AntCertificationUtil.getCertificate(serialNo); + if (null == certificate) { + refreshCertificate(); + certificate = AntCertificationUtil.getCertificate(serialNo); + } + + + return certificate; + } + + +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/ProfitSharingService.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/ProfitSharingService.java new file mode 100644 index 0000000000000000000000000000000000000000..bdae1f31ed15ac969c638dc13690f10c0cfdb990 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/ProfitSharingService.java @@ -0,0 +1,36 @@ +package com.egzosn.pay.wx.v3.api; + +import java.util.Map; + +import com.egzosn.pay.common.bean.PayOrder; + +/** + * 分账服务 + * @author Egan + *

+ * email egan@egzosn.com
+ * date 2021/10/6
+ * 
+ */ +public interface ProfitSharingService { + + /** + * 添加分账接收方 + * @param order 添加分账 + * @return 结果 + */ + Map add(PayOrder order); + /** + * 删除分账接收方 + * @param order 删除分账 + * @return 结果 + */ + Map delete(PayOrder order); + /** + * 解冻剩余资金 + * @param order 解冻 + * @return 结果 + */ + Map unfreeze(PayOrder order); + +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxCombinePayService.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxCombinePayService.java new file mode 100644 index 0000000000000000000000000000000000000000..de4a6e84771c1b88997da77f4a3e0adb201e3f48 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxCombinePayService.java @@ -0,0 +1,168 @@ +package com.egzosn.pay.wx.v3.api; + +import java.util.LinkedHashMap; +import java.util.Map; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.common.bean.Order; +import com.egzosn.pay.common.bean.OrderParaStructure; +import com.egzosn.pay.common.bean.PayMessage; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.result.PayException; +import com.egzosn.pay.common.exception.PayErrorException; +import com.egzosn.pay.common.http.HttpConfigStorage; +import com.egzosn.pay.common.util.DateUtils; +import com.egzosn.pay.common.util.MapGen; +import com.egzosn.pay.common.util.str.StringUtils; +import com.egzosn.pay.wx.v3.bean.WxTransactionType; +import com.egzosn.pay.wx.v3.bean.combine.CombinePayMessage; +import com.egzosn.pay.wx.v3.utils.WxConst; + +/** + * 微信合单支付服务 + * + * @author egan + *
+ * email egan@egzosn.com
+ * date 2021/10/6
+ * 
+ */ +public class WxCombinePayService extends WxPayService { + + /** + * 创建支付服务 + * + * @param payConfigStorage 微信对应的支付配置 + */ + public WxCombinePayService(WxPayConfigStorage payConfigStorage) { + super(payConfigStorage); + } + + /** + * 创建支付服务 + * + * @param payConfigStorage 微信对应的支付配置 + * @param configStorage 微信对应的网络配置,包含代理配置、ssl证书配置 + */ + public WxCombinePayService(WxPayConfigStorage payConfigStorage, HttpConfigStorage configStorage) { + super(payConfigStorage, configStorage); + } + + + /** + * 获取公共参数 + * + * @return 公共参数 + */ + public Map getPublicParameters() { + Map parameters = new LinkedHashMap<>(); + parameters.put(WxConst.COMBINE_APPID, payConfigStorage.getAppId()); + parameters.put(WxConst.COMBINE_MCH_ID, payConfigStorage.getMchId()); + return parameters; + } + + /** + * 初始化通知URL必须为直接可访问的URL,不允许携带查询串,要求必须为https地址。 + * + * @param parameters 订单参数 + * @param order 订单信息 + */ + public void initNotifyUrl(Map parameters, Order order) { + OrderParaStructure.loadParameters(parameters, WxConst.NOTIFY_URL, payConfigStorage.getNotifyUrl()); + OrderParaStructure.loadParameters(parameters, WxConst.NOTIFY_URL, order); + } + + /** + * 微信统一下单接口 + * + * @param order 支付订单集 + * @return 下单结果 + */ + @Override + public JSONObject unifiedOrder(PayOrder order) { + + //统一下单 + Map parameters = getPublicParameters(); + + // 订单号 + parameters.put(WxConst.COMBINE_OUT_TRADE_NO, order.getOutTradeNo()); + + OrderParaStructure.loadDateParameters(parameters, WxConst.TIME_START, order, DateUtils.YYYY_MM_DD_T_HH_MM_SS_XX); + OrderParaStructure.loadDateParameters(parameters, WxConst.TIME_EXPIRE, order, DateUtils.YYYY_MM_DD_T_HH_MM_SS_XX); + initNotifyUrl(parameters, order); + //支付场景描述 + OrderParaStructure.loadParameters(parameters, WxConst.SCENE_INFO, order); + //子单信息 最多支持子单条数:50 + OrderParaStructure.loadParameters(parameters, WxConst.SUB_ORDERS, order); + //支付者信息 + if (StringUtils.isNotEmpty(order.getOpenid())) { + parameters.put("combine_payer_info", new MapGen<>("openid", order.getOpenid()).getAttr()); + } + + return getAssistService().doExecute(parameters, order); + } + + + /** + * 交易查询接口 + * + * @param transactionId 微信支付平台订单号 + * @param outTradeNo 商户单号 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(String transactionId, String outTradeNo) { + return query(new AssistOrder(outTradeNo)); + } + /** + * 交易查询接口 + * + * @param assistOrder 查询条件 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(AssistOrder assistOrder) { + return getAssistService().doExecute("", WxTransactionType.COMBINE_TRANSACTION, assistOrder.getOutTradeNo()); + } + + /** + * 交易关闭接口 + * + * @param transactionId 支付平台订单号 + * @param outTradeNo 商户单号 + * @return 返回支付方交易关闭后的结果 + */ + @Override + public Map close(String transactionId, String outTradeNo) { + throw new PayErrorException(new PayException("failure", "合单关闭必须要有子单")); + } + + /** + * 交易关闭接口 + * + * @param assistOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 + */ + @Override + public Map close(AssistOrder assistOrder) { + Map parameters = new MapGen(WxConst.COMBINE_APPID, payConfigStorage.getAppId()) + .keyValue(WxConst.SUB_ORDERS, assistOrder.getAttr(WxConst.SUB_ORDERS)) + .getAttr(); + String requestBody = JSON.toJSONString(parameters, SerializerFeature.WriteMapNullValue); + return getAssistService().doExecute(requestBody, WxTransactionType.COMBINE_CLOSE, assistOrder.getOutTradeNo()); + } + + /** + * 创建消息 + * + * @param message 支付平台返回的消息 + * @return 支付消息对象 + */ + @Override + public PayMessage createMessage(Map message) { + return CombinePayMessage.create(message); + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxParameterStructure.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxParameterStructure.java new file mode 100644 index 0000000000000000000000000000000000000000..21f2cbe30093c34c4ee4ee68388162325e7b8998 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxParameterStructure.java @@ -0,0 +1,134 @@ +package com.egzosn.pay.wx.v3.api; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.common.bean.OrderParaStructure; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.util.MapGen; +import com.egzosn.pay.wx.v3.utils.WxConst; + +/** + * 微信参数构造器 + * + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2021/8/16
+ * 
+ */ +public class WxParameterStructure { + private final WxPayConfigStorage payConfigStorage; + + public WxParameterStructure(WxPayConfigStorage payConfigStorage) { + this.payConfigStorage = payConfigStorage; + } + + /** + * 获取公共参数 + * @param parameters 参数 + * @return 公共参数 + */ + public Map getPublicParameters(Map parameters) { + if (payConfigStorage.isPartner()) { + return parameters; + } + if (null == parameters) { + parameters = new LinkedHashMap<>(); + } + parameters.put(WxConst.APPID, payConfigStorage.getAppId()); + parameters.put(WxConst.MCH_ID, payConfigStorage.getMchId()); + return parameters; + } + + + /** + * 加载结算信息 + * + * @param parameters 订单参数 + * @param order 支付订单 + */ + public void loadSettleInfo(Map parameters, PayOrder order) { + Object profitSharing = order.getAttr("profit_sharing"); + if (null != profitSharing) { + Map settleInfo = new MapGen<>("profit_sharing", profitSharing).getAttr(); + parameters.put("settle_info", settleInfo); + return; + } + //结算信息 + OrderParaStructure.loadParameters(parameters, "settle_info", order); + + + } + + + /** + * 初始化通知URL必须为直接可访问的URL,不允许携带查询串,要求必须为https地址。 + * + * @param parameters 订单参数 + * @param order 订单信息 + */ + public void initNotifyUrl(Map parameters, AssistOrder order) { + OrderParaStructure.loadParameters(parameters, WxConst.NOTIFY_URL, payConfigStorage.getNotifyUrl()); + OrderParaStructure.loadParameters(parameters, WxConst.NOTIFY_URL, order.getNotifyUrl()); + OrderParaStructure.loadParameters(parameters, WxConst.NOTIFY_URL, order); + } + + + /** + * 获取商户相关信息 + * + * @return 商户相关信息 + */ + public Map getMchParameters() { + Map attr = initSubMchId(null); + OrderParaStructure.loadParameters(attr, payConfigStorage.isPartner() ? WxConst.SP_MCH_ID : WxConst.MCH_ID, payConfigStorage.getMchId()); + return attr; + } + + + /** + * 初始化商户相关信息 + * + * @param parameters 参数信息 + * @return 参数信息 + */ + public Map initPartner(Map parameters) { + if (null == parameters) { + parameters = new LinkedHashMap<>(); + } + if (payConfigStorage.isPartner()) { + parameters.put("sp_appid", payConfigStorage.getAppId()); + parameters.put(WxConst.SP_MCH_ID, payConfigStorage.getMchId()); + OrderParaStructure.loadParameters(parameters, "sub_appid", payConfigStorage.getSubAppId()); + OrderParaStructure.loadParameters(parameters, WxConst.SUB_MCH_ID, payConfigStorage.getSubMchId()); + return parameters; + } + + parameters.put(WxConst.APPID, payConfigStorage.getAppId()); + parameters.put(WxConst.MCH_ID, payConfigStorage.getMchId()); + return parameters; + } + + + /** + * 初始化商户相关信息 + * + * @param parameters 参数信息 + * @return 参数信息 + */ + public Map initSubMchId(Map parameters) { + if (null == parameters) { + parameters = new HashMap<>(); + } + if (payConfigStorage.isPartner()) { + OrderParaStructure.loadParameters(parameters, WxConst.SUB_MCH_ID, payConfigStorage.getSubMchId()); + } + + return parameters; + + } + +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayAssistService.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayAssistService.java new file mode 100644 index 0000000000000000000000000000000000000000..92e45ef76e5bec32bf1b2cb8d39893d5314175f6 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayAssistService.java @@ -0,0 +1,87 @@ +package com.egzosn.pay.wx.v3.api; + +import java.security.cert.Certificate; +import java.util.Map; + +import org.apache.http.HttpEntity; + +import com.alibaba.fastjson.JSONObject; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.TransactionType; +import com.egzosn.pay.common.http.ResponseEntity; + +/** + * 微信支付辅助服务 + * + * @author Egan + * email egzosn@gmail.com + * date 2021/8/7 + */ +public interface WxPayAssistService { + + + /** + * 发起请求 + * + * @param parameters 支付参数 + * @param transactionType 交易类型 + * @return 响应内容体 + */ + JSONObject doExecute(Map parameters, TransactionType transactionType); + + + /** + * 发起请求 + * + * @param body 请求内容 + * @param transactionType 交易类型 + * @param uriVariables 用于匹配表达式 + * @return 响应内容体 + */ + JSONObject doExecute(String body, TransactionType transactionType, Object... uriVariables); + + /** + * 发起请求 + * + * @param body 请求内容 + * @param transactionType 交易类型 + * @param uriVariables 用于匹配表达式 + * @return 响应内容体 + */ + ResponseEntity doExecuteEntity(String body, TransactionType transactionType, Object... uriVariables); + + /** + * 发起请求 + * + * @param parameters 支付参数 + * @param order 订单 + * @return 请求响应 + */ + JSONObject doExecute(Map parameters, PayOrder order); + + /** + * 构建请求实体 + * 这里也做签名处理 + * + * @param url url + * @param body 请求内容体 + * @param method 请求方法 + * @return 请求实体 + */ + HttpEntity buildHttpEntity(String url, String body, String method); + + /** + * 当缓存中平台证书不存在事进行刷新重新获取平台证书 + * 调用/v3/certificates + * + */ + void refreshCertificate(); + + /** + * 通过证书序列获取平台证书 + * @param serialNo 证书序列 + * @return 平台证书 + */ + Certificate getCertificate(String serialNo); + +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayConfigStorage.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayConfigStorage.java new file mode 100644 index 0000000000000000000000000000000000000000..1457d1b0246641bd0e52ce9ad3389b704eeb8d1b --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayConfigStorage.java @@ -0,0 +1,276 @@ +package com.egzosn.pay.wx.v3.api; + +import java.io.IOException; +import java.io.InputStream; + +import com.egzosn.pay.common.api.BasePayConfigStorage; +import com.egzosn.pay.common.bean.CertStoreType; +import com.egzosn.pay.common.bean.result.PayException; +import com.egzosn.pay.common.exception.PayErrorException; +import com.egzosn.pay.wx.v3.bean.CertEnvironment; +import com.egzosn.pay.wx.v3.utils.AntCertificationUtil; +import com.egzosn.pay.wx.v3.utils.WxConst; + +/** + * 微信配置存储 + * + * @author egan + * + *
+ * email egzosn@gmail.com
+ * date 2016-5-18 14:09:01
+ * 
+ */ +public class WxPayConfigStorage extends BasePayConfigStorage { + + + /** + * 微信分配的公众账号ID + */ + private String appId; + /** + * 服务商申请的公众号appid。 + */ + private String spAppId; + /** + * 服务商户号,由微信支付生成并下发 。 + */ + private String spMchId; + /** + * 子商户应用ID, 非必填 + * 子商户申请的公众号appid。 + * 若sub_openid有传的情况下,sub_appid必填,且sub_appid需与sub_openid对应 + * 示例值:wxd678efh567hg6999 + */ + private String subAppId; + /** + * 微信支付分配的商户号 合作者id + */ + private String mchId; + /** + * 微信支付分配的子商户号,开发者模式下必填 合作者id + */ + private String subMchId; + /** + * V2 Api密钥 + */ + private String apiKey; + + /** + * 商户API证书 + * 包含商户的商户号、公司名称、公钥信息 + * 详情 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_1.shtml + */ + private Object apiClientKeyP12; + + /** + * 证书存储类型 + */ + private CertStoreType certStoreType; + + + /** + * 证书信息 + */ + private volatile CertEnvironment certEnvironment; + + /** + * 是否为服务商模式, 默认为false + */ + private boolean partner = false; + /** + * 微信支付分服务服务ID + */ + private String serviceId; + + @Deprecated + @Override + public String getAppid() { + return appId; + } + + + @Deprecated + public void setAppid(String appId) { + this.appId = appId; + } + + + /** + * 合作商唯一标识 + */ + @Override + public String getPid() { + return mchId; + } + + + @Override + public String getSeller() { + return mchId; + } + + + public String getMchId() { + return mchId; + } + + public void setMchId(String mchId) { + this.mchId = mchId; + addAttr("mchId", mchId); + } + + /** + * 为商户平台设置的密钥key + * + * @return 微信v2密钥 + */ + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + addAttr("apiKey", apiKey); + } + + public void setV3ApiKey(String v3ApiKey) { + setKeyPrivate(v3ApiKey); + } + /** + * 为商户平台设置的密钥key + * + * @return 微信v3密钥 + */ + public String getV3ApiKey() { + return getKeyPrivate(); + } + + public void setAppId(String appId) { + this.appId = appId; + } + + /** + * 应用id + * 纠正名称 + * + * @return 应用id + */ + @Override + public String getAppId() { + return appId; + } + + public String getSubAppId() { + return subAppId; + } + + public void setSubAppId(String subAppId) { + this.subAppId = subAppId; + addAttr("subAppId", subAppId); + } + + public String getSpAppId() { + return getAppId(); + } + + public void setSpAppId(String spAppId) { + setAppId(spAppId); + addAttr("spAppId", spAppId); + } + + public String getSpMchId() { + return getMchId(); + } + + public void setSpMchId(String spMchId) { + setMchId(spMchId); + addAttr("spMchId", spMchId); + } + + /** + * 应用id + * 纠正名称 + * + * @return 应用id + * @see #getSubAppId() + */ + @Deprecated + public String getSubAppid() { + return subAppId; + } + + @Deprecated + public void setSubAppid(String subAppid) { + this.subAppId = subAppid; + addAttr("subAppId", subAppId); + } + + + public String getSubMchId() { + return subMchId; + } + + public void setSubMchId(String subMchId) { + this.subMchId = subMchId; + addAttr("subMchId", subMchId); + } + + public Object getApiClientKeyP12() { + return apiClientKeyP12; + } + + public void setApiClientKeyP12(Object apiClientKeyP12) { + this.apiClientKeyP12 = apiClientKeyP12; + addAttr("apiClientKeyP12", apiClientKeyP12); + } + + public CertStoreType getCertStoreType() { + return certStoreType; + } + + public void setCertStoreType(CertStoreType certStoreType) { + this.certStoreType = certStoreType; + addAttr("certStoreType", certStoreType); + } + + public CertEnvironment getCertEnvironment() { + loadCertEnvironment(); + return certEnvironment; + } + + public void setCertEnvironment(CertEnvironment certEnvironment) { + this.certEnvironment = certEnvironment; + } + + /** + * 初始化证书信息 + */ + public void loadCertEnvironment() { + if (null != this.certEnvironment) { + return; + } + try (InputStream apiKeyCert = certStoreType.getInputStream(getApiClientKeyP12())) { + this.certEnvironment = AntCertificationUtil.initCertification(apiKeyCert, WxConst.CERT_ALIAS, getMchId()); + } + catch (IOException e) { + throw new PayErrorException(new PayException("读取证书异常", e.getMessage())); + } + } + + public boolean isPartner() { + return partner; + } + + public void setPartner(boolean partner) { + this.partner = partner; + } + + public String getServiceId() { + return serviceId; + } + + public void setServiceId(String serviceId) { + this.serviceId = serviceId; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayScoreService.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayScoreService.java new file mode 100644 index 0000000000000000000000000000000000000000..7a857befbac763f731d1a1f67b97a5aa57dfe7b5 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayScoreService.java @@ -0,0 +1,179 @@ +package com.egzosn.pay.wx.v3.api; + +import com.alibaba.fastjson.JSON; +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.common.bean.Order; +import com.egzosn.pay.common.bean.OrderParaStructure; +import com.egzosn.pay.common.http.HttpConfigStorage; +import com.egzosn.pay.common.http.UriVariables; +import com.egzosn.pay.common.util.DateUtils; +import com.egzosn.pay.common.util.str.StringUtils; +import com.egzosn.pay.wx.v3.bean.WxPayScoreTransactionType; +import com.egzosn.pay.wx.v3.bean.payscore.*; +import com.egzosn.pay.wx.v3.utils.WxConst; + +import java.net.URLEncoder; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; + +/** + * 微信支付分API服务 + * + * @author neon + * date 2023/9/12 + */ +public class WxPayScoreService extends WxPayService{ + + public WxPayScoreService(WxPayConfigStorage payConfigStorage) { + super(payConfigStorage); + } + + public WxPayScoreService(WxPayConfigStorage payConfigStorage, HttpConfigStorage configStorage) { + super(payConfigStorage, configStorage); + } + + private Map getPublicParameters() { + Map parameters = new TreeMap<>(); + parameters.put("appid", payConfigStorage.getAppId()); + parameters.put("mch_id", payConfigStorage.getPid()); + parameters.put("service_id", payConfigStorage.getServiceId()); + return parameters; + } + + /** + * 初始化通知URL必须为直接可访问的URL,不允许携带查询串,要求必须为https地址。 + * + * @param parameters 订单参数 + * @param order 订单信息 + */ + public void initNotifyUrl(Map parameters, Order order) { + OrderParaStructure.loadParameters(parameters, WxConst.NOTIFY_URL, payConfigStorage.getNotifyUrl()); + OrderParaStructure.loadParameters(parameters, WxConst.NOTIFY_URL, order); + } + + /** + * 商户预授权API + */ + public Map permissions(String authorizationCode) { + Map parameters = getPublicParameters(); + OrderParaStructure.loadParameters(parameters,WxConst.AUTHORIZATION_CODE,authorizationCode); + OrderParaStructure.loadParameters(parameters, WxConst.NOTIFY_URL, payConfigStorage.getNotifyUrl()); + return getAssistService().doExecute(parameters, WxPayScoreTransactionType.PERMISSIONS); + } + + public Map queryPermissionsByAuthorizationCode(String authorizationCode) { + Map parameters = getPublicParameters(); + OrderParaStructure.loadParameters(parameters,WxConst.AUTHORIZATION_CODE,authorizationCode); + String params = JSON.toJSONString(parameters); + return getAssistService().doExecute(params, WxPayScoreTransactionType.QUERY_PERMISSIONS_AUTHORIZATION_CODE,authorizationCode); + } + + public Map terminatePermissionsByAuthorizationCode(String authorizationCode,String reason) { + Map parameters = getPublicParameters(); + OrderParaStructure.loadParameters(parameters,WxConst.AUTHORIZATION_CODE,authorizationCode); + OrderParaStructure.loadParameters(parameters,"reason",reason); + String params = JSON.toJSONString(parameters); + return getAssistService().doExecute(params, WxPayScoreTransactionType.UNBIND_PERMISSIONS_AUTHORIZATION_CODE,authorizationCode); + } + + public Map queryPermissionsByOpenId(String openId) { + Map parameters = getPublicParameters(); + OrderParaStructure.loadParameters(parameters,"open_id",openId); + String params = JSON.toJSONString(parameters); + return getAssistService().doExecute(params, WxPayScoreTransactionType.QUERY_PERMISSIONS_OPENID,openId); + } + + public Map terminatePermissionsByOpenId(String openId,String reason) { + Map parameters = getPublicParameters(); + OrderParaStructure.loadParameters(parameters,"open_id",openId); + OrderParaStructure.loadParameters(parameters,"reason",reason); + String params = JSON.toJSONString(parameters); + return getAssistService().doExecute(params, WxPayScoreTransactionType.UNBIND_PERMISSIONS_OPENID,openId); + } + + /** + * 微信创建支付分订单 + */ + public Map create(CreateOrder createOrder) { + Map parameters = getPublicParameters(); + OrderParaStructure.loadParameters(parameters,"out_order_no", createOrder.getOutTradeNo()); + OrderParaStructure.loadParameters(parameters,"service_introduction", createOrder.getServiceIntroduction()); + RiskFund riskFund = new RiskFund(); + riskFund.setName(createOrder.getRiskFundName()); + riskFund.setAmount(createOrder.getRiskFundAmount()); + parameters.put("risk_fund",riskFund); + + String attach = createOrder.getAttach(); + if (StringUtils.isNotBlank(attach)) { + String attachEncode = URLEncoder.encode(attach); + OrderParaStructure.loadParameters(parameters,"attach",attachEncode.length() <= 256 ? attachEncode : attachEncode.substring(0, 255)); + } + TimeRange timeRange = new TimeRange(); + timeRange.setStartTime(createOrder.getStartTime()); + timeRange.setEndTime(createOrder.getEndTime()); + parameters.put("time_range", timeRange); + initNotifyUrl(parameters, createOrder); + OrderParaStructure.loadParameters(parameters,"openid", createOrder.getOpenId()); + if (null != createOrder.getNeedUserConfirm()) { + OrderParaStructure.loadParameters(parameters,"need_user_confirm", createOrder.getNeedUserConfirm().toString()); + } + return getAssistService().doExecute(parameters, WxPayScoreTransactionType.CREATE); + } + + /** + * 支付分订单撤销 + */ + @Override + public Map cancel(String orderNo, String reason) { + Map parameters = getPublicParameters(); + reason = reason.length() <= 50 ? reason : reason.substring(0, 50); + parameters.put("reason", reason); + String params = JSON.toJSONString(parameters); + return getAssistService().doExecute(params, WxPayScoreTransactionType.CANCEL, orderNo); + } + + + public Map modify(ModifyOrder modifyOrder) { + Map parameters = getPublicParameters(); + + parameters.put("post_payments", modifyOrder.getPostPayments()); + parameters.put("total_amount", modifyOrder.getTotalAmount()); + OrderParaStructure.loadParameters(parameters,"reason", modifyOrder.getReason()); + String params = JSON.toJSONString(parameters); + return getAssistService().doExecute(params, WxPayScoreTransactionType.MODIFY, modifyOrder.getOutTradeNo()); + } + + + public Map complete(CompleteOrder completeOrder){ + Map parameters = getPublicParameters(); + parameters.put("post_payments", completeOrder.getPostPayments()); + parameters.put("total_amount", completeOrder.getTotalAmount()); + String params = JSON.toJSONString(parameters); + return getAssistService().doExecute(params, WxPayScoreTransactionType.COMPLETE, completeOrder.getOutTradeNo()); + } + + + public Map sync(String outOrderNo, Date payTime){ + Map parameters = getPublicParameters(); + parameters.put("type", "Order_Paid"); + Map detail = new HashMap<>(); + detail.put("paid_time", DateUtils.formatDate(payTime,DateUtils.YYYYMMDDHHMMSS)); + parameters.put("detail", detail); + String params = JSON.toJSONString(parameters); + return getAssistService().doExecute(params, WxPayScoreTransactionType.SYNC,outOrderNo); + } + + + @Override + public Map query(AssistOrder assistOrder){ + String outTradeNo = assistOrder.getOutTradeNo(); + Map publicParameters = getPublicParameters(); + if (StringUtils.isNotBlank(outTradeNo)) { + OrderParaStructure.loadParameters(publicParameters,"out_order_no",outTradeNo); + } + String parameters = UriVariables.getMapToParameters(publicParameters); + return getAssistService().doExecute(parameters, WxPayScoreTransactionType.QUERY); + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayService.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayService.java new file mode 100644 index 0000000000000000000000000000000000000000..31701552604a7b529388ea0fd73b16b16bbd9d14 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayService.java @@ -0,0 +1,748 @@ +package com.egzosn.pay.wx.v3.api; + +import java.io.IOException; +import java.io.InputStream; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.http.HttpEntity; + +import static com.egzosn.pay.wx.api.WxConst.OUT_TRADE_NO; +import static com.egzosn.pay.wx.api.WxConst.SANDBOXNEW; +import static com.egzosn.pay.wx.v3.utils.WxConst.FAILURE; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.egzosn.pay.common.api.BasePayService; +import com.egzosn.pay.common.api.TransferService; +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.common.bean.BillType; +import com.egzosn.pay.common.bean.CurType; +import com.egzosn.pay.common.bean.MethodType; +import com.egzosn.pay.common.bean.NoticeParams; +import com.egzosn.pay.common.bean.NoticeRequest; +import com.egzosn.pay.common.bean.OrderParaStructure; +import com.egzosn.pay.common.bean.PayMessage; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.PayOutMessage; +import com.egzosn.pay.common.bean.RefundOrder; +import com.egzosn.pay.common.bean.RefundResult; +import com.egzosn.pay.common.bean.TransactionType; +import com.egzosn.pay.common.bean.TransferOrder; +import com.egzosn.pay.common.bean.result.PayException; +import com.egzosn.pay.common.exception.PayErrorException; +import com.egzosn.pay.common.http.HttpConfigStorage; +import com.egzosn.pay.common.http.ResponseEntity; +import com.egzosn.pay.common.http.UriVariables; +import com.egzosn.pay.common.util.DateUtils; +import com.egzosn.pay.common.util.IOUtils; +import com.egzosn.pay.common.util.MapGen; +import com.egzosn.pay.common.util.Util; +import com.egzosn.pay.common.util.sign.SignTextUtils; +import com.egzosn.pay.common.util.sign.SignUtils; +import com.egzosn.pay.common.util.sign.encrypt.RSA2; +import com.egzosn.pay.common.util.str.StringUtils; +import com.egzosn.pay.wx.bean.WxPayError; +import com.egzosn.pay.wx.v3.bean.WxAccountType; +import com.egzosn.pay.wx.v3.bean.WxBillType; +import com.egzosn.pay.wx.v3.bean.WxTransactionType; +import com.egzosn.pay.wx.v3.bean.WxTransferType; +import com.egzosn.pay.wx.v3.bean.order.Amount; +import com.egzosn.pay.wx.v3.bean.order.RefundAmount; +import com.egzosn.pay.wx.v3.bean.response.Resource; +import com.egzosn.pay.wx.v3.bean.response.WxNoticeParams; +import com.egzosn.pay.wx.v3.bean.response.WxPayMessage; +import com.egzosn.pay.wx.v3.bean.response.WxRefundResult; +import com.egzosn.pay.wx.v3.bean.transfer.TransferDetail; +import com.egzosn.pay.wx.v3.utils.AntCertificationUtil; +import com.egzosn.pay.wx.v3.utils.WxConst; + +/** + * 微信支付服务 + * + * @author egan + *
+ * email egan@egzosn.com
+ * date 2021/10/6
+ * 
+ */ +public class WxPayService extends BasePayService implements TransferService, WxPayServiceInf { + + + /** + * api服务地址,默认为国内 + */ + private String apiServerUrl = WxConst.URI; + + + /** + * 辅助api + */ + private volatile WxPayAssistService assistService; + + /** + * 微信参数构造器 + */ + private volatile WxParameterStructure wxParameterStructure; + + + /** + * 创建支付服务 + * + * @param payConfigStorage 微信对应的支付配置 + */ + public WxPayService(WxPayConfigStorage payConfigStorage) { + this(payConfigStorage, null); + } + + /** + * 创建支付服务 + * + * @param payConfigStorage 微信对应的支付配置 + * @param configStorage 微信对应的网络配置,包含代理配置、ssl证书配置 + */ + public WxPayService(WxPayConfigStorage payConfigStorage, HttpConfigStorage configStorage) { + super(payConfigStorage, configStorage); + } + + { + initAfter(); + } + + /** + * 辅助api + * + * @return 辅助api + */ + @Override + public WxPayAssistService getAssistService() { + if (null == assistService) { + assistService = new DefaultWxPayAssistService(this); + } + //在这预先进行初始化 + assistService.refreshCertificate(); + return assistService; + } + + public void setAssistService(WxPayAssistService assistService) { + this.assistService = assistService; + } + + /** + * 初始化之后执行 + */ + protected void initAfter() { + payConfigStorage.setPartner(StringUtils.isNotEmpty(payConfigStorage.getSubMchId())); +// new Thread(() -> { + payConfigStorage.loadCertEnvironment(); + wxParameterStructure = new WxParameterStructure(payConfigStorage); + setApiServerUrl(WxConst.URI); +// }).start(); + + } + + /** + * 设置api服务器地址 + * + * @param apiServerUrl api服务器地址 + * @return 自身 + */ + @Override + public WxPayService setApiServerUrl(String apiServerUrl) { + this.apiServerUrl = apiServerUrl; + getAssistService(); + return this; + } + + @Override + public String getApiServerUrl() { + return apiServerUrl; + } + + public WxParameterStructure getWxParameterStructure() { + return wxParameterStructure; + } + + /** + * 根据交易类型获取url + * + * @param transactionType 交易类型 + * @return 请求url + */ + @Override + public String getReqUrl(TransactionType transactionType) { + String type = transactionType.getType(); + String partnerStr = ""; + if (payConfigStorage.isPartner()) { + partnerStr = "/partner"; + } + type = type.replace("{partner}", partnerStr); + return apiServerUrl + (payConfigStorage.isTest() ? SANDBOXNEW : "") + type; + } + + + /** + * 回调校验 + * + * @param params 回调回来的参数集 + * @return 签名校验 true通过 + */ + @Override + public boolean verify(Map params) { + throw new PayErrorException(new WxPayError("", "微信V3不支持方式")); + + } + + /** + * 验签,使用微信平台证书. + * + * @param noticeParams 通知参数 + * @return the boolean + */ + @Override + public boolean verify(NoticeParams noticeParams) { + + //当前使用的微信平台证书序列号 + String serial = noticeParams.getHeader("wechatpay-serial"); + //微信服务器的时间戳 + String timestamp = noticeParams.getHeader("wechatpay-timestamp"); + //微信服务器提供的随机串 + String nonce = noticeParams.getHeader("wechatpay-nonce"); + //微信平台签名 + String signature = noticeParams.getHeader("wechatpay-signature"); + + Certificate certificate = getAssistService().getCertificate(serial); + + + //这里为微信回调时的请求内容体,原值数据 + String body = noticeParams.getBodyStr(); + //签名信息 + String signText = StringUtils.joining("\n", timestamp, nonce, body); + + return RSA2.verify(signText, signature, certificate, payConfigStorage.getInputCharset()); + } + + + /** + * 微信统一下单接口 + * + * @param order 支付订单集 + * @return 下单结果 + */ + public JSONObject unifiedOrder(PayOrder order) { + + //统一下单 + Map parameters = wxParameterStructure.initPartner(null); +// wxParameterStructure.getPublicParameters(parameters); + // 商品描述 + OrderParaStructure.loadParameters(parameters, WxConst.DESCRIPTION, order.getSubject()); + OrderParaStructure.loadParameters(parameters, WxConst.DESCRIPTION, order.getBody()); + // 订单号 + parameters.put(WxConst.OUT_TRADE_NO, order.getOutTradeNo()); + //交易结束时间 + if (null != order.getExpirationTime()) { + parameters.put("time_expire", DateUtils.formatDate(order.getExpirationTime(), DateUtils.YYYY_MM_DD_T_HH_MM_SS_XX)); + } + OrderParaStructure.loadParameters(parameters, "attach", order.getAddition()); + wxParameterStructure.initNotifyUrl(parameters, order); + //订单优惠标记 + OrderParaStructure.loadParameters(parameters, "goods_tag", order); + parameters.put("amount", Amount.getAmount(order.getPrice(), order.getCurType())); + + //优惠功能 + OrderParaStructure.loadParameters(parameters, "detail", order); + //支付场景描述 + OrderParaStructure.loadParameters(parameters, WxConst.SCENE_INFO, order); + wxParameterStructure.loadSettleInfo(parameters, order); + + TransactionType transactionType = order.getTransactionType(); + ((WxTransactionType) transactionType).setAttribute(parameters, order); + // 订单附加信息,可用于预设未提供的参数,这里会覆盖以上所有的订单信息, + parameters.putAll(order.getAttrs()); + return getAssistService().doExecute(parameters, order); + } + + + /** + * 返回创建的订单信息 + * + * @param order 支付订单 + * @return 订单信息 + * @see PayOrder 支付订单信息 + */ + @Override + public Map orderInfo(PayOrder order) { + + ////统一下单 + JSONObject result = unifiedOrder(order); + //如果是扫码支付或者刷卡付无需处理,直接返回 + if (((WxTransactionType) order.getTransactionType()).isReturn()) { + return result; + } + + Map params = new LinkedHashMap<>(); + String appId = payConfigStorage.getAppId(); + if (payConfigStorage.isPartner() && StringUtils.isNotEmpty(payConfigStorage.getSubAppId())) { + appId = payConfigStorage.getSubAppId(); + } + String timeStamp = String.valueOf(DateUtils.toEpochSecond()); + String randomStr = SignTextUtils.randomStr(); + String prepayId = result.getString("prepay_id"); + if (WxTransactionType.JSAPI == order.getTransactionType()) { + params.put("appId", appId); + params.put("timeStamp", timeStamp); + params.put("nonceStr", randomStr); + prepayId = "prepay_id=" + prepayId; + params.put("package", prepayId); + params.put("signType", SignUtils.RSA.getName()); + } + else if (WxTransactionType.APP == order.getTransactionType()) { + params.put(WxConst.APPID, appId); + params.put("partnerid", payConfigStorage.getMchId()); + params.put("timestamp", timeStamp); + params.put("noncestr", randomStr); + params.put("prepayid", prepayId); + params.put("package", "Sign=WXPay"); + } + String signText = StringUtils.joining("\n", appId, timeStamp, randomStr, prepayId); + String paySign = createSign(signText, payConfigStorage.getInputCharset()); + params.put(WxTransactionType.JSAPI.equals(order.getTransactionType()) ? "paySign" : "sign", paySign); + return params; + + } + + + /** + * 签名 + * + * @param content 需要签名的内容 不包含key + * @param characterEncoding 字符编码 + * @return 签名结果 + */ + @Override + public String createSign(String content, String characterEncoding) { + PrivateKey privateKey = payConfigStorage.getCertEnvironment().getPrivateKey(); + return RSA2.sign(content, privateKey, characterEncoding); + } + + /** + * 将请求参数或者请求流转化为 Map + * + * @param parameterMap 请求参数 + * @param is 请求流 + * @return 获得回调的请求参数 + */ + @Deprecated + @Override + public Map getParameter2Map(Map parameterMap, InputStream is) { + throw new PayErrorException(new WxPayError(FAILURE, "微信V3不支持方式")); + + } + + /** + * 将请求参数或者请求流转化为 Map + * + * @param request 通知请求 + * @return 获得回调的请求参数 + */ + @Override + public NoticeParams getNoticeParams(NoticeRequest request) { + WxNoticeParams noticeParams = null; + try (InputStream is = request.getInputStream()) { + String body = IOUtils.toString(is); + noticeParams = JSON.parseObject(body, WxNoticeParams.class); + noticeParams.setAttr(new MapGen(WxConst.RESP_BODY, body).getAttr()); + noticeParams.setBodyStr(body); + Resource resource = noticeParams.getResource(); + String associatedData = resource.getAssociatedData(); + String nonce = resource.getNonce(); + String ciphertext = resource.getCiphertext(); + String data = AntCertificationUtil.decryptToString(associatedData, nonce, ciphertext, payConfigStorage.getV3ApiKey(), payConfigStorage.getInputCharset()); + noticeParams.setBody(JSON.parseObject(data)); + } + catch (IOException e) { + throw new PayErrorException(new WxPayError(FAILURE, "获取回调参数异常"), e); + } + Map> headers = new HashMap<>(); + Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String name = headerNames.nextElement(); + headers.put(name, Collections.list(request.getHeaders(name))); + } + noticeParams.setHeaders(headers); + return noticeParams; + } + + /** + * 获取输出消息,用户返回给支付端 + * + * @param code 状态 + * @param message 消息 + * @return 返回输出消息 + */ + @Override + public PayOutMessage getPayOutMessage(String code, String message) { + return PayOutMessage.JSON().content("code", code).content("message", message).build(); + } + + + /** + * 获取成功输出消息,用户返回给支付端 + * 主要用于拦截器中返回 + * + * @param payMessage 支付回调消息 + * @return 返回输出消息 + */ + @Override + public PayOutMessage successPayOutMessage(PayMessage payMessage) { + return getPayOutMessage("SUCCESS", "成功"); + } + + + /** + * 获取输出消息,用户返回给支付端, 针对于web端 + * + * @param orderInfo 发起支付的订单信息 + * @param method 请求方式 "post" "get", + * @return 获取输出消息,用户返回给支付端, 针对于web端 + * @see MethodType 请求类型 + */ + @Override + public String buildRequest(Map orderInfo, MethodType method) { + String redirectUrl = StringUtils.isEmpty(payConfigStorage.getReturnUrl()) ? "" : "&redirect_url=" + UriVariables.urlEncoder(payConfigStorage.getReturnUrl()); + return String.format("", orderInfo.get("h5_url"), redirectUrl); + } + + /** + * 获取输出二维码信息, + * + * @param order 发起支付的订单信息 + * @return 返回二维码信息,,支付时需要的 + */ + @Override + public String getQrPay(PayOrder order) { + order.setTransactionType(WxTransactionType.NATIVE); + Map orderInfo = orderInfo(order); + + return (String) orderInfo.get("code_url"); + } + + /** + * 刷卡付,pos主动扫码付款 + * + * @param order 发起支付的订单信息 + * @return 返回支付结果 + */ + @Override + public Map microPay(PayOrder order) { + throw new PayErrorException(new PayException("failure", "V3暂时没有提供此功能,请查看V2版本功能")); + } + + /** + * 交易查询接口 + * + * @param transactionId 微信支付平台订单号 + * @param outTradeNo 商户单号 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(String transactionId, String outTradeNo) { + return query(new AssistOrder(transactionId, outTradeNo)); + } + + /** + * 交易查询接口 + * + * @param assistOrder 查询条件 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(AssistOrder assistOrder) { + String transactionId = assistOrder.getTradeNo(); + String outTradeNo = assistOrder.getOutTradeNo(); + String parameters = UriVariables.getMapToParameters(wxParameterStructure.getMchParameters()); + WxTransactionType transactionType = WxTransactionType.QUERY_TRANSACTION_ID; + String uriVariable = transactionId; + if (StringUtils.isNotEmpty(outTradeNo)) { + transactionType = WxTransactionType.QUERY_OUT_TRADE_NO; + uriVariable = outTradeNo; + } + + return getAssistService().doExecute(parameters, transactionType, uriVariable); + + } + + + /** + * 交易关闭接口 + * + * @param transactionId 支付平台订单号 + * @param outTradeNo 商户单号 + * @return 返回支付方交易关闭后的结果 + */ + @Override + public Map close(String transactionId, String outTradeNo) { + return close(new AssistOrder(outTradeNo)); + } + + /** + * 交易关闭接口 + * + * @param assistOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 + */ + @Override + public Map close(AssistOrder assistOrder) { + String parameters = JSON.toJSONString(wxParameterStructure.getMchParameters()); + final ResponseEntity responseEntity = getAssistService().doExecuteEntity(parameters, WxTransactionType.CLOSE, assistOrder.getOutTradeNo()); + if (responseEntity.getStatusCode() == 204) { + return new MapGen("statusCode", responseEntity.getStatusCode()).getAttr(); + } + return responseEntity.getBody(); + } + + + /** + * 申请退款接口 + * + * @param refundOrder 退款订单信息 + * @return 返回支付方申请退款后的结果 + */ + @Override + public RefundResult refund(RefundOrder refundOrder) { + //获取公共参数 + Map parameters = wxParameterStructure.initSubMchId(null); + + OrderParaStructure.loadParameters(parameters, "transaction_id", refundOrder.getTradeNo()); + OrderParaStructure.loadParameters(parameters, OUT_TRADE_NO, refundOrder.getOutTradeNo()); + OrderParaStructure.loadParameters(parameters, "out_refund_no", refundOrder.getRefundNo()); + OrderParaStructure.loadParameters(parameters, "reason", refundOrder.getDescription()); + OrderParaStructure.loadParameters(parameters, "funds_account", refundOrder); + wxParameterStructure.initNotifyUrl(parameters, refundOrder); + RefundAmount refundAmount = new RefundAmount(); + refundAmount.setRefund(Util.conversionCentAmount(refundOrder.getRefundAmount())); + refundAmount.setTotal(Util.conversionCentAmount(refundOrder.getTotalAmount())); + CurType curType = refundOrder.getCurType(); + if (null != curType) { + refundAmount.setCurrency(curType.getType()); + } + parameters.put("amount", refundAmount); + OrderParaStructure.loadParameters(parameters, "amount", refundOrder); + return WxRefundResult.create(getAssistService().doExecute(parameters, WxTransactionType.REFUND)); + } + + + /** + * 查询退款 + * + * @param refundOrder 退款订单单号信息 + * @return 返回支付方查询退款后的结果 + */ + @Override + public Map refundquery(RefundOrder refundOrder) { + String parameters = UriVariables.getMapToParameters(wxParameterStructure.initSubMchId(null)); + return getAssistService().doExecute(parameters, WxTransactionType.REFUND_QUERY, refundOrder.getRefundNo()); + } + + /** + * 下载对账单 + * + * @param billDate 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 + * @param billType 账单类型 内部自动转化 {@link BillType} + * @return 返回支付方下载对账单的结果 + */ + @Override + public Map downloadBill(Date billDate, String billType) { + BillType wxBillType = WxBillType.forType(billType); + if (null == wxBillType) { + wxBillType = WxAccountType.forType(billType); + } + return downloadBill(billDate, wxBillType); + } + + /** + * 目前只支持 + * 对账单中涉及金额的字段单位为“元”。 + * + * @param billDate 下载对账单的日期,格式:20140603 + * @param billType 账单类型 {@link WxBillType} 与 {@link WxAccountType} + * @return 返回支付方下载对账单的结果, 如果【账单类型】为gzip的话则返回值中key为data值为gzip的输入流 + */ + @Override + public Map downloadBill(Date billDate, BillType billType) { + //获取公共参数 + Map parameters = new HashMap<>(5); + + //目前只支持日账单 + parameters.put(WxConst.BILL_DATE, DateUtils.formatDate(billDate, DateUtils.YYYY_MM_DD)); + String fileType = billType.getFileType(); + OrderParaStructure.loadParameters(parameters, "tar_type", fileType); + if (billType instanceof WxAccountType) { + OrderParaStructure.loadParameters(parameters, "account_type", billType.getType()); + } + else { + wxParameterStructure.initSubMchId(parameters).put("bill_type", billType.getType()); + } + String body = UriVariables.getMapToParameters(parameters); + JSONObject result = getAssistService().doExecute(body, WxTransactionType.valueOf(billType.getCustom())); + String downloadUrl = result.getString("download_url"); + MethodType methodType = MethodType.GET; + HttpEntity entity = getAssistService().buildHttpEntity(downloadUrl, "", methodType.name()); + ResponseEntity responseEntity = requestTemplate.doExecuteEntity(downloadUrl, entity, InputStream.class, methodType); + InputStream inputStream = responseEntity.getBody(); + int statusCode = responseEntity.getStatusCode(); + if (statusCode >= 400) { + try { + String errorText = IOUtils.toString(inputStream); + JSONObject json = JSON.parseObject(errorText); + throw new PayErrorException(new WxPayError(statusCode + "", json.getString(WxConst.MESSAGE), errorText)); + } + catch (IOException e) { + throw new PayErrorException(new WxPayError(statusCode + "", "")); + } + } + Map data = new HashMap<>(); + data.put("file", inputStream); + return data; + } + + + /** + * 发起商家转账, 转账账单电子回单申请受理接口 + * + * @param transferOrder 转账订单 + * @return 对应的转账结果 + */ + @Override + public Map transfer(TransferOrder transferOrder) { + //转账账单电子回单申请受理接口 + if (transferOrder.getTransferType() == WxTransferType.TRANSFER_BILL_RECEIPT) { + Map attr = new MapGen(WxConst.OUT_BATCH_NO, transferOrder.getBatchNo()).getAttr(); + return getAssistService().doExecute(attr, transferOrder.getTransferType()); + } + + Map parameters = new HashMap<>(12); + parameters.put(WxConst.APPID, payConfigStorage.getAppId()); + parameters.put(WxConst.OUT_BATCH_NO, transferOrder.getBatchNo()); + OrderParaStructure.loadParameters(parameters, WxConst.BATCH_NAME, transferOrder); + parameters.put(WxConst.BATCH_REMARK, transferOrder.getRemark()); + parameters.put(WxConst.TOTAL_AMOUNT, Util.conversionCentAmount(transferOrder.getAmount())); + parameters.put(WxConst.TOTAL_NUM, transferOrder.getAttr(WxConst.TOTAL_NUM)); + Object transferDetailListAttr = transferOrder.getAttr(WxConst.TRANSFER_DETAIL_LIST); + List transferDetails = initTransferDetailListAttr(transferDetailListAttr); + parameters.put(WxConst.TRANSFER_DETAIL_LIST, transferDetails); + OrderParaStructure.loadParameters(parameters, WxConst.TRANSFER_SCENE_ID, transferOrder); + return getAssistService().doExecute(parameters, transferOrder.getTransferType()); + } + + private List initTransferDetailListAttr(Object transferDetailListAttr) { + List transferDetails = null; + if (transferDetailListAttr instanceof String) { + transferDetails = JSON.parseArray((String) transferDetailListAttr, TransferDetail.class); + } + else if (null != transferDetailListAttr) { + //偷懒的做法 + transferDetails = JSON.parseArray(JSON.toJSONString(transferDetailListAttr), TransferDetail.class); + } + else { + return null; + } + + // 商户上送敏感信息时使用`微信支付平台公钥`加密 + String serialNumber = payConfigStorage.getCertEnvironment().getPlatformSerialNumber(); + Certificate certificate = getAssistService().getCertificate(serialNumber); + return transferDetails.stream() + .peek(transferDetailListItem -> { + String userName = transferDetailListItem.getUserName(); + if (StringUtils.isNotEmpty(userName)) { + String encryptedUserName = AntCertificationUtil.encryptToString(userName, certificate); + transferDetailListItem.setUserName(encryptedUserName); + } + String userIdCard = transferDetailListItem.getUserIdCard(); + if (StringUtils.isNotEmpty(userIdCard)) { + String encryptedUserIdCard = AntCertificationUtil.encryptToString(userIdCard, certificate); + transferDetailListItem.setUserIdCard(encryptedUserIdCard); + } + }).collect(Collectors.toList()); + } + + /** + * 转账查询API + * 通过批次单号查询批次单 与 通过明细单号查询明细单 + * + * @param assistOrder 辅助交易订单 + * @return 对应的转账订单 + */ + @Override + public Map transferQuery(AssistOrder assistOrder) { + Map parameters = new HashMap<>(6); + TransactionType transactionType = assistOrder.getTransactionType(); + List uriVariables = new ArrayList<>(3); + + if (StringUtils.isNotEmpty(assistOrder.getTradeNo())) { + uriVariables.add(assistOrder.getTradeNo()); + String detailId = assistOrder.getAttrForString(WxConst.DETAIL_ID); + if (StringUtils.isNotEmpty(detailId)) { + uriVariables.add(detailId); + } + } + else if (StringUtils.isNotEmpty(assistOrder.getOutTradeNo())) { + uriVariables.add(assistOrder.getOutTradeNo()); + String outDetailNo = assistOrder.getAttrForString(WxConst.OUT_DETAIL_NO); + if (StringUtils.isNotEmpty(outDetailNo)) { + uriVariables.add(outDetailNo); + } + } + + if (transactionType == WxTransferType.QUERY_BATCH_BY_BATCH_ID || transactionType == WxTransferType.QUERY_BATCH_BY_OUT_BATCH_NO) { + OrderParaStructure.loadParameters(parameters, WxConst.NEED_QUERY_DETAIL, assistOrder); + OrderParaStructure.loadParameters(parameters, WxConst.OFFSET, assistOrder); + OrderParaStructure.loadParameters(parameters, WxConst.LIMIT, assistOrder); + OrderParaStructure.loadParameters(parameters, WxConst.DETAIL_STATUS, assistOrder); + } + + + String requestBody = UriVariables.getMapToParameters(parameters); + return getAssistService().doExecute(requestBody, assistOrder.getTransactionType(), uriVariables.toArray()); + } + + + /** + * 转账查询 + * + * @param outNo 商户转账订单号 + * @param wxTransferType 微信转账类型,.....这里没办法了只能这样写(┬_┬),请见谅 {@link WxTransferType} + *

+ * 企业付款到零钱 + * 商户企业付款到银行卡 + *

+ * @return 对应的转账订单 + */ + @Override + public Map transferQuery(String outNo, String wxTransferType) { + throw new PayErrorException(new WxPayError("", "V3不支持此转账查询:替代方法transferQuery(AssistOrder assistOrder)")); + } + + + /** + * 创建消息 + * + * @param message 支付平台返回的消息 + * @return 支付消息对象 + */ + @Override + public PayMessage createMessage(Map message) { + return WxPayMessage.create(message); + } + + +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayServiceInf.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayServiceInf.java new file mode 100644 index 0000000000000000000000000000000000000000..dbf54366b218264ac8d51473006da94116c5b434 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayServiceInf.java @@ -0,0 +1,68 @@ +package com.egzosn.pay.wx.v3.api; + +import com.egzosn.pay.common.api.PayService; +import com.egzosn.pay.common.bean.NoticeParams; +import com.egzosn.pay.common.bean.TransactionType; +import com.egzosn.pay.common.http.HttpStringEntity; + +/** + * 微信支付接口 + * @author Egan + * email egan@egzosn.com + * date 2023/9/11 + */ +public interface WxPayServiceInf extends PayService { + /** + * 辅助api + * + * @return 辅助api + */ + WxPayAssistService getAssistService(); + + /** + * 设置api服务器地址 + * + * @param apiServerUrl api服务器地址 + * @return 自身 + */ + WxPayService setApiServerUrl(String apiServerUrl); + + String getApiServerUrl(); + + /** + * 根据交易类型获取url + * + * @param transactionType 交易类型 + * @return 请求url + */ + @Override + String getReqUrl(TransactionType transactionType); + + /** + * 验签,使用微信平台证书. + * + * @param noticeParams 通知参数 + * @return the boolean + */ + @Override + boolean verify(NoticeParams noticeParams); + + /** + * 签名 + * + * @param content 需要签名的内容 不包含key + * @param characterEncoding 字符编码 + * @return 签名结果 + */ + @Override + String createSign(String content, String characterEncoding); + + /** + * http 实体 钩子 + * @param entity 实体 + * @return 返回处理后的实体 + */ + default HttpStringEntity hookHttpEntity(HttpStringEntity entity){ + return entity; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxProfitSharingService.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxProfitSharingService.java new file mode 100644 index 0000000000000000000000000000000000000000..d4bc919733dfe62b35f14f16bf2917ffe7917072 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxProfitSharingService.java @@ -0,0 +1,462 @@ +package com.egzosn.pay.wx.v3.api; + +import java.io.InputStream; +import java.util.Date; +import java.util.Map; + +import org.apache.http.message.BasicHeader; + +import com.alibaba.fastjson.JSONObject; +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.common.bean.BillType; +import com.egzosn.pay.common.bean.MethodType; +import com.egzosn.pay.common.bean.NoticeParams; +import com.egzosn.pay.common.bean.OrderParaStructure; +import com.egzosn.pay.common.bean.PayMessage; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.RefundOrder; +import com.egzosn.pay.common.bean.RefundResult; +import com.egzosn.pay.common.bean.TransferOrder; +import com.egzosn.pay.common.bean.result.PayException; +import com.egzosn.pay.common.exception.PayErrorException; +import com.egzosn.pay.common.http.HttpConfigStorage; +import com.egzosn.pay.common.http.HttpStringEntity; +import com.egzosn.pay.common.http.UriVariables; +import com.egzosn.pay.common.util.DateUtils; +import com.egzosn.pay.common.util.MapGen; +import com.egzosn.pay.wx.bean.WxPayError; +import com.egzosn.pay.wx.bean.WxTransferType; +import com.egzosn.pay.wx.v3.bean.WxProfitSharingTransactionType; +import com.egzosn.pay.wx.v3.bean.sharing.ProfitSharingBillType; +import com.egzosn.pay.wx.v3.bean.sharing.ProfitSharingPayMessage; +import com.egzosn.pay.wx.v3.bean.sharing.WxProfitSharingReturnResult; +import com.egzosn.pay.wx.v3.utils.WxConst; + +/** + * 微信分账API服务 + * + * @author egan + *
+ * email egan@egzosn.com
+ * date 2021/10/6
+ * 
+ */ +public class WxProfitSharingService extends WxPayService implements ProfitSharingService { + + /** + * 创建支付服务 + * + * @param payConfigStorage 微信对应的支付配置 + */ + public WxProfitSharingService(WxPayConfigStorage payConfigStorage) { + super(payConfigStorage); + } + + /** + * 创建支付服务 + * + * @param payConfigStorage 微信对应的支付配置 + * @param configStorage 微信对应的网络配置,包含代理配置、ssl证书配置 + */ + public WxProfitSharingService(WxPayConfigStorage payConfigStorage, HttpConfigStorage configStorage) { + super(payConfigStorage, configStorage); + } + + + /** + * 初始化之后执行 + */ + @Override + protected void initAfter() { +// new Thread(() -> { + payConfigStorage.loadCertEnvironment(); + setApiServerUrl(WxConst.URI); +// }).start(); + + } + + /** + * 回调校验 + * + * @param params 回调回来的参数集 + * @return 签名校验 true通过 + */ + @Override + public boolean verify(Map params) { + throw new PayErrorException(new WxPayError("", "分账不支持方式")); + + } + + /** + * 验签,使用微信平台证书. + * + * @param noticeParams 通知参数 + * @return the boolean + */ + @Override + public boolean verify(NoticeParams noticeParams) { + throw new PayErrorException(new WxPayError("", "分账不支持方式")); + } + + + /** + * 微信统一下单接口 + * + * @param order 支付订单集 + * @return 下单结果 + */ + @Override + public JSONObject unifiedOrder(PayOrder order) { + + Map parameters = new MapGen(WxConst.APPID, payConfigStorage.getAppId()) + .keyValue(WxConst.TRANSACTION_ID, order.getTradeNo()) + .keyValue(WxConst.OUT_ORDER_NO, order.getOutTradeNo()) + .keyValue(WxConst.RECEIVERS, order.getAttr(WxConst.RECEIVERS)) + .keyValue(WxConst.UNFREEZE_UNSPLIT, order.getAttr(WxConst.UNFREEZE_UNSPLIT)) + .getAttr(); + //以下服务商模式必填 + OrderParaStructure.loadParameters(parameters, WxConst.SUB_MCH_ID, order); + OrderParaStructure.loadParameters(parameters, WxConst.SUB_APPID, order); + return getAssistService().doExecute(parameters, order); + } + + /** + * http 实体 钩子 + * + * @param entity 实体 + * @return 返回处理后的实体 + */ + @Override + public HttpStringEntity hookHttpEntity(HttpStringEntity entity) { + entity.addHeader(new BasicHeader(WxConst.WECHATPAY_SERIAL, payConfigStorage.getCertEnvironment().getPlatformSerialNumber())); + return entity; + } + + /** + * 返回创建的订单信息 + * + * @param order 支付订单 + * @return 订单信息 + * @see PayOrder 支付订单信息 + */ + @Override + public Map orderInfo(PayOrder order) { + + if (null == order.getTransactionType()) { + order.setTransactionType(WxProfitSharingTransactionType.ORDERS); + } + switch ((WxProfitSharingTransactionType) order.getTransactionType()) { + case ORDERS_UNFREEZE: + return unfreeze(order); + case RECEIVERS_ADD: + return add(order); + case RECEIVERS_DELETE: + return delete(order); + default: + return unifiedOrder(order); + } + } + + + /** + * 将请求参数或者请求流转化为 Map + * + * @param parameterMap 请求参数 + * @param is 请求流 + * @return 获得回调的请求参数 + */ + @Deprecated + @Override + public Map getParameter2Map(Map parameterMap, InputStream is) { + throw new PayErrorException(new WxPayError("", "分账不支持方式")); + + } + + /** + * 获取输出消息,用户返回给支付端, 针对于web端 + * + * @param orderInfo 发起支付的订单信息 + * @param method 请求方式 "post" "get", + * @return 获取输出消息,用户返回给支付端, 针对于web端 + * @see MethodType 请求类型 + */ + @Override + public String buildRequest(Map orderInfo, MethodType method) { + throw new PayErrorException(new WxPayError("", "分账不支持方式")); + } + + /** + * 获取输出二维码信息, + * + * @param order 发起支付的订单信息 + * @return 返回二维码信息,,支付时需要的 + */ + @Override + public String getQrPay(PayOrder order) { + throw new PayErrorException(new WxPayError("", "分账不支持方式")); + } + + /** + * 刷卡付,pos主动扫码付款 + * + * @param order 发起支付的订单信息 + * @return 返回支付结果 + */ + @Override + public Map microPay(PayOrder order) { + throw new PayErrorException(new WxPayError("", "分账不支持方式")); + } + + /** + * 查询分账结果API + * 非服务商模式使用 + * + * @param transactionId 微信支付平台订单号 + * @param outTradeNo 商户单号 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Deprecated + @Override + public Map query(String transactionId, String outTradeNo) { + return query(new AssistOrder(transactionId, outTradeNo)); + } + + /** + * 查询分账结果API + *

+ * 发起分账请求后,可调用此接口查询分账结果 + *

+ * 注意: 发起解冻剩余资金请求后,可调用此接口查询解冻剩余资金的结果 + * + * @param assistOrder 查询条件 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(AssistOrder assistOrder) { + if (null == assistOrder.getTransactionType()) { + assistOrder.setTransactionType(WxProfitSharingTransactionType.ORDERS_RESULT); + } + switch ((WxProfitSharingTransactionType) assistOrder.getTransactionType()) { + case AMOUNTS: + return getAssistService().doExecute("", assistOrder.getTransactionType(), assistOrder.getTradeNo()); + case MCH_CONFIG: + return getAssistService().doExecute("", assistOrder.getTransactionType(), assistOrder.getAttr(WxConst.SUB_MCH_ID)); + default: + Map parameters = new MapGen(WxConst.TRANSACTION_ID, assistOrder.getTradeNo()).getAttr(); + //服务商模式使用 + OrderParaStructure.loadParameters(parameters, WxConst.SUB_MCH_ID, assistOrder); + return getAssistService().doExecute(UriVariables.getMapToParameters(parameters), assistOrder.getTransactionType(), assistOrder.getOutTradeNo()); + + } + + } + + + /** + * 交易关闭接口 + * + * @param transactionId 支付平台订单号 + * @param outTradeNo 商户单号 + * @return 返回支付方交易关闭后的结果 + */ + @Override + public Map close(String transactionId, String outTradeNo) { + return close(new AssistOrder(outTradeNo)); + } + + + /** + * 交易关闭接口 + * + * @param assistOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 + */ + @Override + public Map close(AssistOrder assistOrder) { + throw new PayErrorException(new PayException("failure", "V3暂时没有提供此功能,请查看V2版本功能")); + } + + /** + * 申请退款接口 + * + * @param refundOrder 退款订单信息 + * @return 返回支付方申请退款后的结果 + */ + @Override + public RefundResult refund(RefundOrder refundOrder) { + + Map parameters = new MapGen("return_mchid", payConfigStorage.getMchId()) + .keyValue("out_return_no", refundOrder.getRefundNo()) + .keyValue("amount", refundOrder.getRefundAmount().intValue()) + .keyValue(WxConst.DESCRIPTION, refundOrder.getDescription()) + .getAttr(); + //服务商模式使用 + OrderParaStructure.loadParameters(parameters, WxConst.SUB_MCH_ID, refundOrder); + OrderParaStructure.loadParameters(parameters, "order_id", refundOrder.getTradeNo()); + OrderParaStructure.loadParameters(parameters, "out_order_no", refundOrder.getOutTradeNo()); + return WxProfitSharingReturnResult.create(getAssistService().doExecute(parameters, WxProfitSharingTransactionType.RETURN_ORDERS)); + } + + + /** + * 查询退款 + * + * @param refundOrder 退款订单单号信息 + * @return 返回支付方查询退款后的结果 + */ + @Override + public Map refundquery(RefundOrder refundOrder) { + + Map parameters = new MapGen(WxConst.OUT_ORDER_NO, refundOrder.getOutTradeNo()).getAttr(); + //服务商模式使用 + OrderParaStructure.loadParameters(parameters, WxConst.SUB_MCH_ID, refundOrder); + String requestBody = UriVariables.getMapToParameters(parameters); + return getAssistService().doExecute(requestBody, WxProfitSharingTransactionType.RETURN_ORDERS_RESULT, refundOrder.getRefundNo()); + } + + /** + * 下载对账单 + * + * @param billDate 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 + * @param billType 账单类型 内部自动转化 {@link BillType} + * @return 返回支付方下载对账单的结果 + */ + @Override + public Map downloadBill(Date billDate, String billType) { + BillType wxBillType = ProfitSharingBillType.valueOf(billType); + return downloadBill(billDate, wxBillType); + } + + /** + * 申请分账账单 + * 目前不支持指定子商户号查询 + * + * @param billDate 下载对账单的日期 + * @param billType 账单类型 {@link ProfitSharingBillType} + * @return 返回支付方下载对账单的结果, 如果【账单类型】为gzip的话则返回值中key为data值为gzip的输入流 + */ + @Override + public Map downloadBill(Date billDate, BillType billType) { + + Map parameters = new MapGen(WxConst.BILL_DATE, DateUtils.formatDate(billDate, DateUtils.YYYY_MM_DD)) + .getAttr(); + OrderParaStructure.loadParameters(parameters, WxConst.TAR_TYPE, billType.getType()); + + return getAssistService().doExecute(UriVariables.getMapToParameters(parameters), WxProfitSharingTransactionType.BILLS); + } + + + /** + * 转账 + * + * @param order 转账订单 + * @return 对应的转账结果 + */ + @Override + public Map transfer(TransferOrder order) { + throw new PayErrorException(new WxPayError("", "分账不支持方式")); + } + + + /** + * 转账查询 + * + * @param outNo 商户转账订单号 + * @param wxTransferType 微信转账类型,.....这里没办法了只能这样写(┬_┬),请见谅 {@link WxTransferType} + *

+ * 企业付款到零钱 + * 商户企业付款到银行卡 + *

+ * @return 对应的转账订单 + */ + @Override + public Map transferQuery(String outNo, String wxTransferType) { + throw new PayErrorException(new WxPayError("", "分账不支持方式")); + } + + + /** + * 创建消息 + * + * @param message 支付平台返回的消息 + * @return 支付消息对象 + */ + @Override + public PayMessage createMessage(Map message) { + return ProfitSharingPayMessage.create(message); + } + + + /** + * 添加分账接收方 + * + * @param order 添加分账 + * @return 结果 + */ + @Override + public Map add(PayOrder order) { + if (null == order.getTransactionType()) { + order.setTransactionType(WxProfitSharingTransactionType.RECEIVERS_ADD); + } + Map parameters = new MapGen(WxConst.APPID, payConfigStorage.getAppId()) + .getAttr(); + + OrderParaStructure.loadParameters(parameters, WxConst.TYPE, order); + OrderParaStructure.loadParameters(parameters, WxConst.ACCOUNT, order); + OrderParaStructure.loadParameters(parameters, WxConst.NAME, order); + OrderParaStructure.loadParameters(parameters, WxConst.RELATION_TYPE, order); + OrderParaStructure.loadParameters(parameters, WxConst.CUSTOM_RELATION, order); + //以下服务商模式必填 + OrderParaStructure.loadParameters(parameters, WxConst.SUB_MCH_ID, order); + OrderParaStructure.loadParameters(parameters, WxConst.SUB_APPID, order); + + return getAssistService().doExecute(parameters, order); + } + + /** + * 删除分账接收方 + * + * @param order 删除分账 + * @return 结果 + */ + @Override + public Map delete(PayOrder order) { + if (null == order.getTransactionType()) { + order.setTransactionType(WxProfitSharingTransactionType.RECEIVERS_DELETE); + } + Map parameters = new MapGen(WxConst.APPID, payConfigStorage.getAppId()) + .getAttr(); + + OrderParaStructure.loadParameters(parameters, WxConst.TYPE, order); + OrderParaStructure.loadParameters(parameters, WxConst.ACCOUNT, order); + //以下服务商模式必填 + OrderParaStructure.loadParameters(parameters, WxConst.SUB_MCH_ID, order); + OrderParaStructure.loadParameters(parameters, WxConst.SUB_APPID, order); + + return getAssistService().doExecute(parameters, order); + } + + /** + * 解冻剩余资金 + * + * @param order 解冻 + * @return 结果 + */ + @Override + public Map unfreeze(PayOrder order) { + if (null == order.getTransactionType()) { + order.setTransactionType(WxProfitSharingTransactionType.ORDERS_UNFREEZE); + } + Map parameters = new MapGen(WxConst.TRANSACTION_ID, order.getTradeNo()) + .keyValue(WxConst.OUT_ORDER_NO, order.getOutTradeNo()) + .getAttr(); + // 商品描述 + OrderParaStructure.loadParameters(parameters, WxConst.DESCRIPTION, order.getSubject()); + OrderParaStructure.loadParameters(parameters, WxConst.DESCRIPTION, order.getBody()); + OrderParaStructure.loadParameters(parameters, WxConst.DESCRIPTION, order); + + //以下服务商模式必填 + OrderParaStructure.loadParameters(parameters, WxConst.SUB_MCH_ID, order); + return getAssistService().doExecute(parameters, order); + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/CertEnvironment.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/CertEnvironment.java new file mode 100644 index 0000000000000000000000000000000000000000..4b226a33b1ebd93c91a9c9c3741924be7f7f4d42 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/CertEnvironment.java @@ -0,0 +1,80 @@ +package com.egzosn.pay.wx.v3.bean; + +import java.security.PrivateKey; +import java.security.PublicKey; + +import com.egzosn.pay.common.util.str.StringUtils; + +/** + * 证书模式运行时环境 + * + * @author egan + * email egzosn@gmail.com + * date 2021/07/18.20:29 + */ +public class CertEnvironment { + /** + * 存放私钥 + */ + private PrivateKey privateKey; + + /** + * 存放公钥 + */ + private PublicKey publicKey; + + /** + * 公钥序列 + */ + private String serialNumber; + + /** + * 微信平台证书序列号 + */ + private String platformSerialNumber; + + + public CertEnvironment() { + } + + public CertEnvironment(PrivateKey privateKey, PublicKey publicKey, String serialNumber) { + this.privateKey = privateKey; + this.publicKey = publicKey; + this.serialNumber = serialNumber; + } + + public PrivateKey getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(PrivateKey privateKey) { + this.privateKey = privateKey; + } + + public PublicKey getPublicKey() { + return publicKey; + } + + public void setPublicKey(PublicKey publicKey) { + this.publicKey = publicKey; + } + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + public String getPlatformSerialNumber() { + if (StringUtils.isEmpty(platformSerialNumber)) { + setPlatformSerialNumber(serialNumber); + } + return platformSerialNumber; + } + + public void setPlatformSerialNumber(String platformSerialNumber) { + this.platformSerialNumber = platformSerialNumber; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxAccountType.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxAccountType.java new file mode 100644 index 0000000000000000000000000000000000000000..fc641931c49eafd33e68a4547ae5daffb5f2950e --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxAccountType.java @@ -0,0 +1,116 @@ +package com.egzosn.pay.wx.v3.bean; + +import com.egzosn.pay.common.bean.BillType; +import com.egzosn.pay.common.util.str.StringUtils; +import com.egzosn.pay.wx.api.WxConst; + +/** + * 资金账户类型 + * + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2021/2/22
+ * 
+ */ +public enum WxAccountType implements BillType { + /** + * 基本账户, 不填则默认是数据流 + */ + BASIC("BASIC"), + /** + * 基本账户 + * 返回格式为.gzip的压缩包账单 + */ + BASIC_GZIP("BASIC", WxConst.GZIP), + /** + * 运营账户 + */ + OPERATION("OPERATION"), + /** + * 运营账户 + * 返回格式为.gzip的压缩包账单 + */ + OPERATION_GZIP("OPERATION", WxConst.GZIP), + /** + * 手续费账户 + */ + FEES("FEES"), + /** + * 手续费账户 + * 返回格式为.gzip的压缩包账单 + */ + FEES_GZIP("FEES", WxConst.GZIP); + + /** + * 账单类型 + */ + private String type; + /** + * 日期格式化表达式 + */ + private String tarType; + + + + WxAccountType(String type) { + this.type = type; + } + + + WxAccountType(String type, String tarType) { + this.type = type; + this.tarType = tarType; + } + + /** + * 获取类型名称 + * + * @return 类型 + */ + @Override + public String getType() { + return type; + } + + /** + * 获取类型对应的日期格式化表达式 + * + * @return 日期格式化表达式 + */ + @Override + public String getDatePattern() { + return null; + } + + /** + * 获取文件类型 + * + * @return 文件类型 + */ + @Override + public String getFileType() { + return tarType; + } + + + /** + * 返回交易类型 + * + * @return 交易类型 + */ + @Override + public String getCustom() { + return WxTransactionType.FUND_FLOW_BILL.name(); + } + + public static WxAccountType forType(String type) { + for (WxAccountType wxPayBillType : WxAccountType.values()) { + if (wxPayBillType.getType().equals(type) && StringUtils.isEmpty(wxPayBillType.getFileType())) { + return wxPayBillType; + } + } + return null; + } + +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxBillType.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxBillType.java new file mode 100644 index 0000000000000000000000000000000000000000..6b0cd7b6369cb63f1dc201f28a821575beb75f7a --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxBillType.java @@ -0,0 +1,113 @@ +package com.egzosn.pay.wx.v3.bean; + +import com.egzosn.pay.common.bean.BillType; +import com.egzosn.pay.common.util.str.StringUtils; +import com.egzosn.pay.wx.api.WxConst; + +/** + * 微信账单类型 + * + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2021/08/01
+ * 
+ */ +public enum WxBillType implements BillType { + /** + * 返回当日所有订单信息(不含充值退款订单) + */ + ALL("ALL"), + /** + * 返回当日所有订单信息(不含充值退款订单) 返回格式为.gzip的压缩包账单 + */ + ALL_GZIP("ALL", WxConst.GZIP), + /** + * 返回当日成功支付的订单(不含充值退款订单) + */ + SUCCESS(WxConst.SUCCESS), + /** + * 返回当日成功支付的订单(不含充值退款订单) + * 返回格式为.gzip的压缩包账单 + */ + SUCCESS_GZIP(WxConst.SUCCESS, WxConst.GZIP), + /** + * 返回当日退款订单(不含充值退款订单) + */ + REFUND("REFUND"), + /** + * 返回当日退款订单(不含充值退款订单) + * 返回格式为.gzip的压缩包账单 + */ + REFUND_GZIP("REFUND", WxConst.GZIP); + + /** + * 账单类型 + */ + private String type; + /** + * 日期格式化表达式 + */ + private String tarType; + + WxBillType(String type) { + this.type = type; + } + + + WxBillType(String type, String tarType) { + this.type = type; + this.tarType = tarType; + } + + /** + * 获取类型名称 + * + * @return 类型 + */ + @Override + public String getType() { + return type; + } + + /** + * 获取类型对应的日期格式化表达式 + * + * @return 日期格式化表达式 + */ + @Override + public String getDatePattern() { + return null; + } + + /** + * 获取文件类型 + * + * @return 文件类型 + */ + @Override + public String getFileType() { + return tarType; + } + + + /** + * 自定义属性 + * + * @return 自定义属性 + */ + @Override + public String getCustom() { + return WxTransactionType.TRADE_BILL.name(); + } + + public static WxBillType forType(String type) { + for (WxBillType wxPayBillType : WxBillType.values()) { + if (wxPayBillType.getType().equals(type) && StringUtils.isEmpty(wxPayBillType.getFileType())) { + return wxPayBillType; + } + } + return null; + } + +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxPayScoreTransactionType.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxPayScoreTransactionType.java new file mode 100644 index 0000000000000000000000000000000000000000..e7b37fcabf2e4c93b65b7482092a3b97fab1d3d4 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxPayScoreTransactionType.java @@ -0,0 +1,54 @@ +package com.egzosn.pay.wx.v3.bean; + +import com.egzosn.pay.common.bean.MethodType; +import com.egzosn.pay.common.bean.TransactionType; + +public enum WxPayScoreTransactionType implements TransactionType { + + + + PERMISSIONS("/v3/payscore/permissions", MethodType.POST), + + QUERY_PERMISSIONS_AUTHORIZATION_CODE("/v3/payscore/permissions/authorization-code/{authorization_code}", MethodType.POST), + + UNBIND_PERMISSIONS_AUTHORIZATION_CODE("/v3/payscore/permissions/authorization-code/{authorization_code}/terminate", MethodType.POST), + + QUERY_PERMISSIONS_OPENID("/v3/payscore/permissions/openid/{openid}", MethodType.POST), + + UNBIND_PERMISSIONS_OPENID("/v3/payscore/permissions/openid/{openid}/terminate", MethodType.POST), + + CREATE("/v3/payscore/serviceorder", MethodType.POST), + + CANCEL("/v3/payscore/serviceorder/{out_order_no}/cancel", MethodType.POST), + + COMPLETE("/v3/payscore/serviceorder/{out_order_no}/complete", MethodType.POST), + + SYNC("/v3/payscore/serviceorder/{out_order_no}/sync", MethodType.POST), + + MODIFY("/v3/payscore/serviceorder/{out_order_no}/modify", MethodType.POST), + + QUERY("/v3/payscore/serviceorder", MethodType.GET), + + ; + + WxPayScoreTransactionType(String type, MethodType method) { + this.type = type; + this.method = method; + } + + private String type; + private MethodType method; + + + + @Override + public String getType() { + return type; + } + + @Override + public String getMethod() { + return this.method.name(); + } + +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxProfitSharingTransactionType.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxProfitSharingTransactionType.java new file mode 100644 index 0000000000000000000000000000000000000000..edb9b913721d565fc55431042d1d574d0f12ab8a --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxProfitSharingTransactionType.java @@ -0,0 +1,79 @@ +package com.egzosn.pay.wx.v3.bean; + +import com.egzosn.pay.common.bean.MethodType; +import com.egzosn.pay.common.bean.TransactionType; + +/** + * 微信V3分账交易类型 + * + * @author egan + *
+ * email egan@egzosn.com
+ * date 2021/10/6
+ * 
+ */ +public enum WxProfitSharingTransactionType implements TransactionType { + /** + * 请求分账 + */ + ORDERS("/v3/profitsharing/orders", MethodType.POST), + /** + * 查询分账结果 + */ + ORDERS_RESULT("/v3/profitsharing/orders/{out_order_no}", MethodType.GET), + /** + * 请求分账回退 + */ + RETURN_ORDERS("/v3/profitsharing/return-orders", MethodType.POST), + /** + * 查询分账回退结果 + */ + RETURN_ORDERS_RESULT("/v3/profitsharing/return-orders/{out_return_no}", MethodType.GET), + /** + * 解冻剩余资金 + */ + ORDERS_UNFREEZE("/v3/profitsharing/orders/unfreeze", MethodType.POST), + /** + * 查询剩余待分金额 + */ + AMOUNTS("/v3/profitsharing/transactions/{transaction_id}/amounts", MethodType.GET), + /** + * 服务商专用-查询最大分账比例 + */ + MCH_CONFIG("/v3/profitsharing/merchant-configs/{sub_mchid}", MethodType.GET), + /** + * 添加分账接收方 + */ + RECEIVERS_ADD("/v3/profitsharing/receivers/add", MethodType.POST), + /** + * 删除分账接收方 + */ + RECEIVERS_DELETE("/v3/profitsharing/receivers/add", MethodType.POST), + + BILLS("/v3/profitsharing/bills", MethodType.GET), + + ; + + + + WxProfitSharingTransactionType(String type, MethodType method) { + this.type = type; + this.method = method; + } + + private String type; + private MethodType method; + + + + @Override + public String getType() { + return type; + } + + @Override + public String getMethod() { + return this.method.name(); + } + +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxTransactionType.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxTransactionType.java new file mode 100644 index 0000000000000000000000000000000000000000..873e2eed8402a2617d0059dae883c4ff2c66a0f7 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxTransactionType.java @@ -0,0 +1,200 @@ +package com.egzosn.pay.wx.v3.bean; + +import java.util.Map; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.egzosn.pay.common.bean.MethodType; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.TransactionType; +import com.egzosn.pay.common.util.MapGen; +import com.egzosn.pay.common.util.str.StringUtils; +import com.egzosn.pay.wx.v3.bean.order.H5Info; +import com.egzosn.pay.wx.v3.bean.order.SceneInfo; +import com.egzosn.pay.wx.v3.utils.WxConst; + +/** + * 微信V3交易类型 + * + * @author egan + *
+ * email egan@egzosn.com
+ * date 2016/10/19 22:58
+ * 
+ */ +public enum WxTransactionType implements TransactionType { + /** + * 获取证书. + */ + CERT("/v3/certificates", MethodType.GET), + + //----------------------------------------------------------------- + //以下为直连与服务商支付方式 + /** + * 微信公众号支付或者小程序支付 + */ + JSAPI("/v3/pay{partner}/transactions/jsapi", MethodType.POST) { + @Override + public void setAttribute(Map parameters, PayOrder order) { + String key = parameters.containsKey("sub_mchid") ? "sub_openid" : "openid"; + MapGen mapGen = new MapGen(key, order.getOpenid()); + parameters.put("payer", mapGen.getAttr()); + } + }, + /** + * 二维码支付 + */ + NATIVE("/v3/pay{partner}/transactions/native", MethodType.POST, true), + /** + * 移动支付 + */ + APP("/v3/pay{partner}/transactions/app", MethodType.POST), + /** + * H5支付 + */ + H5("/v3/pay{partner}/transactions/h5", MethodType.POST, true) { + @Override + public void setAttribute(Map parameters, PayOrder order) { + Object sceneInfoObj = parameters.get(WxConst.SCENE_INFO); + SceneInfo sceneInfo = null; + if (null == sceneInfoObj) { + sceneInfo = new SceneInfo(); + } + else if (sceneInfoObj instanceof SceneInfo) { + sceneInfo = (SceneInfo) sceneInfoObj; + } + else { + String jsonString = JSON.toJSONString(sceneInfoObj, SerializerFeature.WriteMapNullValue); + sceneInfo = JSON.parseObject(jsonString, SceneInfo.class); + } + String billCreateIp = order.getSpbillCreateIp(); + if (StringUtils.isNotEmpty(billCreateIp)) { + sceneInfo.setPayerClientIp(billCreateIp); + } + H5Info h5Info = sceneInfo.getH5Info(); + if (null == h5Info) { + sceneInfo.setH5Info(new H5Info(order.getWapName(), order.getWapUrl())); + } + parameters.put("scene_info", sceneInfo); + } + + }, + /** + * H5支付 + * 兼容 后期会抛弃 + */ + @Deprecated + MWEB("/v3/pay{partner}/transactions/h5", MethodType.POST, true) { + @Override + public void setAttribute(Map parameters, PayOrder order) { + H5.setAttribute(parameters, order); + } + + }, + + /** + * 查询订单 + * 兼容V2的方式,通过入参来决定 + */ + QUERY("/v3/pay{partner}/transactions/", MethodType.GET), + /** + * 微信支付订单号查询 + */ + QUERY_TRANSACTION_ID("/v3/pay{partner}/transactions/id/{transaction_id}", MethodType.GET), + /** + * 商户订单号查询 + */ + QUERY_OUT_TRADE_NO("/v3/pay{partner}/transactions/out-trade-no/{out_trade_no}", MethodType.GET), + /** + * 关闭订单 + */ + CLOSE("/v3/pay{partner}/transactions/out-trade-no/{out_trade_no}/close", MethodType.POST), + /** + * 申请退款 + */ + REFUND("/v3/refund/domestic/refunds", MethodType.POST), + /** + * 查询退款 + */ + REFUND_QUERY("/v3/refund/domestic/refunds/{out_refund_no}", MethodType.GET), + /** + * 申请交易账单 + */ + TRADE_BILL("/v3/bill/tradebill", MethodType.GET), + /** + * 申请资金账单 + */ + FUND_FLOW_BILL("/v3/bill/fundflowbill", MethodType.GET), + + //----------------------------------------------------------------- + //以下为合并支付 + /** + * 合单下单-APP支付 + */ + COMBINE_APP("/v3/combine-transactions/app", MethodType.POST), + + /** + * 合单下单-微信公众号支付或者小程序支付. + */ + COMBINE_JSAPI("/v3/combine-transactions/jsapi", MethodType.POST), + /** + * 合单下单-H5支付 + */ + COMBINE_H5("/v3/combine-transactions/h5", MethodType.POST, true), + /** + * 合单下单-Native支付 + */ + COMBINE_NATIVE("/v3/combine-transactions/native", MethodType.POST, true), + /** + * 合单查询订单 + */ + COMBINE_TRANSACTION("/v3/combine-transactions/out-trade-no/{combine_out_trade_no}", MethodType.GET), + + /** + * 合单关闭订单 + */ + COMBINE_CLOSE("/v3/combine-transactions/out-trade-no/{combine_out_trade_no}/close", MethodType.POST) + ; + + WxTransactionType(String type, MethodType method) { + this(type, method, false); + + } + + WxTransactionType(String type, MethodType method, boolean back) { + this.type = type; + this.method = method; + this.back = back; + } + + private String type; + private MethodType method; + /** + * 是否直接返回 + */ + private boolean back; + + + @Override + public String getType() { + return type; + } + + @Override + public String getMethod() { + return this.method.name(); + } + + /** + * 是否直接返回 + * + * @return 是否直接返回 + */ + public boolean isReturn() { + return back; + } + + public void setAttribute(Map parameters, PayOrder order) { + + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxTransferType.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxTransferType.java new file mode 100644 index 0000000000000000000000000000000000000000..1e6c55a7da650220bce6f119d373d7361b16864e --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxTransferType.java @@ -0,0 +1,89 @@ +package com.egzosn.pay.wx.v3.bean; + +import java.util.Map; + +import com.egzosn.pay.common.bean.MethodType; +import com.egzosn.pay.common.bean.TransferOrder; +import com.egzosn.pay.common.bean.TransferType; + +/** + * 微信转账类型 + * + * @author egan + * email egzosn@gmail.com + * date 2018/9/28.19:56 + */ +public enum WxTransferType implements TransferType { + /** + * 转账到零钱 + */ + TRANSFER_BATCHES("/v3/transfer/batches", MethodType.POST), + /** + * 查询转账到零钱的记录,通过微信批次单号查询批次单 + */ + QUERY_BATCH_BY_BATCH_ID("/v3/transfer/batches/batch-id/{batch_id}"), + /** + * 查询转账到零钱的记录,通过商家批次单号查询批次单 + */ + QUERY_BATCH_BY_OUT_BATCH_NO("/v3/transfer/batches/out-batch-no/{out_batch_no}"), + /** + * 通过微信明细单号查询明细单 + */ + QUERY_BATCH_DETAIL_BY_BATCH_ID("/v3/transfer/batches/batch-id/{batch_id}/details/detail-id/{detail_id}"), + /** + * 通过商家明细单号查询明细单 + */ + QUERY_BATCH_DETAIL_BY_OUT_BATCH_NO("/v3/transfer/batches/out-batch-no/{out_batch_no}/details/out-detail-no/{out_detail_no}"), + /** + * 转账账单电子回单申请受理接口 + */ + TRANSFER_BILL_RECEIPT("/v3/transfer/bill-receipt", MethodType.POST), + /** + * 查询转账账单电子回单接口 + */ + QUERY_TRANSFER_BILL_RECEIPT("/v3/transfer/bill-receipt/{out_batch_no}"), + /** + * 受理转账明细电子回单API + */ + TRANSFER_DETAIL_ELECTRONIC_RECEIPTS("/v3/transfer-detail/electronic-receipts", MethodType.POST), + /** + * 查询转账明细电子回单受理结果API + */ + QUERY_TRANSFER_DETAIL_ELECTRONIC_RECEIPTS("/v3/transfer-detail/electronic-receipts"), + ; + + WxTransferType(String type, MethodType method) { + this.type = type; + this.method = method; + } + + WxTransferType(String type) { + this(type, MethodType.GET); + } + + private String type; + private MethodType method; + + + @Override + public String getType() { + return type; + } + + @Override + public String getMethod() { + return this.method.name(); + } + + /** + * 设置属性 + * + * @param attr 已有属性对象 + * @param order 转账订单 + * @return 属性对象 + */ + @Override + public Map setAttr(Map attr, TransferOrder order) { + return attr; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombineAmount.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombineAmount.java new file mode 100644 index 0000000000000000000000000000000000000000..8a677f624576333cc04f1261f7447eea2ef96c49 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombineAmount.java @@ -0,0 +1,40 @@ +package com.egzosn.pay.wx.v3.bean.combine; + +import com.alibaba.fastjson.annotation.JSONField; +import com.egzosn.pay.wx.v3.bean.response.order.Amount; + +/** + * 合单支付订单金额信息. + * + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/10/5
+ * 
+ */ +public class CombineAmount extends Amount { + + /** + * 子单金额,单位为分,必填 + * 境外场景下,标价金额要超过商户结算币种的最小单位金额,例如结算币种为美元,则标价金额必须大于1美分 + */ + @JSONField(name = "total_amount") + private Integer totalAmount; + + public CombineAmount() { + } + + public CombineAmount(Integer totalAmount) { + this.totalAmount = totalAmount; + } + + public Integer getTotalAmount() { + return totalAmount; + } + + public void setTotalAmount(Integer totalAmount) { + this.totalAmount = totalAmount; + } + + +} \ No newline at end of file diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombineCloseOrder.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombineCloseOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..ae328bd84ac405223f439aa965f0687e9e82a36e --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombineCloseOrder.java @@ -0,0 +1,33 @@ +package com.egzosn.pay.wx.v3.bean.combine; + +import java.util.List; + +import com.egzosn.pay.common.bean.AssistOrder; + +import com.egzosn.pay.wx.v3.utils.WxConst; + +/** + * 微信合单关闭订单 + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/10/6
+ * 
+ */ +public class CombineCloseOrder extends AssistOrder { + + /** + * 子单信息,必填,最多50单 + */ + private List subOrders; + + + public List getSubOrders() { + return subOrders; + } + + public void setSubOrders(List subOrders) { + this.subOrders = subOrders; + addAttr(WxConst.SUB_ORDERS, subOrders); + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombinePayMessage.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombinePayMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..cda2d61fe0da77851cd66dfcf7086c7c6b32e4cf --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombinePayMessage.java @@ -0,0 +1,115 @@ +package com.egzosn.pay.wx.v3.bean.combine; + +import java.util.List; +import java.util.Map; + +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.annotation.JSONField; +import com.egzosn.pay.common.bean.PayMessage; +import com.egzosn.pay.wx.v3.bean.order.SceneInfo; +import com.egzosn.pay.wx.v3.bean.response.order.Payer; +import com.egzosn.pay.wx.v3.utils.WxConst; + +/** + * 合单支付回调消息,兼容退款回调 + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/10/4
+ * 
+ */ +public class CombinePayMessage extends PayMessage { + + + /** + * 合单商户appid,即合单发起方的appid + */ + @JSONField(name = WxConst.COMBINE_APPID) + private String combineAppid; + + /** + * 合单商户号. + */ + @JSONField(name = WxConst.COMBINE_MCH_ID) + private String combineMchid; + + /** + * 合单商户订单号. + */ + @JSONField(name = WxConst.COMBINE_OUT_TRADE_NO) + private String combineOutTradeNo; + + + /** + * 支付者信息 + */ + @JSONField(name = "combine_payer_info") + private Payer combinePayerInfo; + + /** + * 场景信息,合单支付回调只返回device_id + */ + @JSONField(name = WxConst.SCENE_INFO) + private SceneInfo sceneInfo; + + /** + * 合单支付回调子订单. + */ + @JSONField(name = WxConst.SUB_ORDERS) + private List subOrders; + + public String getCombineAppid() { + return combineAppid; + } + + public void setCombineAppid(String combineAppid) { + this.combineAppid = combineAppid; + } + + public String getCombineMchid() { + return combineMchid; + } + + public void setCombineMchid(String combineMchid) { + this.combineMchid = combineMchid; + } + + public String getCombineOutTradeNo() { + return combineOutTradeNo; + } + + public void setCombineOutTradeNo(String combineOutTradeNo) { + this.combineOutTradeNo = combineOutTradeNo; + } + + public Payer getCombinePayerInfo() { + return combinePayerInfo; + } + + public void setCombinePayerInfo(Payer combinePayerInfo) { + this.combinePayerInfo = combinePayerInfo; + } + + public SceneInfo getSceneInfo() { + return sceneInfo; + } + + public void setSceneInfo(SceneInfo sceneInfo) { + this.sceneInfo = sceneInfo; + } + + public List getSubOrders() { + return subOrders; + } + + public void setSubOrders(List subOrders) { + this.subOrders = subOrders; + } + + public static final CombinePayMessage create(Map message) { + CombinePayMessage payMessage = new JSONObject(message).toJavaObject(CombinePayMessage.class); +// payMessage.setPayType(""); + payMessage.setPayMessage(message); + return payMessage; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombinePayOrder.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombinePayOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..280aef34d745f10d768ec77dd8e52966ec3ae43d --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombinePayOrder.java @@ -0,0 +1,92 @@ +package com.egzosn.pay.wx.v3.bean.combine; + +import java.util.Date; +import java.util.List; + +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.wx.v3.bean.order.SceneInfo; +import com.egzosn.pay.wx.v3.bean.order.SubOrder; +import com.egzosn.pay.wx.v3.utils.WxConst; + +/** + * 合并支付订单 + * + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/10/5
+ * 
+ */ +public class CombinePayOrder extends PayOrder { + + /** + * 子单信息,必填,最多50单 + */ + private List subOrders; + /** + * 交易起始时间,选填 + */ + private Date timeStart; + + /** + * 交易结束时间,选填 + */ + private Date timeExpire; + + /** + * 支付场景信息描述 + */ + private SceneInfo sceneInfo; + + public List getSubOrders() { + return subOrders; + } + + public void setSubOrders(List subOrders) { + this.subOrders = subOrders; + addAttr(WxConst.SUB_ORDERS, subOrders); + } + + public Date getTimeStart() { + return timeStart; + } + + public void setTimeStart(Date timeStart) { + this.timeStart = timeStart; + addAttr(WxConst.TIME_START, timeStart); + } + + public Date getTimeExpire() { + return timeExpire; + } + + public void setTimeExpire(Date timeExpire) { + this.timeExpire = timeExpire; + addAttr(WxConst.TIME_EXPIRE, timeExpire); + } + + /** + * 合单支付总订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一 。 + * @return 合单支付总订单号 + */ + public String getCombineOutTradeNo() { + return getOutTradeNo(); + } + + /** + * 合单支付总订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一 。 + * @param combineOutTradeNo 合单支付总订单号 + */ + public void setCombineOutTradeNo(String combineOutTradeNo) { + setOutTradeNo(combineOutTradeNo); + } + + public SceneInfo getSceneInfo() { + return sceneInfo; + } + + public void setSceneInfo(SceneInfo sceneInfo) { + this.sceneInfo = sceneInfo; + addAttr(WxConst.SCENE_INFO, sceneInfo); + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombineSubOrder.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombineSubOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..3fd4f14019ba5c1b0542bd2e2b40784743efab48 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombineSubOrder.java @@ -0,0 +1,59 @@ +package com.egzosn.pay.wx.v3.bean.combine; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * 子单信息,最多50单. + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/10/5
+ * 
+ */ +public class CombineSubOrder { + + /** + * 子单发起方商户号,必填,必须与发起方appid有绑定关系。 + */ + private String mchid; + /** + * 二级商户商户号,由微信支付生成并下发。 + *

+ * 服务商子商户的商户号,被合单方。 + *

+ * 直连商户不用传二级商户号。 + */ + @JSONField(name = "sub_mchid") + private String subMchid; + + /** + * 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。 + * 示例值:20150806125346 + */ + @JSONField(name = "out_trade_no") + private String outTradeNo; + + public String getMchid() { + return mchid; + } + + public void setMchid(String mchid) { + this.mchid = mchid; + } + + public String getSubMchid() { + return subMchid; + } + + public void setSubMchid(String subMchid) { + this.subMchid = subMchid; + } + + public String getOutTradeNo() { + return outTradeNo; + } + + public void setOutTradeNo(String outTradeNo) { + this.outTradeNo = outTradeNo; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/Amount.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/Amount.java new file mode 100644 index 0000000000000000000000000000000000000000..ae15d18509e14232847c139285ba92f80c4e02f3 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/Amount.java @@ -0,0 +1,76 @@ +package com.egzosn.pay.wx.v3.bean.order; + + +import java.math.BigDecimal; + +import com.egzosn.pay.common.bean.CurType; +import com.egzosn.pay.common.bean.DefaultCurType; +import com.egzosn.pay.common.util.Util; + +/** + * 订单金额信息 + * @author Egan + * email egzosn@gmail.com + * date 2021/8/1 + */ +public class Amount { + + /** + * 订单总金额,单位为分。 + */ + private Integer total; + /** + * 货币类型 CNY:人民币,境内商户号仅支持人民币。 + * {@link com.egzosn.pay.common.bean.CurType} + */ + private String currency = DefaultCurType.CNY.getType(); + + public Amount() { + } + + public Amount(Integer total) { + this.total = total; + } + + public Amount(Integer total, String currency) { + this.total = total; + this.currency = currency; + } + + public Integer getTotal() { + return total; + } + + public void setTotal(Integer total) { + this.total = total; + } + + public String getCurrency() { + return currency; + } + + public void setCurrency(String currency) { + this.currency = currency; + } + /** + * 订单金额信息 + * + * @param order 支付订单 + * @return 订单金额信息 + */ + /** + * 订单金额信息 + * @param total 金额,这里单位为元 + * @param curType 货币类型 + * @return 订单金额信息 + */ + public static Amount getAmount(BigDecimal total, CurType curType ) { + // 总金额单位为分 + Amount amount = new Amount(Util.conversionCentAmount(total)); + if (null == curType) { + curType = DefaultCurType.CNY; + } + amount.setCurrency(curType.getType()); + return amount; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/Detail.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/Detail.java new file mode 100644 index 0000000000000000000000000000000000000000..d11875d30b4e75c7ab40565757b65f24455ff667 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/Detail.java @@ -0,0 +1,51 @@ +package com.egzosn.pay.wx.v3.bean.order; + +import java.util.List; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * 优惠功能 + * @author Egan + * email egzosn@gmail.com + * date 2021/8/1 + */ +public class Detail { + /** + * 订单原价 + */ + @JSONField(name = "cost_price") + private Integer costPrice; + /** + * 商家小票 + */ + @JSONField(name = "invoice_id") + private String invoiceId; + + @JSONField(name = "goods_detail") + private List goodsDetail; + + public Integer getCostPrice() { + return costPrice; + } + + public void setCostPrice(Integer costPrice) { + this.costPrice = costPrice; + } + + public String getInvoiceId() { + return invoiceId; + } + + public void setInvoiceId(String invoiceId) { + this.invoiceId = invoiceId; + } + + public List getGoodsDetail() { + return goodsDetail; + } + + public void setGoodsDetail(List goodsDetail) { + this.goodsDetail = goodsDetail; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/From.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/From.java new file mode 100644 index 0000000000000000000000000000000000000000..7c7a9e0deee9e23fabe188e8e63820dda67b0c88 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/From.java @@ -0,0 +1,39 @@ +package com.egzosn.pay.wx.v3.bean.order; + +/** + * 退款出资的账户类型及金额信息 + * @author Egan + * email egzosn@gmail.com + * date 2021/8/1 + */ +public class From { + + /** + * 出资账户类型 + * 下面枚举值多选一。 + * 枚举值: + * AVAILABLE : 可用余额 + * UNAVAILABLE : 不可用余额 + */ + private String account; + /** + * 对应账户出资金额 + */ + private Integer amount; + + public String getAccount() { + return account; + } + + public void setAccount(String account) { + this.account = account; + } + + public Integer getAmount() { + return amount; + } + + public void setAmount(Integer amount) { + this.amount = amount; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/GoodsDetail.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/GoodsDetail.java new file mode 100644 index 0000000000000000000000000000000000000000..ad09ccb0c009c42c723b938cffb840a400a01b91 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/GoodsDetail.java @@ -0,0 +1,84 @@ +package com.egzosn.pay.wx.v3.bean.order; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * 单品列表信息 + * @author Egan + * email egzosn@gmail.com + * date 2021/8/1 + */ +public class GoodsDetail { + + + /** + * 商户侧商品编码 + */ + @JSONField(name = "merchant_goods_id") + private String merchantGoodsId; + + /** + * 微信侧商品编码 + */ + @JSONField(name = "wechatpay_goods_id") + private String wechatpayGoodsId ; + + + /** + * 商品名称 + */ + @JSONField(name = "goods_name") + private String goodsName; + /** + * 商品数量 + */ + + private Integer quantity; + /** + * 商品单价 + * 商品单价,单位为分 + */ + @JSONField(name = "unit_price") + private Integer unitPrice ; + + + public String getMerchantGoodsId() { + return merchantGoodsId; + } + + public void setMerchantGoodsId(String merchantGoodsId) { + this.merchantGoodsId = merchantGoodsId; + } + + public String getWechatpayGoodsId() { + return wechatpayGoodsId; + } + + public void setWechatpayGoodsId(String wechatpayGoodsId) { + this.wechatpayGoodsId = wechatpayGoodsId; + } + + public String getGoodsName() { + return goodsName; + } + + public void setGoodsName(String goodsName) { + this.goodsName = goodsName; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + public Integer getUnitPrice() { + return unitPrice; + } + + public void setUnitPrice(Integer unitPrice) { + this.unitPrice = unitPrice; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/H5Info.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/H5Info.java new file mode 100644 index 0000000000000000000000000000000000000000..5568533f0767fdb0f6d9c8792d14c7409c4d2b52 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/H5Info.java @@ -0,0 +1,99 @@ +package com.egzosn.pay.wx.v3.bean.order; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * H5场景信息 + * + * @author Egan + * email egzosn@gmail.com + * date 2021/8/1 + */ +public class H5Info { + + /** + * 场景类型 + * 示例值:iOS, Android, Wap + */ + private String type; + /** + * 应用名称 + * 示例值:王者荣耀 + */ + @JSONField(name = "app_name") + private String appName; + /** + * 网站URL + * 示例值:https://pay.qq.com + */ + @JSONField(name = "app_url") + private String appUrl; + /** + * iOS平台BundleID + * 示例值:com.tencent.wzryiOS + */ + @JSONField(name = "bundle_id") + private String bundleId; + /** + * Android平台PackageName + * 示例值:com.tencent.tmgp.sgame + */ + @JSONField(name = "package_name") + private String packageName; + + + + public H5Info() { + this.type = "Wap"; + } + + public H5Info(String type) { + this.type = type; + } + + public H5Info(String appName, String appUrl) { + this(); + this.appName = appName; + this.appUrl = appUrl; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getAppName() { + return appName; + } + + public void setAppName(String appName) { + this.appName = appName; + } + + public String getAppUrl() { + return appUrl; + } + + public void setAppUrl(String appUrl) { + this.appUrl = appUrl; + } + + public String getBundleId() { + return bundleId; + } + + public void setBundleId(String bundleId) { + this.bundleId = bundleId; + } + + public String getPackageName() { + return packageName; + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/RefundAmount.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/RefundAmount.java new file mode 100644 index 0000000000000000000000000000000000000000..acc691f8c6949f58d88041c1b7b6f9827f40b9cf --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/RefundAmount.java @@ -0,0 +1,113 @@ +package com.egzosn.pay.wx.v3.bean.order; + +import java.util.List; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * 退款金额 + * + * @author Egan + * email egzosn@gmail.com + * date 2021/8/1 + */ +public class RefundAmount extends Amount { + + + /** + * 退款金额,单位分 + */ + private Integer refund; + + /** + * 退款出资的账户类型及金额信息 + */ + private List from; + + /** + * 用户支付金额,单位分 + */ + @JSONField(name = "payer_total") + private Integer payerTotal; + /** + * 用户退款金额 + * 退款给用户的金额,不包含所有优惠券金额 + */ + @JSONField(name = "payer_refund") + private Integer payerRefund; + /** + * 应结退款金额 + * 去掉非充值代金券退款金额后的退款金额,单位为分,退款金额=申请退款金额-非充值代金券退款金额,退款金额<=申请退款金额 + */ + @JSONField(name = "settlement_refund") + private Integer settlementRefund; + /** + * 应结订单金额 + * 应结订单金额=订单金额-免充值代金券金额,应结订单金额<=订单金额,单位为分 + */ + @JSONField(name = "settlement_total") + private Integer settlementTotal; + /** + * 优惠退款金额 + * 优惠退款金额<=退款金额,退款金额-代金券或立减优惠退款金额为现金,说明详见代金券或立减优惠,单位为分 + */ + @JSONField(name = "discount_refund") + private Integer discountRefund; + + + public Integer getRefund() { + return refund; + } + + public void setRefund(Integer refund) { + this.refund = refund; + } + + public List getFrom() { + return from; + } + + public void setFrom(List from) { + this.from = from; + } + + public Integer getPayerTotal() { + return payerTotal; + } + + public void setPayerTotal(Integer payerTotal) { + this.payerTotal = payerTotal; + } + + public Integer getPayerRefund() { + return payerRefund; + } + + public void setPayerRefund(Integer payerRefund) { + this.payerRefund = payerRefund; + } + + public Integer getSettlementRefund() { + return settlementRefund; + } + + public void setSettlementRefund(Integer settlementRefund) { + this.settlementRefund = settlementRefund; + } + + public Integer getSettlementTotal() { + return settlementTotal; + } + + public void setSettlementTotal(Integer settlementTotal) { + this.settlementTotal = settlementTotal; + } + + public Integer getDiscountRefund() { + return discountRefund; + } + + public void setDiscountRefund(Integer discountRefund) { + this.discountRefund = discountRefund; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/SceneInfo.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/SceneInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..952a3c9810b8ca0cdc16379fed818536fb1dd5b5 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/SceneInfo.java @@ -0,0 +1,70 @@ +package com.egzosn.pay.wx.v3.bean.order; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * 支付场景信息描述 + * @author Egan + *

+ * email egzosn@gmail.com
+ * date 2021/8/1
+ * 
+ */ +public class SceneInfo { + + /** + * 用户的客户端IP,支持IPv4和IPv6两种格式的IP地址。 + */ + @JSONField(name = "payer_client_ip") + private String payerClientIp; + /** + * 商户端设备号(门店号或收银设备ID)。 + */ + @JSONField(name = "device_id") + private String deviceId; + + /** + * 商户门店信息 + */ + @JSONField(name = "store_info ") + private StoreInfo storeInfo; + /** + * H5场景信息 + */ + @JSONField(name = "h5_info") + private H5Info h5Info; + + + + public String getPayerClientIp() { + return payerClientIp; + } + + public void setPayerClientIp(String payerClientIp) { + this.payerClientIp = payerClientIp; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public StoreInfo getStoreInfo() { + return storeInfo; + } + + public void setStoreInfo(StoreInfo storeInfo) { + this.storeInfo = storeInfo; + } + + public H5Info getH5Info() { + return h5Info; + } + + public void setH5Info(H5Info h5Info) { + this.h5Info = h5Info; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/SettleInfo.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/SettleInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..4ce0a7ce67c1cfab6f2a1c1878275ad9c86b1c96 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/SettleInfo.java @@ -0,0 +1,45 @@ +package com.egzosn.pay.wx.v3.bean.order; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * 结算信息 + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/10/5
+ * 
+ */ +public class SettleInfo { + /** + * 是否指定分账,枚举值 + * true:是 + * false:否 + * 示例值:true + */ + @JSONField(name = "profit_sharing") + private Boolean profitSharing; + /** + * 补差金额 + * SettleInfo.profit_sharing为true时,该金额才生效。 + * 注意:单笔订单最高补差金额为5000元 + */ + @JSONField(name = "subsidy_amount") + private Integer subsidyAmount; + + public Boolean getProfitSharing() { + return profitSharing; + } + + public void setProfitSharing(Boolean profitSharing) { + this.profitSharing = profitSharing; + } + + public Integer getSubsidyAmount() { + return subsidyAmount; + } + + public void setSubsidyAmount(Integer subsidyAmount) { + this.subsidyAmount = subsidyAmount; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/StoreInfo.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/StoreInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..271d5e6ee0cb3fc1a6f7a5d6ab96f78ae3e2c55b --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/StoreInfo.java @@ -0,0 +1,62 @@ +package com.egzosn.pay.wx.v3.bean.order; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * 商户门店信息 + * @author Egan0 + * email egzosn@gmail.com + * date 2021/8/1 + */ +public class StoreInfo { + /** + * 商户侧门店编号 + */ + private String id; + /** + * 商户侧门店名称 + */ + private String name; + /** + * 地区编码,详细请见省市区编号对照表。 + * https://pay.weixin.qq.com/wiki/doc/apiv3/terms_definition/chapter1_1_3.shtml#part-5 + */ + @JSONField(name = "area_code") + private String areaCode; + /** + * 详细的商户门店地址 + */ + private String address; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAreaCode() { + return areaCode; + } + + public void setAreaCode(String areaCode) { + this.areaCode = areaCode; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/SubOrder.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/SubOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..5a579d890c07acba7a9ba9afeffc83c5c1e8e192 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/SubOrder.java @@ -0,0 +1,172 @@ +package com.egzosn.pay.wx.v3.bean.order; + +import java.util.Date; +import java.util.List; + +import com.alibaba.fastjson.annotation.JSONField; +import com.egzosn.pay.wx.v3.bean.WxTransactionType; +import com.egzosn.pay.wx.v3.bean.combine.CombineAmount; +import com.egzosn.pay.wx.v3.bean.combine.CombineSubOrder; +import com.egzosn.pay.wx.v3.bean.response.order.PromotionDetail; +import com.egzosn.pay.wx.v3.bean.response.order.TradeState; + +/** + * 子单信息,最多50单. + * + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/10/5
+ * 
+ */ +public class SubOrder extends CombineSubOrder { + /** + * 合单支付订单金额信息,必填。 + */ + private CombineAmount amount; + + + /** + * 商品描述,必填,需传入应用市场上的APP名字-实际商品名称,例如:天天爱消除-游戏充值。 + */ + private String description; + + + + + /** + * 结算信息,选填 + */ + @JSONField(name = "settle_info") + private SettleInfo settleInfo; + + + /** + * 银行类型,采用字符串类型的银行标识。 + * 银行标识请参考 《银行类型对照表》 + * 示例值:CMC + */ + @JSONField(name = "bank_type") + private String bankType; + + /** + * 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用 + */ + private String attach; + + /** + * 支付完成时间|| 退款完成时间,遵循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秒。 + * 示例值:2018-06-08T10:34:56+08:00 + */ + @JSONField(name = "success_time", format = "yyyy-MM-dd'T'HH:mm:ssXXX") + private Date successTime; + + /** + * 交易状态 + */ + @JSONField(name = "trade_state") + private TradeState tradeState; + + /** + * 交易类型 + */ + @JSONField(name = "trade_type") + private WxTransactionType tradeType; + + /** + * 微信支付侧订单号 + */ + @JSONField(name = "transaction_id") + private String transactionId; + + /** + * 优惠功能,子单有核销优惠券时有返回 + */ + @JSONField(name = "promotion_detail") + private List promotionDetail; + + public CombineAmount getAmount() { + return amount; + } + + public void setAmount(CombineAmount amount) { + this.amount = amount; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + + + public SettleInfo getSettleInfo() { + return settleInfo; + } + + public void setSettleInfo(SettleInfo settleInfo) { + this.settleInfo = settleInfo; + } + + public String getBankType() { + return bankType; + } + + public void setBankType(String bankType) { + this.bankType = bankType; + } + + public String getAttach() { + return attach; + } + + public void setAttach(String attach) { + this.attach = attach; + } + + public Date getSuccessTime() { + return successTime; + } + + public void setSuccessTime(Date successTime) { + this.successTime = successTime; + } + + public TradeState getTradeState() { + return tradeState; + } + + public void setTradeState(TradeState tradeState) { + this.tradeState = tradeState; + } + + public WxTransactionType getTradeType() { + return tradeType; + } + + public void setTradeType(WxTransactionType tradeType) { + this.tradeType = tradeType; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } + + public List getPromotionDetail() { + return promotionDetail; + } + + public void setPromotionDetail(List promotionDetail) { + this.promotionDetail = promotionDetail; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/CancelOrder.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/CancelOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..bac21da5ed0e7232e05053a686fb74a0ce30ea9b --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/CancelOrder.java @@ -0,0 +1,16 @@ +package com.egzosn.pay.wx.v3.bean.payscore; + +import com.egzosn.pay.common.bean.AssistOrder; + +public class CancelOrder extends AssistOrder { + + private String reason; + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/CompleteOrder.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/CompleteOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..3f705124b5686cea31b9c0399cea0a1dfd90cd5f --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/CompleteOrder.java @@ -0,0 +1,39 @@ +package com.egzosn.pay.wx.v3.bean.payscore; + +import com.alibaba.fastjson.annotation.JSONField; + +import java.math.BigDecimal; +import java.util.List; + +public class CompleteOrder extends CreateOrder { + + private BigDecimal totalAmount; + @JSONField(name="post_payments") + private List postPayments; + + private Boolean profitSharing = false; + + public BigDecimal getTotalAmount() { + return totalAmount; + } + + public void setTotalAmount(BigDecimal totalAmount) { + this.totalAmount = totalAmount; + } + + public List getPostPayments() { + return postPayments; + } + + public void setPostPayments(List postPayments) { + this.postPayments = postPayments; + } + + public Boolean getProfitSharing() { + return profitSharing; + } + + public void setProfitSharing(Boolean profitSharing) { + this.profitSharing = profitSharing; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/CreateOrder.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/CreateOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..68dd755f062af56ce102e5305eaa815a202d599a --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/CreateOrder.java @@ -0,0 +1,95 @@ +package com.egzosn.pay.wx.v3.bean.payscore; + +import com.egzosn.pay.common.bean.AssistOrder; + +import java.math.BigDecimal; + +public class CreateOrder extends AssistOrder { + + private String openId; + + private String serviceIntroduction; + + /** + * 服务开始时间 + * 支持三种格式:yyyyMMddHHmmss、yyyyMMdd和OnAccept + */ + private String startTime; + + private String endTime; + + + private String riskFundName; + + private BigDecimal riskFundAmount; + + private String attach; + + + private Boolean needUserConfirm; + + + public String getOpenId() { + return openId; + } + + public void setOpenId(String openId) { + this.openId = openId; + } + + public String getServiceIntroduction() { + return serviceIntroduction; + } + + public void setServiceIntroduction(String serviceIntroduction) { + this.serviceIntroduction = serviceIntroduction; + } + + public String getStartTime() { + return startTime; + } + + public void setStartTime(String startTime) { + this.startTime = startTime; + } + + public String getEndTime() { + return endTime; + } + + public void setEndTime(String endTime) { + this.endTime = endTime; + } + + public String getRiskFundName() { + return riskFundName; + } + + public void setRiskFundName(String riskFundName) { + this.riskFundName = riskFundName; + } + + public BigDecimal getRiskFundAmount() { + return riskFundAmount; + } + + public void setRiskFundAmount(BigDecimal riskFundAmount) { + this.riskFundAmount = riskFundAmount; + } + + public String getAttach() { + return attach; + } + + public void setAttach(String attach) { + this.attach = attach; + } + + public Boolean getNeedUserConfirm() { + return needUserConfirm; + } + + public void setNeedUserConfirm(Boolean needUserConfirm) { + this.needUserConfirm = needUserConfirm; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/ModifyOrder.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/ModifyOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..3edc0864aa21d17c240c43271dda8cba94cd4496 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/ModifyOrder.java @@ -0,0 +1,42 @@ +package com.egzosn.pay.wx.v3.bean.payscore; + +import com.alibaba.fastjson.annotation.JSONField; +import com.egzosn.pay.common.bean.AssistOrder; + +import java.math.BigDecimal; +import java.util.List; + +public class ModifyOrder extends AssistOrder { + + + private BigDecimal totalAmount; + + @JSONField(name="post_payments") + private List postPayments; + + private String reason; + + public BigDecimal getTotalAmount() { + return totalAmount; + } + + public void setTotalAmount(BigDecimal totalAmount) { + this.totalAmount = totalAmount; + } + + public List getPostPayments() { + return postPayments; + } + + public void setPostPayments(List postPayments) { + this.postPayments = postPayments; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/PostPayment.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/PostPayment.java new file mode 100644 index 0000000000000000000000000000000000000000..288266cab2256b2d65343fc370191a12d482dd4d --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/PostPayment.java @@ -0,0 +1,59 @@ +package com.egzosn.pay.wx.v3.bean.payscore; + +import java.io.Serializable; +import java.math.BigDecimal; + +public class PostPayment implements Serializable { + + /** + * 付费项目名称 + */ + private String name; + + /** + * 金额 + */ + private BigDecimal amount; + + /** + * 说明 + */ + private String description; + + /** + * 数量 + */ + private Integer count; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public BigDecimal getAmount() { + return amount; + } + + public void setAmount(BigDecimal amount) { + this.amount = amount; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Integer getCount() { + return count; + } + + public void setCount(Integer count) { + this.count = count; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/RiskFund.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/RiskFund.java new file mode 100644 index 0000000000000000000000000000000000000000..d432948778b9f705bfffaf7cfaeac84d1e60c7b0 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/RiskFund.java @@ -0,0 +1,48 @@ +package com.egzosn.pay.wx.v3.bean.payscore; + +import com.egzosn.pay.common.util.Util; + +import java.io.Serializable; +import java.math.BigDecimal; + +public class RiskFund implements Serializable { + + /** + * 风险金名称 + */ + private String name; + + /** + * 风险金额 + */ + private BigDecimal amount; + + /** + * 风险说明 + */ + private String description; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAmount() { + return Util.conversionCentAmount(amount); + } + + public void setAmount(BigDecimal amount) { + this.amount = amount; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/SyncOrder.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/SyncOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..333fd3c6ef9ee95a2ab3a04b6a683b6ff0567025 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/SyncOrder.java @@ -0,0 +1,18 @@ +package com.egzosn.pay.wx.v3.bean.payscore; + +import com.egzosn.pay.common.bean.AssistOrder; + +import java.util.Date; + +public class SyncOrder extends AssistOrder { + + private Date paidTime; + + public Date getPaidTime() { + return paidTime; + } + + public void setPaidTime(Date paidTime) { + this.paidTime = paidTime; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/TimeRange.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/TimeRange.java new file mode 100644 index 0000000000000000000000000000000000000000..02d466a391042b5bdf845a47d2b44c1d3dab49ed --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/TimeRange.java @@ -0,0 +1,30 @@ +package com.egzosn.pay.wx.v3.bean.payscore; + +import com.alibaba.fastjson.annotation.JSONField; + +import java.io.Serializable; + +public class TimeRange implements Serializable { + + @JSONField(name = "start_time") + private String startTime; + + @JSONField(name = "end_time") + private String endTime; + + public String getStartTime() { + return startTime; + } + + public void setStartTime(String startTime) { + this.startTime = startTime; + } + + public String getEndTime() { + return endTime; + } + + public void setEndTime(String endTime) { + this.endTime = endTime; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/Resource.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/Resource.java new file mode 100644 index 0000000000000000000000000000000000000000..a6910dea13d511cd7a81310105291aa23e1f3047 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/Resource.java @@ -0,0 +1,80 @@ +package com.egzosn.pay.wx.v3.bean.response; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * 通知资源数据 + * json格式,见示例 + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/10/4
+ * 
+ */ +public class Resource { + + + /** + * 对开启结果数据进行加密的加密算法,目前只支持AEAD_AES_256_GCM。 + */ + private String algorithm; + /** + * Base64编码后的开启/停用结果数据密文。 + */ + private String ciphertext; + /** + * 附加数据。 + */ + @JSONField(name = "associated_data") + private String associatedData; + + /** + * 原始回调类型。 + */ + @JSONField(name = "original_type") + private String originalType; + /** + * 加密使用的随机串。 + */ + private String nonce; + + public String getAlgorithm() { + return algorithm; + } + + public void setAlgorithm(String algorithm) { + this.algorithm = algorithm; + } + + public String getCiphertext() { + return ciphertext; + } + + public void setCiphertext(String ciphertext) { + this.ciphertext = ciphertext; + } + + public String getAssociatedData() { + return associatedData; + } + + public void setAssociatedData(String associatedData) { + this.associatedData = associatedData; + } + + public String getOriginalType() { + return originalType; + } + + public void setOriginalType(String originalType) { + this.originalType = originalType; + } + + public String getNonce() { + return nonce; + } + + public void setNonce(String nonce) { + this.nonce = nonce; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/WxNoticeParams.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/WxNoticeParams.java new file mode 100644 index 0000000000000000000000000000000000000000..9c7ab19b8fe537ba65a99f038e24fabb8a32193d --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/WxNoticeParams.java @@ -0,0 +1,108 @@ +package com.egzosn.pay.wx.v3.bean.response; + +import com.alibaba.fastjson.annotation.JSONField; +import com.egzosn.pay.common.bean.NoticeParams; + +/** + * 微信通知参数 + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2021/10/4
+ *
+ */ +public class WxNoticeParams extends NoticeParams { + + + /** + * 通知的唯一ID + * 示例值:EV-2018022511223320873 + */ + private String id; + + /** + *通知创建的时间,遵循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秒。 + * 示例值:2018-06-08T10:34:56+08:00 + */ + @JSONField(name = "create_time") + private String createTime; + /** + * 通知的类型: + * TRANSACTION.SUCCESS 支付成功通知 + * REFUND.SUCCESS:退款成功通知 + * REFUND.ABNORMAL:退款异常通知 + * REFUND.CLOSED:退款关闭通知 + * 示例值:REFUND.SUCCESS + */ + @JSONField(name = "event_type") + private String eventType; + + + + /** + * 通知的资源数据类型,支付成功通知为encrypt-resource + * 示例值:encrypt-resource + */ + @JSONField(name = "resource_type") + private String resourceType; + + /** + * 通知资源数据 + * json格式,见示例 + */ + private Resource resource; + /** + * 通知简要说明 + * 示例值:退款成功 + * 示例值:支付成功 + */ + private String summary; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getCreateTime() { + return createTime; + } + + public void setCreateTime(String createTime) { + this.createTime = createTime; + } + + public String getEventType() { + return eventType; + } + + public void setEventType(String eventType) { + this.eventType = eventType; + } + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public String getResourceType() { + return resourceType; + } + + public void setResourceType(String resourceType) { + this.resourceType = resourceType; + } + + public Resource getResource() { + return resource; + } + + public void setResource(Resource resource) { + this.resource = resource; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/WxPayMessage.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/WxPayMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..2dccdeb4d8ffc442cfa419805732cfb2c54e9cbb --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/WxPayMessage.java @@ -0,0 +1,370 @@ +package com.egzosn.pay.wx.v3.bean.response; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.annotation.JSONField; +import com.egzosn.pay.common.bean.PayMessage; +import com.egzosn.pay.wx.v3.bean.WxTransactionType; +import com.egzosn.pay.wx.v3.bean.order.SceneInfo; +import com.egzosn.pay.wx.v3.bean.response.order.Amount; +import com.egzosn.pay.wx.v3.bean.response.order.Payer; +import com.egzosn.pay.wx.v3.bean.response.order.PromotionDetail; +import com.egzosn.pay.wx.v3.bean.response.order.TradeState; + +/** + * 支付回调消息,兼容退款回调 + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/10/4
+ * 
+ */ +public class WxPayMessage extends PayMessage { + + + + + /** + * 直连模式应用ID,服务商模式请解析spAppid + */ + private String appid; + /** + * 直连模式商户号,服务商模式请解析spMchid + */ + private String mchid; + /** + * 服务商模式服务商APPID + */ + @JSONField(name = "sp_appid") + private String spAppid; + /** + * 服务商模式服务商户号 + */ + @JSONField(name = "sp_mchid") + private String spMchid; + /** + * 服务商模式-子商户appid + */ + @JSONField(name = "sub_appid") + private String subAppid; + /** + * 服务商模式-子商户商户id + */ + @JSONField(name = "sub_mchid") + private String subMchid; + + /** + * 商户订单号 + * 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一。 + * 示例值:1217752501201407033233368018 + */ + @JSONField(name = "out_trade_no") + private String outTradeNo; + + /** + * 微信支付订单号 + * 微信支付系统生成的订单号。 + * 示例值:1217752501201407033233368018 + */ + @JSONField(name = "transaction_id") + private String transactionId; + + /** + * 交易类型,枚举值: + * JSAPI:公众号支付 + * NATIVE:扫码支付 + * APP:APP支付 + * MICROPAY:付款码支付 + * MWEB:H5支付 + * FACEPAY:刷脸支付 + * 示例值:MICROPAY + */ + @JSONField(name = "trade_type") + private WxTransactionType tradeType; + + + /** + * 交易状态,枚举值: + * SUCCESS:支付成功 + * REFUND:转入退款 + * NOTPAY:未支付 + * CLOSED:已关闭 + * REVOKED:已撤销(付款码支付) + * USERPAYING:用户支付中(付款码支付) + * PAYERROR:支付失败(其他原因,如银行返回失败) + */ + @JSONField(name = "trade_state") + private TradeState tradeState; + /** + * 商户退款单号 + */ + @JSONField(name = "out_refund_no") + private String outRefundNo; + /** + * 微信退款单号 + */ + @JSONField(name = "refund_id") + private String refundId; + /** + * 退款状态,枚举值: + * SUCCESS:退款成功 + * CLOSE:退款关闭 + * ABNORMAL: 退款异常,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往【商户平台—交易中心】,手动处理此笔退款 + * 示例值:SUCCESS + */ + @JSONField(name = "refund_status") + private TradeState refundStatus; + /** + * 退款入账账户 + * 取当前退款单的退款入账方。 + * 1、退回银行卡:{银行名称}{卡类型}{卡尾号} + * 2、退回支付用户零钱: 支付用户零钱 + * 3、退还商户: 商户基本账户、商户结算银行账户 + * 4、退回支付用户零钱通:支付用户零钱通 + * 示例值:招商银行信用卡0403 + */ + @JSONField(name = "user_received_account") + private String userReceivedAccount; + /** + * 交易状态描述 + * 示例值:支付成功 + */ + @JSONField(name = "trade_state_desc") + private String tradeStateDesc; + /** + * 银行类型,采用字符串类型的银行标识。 + * 银行标识请参考 《银行类型对照表》 + * 示例值:CMC + */ + @JSONField(name = "bank_type") + private String bankType; + + /** + * 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用 + */ + private String attach; + + /** + * 支付完成时间|| 退款完成时间,遵循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秒。 + * 示例值:2018-06-08T10:34:56+08:00 + */ + @JSONField(name = "success_time", format = "yyyy-MM-dd'T'HH:mm:ssXXX") + private Date successTime; + /** + * 支付者信息 + */ + private Payer payer; + + + /** + * 订单金额 + */ + private Amount amount; + /** + * 支付场景信息描述 + */ + @JSONField(name = "scene_info") + private SceneInfo sceneInfo; + + /** + * 优惠功能,享受优惠时返回该字段。 + */ + @JSONField(name = "promotion_detail") + private List promotionDetail; + + + public String getAppid() { + return appid; + } + + public void setAppid(String appid) { + this.appid = appid; + } + + public String getMchid() { + return mchid; + } + + public void setMchid(String mchid) { + this.mchid = mchid; + } + + public String getSpAppid() { + return spAppid; + } + + public void setSpAppid(String spAppid) { + this.spAppid = spAppid; + } + + public String getSpMchid() { + return spMchid; + } + + public void setSpMchid(String spMchid) { + this.spMchid = spMchid; + } + + public String getSubAppid() { + return subAppid; + } + + public void setSubAppid(String subAppid) { + this.subAppid = subAppid; + } + + public String getSubMchid() { + return subMchid; + } + + public void setSubMchid(String subMchid) { + this.subMchid = subMchid; + } + + public String getOutTradeNo() { + return outTradeNo; + } + + public void setOutTradeNo(String outTradeNo) { + this.outTradeNo = outTradeNo; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } + + public WxTransactionType getTradeType() { + return tradeType; + } + + public void setTradeType(WxTransactionType tradeType) { + this.tradeType = tradeType; + } + + public TradeState getTradeState() { + return tradeState; + } + + public void setTradeState(TradeState tradeState) { + this.tradeState = tradeState; + } + + public String getTradeStateDesc() { + return tradeStateDesc; + } + + public void setTradeStateDesc(String tradeStateDesc) { + this.tradeStateDesc = tradeStateDesc; + } + + public String getBankType() { + return bankType; + } + + public void setBankType(String bankType) { + this.bankType = bankType; + } + + public String getAttach() { + return attach; + } + + public void setAttach(String attach) { + this.attach = attach; + } + + public Date getSuccessTime() { + return successTime; + } + + public void setSuccessTime(Date successTime) { + this.successTime = successTime; + } + + public Payer getPayer() { + return payer; + } + + public void setPayer(Payer payer) { + this.payer = payer; + } + + public Amount getAmount() { + return amount; + } + + public void setAmount(Amount amount) { + this.amount = amount; + } + + public SceneInfo getSceneInfo() { + return sceneInfo; + } + + public void setSceneInfo(SceneInfo sceneInfo) { + this.sceneInfo = sceneInfo; + } + + public List getPromotionDetail() { + return promotionDetail; + } + + public void setPromotionDetail(List promotionDetail) { + this.promotionDetail = promotionDetail; + } + + @Override + public BigDecimal getTotalFee() { + return BigDecimal.valueOf(getAmount().getTotal()); + } + + public String getOutRefundNo() { + return outRefundNo; + } + + public void setOutRefundNo(String outRefundNo) { + this.outRefundNo = outRefundNo; + } + + public String getRefundId() { + return refundId; + } + + public void setRefundId(String refundId) { + this.refundId = refundId; + } + + public TradeState getRefundStatus() { + return refundStatus; + } + + public void setRefundStatus(TradeState refundStatus) { + this.refundStatus = refundStatus; + } + + public String getUserReceivedAccount() { + return userReceivedAccount; + } + + public void setUserReceivedAccount(String userReceivedAccount) { + this.userReceivedAccount = userReceivedAccount; + } + + public static WxPayMessage create(Map message) { + WxPayMessage payMessage = new JSONObject(message).toJavaObject(WxPayMessage.class); +// payMessage.setPayType(""); + payMessage.setPayMessage(message); + return payMessage; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/WxRefundResult.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/WxRefundResult.java new file mode 100644 index 0000000000000000000000000000000000000000..fc9fb1920a5f61c2a0aef636bfb5caa07b48f024 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/WxRefundResult.java @@ -0,0 +1,324 @@ +package com.egzosn.pay.wx.v3.bean.response; + +import java.math.BigDecimal; +import java.util.Map; + +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.annotation.JSONField; +import com.egzosn.pay.common.bean.BaseRefundResult; +import com.egzosn.pay.common.bean.CurType; +import com.egzosn.pay.common.bean.DefaultCurType; +import com.egzosn.pay.common.util.Util; +import com.egzosn.pay.wx.v3.bean.order.RefundAmount; + +/** + * 微信退款结果 + * + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2020/8/16 21:29
+ * 
+ */ +public class WxRefundResult extends BaseRefundResult { + + /** + * 500 SYSTEM_ERROR 系统超时 请不要更换商户退款单号,请使用相同参数再次调用API。 + * 403 USER_ACCOUNT_ABNORMAL 退款请求失败 此状态代表退款申请失败,商户可自行处理退款。 + * 403 NOT_ENOUGH 余额不足 此状态代表退款申请失败,商户可根据具体的错误提示做相应的处理。 + * 400 PARAM_ERROR 参数错误 请求参数错误,请重新检查再调用申请退款接口 + * 404 MCH_NOT_EXISTS MCHID不存在 请检查MCHID是否正确 + * 404 RESOURCE_NOT_EXISTS 订单号不存在 请检查你的订单号是否正确且是否已支付,未支付的订单不能发起退款 + * 401 SIGN_ERROR 签名错误 请检查签名参数和方法是否都符合签名算法要求 + * 429 FREQUENCY_LIMITED 频率限制 该笔退款未受理,请降低频率后重试 + * 400 INVALID_REQUEST 请求参数符合参数格式,但不符合业务规则 此状态代表退款申请失败,商户可根据具体的错误提示做相应的处理。 + * 403 NO_AUTH 没有退款权限 此状态代表退款申请失败,请检查是否有该笔订单的退款权限 + */ + private String code; + /** + * 返回信息 + */ + private String message; + /** + * 微信退款单号 + */ + @JSONField(name = "refund_id") + private String refundId; + /** + * 商户退款单号 + */ + @JSONField(name = "out_refund_no") + private String outRefundNo; + /** + * 微信订单号 + */ + @JSONField(name = "transaction_id") + private String transactionId; + /** + * 商户订单号 + */ + @JSONField(name = "out_trade_no") + private String outTradeNo; + + + /** + * 退款渠道 + * 枚举值: + * ORIGINAL:原路退款 + * BALANCE:退回到余额 + * OTHER_BALANCE:原账户异常退到其他余额账户 + * OTHER_BANKCARD:原银行卡异常退到其他银行卡 + * 示例值:ORIGINAL + */ + private String channel; + + /** + * 退款入账账户 + * 取当前退款单的退款入账方,有以下几种情况: + * 1)退回银行卡:{银行名称}{卡类型}{卡尾号} + * 2)退回支付用户零钱:支付用户零钱 + * 3)退还商户:商户基本账户商户结算银行账户 + * 4)退回支付用户零钱通:支付用户零钱通 + * 示例值:招商银行信用卡0403 + */ + @JSONField(name = "user_received_account") + private String userReceivedAccount; + /** + * 退款成功时间 + * 退款成功时间,当退款状态为退款成功时有返回。 + * 示例值:2020-12-01T16:18:12+08:00 + */ + @JSONField(name = "success_time") + private String successTime; + /** + * 退款创建时间 + */ + @JSONField(name = "create_time") + private String createTime; + + /** + * 退款状态 + * 退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往服务商平台-交易中心,手动处理此笔退款。 + * 枚举值: + * SUCCESS:退款成功 + * CLOSED:退款关闭 + * PROCESSING:退款处理中 + * ABNORMAL:退款异常 + * 示例值:SUCCESS + */ + private String status; + + /** + * 资金账户 + * 退款所使用资金对应的资金账户类型 + * 枚举值: + * UNSETTLED : 未结算资金 + * AVAILABLE : 可用余额 + * UNAVAILABLE : 不可用余额 + * OPERATION : 运营户 + * BASIC : 基本账户(含可用余额和不可用余额) + */ + @JSONField(name = "funds_account") + private String fundsAccount; + + /** + * 金额详细信息 + */ + private RefundAmount amount; + + /** + * 获取退款请求结果状态码 + * + * @return 状态码 + */ + @Override + public String getCode() { + return code; + } + + /** + * 获取退款请求结果状态提示信息 + * + * @return 提示信息 + */ + @Override + public String getMsg() { + return message; + } + + /** + * 返回业务结果状态码 + * + * @return 业务结果状态码 + */ + @Override + public String getResultCode() { + return status; + } + + /** + * 返回业务结果状态提示信息 + * + * @return 业务结果状态提示信息 + */ + @Override + public String getResultMsg() { + return message; + } + + /** + * 退款金额, 金额元 + * + * @return 退款金额 + */ + @Override + public BigDecimal getRefundFee() { + + return new BigDecimal(amount.getRefund()).divide(Util.HUNDRED, 2, BigDecimal.ROUND_HALF_UP); + } + + /** + * 退款币种信息 + * + * @return 币种信息 + */ + @Override + public CurType getRefundCurrency() { + return DefaultCurType.valueOf(amount.getCurrency()); + } + + /** + * 支付平台交易号 + * 发起支付时 支付平台(如支付宝)返回的交易订单号 + * + * @return 支付平台交易号 + */ + @Override + public String getTradeNo() { + return transactionId; + } + + /** + * 支付订单号 + * 发起支付时,用户系统的订单号 + * + * @return 支付订单号 + */ + @Override + public String getOutTradeNo() { + return outTradeNo; + } + + /** + * 商户退款单号 + * + * @return 商户退款单号 + */ + @Override + public String getRefundNo() { + return outRefundNo; + } + + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } + + public void setOutTradeNo(String outTradeNo) { + this.outTradeNo = outTradeNo; + } + + public String getOutRefundNo() { + return outRefundNo; + } + + public void setOutRefundNo(String outRefundNo) { + this.outRefundNo = outRefundNo; + } + + public String getRefundId() { + return refundId; + } + + public void setRefundId(String refundId) { + this.refundId = refundId; + } + + public void setCode(String code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getChannel() { + return channel; + } + + public void setChannel(String channel) { + this.channel = channel; + } + + public String getUserReceivedAccount() { + return userReceivedAccount; + } + + public void setUserReceivedAccount(String userReceivedAccount) { + this.userReceivedAccount = userReceivedAccount; + } + + public String getSuccessTime() { + return successTime; + } + + public void setSuccessTime(String successTime) { + this.successTime = successTime; + } + + public String getCreateTime() { + return createTime; + } + + public void setCreateTime(String createTime) { + this.createTime = createTime; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getFundsAccount() { + return fundsAccount; + } + + public void setFundsAccount(String fundsAccount) { + this.fundsAccount = fundsAccount; + } + + public RefundAmount getAmount() { + return amount; + } + + public void setAmount(RefundAmount amount) { + this.amount = amount; + } + + public static final WxRefundResult create(Map result) { + WxRefundResult refundResult = new JSONObject(result).toJavaObject(WxRefundResult.class); + refundResult.setAttrs(result); + return refundResult; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/Amount.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/Amount.java new file mode 100644 index 0000000000000000000000000000000000000000..b134a8c6c62749ca0094bf5ec2b47d29b62f2554 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/Amount.java @@ -0,0 +1,71 @@ +package com.egzosn.pay.wx.v3.bean.response.order; + +import java.math.BigDecimal; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * 回调中的订单金额信息 + * + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/10/4
+ * 
+ */ +public class Amount extends com.egzosn.pay.wx.v3.bean.order.Amount { + + /** + * 用户支付金额,单位为分。 + */ + @JSONField(name = "payer_total") + private BigDecimal payerTotal; + /** + * 用户支付币种 + */ + @JSONField(name = "payer_currency") + private String payerCurrency; + + /** + * 退款金额,单位为分 + */ + private Integer refund; + + /** + * 退款给用户的金额,单位为分,不包含所有优惠券金额 + */ + @JSONField(name = "payer_refund") + private Integer payerRefund; + + public BigDecimal getPayerTotal() { + return payerTotal; + } + + public void setPayerTotal(BigDecimal payerTotal) { + this.payerTotal = payerTotal; + } + + public String getPayerCurrency() { + return payerCurrency; + } + + public void setPayerCurrency(String payerCurrency) { + this.payerCurrency = payerCurrency; + } + + public Integer getRefund() { + return refund; + } + + public void setRefund(Integer refund) { + this.refund = refund; + } + + public Integer getPayerRefund() { + return payerRefund; + } + + public void setPayerRefund(Integer payerRefund) { + this.payerRefund = payerRefund; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/GoodsDetail.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/GoodsDetail.java new file mode 100644 index 0000000000000000000000000000000000000000..d48675cf643d24ef6707f2b9aa89e48bbcd6be09 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/GoodsDetail.java @@ -0,0 +1,81 @@ +package com.egzosn.pay.wx.v3.bean.response.order; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * 单品列表信息 + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/10/4
+ * 
+ */ +public class GoodsDetail { + + /** + * 商品编码 + */ + @JSONField(name = "goods_id") + private String goodsId; + /** + * 商品数量 + */ + @JSONField(name = "quantity") + private Long quantity; + /** + * 商品单价 + */ + @JSONField(name = "unit_price") + private Long unitPrice; + /** + * 商品优惠金额,单位【分】 + */ + @JSONField(name = "discount_amount") + private Long discountAmount; + /** + * 商品备注 + */ + @JSONField(name = "goods_remark") + private String goodsRemark; + + + public String getGoodsId() { + return goodsId; + } + + public void setGoodsId(String goodsId) { + this.goodsId = goodsId; + } + + public Long getQuantity() { + return quantity; + } + + public void setQuantity(Long quantity) { + this.quantity = quantity; + } + + public Long getUnitPrice() { + return unitPrice; + } + + public void setUnitPrice(Long unitPrice) { + this.unitPrice = unitPrice; + } + + public Long getDiscountAmount() { + return discountAmount; + } + + public void setDiscountAmount(Long discountAmount) { + this.discountAmount = discountAmount; + } + + public String getGoodsRemark() { + return goodsRemark; + } + + public void setGoodsRemark(String goodsRemark) { + this.goodsRemark = goodsRemark; + } +} \ No newline at end of file diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/Payer.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/Payer.java new file mode 100644 index 0000000000000000000000000000000000000000..7f359f156eb44854617ea688b3c960430986393f --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/Payer.java @@ -0,0 +1,25 @@ +package com.egzosn.pay.wx.v3.bean.response.order; + +/** + * 支付者信息 + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/10/4
+ * 
+ */ +public class Payer { + /** + * 用户在直连商户appid下的唯一标识。 + * 使用合单appid获取的对应用户openid。是用户在商户appid下的唯一标识。 + */ + private String openid; + + public String getOpenid() { + return openid; + } + + public void setOpenid(String openid) { + this.openid = openid; + } +} \ No newline at end of file diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/PromotionDetail.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/PromotionDetail.java new file mode 100644 index 0000000000000000000000000000000000000000..2d6e6e63f68ffc6897846de6627eba89fbfec848 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/PromotionDetail.java @@ -0,0 +1,178 @@ +package com.egzosn.pay.wx.v3.bean.response.order; + +import java.util.List; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * 优惠功能 + * + *
+ * email egan@egzosn.com
+ * date 2021/10/6
+ * 
+ */ + +public class PromotionDetail { + /** + * 券ID + */ + @JSONField(name = "coupon_id") + private String couponId; + + /** + * 优惠名称 + */ + private String name; + + /** + * 优惠范围 + *
    + *
  • GLOBAL:全场代金券
  • + *
  • SINGLE:单品优惠
  • + *
+ * 示例值:GLOBAL + */ + private String scope; + + /** + * 优惠类型 + *
    + *
  • CASH:充值
  • + *
  • NOCASH:预充值
  • + *
+ * 示例值:CASH + */ + private String type; + /** + * 优惠券面额,单位【分】 + */ + private Long amount; + + + + /** + * 活动ID + */ + @JSONField(name = "stock_id") + private String stockId; + + /** + * 微信出资,单位为分 + */ + @JSONField(name = "wechatpay_contribute") + private Long wechatpayContribute; + + /** + * 商户出资,单位为分 + */ + @JSONField(name = "merchant_contribute") + private Long merchantContribute; + + /** + * 其他出资,单位为分 + */ + @JSONField(name = "other_contribute") + private Long otherContribute; + + /** + * 优惠币种, + * CNY:人民币,境内商户号仅支持人民币。 + * 示例值:CNY + */ + private String currency; + /** + * 单品列表信息 + */ + @JSONField(name = "goods_detail") + private List goodsDetail; + + + public String getCouponId() { + return couponId; + } + + public void setCouponId(String couponId) { + this.couponId = couponId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public Long getAmount() { + return amount; + } + + public void setAmount(Long amount) { + this.amount = amount; + } + + public String getStockId() { + return stockId; + } + + public void setStockId(String stockId) { + this.stockId = stockId; + } + + public Long getWechatpayContribute() { + return wechatpayContribute; + } + + public void setWechatpayContribute(Long wechatpayContribute) { + this.wechatpayContribute = wechatpayContribute; + } + + public Long getMerchantContribute() { + return merchantContribute; + } + + public void setMerchantContribute(Long merchantContribute) { + this.merchantContribute = merchantContribute; + } + + public Long getOtherContribute() { + return otherContribute; + } + + public void setOtherContribute(Long otherContribute) { + this.otherContribute = otherContribute; + } + + public String getCurrency() { + return currency; + } + + public void setCurrency(String currency) { + this.currency = currency; + } + + public List getGoodsDetail() { + return goodsDetail; + } + + public void setGoodsDetail(List goodsDetail) { + this.goodsDetail = goodsDetail; + } +} \ No newline at end of file diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/TradeState.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/TradeState.java new file mode 100644 index 0000000000000000000000000000000000000000..260c2ae6905fd34dad3d2cccf5960a51cb1df07b --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/TradeState.java @@ -0,0 +1,60 @@ +package com.egzosn.pay.wx.v3.bean.response.order; + +/** + * 微信侧返回交易状态,兼容退款状态 + * + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/10/4
+ * 
+ */ +public enum TradeState { + /** + * 支付成功 + * 退款成功 + */ + SUCCESS, + /** + * 转入退款 + */ + REFUND, + /** + * 未支付 + */ + NOTPAY, + /** + * 已关闭 + * 退款关闭 + */ + CLOSED, + /** + * 退款异常. + */ + ABNORMAL, + /** + * 已撤销(付款码支付) + */ + REVOKED, + /** + * 用户支付中(付款码支付) + */ + USERPAYING, + /** + * 支付失败(其他原因,如银行返回失败) + */ + PAYERROR, + /** + * 已接收,等待扣款 + */ + ACCEPT, + /** + * 如果请求返回为处理中,则商户可以通过调用回退结果查询接口获取请求的最终处理结果。如果查询到回退结果在处理中,请勿变更商户回退单号,使用相同的参数再次发起分账回退,否则会出现资金风险。在处理中状态的回退单如果5天没有成功,会因为超时被设置为已失败。 + * 处理中 + */ + PROCESSING, + /** + * 失败 + */ + FAILED; +} \ No newline at end of file diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ProfitSharingBillType.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ProfitSharingBillType.java new file mode 100644 index 0000000000000000000000000000000000000000..487e16ff713bface26677bc18fded8d9a44b3a64 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ProfitSharingBillType.java @@ -0,0 +1,73 @@ +package com.egzosn.pay.wx.v3.bean.sharing; + +import com.egzosn.pay.common.bean.BillType; + +/** + * 分账账单类型 + * + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/10/6
+ * 
+ */ +public enum ProfitSharingBillType implements BillType { + + /** + * 数据流 + */ + STREAM, + /** + * 返回格式为.gzip的压缩包账单 + */ + GZIP("GZIP"); + + ProfitSharingBillType() { + } + + ProfitSharingBillType(String type) { + this.type = type; + } + + private String type; + + /** + * 获取类型名称 + * + * @return 类型 + */ + @Override + public String getType() { + return type; + } + + /** + * 获取类型对应的日期格式化表达式 + * + * @return 日期格式化表达式 + */ + @Override + public String getDatePattern() { + return null; + } + + /** + * 获取文件类型 + * + * @return 文件类型 + */ + @Override + public String getFileType() { + return null; + } + + /** + * 自定义属性 + * + * @return 自定义属性 + */ + @Override + public String getCustom() { + return null; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ProfitSharingOrder.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ProfitSharingOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..4cbdabd3ea528871beb98f52d50aedf77b589ebe --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ProfitSharingOrder.java @@ -0,0 +1,106 @@ +package com.egzosn.pay.wx.v3.bean.sharing; + +import java.util.List; + +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.wx.v3.utils.WxConst; + +/** + * 服务商请求分账订单 + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/10/6
+ * 
+ */ +public class ProfitSharingOrder extends PayOrder { + + /** + * 子商户号,选填,服务商必填 + */ + private String subMchid; + + /** + * 子商户应用ID,选填 + *

+ * 分账接收方类型包含{@code PERSONAL_SUB_OPENID}时必填 + */ + private String subAppid; + + /** + * 分账接收方列表,选填 + *

+ * 可以设置出资商户作为分账接受方,最多可有50个分账接收方 + */ + private List receivers; + /** + * 是否解冻剩余未分资金,必填 + *

    + *
  1. 如果为{@code true},该笔订单剩余未分账的金额会解冻回分账方商户;
  2. + *
  3. 如果为{@code false},该笔订单剩余未分账的金额不会解冻回分账方商户,可以对该笔订单再次进行分账。
  4. + *
+ */ + private Boolean unfreezeUnsplit; + + public String getSubMchid() { + return subMchid; + } + + public void setSubMchid(String subMchid) { + this.subMchid = subMchid; + addAttr(WxConst.SUB_MCH_ID, subMchid); + } + + public String getSubAppid() { + return subAppid; + } + + public void setSubAppid(String subAppid) { + this.subAppid = subAppid; + addAttr(WxConst.SUB_APPID, subAppid); + } + + /** + * 微信支付订单号 + * @return 微信支付订单号 + */ + public String getTransactionId() { + return getTradeNo(); + } + + public void setTransactionId(String transactionId) { + setTradeNo(transactionId); + } + /** + * 商户分账单号,必填 + *

+ * 商户系统内部的分账单号,在商户系统内部唯一,同一分账单号多次请求等同一次。 + * 只能是数字、大小写字母_-|*@ + * @return 商户分账单号,必填 + */ + public String getOutOrderNo() { + return getOutTradeNo(); + } + + public void setOutOrderNo(String outOrderNo) { + setOutTradeNo(outOrderNo); + } + + public List getReceivers() { + return receivers; + } + + public void setReceivers(List receivers) { + this.receivers = receivers; + addAttr(WxConst.RECEIVERS, receivers); + } + + public Boolean getUnfreezeUnsplit() { + return unfreezeUnsplit; + } + + public void setUnfreezeUnsplit(Boolean unfreezeUnsplit) { + this.unfreezeUnsplit = unfreezeUnsplit; + addAttr(WxConst.UNFREEZE_UNSPLIT, unfreezeUnsplit); + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ProfitSharingPayMessage.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ProfitSharingPayMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..18ff526721bd0180c43ba075d73eb6925b213e31 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ProfitSharingPayMessage.java @@ -0,0 +1,119 @@ +package com.egzosn.pay.wx.v3.bean.sharing; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.annotation.JSONField; +import com.egzosn.pay.common.bean.PayMessage; + +/** + * 分账动账通知 + * @author Egan + *

+ * email egan@egzosn.com
+ * date 2021/10/4
+ * 
+ */ +public class ProfitSharingPayMessage extends PayMessage { + + /** + * 直连商户号. + *

+ * 直连模式分账发起和出资商户 + */ + private String mchid; + + /** + * 微信支付订单号 + */ + @JSONField(name = "transaction_id") + private String transactionId; + + /** + * 微信分账/回退单号. + */ + @JSONField(name = "order_id") + private String orderId; + + /** + * 商户分账/回退单号. + * 分账方系统内部的分账/回退单号 + */ + @JSONField(name = "out_order_no") + private String outOrderNo; + + /** + * 分账接收方. + *

+ * 分账接收方对象 + */ + private List receivers; + + /** + * 成功时间. + *

+ * Rfc3339标准 + */ + @JSONField(name = "success_time", format = "yyyy-MM-dd'T'HH:mm:ssXXX") + private Date successTime; + + + public String getMchid() { + return mchid; + } + + public void setMchid(String mchid) { + this.mchid = mchid; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } + + public String getOrderId() { + return orderId; + } + + public void setOrderId(String orderId) { + this.orderId = orderId; + } + + public String getOutOrderNo() { + return outOrderNo; + } + + public void setOutOrderNo(String outOrderNo) { + this.outOrderNo = outOrderNo; + } + + public List getReceivers() { + return receivers; + } + + public void setReceivers(List receivers) { + this.receivers = receivers; + } + + public Date getSuccessTime() { + return successTime; + } + + public void setSuccessTime(Date successTime) { + this.successTime = successTime; + } + + public static ProfitSharingPayMessage create(Map message) { + ProfitSharingPayMessage payMessage = new JSONObject(message).toJavaObject(ProfitSharingPayMessage.class); +// payMessage.setPayType(""); + payMessage.setPayMessage(message); + return payMessage; + } + + +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ProfitSharingReturnOrder.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ProfitSharingReturnOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..8252120bd9671941b00bd34418e93b438457c0bb --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ProfitSharingReturnOrder.java @@ -0,0 +1,29 @@ +package com.egzosn.pay.wx.v3.bean.sharing; + +import com.egzosn.pay.common.bean.RefundOrder; +import com.egzosn.pay.wx.v3.utils.WxConst; + +/** + * 分账回退订单 + * @author Egan + *

+ * email egan@egzosn.com
+ * date 2021/10/6
+ * 
+ */ +public class ProfitSharingReturnOrder extends RefundOrder { + + /** + * 子商户号,选填,服务商必填 + */ + private String subMchid; + + public String getSubMchid() { + return subMchid; + } + + public void setSubMchid(String subMchid) { + this.subMchid = subMchid; + addAttr(WxConst.SUB_MCH_ID, subMchid); + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/Receiver.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/Receiver.java new file mode 100644 index 0000000000000000000000000000000000000000..cd62f342d90edcd25f103d40d3838915b6eb83e0 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/Receiver.java @@ -0,0 +1,84 @@ +package com.egzosn.pay.wx.v3.bean.sharing; + +/** + * 分账接收方信息 + * + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/10/6
+ * 
+ */ + +public class Receiver { + /** + * 分账接收方类型,必填 + */ + private ReceiverType type; + /** + * 分账接收方帐号,必填 + */ + private String account; + /** + * 分账个人接收方姓名,选填 + *

+ * 在接收方类型为个人的时可选填,若有值,会检查与 name 是否实名匹配,不匹配会拒绝分账请求 + *

    + *
  1. 分账接收方类型是{@code PERSONAL_OPENID},是个人姓名的密文(选传,传则校验) 此字段的加密方法详见:敏感信息加密说明
  2. + *
  3. 使用微信支付平台证书中的公钥
  4. + *
  5. 使用RSAES-OAEP算法进行加密
  6. + *
  7. 将请求中HTTP头部的Wechatpay-Serial设置为证书序列号
  8. + *
+ */ + private String name; + /** + * 分账金额,必填 + *

+ * 单位为分,只能为整数,不能超过原订单支付金额及最大分账比例金额 + */ + private Integer amount; + /** + * 分账的原因描述,必填。分账账单中需要体现 + */ + private String description; + + public ReceiverType getType() { + return type; + } + + public void setType(ReceiverType type) { + this.type = type; + } + + public String getAccount() { + return account; + } + + public void setAccount(String account) { + this.account = account; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAmount() { + return amount; + } + + public void setAmount(Integer amount) { + this.amount = amount; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ReceiverType.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ReceiverType.java new file mode 100644 index 0000000000000000000000000000000000000000..00e73c35a490c9e1d4721b82efc800a9bccdb124 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ReceiverType.java @@ -0,0 +1,26 @@ +package com.egzosn.pay.wx.v3.bean.sharing; + + +/** + * 分账接收方类型 + * + * @author Egan + *

+ * email egan@egzosn.com
+ * date 2021/10/6
+ * 
+ */ +public enum ReceiverType { + /** + * 商户号 + */ + MERCHANT_ID, + /** + * 个人openid(由父商户APPID转换得到) + */ + PERSONAL_OPENID, + /** + * 个人sub_openid(由子商户APPID转换得到),服务商模式 + */ + PERSONAL_SUB_OPENID +} \ No newline at end of file diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ReceiversOrder.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ReceiversOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..d435611cc37b58b42a9a3706e7be04c61070ffbe --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ReceiversOrder.java @@ -0,0 +1,120 @@ +package com.egzosn.pay.wx.v3.bean.sharing; + +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.wx.v3.utils.WxConst; + +/** + * 添加分账接收方 + * + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/10/6
+ * 
+ */ +public class ReceiversOrder extends PayOrder { + + /** + * 子商户号,选填 + */ + private String subMchid; + /** + * 子商户应用ID,选填 + *

+ * 分账接收方类型包含{@code PERSONAL_SUB_OPENID}时必填 + */ + private String subAppid; + /** + * 分账接收方类型,必填 + */ + private ReceiverType type; + /** + * 分账接收方帐号,必填 + */ + private String account; + /** + * 分账个人接收方姓名,选填 + *

+ * 分账接收方类型是{@code MERCHANT_ID}时,是商户全称(必传),当商户是小微商户或个体户时,是开户人姓名 分账接收方类型是{@code PERSONAL_OPENID}时,是个人姓名(选传,传则校验) + *

    + *
  1. 分账接收方类型是{@code PERSONAL_OPENID},是个人姓名的密文(选传,传则校验) 此字段的加密方法详见:敏感信息加密说明
  2. + *
  3. 使用微信支付平台证书中的公钥
  4. + *
  5. 使用RSAES-OAEP算法进行加密
  6. + *
  7. 将请求中HTTP头部的Wechatpay-Serial设置为证书序列号
  8. + *
+ */ + private String name; + /** + * 与分账方的关系类型,必填 + */ + private RelationType relationType; + /** + * 自定义的分账关系,选填 + */ + private String customRelation; + + public String getSubMchid() { + return subMchid; + } + + public void setSubMchid(String subMchid) { + this.subMchid = subMchid; + addAttr(WxConst.SUB_MCH_ID, subMchid); + } + + public String getSubAppid() { + return subAppid; + } + + public void setSubAppid(String subAppid) { + this.subAppid = subAppid; + addAttr(WxConst.SUB_APPID, subAppid); + } + + public ReceiverType getType() { + return type; + } + + public void setType(ReceiverType type) { + this.type = type; + addAttr(WxConst.TYPE, type); + } + + public String getAccount() { + return account; + } + + public void setAccount(String account) { + this.account = account; + addAttr(WxConst.ACCOUNT, account); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + addAttr(WxConst.NAME, name); + } + + public RelationType getRelationType() { + return relationType; + } + + public void setRelationType(RelationType relationType) { + this.relationType = relationType; + addAttr(WxConst.RELATION_TYPE, relationType); + } + + public String getCustomRelation() { + return customRelation; + } + + public void setCustomRelation(String customRelation) { + this.customRelation = customRelation; + addAttr(WxConst.CUSTOM_RELATION, customRelation); + } + + +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/RelationType.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/RelationType.java new file mode 100644 index 0000000000000000000000000000000000000000..15118928979e996c4d2dfc7be3298a709b99d286 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/RelationType.java @@ -0,0 +1,53 @@ +package com.egzosn.pay.wx.v3.bean.sharing; + +/** + * 子商户与接收方的关系 + * + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/10/6
+ * 
+ */ +public enum RelationType { + /** + * 门店. + */ + STORE, + /** + * 员工. + */ + STAFF, + /** + * 店主. + */ + STORE_OWNER, + /** + * 合作伙伴. + */ + PARTNER, + /** + * 总部. + */ + HEADQUARTER, + /** + * 品牌方. + */ + BRAND, + /** + * 分销商. + */ + DISTRIBUTOR, + /** + * 用户. + */ + USER, + /** + * 供应商. + */ + SUPPLIER, + /** + * 自定义. + */ + CUSTOM + } \ No newline at end of file diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/WxProfitSharingReturnResult.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/WxProfitSharingReturnResult.java new file mode 100644 index 0000000000000000000000000000000000000000..8f51a5d005768349174212c84a8cbe912b5c9321 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/WxProfitSharingReturnResult.java @@ -0,0 +1,304 @@ +package com.egzosn.pay.wx.v3.bean.sharing; + +import java.math.BigDecimal; +import java.util.Map; + +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.annotation.JSONField; +import com.egzosn.pay.common.bean.BaseRefundResult; +import com.egzosn.pay.common.bean.CurType; +import com.egzosn.pay.wx.v3.bean.response.order.TradeState; +import com.egzosn.pay.wx.v3.utils.WxConst; + +/** + * 微信退款结果 + * + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/10/6
+ * 
+ */ +public class WxProfitSharingReturnResult extends BaseRefundResult { + + /** + * 分账回退的接收商户,对应原分账出资的分账方商户,填写微信支付分配的商户号 + * 直连商户不用传二级商户号。 + */ + @JSONField(name = "sub_mchid") + private String subMchid; + + /** + * 微信分账单号,微信系统返回的唯一标识。 + * 示例值:3008450740201411110007820472 + */ + @JSONField(name = "order_id") + private String orderId; + /** + * 商户分账单号 + * 商户系统内部的分账单号,在商户系统内部唯一,同一分账单号多次请求等同一次。 取值范围:[0-9a-zA-Z_*@-] + * 示例值:P20150806125346 + */ + @JSONField(name = WxConst.OUT_ORDER_NO) + private String outOrderNo; + /** + * 此回退单号是商户在自己后台生成的一个新的回退单号,在商户后台唯一 + * 示例值:R20190516001 + */ + @JSONField(name = "out_return_no") + private String outReturnNo; + /** + * 微信分账回退单号,微信系统返回的唯一标识 + * 示例值:3008450740201411110007820472 + */ + @JSONField(name = "return_id") + private String returnId; + /** + * 回退商户号 + * 只能对原分账请求中成功分给商户接收方进行回退 + * 示例值:86693852 + */ + @JSONField(name = "return_mchid") + private String returnMchid; + + + /** + * 回退金额 + * 需要从分账接收方回退的金额,单位为分,只能为整数 + * 示例值:10 + */ + private Integer amount; + + /** + * 分账回退的原因描述 + * 示例值:用户退款 + */ + private String description; + /** + 如果请求返回为处理中,则商户可以通过调用回退结果查询接口获取请求的最终处理结果。如果查询到回退结果在处理中,请勿变更商户回退单号,使用相同的参数再次发起分账回退,否则会出现资金风险。在处理中状态的回退单如果5天没有成功,会因为超时被设置为已失败。 + 枚举值: + PROCESSING:处理中 + SUCCESS:已成功 + FAILED:已失败 + 示例值:SUCCESS + */ + private TradeState result; + /** + * 失败原因。包含以下枚举值: + * ACCOUNT_ABNORMAL : 分账接收方账户异常 + * TIME_OUT_CLOSED : 超时关单 + * 示例值:TIME_OUT_CLOSED + */ + @JSONField(name = "fail_reason") + private String failReason; + + /** + * 分账回退创建时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss.sss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss.sss表示时分秒毫秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35.120+08:00表示,北京时间2015年5月20日 13点29分35秒。 + * 示例值:2015-05-20T13:29:35.120+08:00 + */ + @JSONField(name = "create_time") + private String createTime; + + /** + * 分账回退完成时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss.sss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss.sss表示时分秒毫秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35.120+08:00表示,北京时间2015年5月20日 13点29分35秒。 + * 示例值:2015-05-20T13:29:35.120+08:00 + */ + @JSONField(name = "finish_time") + private String finishTime; + + + /** + * 获取退款请求结果状态码 + * + * @return 状态码 + */ + @Override + public String getCode() { + return result.name(); + } + + /** + * 获取退款请求结果状态提示信息 + * + * @return 提示信息 + */ + @Override + public String getMsg() { + return failReason; + } + + /** + * 返回业务结果状态码 + * + * @return 业务结果状态码 + */ + @Override + public String getResultCode() { + return result.name(); + } + + /** + * 返回业务结果状态提示信息 + * + * @return 业务结果状态提示信息 + */ + @Override + public String getResultMsg() { + return failReason; + } + + /** + * 退款金额, 金额元 + * + * @return 退款金额 + */ + @Override + public BigDecimal getRefundFee() { + + return new BigDecimal(amount); + } + + /** + * 退款币种信息 + * + * @return 币种信息 + */ + @Override + public CurType getRefundCurrency() { + return null; + } + + /** + * 支付平台交易号 + * 发起支付时 支付平台(如支付宝)返回的交易订单号 + * + * @return 支付平台交易号 + */ + @Override + public String getTradeNo() { + return orderId; + } + + /** + * 支付订单号 + * 发起支付时,用户系统的订单号 + * + * @return 支付订单号 + */ + @Override + public String getOutTradeNo() { + return outOrderNo; + } + + /** + * 商户退款单号 + * + * @return 商户退款单号 + */ + @Override + public String getRefundNo() { + return outReturnNo; + } + + public String getSubMchid() { + return subMchid; + } + + public void setSubMchid(String subMchid) { + this.subMchid = subMchid; + } + + public String getOrderId() { + return orderId; + } + + public void setOrderId(String orderId) { + this.orderId = orderId; + } + + public String getOutOrderNo() { + return outOrderNo; + } + + public void setOutOrderNo(String outOrderNo) { + this.outOrderNo = outOrderNo; + } + + public String getOutReturnNo() { + return outReturnNo; + } + + public void setOutReturnNo(String outReturnNo) { + this.outReturnNo = outReturnNo; + } + + public String getReturnId() { + return returnId; + } + + public void setReturnId(String returnId) { + this.returnId = returnId; + } + + public String getReturnMchid() { + return returnMchid; + } + + public void setReturnMchid(String returnMchid) { + this.returnMchid = returnMchid; + } + + public Integer getAmount() { + return amount; + } + + public void setAmount(Integer amount) { + this.amount = amount; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public TradeState getResult() { + return result; + } + + public void setResult(TradeState result) { + this.result = result; + } + + public String getFailReason() { + return failReason; + } + + public void setFailReason(String failReason) { + this.failReason = failReason; + } + + public String getCreateTime() { + return createTime; + } + + public void setCreateTime(String createTime) { + this.createTime = createTime; + } + + public String getFinishTime() { + return finishTime; + } + + public void setFinishTime(String finishTime) { + this.finishTime = finishTime; + } + + public static final WxProfitSharingReturnResult create(Map result) { + WxProfitSharingReturnResult refundResult = new JSONObject(result).toJavaObject(WxProfitSharingReturnResult.class); + refundResult.setAttrs(result); + return refundResult; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/transfer/TransferDetail.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/transfer/TransferDetail.java new file mode 100644 index 0000000000000000000000000000000000000000..0dd09f98661dd1564d996d682471d59b430f6cab --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/transfer/TransferDetail.java @@ -0,0 +1,103 @@ +package com.egzosn.pay.wx.v3.bean.transfer; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * + * 转账明细 + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2023/1/8
+ * 
+ */ +public class TransferDetail { + /** + * 商家明细单号 + */ + @JSONField(name = "out_detail_no") + private String outDetailNo; + /** + * 转账金额,单位为分 + */ + @JSONField(name = "transfer_amount") + private Integer transferAmount; + /** + * 单条转账备注(微信用户会收到该备注),UTF8编码,最多允许32个字符 + */ + @JSONField(name = "transfer_remark") + private String transferRemark; + /** + * 用户在直连商户appid下的唯一标识 + */ + private String openid; + /** + * 收款用户姓名 + */ + @JSONField(name = "user_name") + private String userName; + /** + * 收款用户身份证 + */ + @JSONField(name = "user_id_card") + private String userIdCard; + + + public TransferDetail() { + } + + public TransferDetail(String outDetailNo, Integer transferAmount, String transferRemark, String openid) { + this.outDetailNo = outDetailNo; + this.transferAmount = transferAmount; + this.transferRemark = transferRemark; + this.openid = openid; + } + + public String getOutDetailNo() { + return outDetailNo; + } + + public void setOutDetailNo(String outDetailNo) { + this.outDetailNo = outDetailNo; + } + + public Integer getTransferAmount() { + return transferAmount; + } + + public void setTransferAmount(Integer transferAmount) { + this.transferAmount = transferAmount; + } + + public String getTransferRemark() { + return transferRemark; + } + + public void setTransferRemark(String transferRemark) { + this.transferRemark = transferRemark; + } + + public String getOpenid() { + return openid; + } + + public void setOpenid(String openid) { + this.openid = openid; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getUserIdCard() { + return userIdCard; + } + + public void setUserIdCard(String userIdCard) { + this.userIdCard = userIdCard; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/transfer/WxTransferOrder.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/transfer/WxTransferOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..a3008dc6d6e6ca2bc440e840ae47f17896571fdf --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/transfer/WxTransferOrder.java @@ -0,0 +1,126 @@ +package com.egzosn.pay.wx.v3.bean.transfer; + +import java.math.BigDecimal; +import java.util.List; + +import com.egzosn.pay.common.bean.TransferOrder; +import com.egzosn.pay.wx.v3.utils.WxConst; + +/** + * 发起商家转账 + * + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2023/1/8
+ * 
+ */ +public class WxTransferOrder extends TransferOrder { + + /** + * 商家批次单号 + */ + private String outBatchNo; + /** + * 批次名称 + */ + private String batchName; + /** + * 批次备注 + */ + private String batchRemark; + /** + * 发起批量转账的明细列表,最多三千笔 + */ + private List transferDetailList; + /** + * 转账总金额,单位为“分”。转账总金额必须与批次内所有明细转账金额之和保持一致,否则无法发起转账操作 + */ + private BigDecimal totalAmount; + /** + * 转账总笔数,一个转账批次单最多发起三千笔转账。转账总笔数必须与批次内所有明细之和保持一致,否则无法发起转账操作 + */ + private Integer totalNum; + + /** + * 必填,指定该笔转账使用的转账场景ID + */ + private String transferSceneId; + + + public String getOutBatchNo() { + return outBatchNo; + } + + public void setOutBatchNo(String outBatchNo) { + setBatchNo(outBatchNo); + this.outBatchNo = outBatchNo; + } + + public String getBatchName() { + return batchName; + } + + public void setBatchName(String batchName) { + addAttr(WxConst.BATCH_NAME, batchName); + this.batchName = batchName; + } + + public String getBatchRemark() { + return batchRemark; + } + + public void setBatchRemark(String batchRemark) { + setRemark(batchRemark); + this.batchRemark = batchRemark; + } + + public List getTransferDetailList() { + return transferDetailList; + } + + public void setTransferDetailList(List transferDetailList) { + addAttr(WxConst.TRANSFER_DETAIL_LIST, transferDetailList); + this.transferDetailList = transferDetailList; + } + + public BigDecimal getTotalAmount() { + return totalAmount; + } + + /** + * 转账金额单位为“元”。转账总金额必须与批次内所有明细转账金额之和保持一致,否则无法发起转账操作 + * @param totalAmount 元 + */ + public void setTotalAmount(BigDecimal totalAmount) { + setAmount(totalAmount); + this.totalAmount = totalAmount; + } + + /** + * 转账金额单位为“分”。转账总金额必须与批次内所有明细转账金额之和保持一致,否则无法发起转账操作 + * + * @param totalAmount 分 + */ + public void setTotalAmount(Integer totalAmount) { + setTotalAmount(new BigDecimal(totalAmount / 100)); + } + + public Integer getTotalNum() { + return totalNum; + } + + public void setTotalNum(Integer totalNum) { + addAttr(WxConst.TOTAL_NUM, totalNum); + this.totalNum = totalNum; + } + + public String getTransferSceneId() { + return transferSceneId; + } + + public void setTransferSceneId(String transferSceneId) { + addAttr(WxConst.TRANSFER_SCENE_ID, transferSceneId); + this.transferSceneId = transferSceneId; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/transfer/WxTransferQueryOrder.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/transfer/WxTransferQueryOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..34cc2df987f57896cf49350789b3e2744b281a9d --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/transfer/WxTransferQueryOrder.java @@ -0,0 +1,133 @@ +package com.egzosn.pay.wx.v3.bean.transfer; + +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.wx.v3.utils.WxConst; + +/** + * 微信转账查询订单 + * + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2023/1/8
+ * 
+ */ +public class WxTransferQueryOrder extends AssistOrder { + + /** + * 微信批次单号 或 商家批次单号 + */ + private String batchId; + + /** + * 微信支付系统内部区分转账批次单下不同转账明细单的唯一标识 + */ + private String detailId; + + + /** + * 商户系统内部的商家批次单号,在商户系统内部唯一 + */ + private String outBatchNo; + /** + * 商户系统内部区分转账批次单下不同转账明细单的唯一标识 + */ + private String outDetailNo; + + /** + * 是否查询转账明细单 + * true-是;false-否,默认否。商户可选择是否查询指定状态的转账明细单,当转账批次单状态为“FINISHED”(已完成)时,才会返回满足条件的转账明细单 + */ + private Boolean needQueryDetail; + /** + * 请求资源起始位置 + * 该次请求资源的起始位置。返回的明细是按照设置的明细条数进行分页展示的,一次查询可能无法返回所有明细,我们使用该参数标识查询开始位置,默认值为0 + */ + private Integer offset; + /** + * 最大资源条数 + * 该次请求可返回的最大明细条数,最小20条,最大100条,不传则默认20条。不足20条按实际条数返回 + */ + private Integer limit; + /** + * 明细状态 + * WAIT_PAY: 待确认。待商户确认, 符合免密条件时, 系统会自动扭转为转账中 + * ALL:全部。需要同时查询转账成功和转账失败的明细单 + * SUCCESS:转账成功 + * FAIL:转账失败。需要确认失败原因后,再决定是否重新发起对该笔明细单的转账(并非整个转账批次单) + */ + private String detailStatus; + + public String getBatchId() { + return batchId; + } + + public void setBatchId(String batchId) { + setTradeNo(batchId); + this.batchId = batchId; + } + + public String getOutBatchNo() { + return outBatchNo; + } + + public void setOutBatchNo(String outBatchNo) { + setOutTradeNo(outBatchNo); + this.outBatchNo = outBatchNo; + } + + public String getDetailId() { + return detailId; + } + + public void setDetailId(String detailId) { + addAttr(WxConst.DETAIL_ID, detailId); + this.detailId = detailId; + } + + public String getOutDetailNo() { + return outDetailNo; + } + + public void setOutDetailNo(String outDetailNo) { + addAttr(WxConst.OUT_DETAIL_NO, detailId); + this.outDetailNo = outDetailNo; + } + + public Boolean getNeedQueryDetail() { + return needQueryDetail; + } + + public void setNeedQueryDetail(Boolean needQueryDetail) { + addAttr(WxConst.NEED_QUERY_DETAIL, needQueryDetail); + this.needQueryDetail = needQueryDetail; + } + + public Integer getOffset() { + return offset; + } + + public void setOffset(Integer offset) { + addAttr(WxConst.OFFSET, offset); + this.offset = offset; + } + + public Integer getLimit() { + return limit; + } + + public void setLimit(Integer limit) { + addAttr(WxConst.LIMIT, limit); + this.limit = limit; + } + + + public String getDetailStatus() { + return detailStatus; + } + + public void setDetailStatus(String detailStatus) { + addAttr(WxConst.DETAIL_STATUS, detailStatus); + this.detailStatus = detailStatus; + } +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/utils/AntCertificationUtil.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/utils/AntCertificationUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..e29e163940694acb700bd61df8719e2728679e2f --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/utils/AntCertificationUtil.java @@ -0,0 +1,188 @@ +package com.egzosn.pay.wx.v3.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Security; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.crypto.Cipher; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import javax.management.openmbean.InvalidKeyException; + +import com.egzosn.pay.common.exception.PayErrorException; +import com.egzosn.pay.common.util.sign.SignUtils; +import com.egzosn.pay.common.util.sign.encrypt.Base64; +import com.egzosn.pay.wx.bean.WxPayError; +import com.egzosn.pay.wx.v3.bean.CertEnvironment; + +/** + * 证书文件可信校验 + * + * @author egan + * email egzosn@gmail.com + * date 2021/07/18.20:29 + */ +public final class AntCertificationUtil { + + /** + * 微信平台证书容器 key = 序列号 value = 证书对象 + */ + private static final Map CERTIFICATE_MAP = new ConcurrentHashMap<>(); + + private AntCertificationUtil() { + } + + private static final KeyStore PKCS12_KEY_STORE; + + private static final CertificateFactory CERTIFICATE_FACTORY; + + static { + String javaVersion = System.getProperty("java.version"); + if (javaVersion.contains("1.8") || javaVersion.startsWith("8")){ + Security.setProperty("crypto.policy", "unlimited"); + } + SignUtils.initBc(); + try { + PKCS12_KEY_STORE = KeyStore.getInstance("PKCS12"); + } + catch (KeyStoreException e) { + throw new PayErrorException(new WxPayError(WxConst.FAILURE, " keystore 初始化失败"), e); + } + + try { + CERTIFICATE_FACTORY = CertificateFactory.getInstance("X509", WxConst.BC_PROVIDER); + } + catch (NoSuchProviderException | CertificateException e) { + throw new PayErrorException(new WxPayError(WxConst.FAILURE, " keystore 初始化失败"), e); + } + + } + + + /** + * 装载平台证书 + * + * @param serialNo 证书序列 + * @param certificateStream 证书流 + * @return 平台证书 + */ + public static Certificate loadCertificate(String serialNo, InputStream certificateStream) { + try { + Certificate certificate = CERTIFICATE_FACTORY.generateCertificate(certificateStream); + CERTIFICATE_MAP.put(serialNo, certificate); + return certificate; + } + catch (CertificateException e) { + throw new PayErrorException(new WxPayError(WxConst.FAILURE, " 在生成微信v3证书时发生错误,原因是" + e.getMessage()), e); + } + + } + + /** + * 获取平台证书 + * + * @param serialNo 证书序列 + * @return 平台证书 + */ + public static Certificate getCertificate(String serialNo) { + return CERTIFICATE_MAP.get(serialNo); + + } + + /** + * 获取公私钥. + * + * @param keyCertStream 商户API证书 + * @param keyAlias 证书别名 + * @param keyPass 证书对应的密码 + * @return 证书信息集合 + */ + public static CertEnvironment initCertification(InputStream keyCertStream, String keyAlias, String keyPass) { + + char[] pem = keyPass.toCharArray(); + try { + PKCS12_KEY_STORE.load(keyCertStream, pem); + X509Certificate certificate = (X509Certificate) PKCS12_KEY_STORE.getCertificate(keyAlias); + certificate.checkValidity(); + String serialNumber = certificate.getSerialNumber().toString(16).toUpperCase(); + PublicKey publicKey = certificate.getPublicKey(); + PrivateKey privateKey = (PrivateKey) PKCS12_KEY_STORE.getKey(keyAlias, pem); + return new CertEnvironment(privateKey, publicKey, serialNumber); + } + catch (InvalidKeyException e) { + throw new PayErrorException(new WxPayError(WxConst.FAILURE, "获取公私钥失败, 解决方式:替换jre包:local_policy.jar,US_export_policy.jar"), e); + } + catch (GeneralSecurityException e) { + throw new PayErrorException(new WxPayError(WxConst.FAILURE, "获取公私钥失败"), e); + } + catch (IOException e) { + throw new PayErrorException(new WxPayError(WxConst.FAILURE, "私钥证书流加载失败"), e); + } + + } + + + /** + * 解密响应体. + * + * @param associatedData 相关数据 + * @param nonce 随机串 + * @param cipherText 需要解密的文本 + * @param secretKey 密钥 + * @param characterEncoding 编码类型 + * @return 解密后的信息 + */ + public static String decryptToString(String associatedData, String nonce, String cipherText, String secretKey, String characterEncoding) { + + try { + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", WxConst.BC_PROVIDER); + SecretKeySpec key = new SecretKeySpec(secretKey.getBytes(Charset.forName(characterEncoding)), "AES"); + GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes(Charset.forName(characterEncoding))); + cipher.init(Cipher.DECRYPT_MODE, key, spec); + cipher.updateAAD(associatedData.getBytes(Charset.forName(characterEncoding))); + byte[] bytes = cipher.doFinal(Base64.decode(cipherText)); + return new String(bytes, Charset.forName(characterEncoding)); + } + catch (GeneralSecurityException e) { + throw new PayErrorException(new WxPayError(WxConst.FAILURE, e.getMessage()), e); + } + } + + /** + * 对请求敏感字段进行加密 + * + * @param message the message + * @param certificate the certificate + * @return 加密后的内容 + */ + public static String encryptToString(String message, Certificate certificate) { + try { + Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding", WxConst.BC_PROVIDER); + cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey()); + + byte[] data = message.getBytes(StandardCharsets.UTF_8); + byte[] cipherData = cipher.doFinal(data); + return Base64.encode(cipherData); + + } + catch (GeneralSecurityException e) { + throw new PayErrorException(new WxPayError(WxConst.FAILURE, e.getMessage()), e); + } + } + + +} diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/utils/WxConst.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/utils/WxConst.java new file mode 100644 index 0000000000000000000000000000000000000000..bbd311db7b94b7367bc879e6d42c7bfe2312e8c0 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/utils/WxConst.java @@ -0,0 +1,88 @@ +package com.egzosn.pay.wx.v3.utils; + +import com.egzosn.pay.wx.v3.api.WxPayService; + +/** + * 微信所需常量 + * + * @author Egan + * email egzosn@gmail.com + * date 2021/8/1 + */ +public final class WxConst { + + private WxConst() { + } + + /** + * 微信默认请求地址 + */ + public static final String URI = "https://api.mch.weixin.qq.com"; + /** + * 证书别名 + */ + public static final String CERT_ALIAS = "Tenpay Certificate"; + /** + * 加密算法提供方 - BouncyCastle + */ + public static final String BC_PROVIDER = "BC"; + + /** + * 沙箱 + */ + public static final String SANDBOXNEW = "sandboxnew/"; + public static final String COMBINE = "combine_"; + public static final String APPID = "appid"; + public static final String SUB_APPID = "sub_appid"; + public static final String TRANSACTION_ID = "transaction_id"; + public static final String OUT_ORDER_NO = "out_order_no"; + public static final String TYPE = "type"; + public static final String ACCOUNT = "account"; + public static final String NAME = "name"; + public static final String RELATION_TYPE = "relationType"; + public static final String CUSTOM_RELATION = "customRelation"; + public static final String DESCRIPTION = "description"; + public static final String BILL_DATE = "bill_date"; + public static final String TAR_TYPE = "tar_type"; + + public static final String COMBINE_APPID = COMBINE + APPID; + public static final String MCH_ID = "mchid"; + public static final String COMBINE_MCH_ID = COMBINE + MCH_ID; + public static final String SUB_MCH_ID = "sub_mchid"; + public static final String SP_MCH_ID = "sp_mchid"; + public static final String OUT_TRADE_NO = "out_trade_no"; + public static final String COMBINE_OUT_TRADE_NO = COMBINE + OUT_TRADE_NO; + public static final String NOTIFY_URL = "notify_url"; + public static final String TIME_START = "time_start"; + public static final String TIME_EXPIRE = "time_expire"; + public static final String SUB_ORDERS = "sub_orders"; + public static final String RECEIVERS = "receivers"; + public static final String UNFREEZE_UNSPLIT = "unfreeze_unsplit"; + + public static final String TOKEN_PATTERN = "mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\""; + + public static final String SCHEMA = "WECHATPAY2-SHA256-RSA2048 "; + + public static final String CODE = "code"; + public static final String MESSAGE = "message"; + public static final String SCENE_INFO = "scene_info"; + public static final String FAILURE = "failure"; + + public static final String RESP_BODY = WxPayService.class.getName() + "$RESP_BODY"; + public static final String OUT_BATCH_NO = "out_batch_no"; + public static final String OUT_DETAIL_NO = "out_detail_no"; + public static final String DETAIL_ID = "detail_id"; + public static final String BATCH_NAME = "batch_name"; + public static final String BATCH_REMARK = "batch_remark"; + public static final String TRANSFER_DETAIL_LIST = "transfer_detail_list"; + public static final String TOTAL_AMOUNT = "total_amount"; + public static final String TOTAL_NUM = "total_num"; + public static final String TRANSFER_SCENE_ID = "transfer_scene_id"; + public static final String BATCH_ID = "batch_id"; + public static final String NEED_QUERY_DETAIL = "need_query_detail"; + public static final String OFFSET = "offset"; + public static final String LIMIT = "limit"; + public static final String DETAIL_STATUS = "detail_status"; + public static final String WECHATPAY_SERIAL = "Wechatpay-Serial"; + public static final String AUTHORIZATION_CODE = "authorization_code"; +} diff --git a/pay-java-wx/src/test/java/PayTest.java b/pay-java-wx/src/test/java/PayTest.java index b0b8dd45685f5b3ad030d95c4843704f07c9fb3f..4b42a608cdcedcb46db7664515118e15635810ad 100644 --- a/pay-java-wx/src/test/java/PayTest.java +++ b/pay-java-wx/src/test/java/PayTest.java @@ -1,9 +1,12 @@ -import com.egzosn.pay.common.api.PayService; +import com.egzosn.pay.common.bean.CertStoreType; import com.egzosn.pay.common.bean.MethodType; import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.http.HttpConfigStorage; import com.egzosn.pay.wx.api.WxPayConfigStorage; import com.egzosn.pay.wx.api.WxPayService; +import com.egzosn.pay.wx.bean.RedpackOrder; +import com.egzosn.pay.wx.bean.WxSendredpackType; import com.egzosn.pay.wx.bean.WxTransactionType; import java.awt.image.BufferedImage; @@ -16,17 +19,22 @@ import java.util.UUID; * * 微信 * @author egan - * @email egzosn@gmail.com - * @date 2017/8/18 + * email egzosn@gmail.com + * date 2017/8/18 */ public class PayTest { public static void main(String[] args) { WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage(); + wxPayConfigStorage.setAppId("公众账号ID"); + wxPayConfigStorage.setMchId("合作者id(商户号)"); - wxPayConfigStorage.setAppid("应用id"); - wxPayConfigStorage.setKeyPublic("密钥"); - wxPayConfigStorage.setKeyPrivate("密钥"); + //以下两个参数在 服务商版模式中必填-------- +// wxPayConfigStorage.setSubAppid("子商户公众账号ID "); +// wxPayConfigStorage.setSubMchId("微信支付分配的子商户号 "); + //----------------------------------------------- + wxPayConfigStorage.setKeyPublic("转账公钥,转账时必填"); + wxPayConfigStorage.setSecretKey("密钥"); wxPayConfigStorage.setNotifyUrl("异步回调地址"); wxPayConfigStorage.setReturnUrl("同步回调地址"); wxPayConfigStorage.setSignType("签名方式"); @@ -34,9 +42,9 @@ public class PayTest { //是否为测试账号,沙箱环境 此处暂未实现 wxPayConfigStorage.setTest(true); //支付服务 - PayService service = new WxPayService(wxPayConfigStorage); + WxPayService service = new WxPayService(wxPayConfigStorage); //支付订单基础信息 - PayOrder payOrder = new PayOrder("订单title", "摘要", new BigDecimal(0.01) , UUID.randomUUID().toString().replace("-", "")); + PayOrder payOrder = new PayOrder("订单title", "摘要", BigDecimal.valueOf(0.01) , UUID.randomUUID().toString().replace("-", "")); /*-----------扫码付-------------------*/ payOrder.setTransactionType(WxTransactionType.NATIVE); //获取扫码付的二维码 @@ -79,5 +87,32 @@ public class PayTest { /*-----------回调处理-------------------*/ + + HttpConfigStorage httpConfigStorage = new HttpConfigStorage(); + //ssl 退款证书相关 + httpConfigStorage.setKeystore("D:/work/pay/src/main/resources/certificates/1220429901_apiclient_cert.p12"); + httpConfigStorage.setStorePassword("默认商户号"); + //设置ssl证书对应的存储方式,这里默认为文件地址 + httpConfigStorage.setCertStoreType(CertStoreType.PATH); + service.setRequestTemplateConfigStorage(httpConfigStorage); + + RedpackOrder redpackOrder = new RedpackOrder(); + + redpackOrder.setSendName("测试"); + //faymanwang- opid + redpackOrder.setReOpenid("om3rxjhD1rhGrP6oLydMgLcN5n10"); + //红包流水 + redpackOrder.setMchBillno("red202005181"); + redpackOrder.setTotalAmount(new BigDecimal(1.5)); + redpackOrder.setSceneId("PRODUCT_1"); + //现金红包,小程序默认为1 裂变默认为3 + redpackOrder.setTotalNum(4); + redpackOrder.setWishing("请勿领取"); + redpackOrder.setActName("请勿领取测试红包"); + redpackOrder.setRemark("测试支付-by fayman"); + //设置发红包方式 + redpackOrder.setTransferType(WxSendredpackType.SENDGROUPREDPACK); + Map sendredpack = service.sendredpack(redpackOrder); + System.out.println(sendredpack); } } diff --git a/pay-java-yiji/pom.xml b/pay-java-yiji/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..8cb8f7f5bc84a8a8b8fe989eb6d2222fdab3db65 --- /dev/null +++ b/pay-java-yiji/pom.xml @@ -0,0 +1,24 @@ + + + + pay-java-parent + com.egzosn + 2.14.7 + + 4.0.0 + + com.egzosn + pay-java-yiji + + + + + + com.egzosn + pay-java-common + + + + \ No newline at end of file diff --git a/pay-java-yiji/src/main/java/com/egzosn/pay/yiji/api/YiJiPayConfigStorage.java b/pay-java-yiji/src/main/java/com/egzosn/pay/yiji/api/YiJiPayConfigStorage.java new file mode 100644 index 0000000000000000000000000000000000000000..8484c27fa0d505f00712c46d003b754553674360 --- /dev/null +++ b/pay-java-yiji/src/main/java/com/egzosn/pay/yiji/api/YiJiPayConfigStorage.java @@ -0,0 +1,89 @@ +package com.egzosn.pay.yiji.api; + +import com.egzosn.pay.common.api.BasePayConfigStorage; + +/** + * 易极付配置存储 + * + * @author egan + * + *
+ * email egzosn@gmail.com
+ * date 2019/04/15 22:50
+ * 
+ */ +public class YiJiPayConfigStorage extends BasePayConfigStorage { + + + /** + * 易极付分配的商户号 合作者id + */ + private String partnerId; + + /** + * 卖家id + */ + private String sellerUserId; + + public String getPartnerId() { + return partnerId; + } + + public void setPartnerId(String partnerId) { + this.partnerId = partnerId; + } + + @Override + public String getAppid() { + return null; + } + + /** + * 应用id + * 纠正名称 + * + * @return 应用id + */ + @Override + public String getAppId() { + return null; + } + + + /** + * 合作商唯一标识 + */ + @Override + public String getPid() { + return partnerId; + } + + + @Override + public String getSeller() { + return sellerUserId; + } + + public String getSellerUserId() { + return sellerUserId; + } + + public void setSellerUserId(String sellerUserId) { + this.sellerUserId = sellerUserId; + } + + /** + * 为商户平台设置的密钥key + * + * @return 密钥 + */ + public String getSecretKey() { + return getKeyPrivate(); + } + + public void setSecretKey(String secretKey) { + setKeyPrivate(secretKey); + } + + +} diff --git a/pay-java-yiji/src/main/java/com/egzosn/pay/yiji/api/YiJiPayService.java b/pay-java-yiji/src/main/java/com/egzosn/pay/yiji/api/YiJiPayService.java new file mode 100644 index 0000000000000000000000000000000000000000..7a93c0db31eb4268b620d1bd87a254a4f0196c73 --- /dev/null +++ b/pay-java-yiji/src/main/java/com/egzosn/pay/yiji/api/YiJiPayService.java @@ -0,0 +1,457 @@ +package com.egzosn.pay.yiji.api; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.Date; +import java.util.Map; +import java.util.TreeMap; + +import com.alibaba.fastjson.JSONObject; +import com.egzosn.pay.common.api.BasePayService; +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.common.bean.BaseRefundResult; +import com.egzosn.pay.common.bean.BillType; +import com.egzosn.pay.common.bean.CurType; +import com.egzosn.pay.common.bean.DefaultCurType; +import com.egzosn.pay.common.bean.MethodType; +import com.egzosn.pay.common.bean.NoticeParams; +import com.egzosn.pay.common.bean.PayMessage; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.PayOutMessage; +import com.egzosn.pay.common.bean.RefundOrder; +import com.egzosn.pay.common.bean.RefundResult; +import com.egzosn.pay.common.bean.TransactionType; +import com.egzosn.pay.common.bean.TransferOrder; +import com.egzosn.pay.common.http.HttpConfigStorage; +import com.egzosn.pay.common.util.DateUtils; +import com.egzosn.pay.common.util.Util; +import com.egzosn.pay.common.util.sign.SignTextUtils; +import com.egzosn.pay.common.util.sign.SignUtils; +import com.egzosn.pay.common.util.str.StringUtils; +import com.egzosn.pay.yiji.bean.YiJiTransactionType; + + +/** + * 易极付支付服务 + * + * @author egan + *

+ * email egzosn@gmail.com + * * date 2019/04/15 22:51 + */ +public class YiJiPayService extends BasePayService { + + /** + * 正式测试环境 + */ + private static final String HTTPS_REQ_URL = "https://api.yiji.com"; + /** + * 全球正式测试环境 + */ + private static final String HTTPS_GLOBAL_REQ_URL = "https://openapiglobal.yiji.com/gateway.html"; + /** + * 沙箱测试环境账号 + */ + private static final String DEV_REQ_URL = "https://openapi.yijifu.net/gateway.html"; + + public static final String SIGN = "sign"; + + + /** + * 获取对应的请求地址 + * + * @return 请求地址 + */ + @Override + public String getReqUrl(TransactionType transactionType) { + if (payConfigStorage.isTest()) { + return DEV_REQ_URL; + } + else if (/*YiJiTransactionType.corderRemittanceSynOrder == transactionType ||*/ YiJiTransactionType.applyRemittranceWithSynOrder == transactionType) { + return HTTPS_GLOBAL_REQ_URL; + } + else { + return HTTPS_REQ_URL; + } + } + + public YiJiPayService(YiJiPayConfigStorage payConfigStorage, HttpConfigStorage configStorage) { + super(payConfigStorage, configStorage); + } + + public YiJiPayService(YiJiPayConfigStorage payConfigStorage) { + super(payConfigStorage); + } + + + /** + * 回调校验 + * + * @param params 回调回来的参数集 + * @return 签名校验 true通过 + */ + @Override + public boolean verify(Map params) { + + return verify(new NoticeParams(params)); + + } + + /** + * 回调校验 + * + * @param noticeParams 回调回来的参数集 + * @return 签名校验 true通过 + */ + @Override + public boolean verify(NoticeParams noticeParams) { + final Map params = noticeParams.getBody(); + if (params.get(SIGN) == null) { + LOG.debug("易极付支付异常:params:{}", params); + return false; + } + + return signVerify(params, (String) params.get(SIGN)); + } + + /** + * 根据反馈回来的信息,生成签名结果 + * + * @param params 通知返回来的参数数组 + * @param sign 比对的签名结果 + * @return 生成的签名结果 + */ + public boolean signVerify(Map params, String sign) { + + return SignUtils.valueOf(payConfigStorage.getSignType()).verify(params, sign, payConfigStorage.getKeyPublic(), payConfigStorage.getInputCharset()); + } + + + /** + * 生成并设置签名 + * + * @param parameters 请求参数 + * @return 请求参数 + */ + private Map setSign(Map parameters) { + parameters.put("signType", payConfigStorage.getSignType()); + String sign = createSign(SignTextUtils.parameterText(parameters, "&", SIGN), payConfigStorage.getInputCharset()); + + parameters.put(SIGN, sign); + return parameters; + } + + + /** + * 返回创建的订单信息 + * + * @param order 支付订单 + * @return 订单信息 + * @see PayOrder 支付订单信息 + */ + @Override + public Map orderInfo(PayOrder order) { + + return setSign(getOrder(order)); + } + + + /** + * 易极付创建订单信息 + * create the order info + * + * @param order 支付订单 + * @return 返回易极付预下单信息 + * @see PayOrder 支付订单信息 + */ + private Map getOrder(PayOrder order) { + + Map orderInfo = getPublicParameters(order.getTransactionType()); + orderInfo.put("orderNo", order.getOutTradeNo()); + orderInfo.put("outOrderNo", order.getOutTradeNo()); + + if (StringUtils.isNotEmpty(payConfigStorage.getSeller())) { + orderInfo.put("sellerUserId", payConfigStorage.getSeller()); + } + + ((YiJiTransactionType) order.getTransactionType()).setAttribute(orderInfo, order); + + orderInfo.put("tradeAmount", Util.conversionAmount(order.getPrice())); + //商品条款信息 商品名称 + orderInfo.put("goodsClauses", String.format("[{'name':'%s'}]", order.getBody())); + //交易名称 + orderInfo.put("tradeName", order.getSubject()); + if (null != order.getCurType()) { + orderInfo.put("currency", order.getCurType()); + } + orderInfo.putAll(order.getAttrs()); + return preOrderHandler(orderInfo, order); + } + + /** + * 获取公共请求参数 + * + * @return 放回公共请求参数 + */ + private Map getPublicParameters(TransactionType transactionType) { + Map orderInfo = new TreeMap<>(); + orderInfo.put("partnerId", payConfigStorage.getPid()); + orderInfo.put("returnUrl", payConfigStorage.getReturnUrl()); + orderInfo.put("notifyUrl", payConfigStorage.getNotifyUrl()); + orderInfo.put("service", transactionType.getMethod()); + return orderInfo; + } + + + /** + * 获取输出消息,用户返回给支付端 + * + * @param code 状态 + * @param message 消息 + * @return 返回输出消息 + */ + @Override + public PayOutMessage getPayOutMessage(String code, String message) { + return PayOutMessage.TEXT().content(code.toLowerCase()).build(); + } + + /** + * 获取成功输出消息,用户返回给支付端 + * 主要用于拦截器中返回 + * + * @param payMessage 支付回调消息 + * @return 返回输出消息 + */ + @Override + public PayOutMessage successPayOutMessage(PayMessage payMessage) { + return PayOutMessage.TEXT().content("success").build(); + } + + /** + * @param orderInfo 发起支付的订单信息 + * @param method 请求方式 "post" "get", + * @return 获取输出消息,用户返回给支付端, 针对于web端 + */ + @Override + public String buildRequest(Map orderInfo, MethodType method) { + StringBuilder formHtml = new StringBuilder(); + formHtml.append("\n"); + formHtml.append(""); + for (Map.Entry entry : orderInfo.entrySet()) { + formHtml.append("\n"); + } + formHtml.append("\n"); + formHtml.append("\n"); + + + return formHtml.toString(); + } + + /** + * 生成二维码支付 + * + * @param order 发起支付的订单信息 + * @return 返回图片信息,支付时需要的 + */ + @Override + public String getQrPay(PayOrder order) { + + return null; + } + + /** + * pos主动扫码付款(条码付) + * + * @param order 发起支付的订单信息 + * @return 支付结果 + */ + @Override + public Map microPay(PayOrder order) { + + return Collections.emptyMap(); + } + + /** + * 交易查询接口 + * + * @param tradeNo 支付平台订单号 + * @param outTradeNo 商户单号 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(String tradeNo, String outTradeNo) { + return Collections.emptyMap(); + } + + /** + * 交易查询接口 + * + * @param assistOrder 查询条件 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(AssistOrder assistOrder) { + return Collections.emptyMap(); + } + + + /** + * 交易关闭接口 + * + * @param tradeNo 支付平台订单号 + * @param outTradeNo 商户单号 + * @return 返回支付方交易关闭后的结果 + */ + @Override + public Map close(String tradeNo, String outTradeNo) { + return Collections.emptyMap(); + } + + /** + * 交易关闭接口 + * + * @param assistOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 + */ + @Override + public Map close(AssistOrder assistOrder) { + return Collections.emptyMap(); + } + + /** + * 申请退款接口 + * + * @param refundOrder 退款订单信息 + * @return 返回支付方申请退款后的结果 + */ + @Override + public RefundResult refund(RefundOrder refundOrder) { + Map orderInfo = getPublicParameters(YiJiTransactionType.tradeRefund); + orderInfo.put("orderNo", refundOrder.getOutTradeNo()); + orderInfo.put("outOrderNo", refundOrder.getOutTradeNo()); + orderInfo.put("refundAmount", refundOrder.getRefundAmount()); + orderInfo.put("refundTime", DateUtils.formatDay(refundOrder.getOrderDate())); + orderInfo.put("refundReason", refundOrder.getDescription()); + setSign(orderInfo); + return new BaseRefundResult(getHttpRequestTemplate().postForObject(getReqUrl(YiJiTransactionType.tradeRefund), orderInfo, JSONObject.class)) { + @Override + public String getCode() { + return null; + } + + @Override + public String getMsg() { + return null; + } + + @Override + public String getResultCode() { + return null; + } + + @Override + public String getResultMsg() { + return null; + } + + @Override + public BigDecimal getRefundFee() { + return null; + } + + @Override + public CurType getRefundCurrency() { + return null; + } + + @Override + public String getTradeNo() { + return null; + } + + @Override + public String getOutTradeNo() { + return null; + } + + @Override + public String getRefundNo() { + return null; + } + }; + } + + /** + * 查询退款 + * + * @param refundOrder 退款订单单号信息 + * @return 返回支付方查询退款后的结果 + */ + @Override + public Map refundquery(RefundOrder refundOrder) { + + return Collections.emptyMap(); + + } + + + /** + * @param billDate 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于易极付交易收单的业务账单;signcustomer是指基于商户易极付余额收入及支出等资金变动的帐务账单; + * @param billType 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 + * @return 返回支付方下载对账单的结果 + */ + @Override + public Map downloadBill(Date billDate, BillType billType) { + + return Collections.emptyMap(); + } + + + /** + * 转账 这里外部进行调用{@link #buildRequest(Map, MethodType)} + * + * @param order 转账订单 + * @return 对应的转账结果 + */ + @Override + public Map transfer(TransferOrder order) { + Map data = getPublicParameters(YiJiTransactionType.applyRemittranceWithSynOrder); + data.put("remittranceBatchNo", order.getBatchNo()); + data.put("outOrderNo", order.getOutNo()); + data.put("payAmount", Util.conversionAmount(order.getAmount())); + data.put("payCurrency", order.getCurType().getType()); + data.put("withdrawCurrency", DefaultCurType.CNY.getType()); + data.put("payMemo", order.getRemark()); + data.put("toCountryCode", order.getCountryCode().getCode()); + data.put("tradeUseCode", "326"); + data.put("payeeName", order.getPayeeName()); + data.put("payeeAddress", order.getPayeeAddress()); + data.put("payeeBankName", order.getBank().getCode()); + data.put("payeeBankAddress", order.getPayeeBankAddress()); + data.put("payeeBankSwiftCode", "CNAPS CODE"); + data.put("payeeBankNo", order.getPayeeAccount()); + setSign(data); + + + return data; + } + + /** + * 转账查询 + * + * @param outNo 商户转账订单号 + * @param tradeNo 支付平台转账订单号 + * @return 对应的转账订单 + */ + @Override + public Map transferQuery(String outNo, String tradeNo) { + + return Collections.emptyMap(); + } + +} diff --git a/pay-java-yiji/src/main/java/com/egzosn/pay/yiji/bean/CurType.java b/pay-java-yiji/src/main/java/com/egzosn/pay/yiji/bean/CurType.java new file mode 100644 index 0000000000000000000000000000000000000000..09e6771a964ae3f2cba7ca75273e27b53e4cf63f --- /dev/null +++ b/pay-java-yiji/src/main/java/com/egzosn/pay/yiji/bean/CurType.java @@ -0,0 +1,94 @@ +package com.egzosn.pay.yiji.bean; + +/** + * 币种 + * @author egan + * email egzosn@gmail.com + * date 2019/4/16.22:48 + */ +public enum CurType implements com.egzosn.pay.common.bean.CurType { + CNY(156, "人民币"), + USD(840, "美元"), + JPY(392, "日元"), + HKD(344, "港币"), + GBP(826, "英镑"), + EUR(978, "欧元"), + AUD(30, "澳元"), + CAD(124, "加元"), + SGD(702, "坡币"), + NZD(554, "新西"), + TWD(901, "台币"), + KRW(410, "韩元"), + DKK(208, "丹朗"), + TRY(949, "土拉"), + MYR(458, "马来"), + THB(764, "泰铢"), + INR(356, "印卢"), + PHP(608, "菲比"), + CHF(756, "瑞士"), + SEK(752, "瑞典"), + ILS(376, "以谢"), + ZAR(710, "南非"), + RUB(643, "俄卢"), + NOK(578, "挪威克朗"), + AED(784, "阿联酋"), + BRL(986, "巴西雷亚尔"), + IDR(360, "印尼卢比"), + SAR(682, "沙特里亚尔"), + MXN(484, "墨西哥比索"), + PLN(985, "波兰兹罗提"), + VND(704, "越南盾"), + CLP(152, "智利比索"), + KZT(398, "哈萨克腾格"), + CZK(203, "捷克克朗"), + EGP(818, "埃及镑"), + VEF(937, "委玻利瓦尔"), + ARS(26, "阿根廷比索"), + MOP(446, "澳门元"), + UAH(980, "乌格里夫纳"), + LBP(422, "黎巴嫩镑"), + JOD(400, "黎巴嫩镑"), + PEN(604, "秘鲁新索尔"), + PKR(586, "巴基斯坦卢比"), + RON(946, "罗马尼亚列伊"), + QAR(634, "卡塔尔里亚尔"), + KWD(414, "科威特第纳尔"), + NGN(566, "尼日利亚奈拉"), + COP(170, "哥伦比亚比索"), + HUF(348, "匈牙利福林"); + + private int code; + /** + * 币种名称 + */ + private String name; + + CurType(int code, String name) { + this.name = name; + this.code = code; + } + + /** + * 获取货币类型 + * + * @return 货币类型 + */ + @Override + public String getType() { + return this.name(); + } + + /** + * 货币名称 + * + * @return 货币名称 + */ + @Override + public String getName() { + return name; + } + + public int getCode() { + return code; + } +} diff --git a/pay-java-yiji/src/main/java/com/egzosn/pay/yiji/bean/YiJiBank.java b/pay-java-yiji/src/main/java/com/egzosn/pay/yiji/bean/YiJiBank.java new file mode 100644 index 0000000000000000000000000000000000000000..9c443add821b0fb210dd8db51a47ad72d5979d4d --- /dev/null +++ b/pay-java-yiji/src/main/java/com/egzosn/pay/yiji/bean/YiJiBank.java @@ -0,0 +1,63 @@ +package com.egzosn.pay.yiji.bean; + +import com.egzosn.pay.common.bean.Bank; + +/** + * 对应的银行列表 + * + * @author egan + *

+ *         email egzosn@gmail.com
+ *         date 2018/1/31
+ *         
+ */ +public enum YiJiBank implements Bank { + ABC("中国农业银行"), + BOC("中国银行"), + COMM("交通银行"), + CCB("中国建设银行"), + CEB("中国光大银行"), + CIB("兴业银行"), + CMB("招商银行"), + CMBC("民生银行"), + CITIC("中信银行"), + CQRCB("重庆农村商业银行"), + ICBC("中国工商银行"), + PSBC("中国邮政储蓄银行"), + SPDB("浦发银行"), + UNION("中国银联"), + CQCB("重庆银行"), + GDB("广东发展银行"), + SDB("深圳发展银行"), + HXB("华夏银行"), + CQTGB("重庆三峡银行"), + PINGANBANK("平安银行"), + BANKSH("上海银行"),; + + private String name; + + + YiJiBank(String name) { + this.name = name; + } + + /** + * 获取银行的代码 + * + * @return 银行的代码 + */ + @Override + public String getCode() { + return this.name(); + } + + /** + * 获取银行的名称 + * + * @return 银行的名称 + */ + @Override + public String getName() { + return name; + } +} diff --git a/pay-java-yiji/src/main/java/com/egzosn/pay/yiji/bean/YiJiTransactionType.java b/pay-java-yiji/src/main/java/com/egzosn/pay/yiji/bean/YiJiTransactionType.java new file mode 100644 index 0000000000000000000000000000000000000000..365356f48c65770595e1968af1e9fa2198868f9e --- /dev/null +++ b/pay-java-yiji/src/main/java/com/egzosn/pay/yiji/bean/YiJiTransactionType.java @@ -0,0 +1,92 @@ +package com.egzosn.pay.yiji.bean; + +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.TransactionType; + +import java.util.HashMap; +import java.util.Map; + +/** + * 易极付交易类型 + *
+ * 说明交易类型主要用于支付接口调用参数所需
+ *
+ * 
+ * + * @author egan + *

+ * email egzosn@gmail.com + * date 2019/04/15 22:58 + */ +public enum YiJiTransactionType implements TransactionType { + /** + * 跳转微支付 + */ + commonWchatTradeRedirect("commonWchatTradeRedirect"), + /** + * 跳转收银台支付 + */ + commonTradePay("commonTradePay"){ + @Override + public String getVersion() { + return "2.0"; + } + }, + tradeRefund("tradeRefund"), + /** + * 跨境订单同步 + *//* + corderRemittanceSynOrder("corderRemittanceSynOrder"), + */ + /** + * 国际转账 + */ + applyRemittranceWithSynOrder("applyRemittranceWithSynOrder") + ; + + private String method; + /** + * 版本 + */ + private String version = "1.0"; + + private static final Map transactiontypes = new HashMap(); + static { + for (TransactionType type : YiJiTransactionType.values()){ + transactiontypes.put(type.getMethod(), type); + } + } + + YiJiTransactionType(String method) { + this.method = method; + } + + @Override + public String getType() { + return this.name(); + } + + public String getVersion() { + return version; + } + + /** + * 获取接口名称 + * + * @return 接口名称 + */ + @Override + public String getMethod() { + return this.method; + } + + + public void setAttribute(Map parameters, PayOrder order) { + parameters.put("version", getVersion()); + } + + public static TransactionType getTransactionType(String method) { + return transactiontypes.get(method); + } + +} diff --git a/pom.xml b/pom.xml index 2c074f19b6220c50d418f7438dadfc69cd59c42a..1220e08466bc96f6a40e1ced8aa9d0bf8ed44167 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.egzosn pay-java-parent pom - 2.12.6-SNAPSHOT + 2.14.7 Pay Java - Parent Pay Java Parent @@ -34,6 +34,11 @@ zhangchenghui.dev@gmail.com https://github.com/objcoding + + hocgin + hocgin@gmail.com + https://github.com/hocgin + scm:git:https://github.com/egzosn/pay-java-parent.git @@ -44,6 +49,7 @@ pay-java-common + pay-java-web-support pay-java-ali pay-java-wx pay-java-wx-youdian @@ -51,16 +57,20 @@ pay-java-union pay-java-payoneer pay-java-paypal + pay-java-yiji + pay-java-baidu pay-java-demo - 2.12.6-SNAPSHOT + 2.14.7 4.5.4 1.2.17 - 1.2.41 + 1.2.83 3.3.1 + 4.0.1 + 1.59 @@ -72,6 +82,11 @@ pay-java-common ${pay.version} + + com.egzosn + pay-java-web-support + ${pay.version} + @@ -85,13 +100,17 @@ fastjson ${fastjson.version} - + + org.bouncycastle + bcprov-jdk15on + ${bcprov-jdk15on.version} + - log4j - log4j - ${log4j.version} + org.slf4j + slf4j-api + 1.7.30 @@ -102,6 +121,13 @@ ${zxing.version} + + javax.servlet + javax.servlet-api + provided + ${servlet-api.version} + + @@ -121,72 +147,92 @@ org.apache.maven.plugins maven-compiler-plugin - 1.7 - 1.7 + 1.8 + 1.8 utf-8 - + + + + + local + + true + + + + proc + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.3 + true + + ossrh + https://oss.sonatype.org/ + false + + + + org.apache.maven.plugins + maven-release-plugin + 2.5.1 + + false + false + release + deploy + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.3 + + + attach-javadocs + install + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.1.0 + + + attach-sources + install + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + install + + sign + + + + + + + + \ No newline at end of file