diff --git a/README.md b/README.md index 20fc53846085dad11a90c429715b5e7ee428aba6..4886488fdb1033ec79ca688456f0adb89449bc1d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -全能第三方支付对接Java开发工具包.优雅的轻量级支付模块集成支付对接支付整合(微信,支付宝,银联,友店,富友,跨境支付paypal,payoneer(P卡派安盈)易极付)app,扫码,网页支付刷卡付条码付刷脸付转账红包服务商模式、支持多种支付类型多支付账户,支付与业务完全剥离,简单几行代码即可实现支付,简单快速完成支付模块的开发,可轻松嵌入到任何系统里 目前仅是一个开发工具包(即SDK),只提供简单Web实现,建议使用maven或gradle引用本项目即可使用本SDK提供的各种支付相关的功能 +全能第三方支付对接Java开发工具包.优雅的轻量级支付模块集成支付对接支付整合(微信,支付宝,银联,友店,富友,跨境支付paypal,payoneer(P卡派安盈)易极付)app,扫码,网页支付刷卡付条码付刷脸付转账红包服务商模式,微信分账,合并支付、支持多种支付类型多支付账户,支付与业务完全剥离,简单几行代码即可实现支付,简单快速完成支付模块的开发,可轻松嵌入到任何系统里 目前仅是一个开发工具包(即SDK),只提供简单Web实现,建议使用maven或gradle引用本项目即可使用本SDK提供的各种支付相关的功能 ### 特性 @@ -8,9 +8,10 @@ 4. 简单快速完成支付模块的开发 5. 支持多种支付类型多支付账户扩展 -### 本项目包含 3 个部分: +### 本项目包含 4 个部分: 1. pay-java-common 公共lib,支付核心与规范定义 + 2. pay-java-web-support web支持包,目前已实现回调相关 2. pay-java-demo 具体的支付demo 3. pay-java-* 具体的支付实现库 @@ -21,7 +22,7 @@ com.egzosn {module-name} - 2.13.2 + 2.14.7 ``` @@ -41,6 +42,9 @@ ###### 支付教程 * [基础模块支付宝微信讲解](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) @@ -57,14 +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://gitee.com/egzosn/pay-java-parent/raw/develop/pay-java-demo/src/main/webapp/gzh.png "gzh.png") -QQ群:542193977 +E-Mail:egan@egzosn.com -微信群: ![微信群](https://www.egzosn.com/images/wx.jpg "wx.jpg") + **QQ群:** + +1. pay-java(1群): 542193977(已满) +2. pay-java(2群):766275051 + + +微信群: 加我前拜托伸个小手关注公众号 + +![微信群](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 e7a2f5eafb3126054fdd3b6a3f32669c4dddd2bc..d48313c4692b67efe3bcec6a4dac6accd36edfb7 100644 --- a/pay-java-ali/README.md +++ b/pay-java-ali/README.md @@ -3,13 +3,14 @@ ## 支付宝支付简单例子 #### 支付配置 - +##### 普通公钥 ```java - - + + AliPayConfigStorage aliPayConfigStorage = new AliPayConfigStorage(); aliPayConfigStorage.setPid("合作者id"); - aliPayConfigStorage.setAppid("应用id"); + aliPayConfigStorage.setAppId("应用id"); +// aliPayConfigStorage.setAppAuthToken("ISV代商户代用,指定appAuthToken"); aliPayConfigStorage.setKeyPublic("支付宝公钥"); aliPayConfigStorage.setKeyPrivate("应用私钥"); aliPayConfigStorage.setNotifyUrl("异步回调地址"); @@ -21,6 +22,31 @@ aliPayConfigStorage.setTest(true); ``` +##### 证书公钥 +```java + + + AliPayConfigStorage aliPayConfigStorage = new AliPayConfigStorage(); + aliPayConfigStorage.setPid("合作者id"); + aliPayConfigStorage.setAppId("应用id"); +// 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("签名方式"); + aliPayConfigStorage.setSeller("收款账号"); + aliPayConfigStorage.setInputCharset("utf-8"); + //是否为测试账号,沙箱环境 + aliPayConfigStorage.setTest(true); + +``` #### 网络请求配置 @@ -85,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("-", "")); ``` @@ -236,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(); ``` @@ -256,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 e279f8e3d45e97d43bb99571f4fb364707ce83e7..9985ca214df92a4e127d092321758096caec8011 100644 --- a/pay-java-ali/pom.xml +++ b/pay-java-ali/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.3-SNAPSHOT + 2.14.8 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 3eef8c4b7ae2d1698ed462ce1b92766fe7c77871..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,21 +1,34 @@ 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 - *

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

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

+ * 以下证书签名相关触发前提是 {@link BasePayConfigStorage#isCertSign}等于true的情况。不然走的就是普通的方式 */ public class AliPayConfigStorage extends BasePayConfigStorage { + /** + * ISV代商户代用,指定appAuthToken + */ + private String appAuthToken; /** * 商户应用id */ - private String appid; + private String appId; /** * 商户签约拿到的pid,partner_id的简称,合作伙伴身份等同于 partner */ @@ -27,15 +40,63 @@ public class AliPayConfigStorage extends BasePayConfigStorage { private String seller; - public void setAppid(String appid) { - this.appid = appid; + /** + * 应用公钥证书 + */ + private Object merchantCert; + + /** + * 支付宝公钥证书 + */ + 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; + return appId; + } + + /** + * 应用id + * 纠正名称 + * + * @return 应用id + */ + @Override + public String getAppId() { + return appId; } + public void setAppId(String appId) { + this.appId = appId; + } @Override public String getPid() { @@ -55,5 +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 9b7aa7d4e08e502a426d57489933a26fababec74..41ba432f4ded7478c5ccb0ae3e59a817253e77e8 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,73 +1,75 @@ 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.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.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"; - - private static final String SIGN = "sign"; +public class AliPayService extends BasePayService implements TransferService, AliPayServiceInf { - private static final String SUCCESS_CODE = "10000"; - - private static final String CODE = "code"; - /** - * 附加参数 - */ - private static final String PASSBACK_PARAMS = "passback_params"; - /** - * 产品代码 - */ - private static final String PRODUCT_CODE = "product_code"; - /** - * 返回地址 - */ - private static final String RETURN_URL = "return_url"; /** - * 请求内容 - */ - private static final String BIZ_CONTENT = "biz_content"; - /** - * 应用授权概述 + * api服务地址,默认为国内 */ - private static final String APP_AUTH_TOKEN = "app_auth_token"; - /** - * 收款方信息 - */ - private static final String PAYEE_INFO = "payee_info"; + private String apiServerUrl; /** * 获取对应的请求地址 @@ -76,8 +78,12 @@ public class AliPayService extends BasePayService { */ @Override public String getReqUrl(TransactionType transactionType) { - return payConfigStorage.isTest() ? DEV_REQ_URL : HTTPS_REQ_URL; + if (StringUtils.isNotEmpty(apiServerUrl)) { + return apiServerUrl; + } + return payConfigStorage.isTest() ? AliPayConst.DEV_REQ_URL : HTTPS_REQ_URL; } + /** * 获取对应的请求地址 * @@ -88,12 +94,24 @@ public class AliPayService extends BasePayService { } + /** + * 设置支付配置 + * + * @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); } @@ -103,18 +121,30 @@ public class AliPayService extends BasePayService { * @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)); } + /** * 根据反馈回来的信息,生成签名结果 * @@ -122,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); } @@ -161,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; @@ -183,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); + } /** * 支付宝创建订单信息 @@ -192,18 +242,17 @@ 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()); @@ -211,21 +260,32 @@ public class AliPayService extends BasePayService { case PAGE: 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"); break; case MINAPP: - bizContent.put("extend_params", order.getAddition()); + bizContent.put(PASSBACK_PARAMS, order.getAddition()); + bizContent.put("op_app_id", order.getAttrForString("op_app_id")); bizContent.put("buyer_id", order.getOpenid()); - bizContent.put(PRODUCT_CODE, "FACE_TO_FACE_PAYMENT"); + bizContent.put(PRODUCT_CODE, "JSAPI_PAY"); break; case BAR_CODE: case WAVE_CODE: @@ -236,12 +296,45 @@ public class AliPayService extends BasePayService { break; } - if (null != order.getExpirationTime()) { - bizContent.put(order.getTransactionType() == AliTransactionType.SWEEPPAY ? "qr_code_timeout_express" : "timeout_express", DateUtils.minutesRemaining(order.getExpirationTime()) + "m"); - } - bizContent.putAll(order.getAttrs()); + + + setExpirationTime(bizContent, order); + + loadAddition(bizContent, order); orderInfo.put(BIZ_CONTENT, JSON.toJSONString(bizContent)); - return preOrderHandler(orderInfo, order); + return preOrderHandler(orderInfo, order); + } + + private void loadAddition(Map bizContent, PayOrder order) { + OrderParaStructure.loadParameters(bizContent, AliPayConst.EXTEND_PARAMS, order); + OrderParaStructure.loadParameters(bizContent, AliPayConst.BUSINESS_PARAMS, order); + OrderParaStructure.loadParameters(bizContent, AliPayConst.DISCOUNTABLE_AMOUNT, order); + OrderParaStructure.loadParameters(bizContent, AliPayConst.UNDISCOUNTABLE_AMOUNT, order); + OrderParaStructure.loadParameters(bizContent, AliPayConst.STORE_ID, order); + OrderParaStructure.loadParameters(bizContent, AliPayConst.ALIPAY_STORE_ID, order); + OrderParaStructure.loadParameters(bizContent, AliPayConst.ENABLE_PAY_CHANNELS, order); + OrderParaStructure.loadParameters(bizContent, AliPayConst.DISABLE_PAY_CHANNELS, order); + OrderParaStructure.loadParameters(bizContent, AliPayConst.QUERY_OPTIONS, order); + OrderParaStructure.loadParameters(bizContent, AliPayConst.AGREEMENT_SIGN_PARAMS, order); + } + + private void setExpirationTime(Map bizContent, PayOrder order) { + if (null == order.getExpirationTime()) { + return; + } + 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: + case MINAPP: + bizContent.put("time_expire", DateUtils.formatDate(order.getExpirationTime(), DateUtils.YYYY_MM_DD_HH_MM_SS)); + break; + default: + } } /** @@ -250,16 +343,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()); + } + } + /** * 获取输出消息,用户返回给支付端 @@ -289,7 +408,8 @@ public class AliPayService extends BasePayService { public String toPay(PayOrder order) { if (null == order.getTransactionType()) { order.setTransactionType(AliTransactionType.PAGE); - } else if (order.getTransactionType() != AliTransactionType.PAGE && order.getTransactionType() != AliTransactionType.WAP) { + } + else if (order.getTransactionType() != AliTransactionType.PAGE && order.getTransactionType() != AliTransactionType.WAP) { throw new PayErrorException(new PayException("-1", "错误的交易类型:" + order.getTransactionType())); } return super.toPay(order); @@ -307,7 +427,7 @@ 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(""); @@ -315,7 +435,6 @@ public class AliPayService extends BasePayService { } - /** * 获取输出二维码信息, * @@ -323,7 +442,7 @@ public class AliPayService extends BasePayService { * @return 返回二维码信息,,支付时需要的 */ @Override - public String getQrPay(PayOrder order){ + public String getQrPay(PayOrder order) { order.setTransactionType(AliTransactionType.SWEEPPAY); Map orderInfo = orderInfo(order); //预订单 @@ -336,6 +455,31 @@ public class AliPayService extends BasePayService { } + /** + * 小程序支付,返回小程序所需的订单构建信息 + * + * @param order 发起支付的订单信息 + * @return 返回支付结果 + */ + @Override + public Map jsApi(PayOrder order) { + if (null == order.getTransactionType()) { + order.setTransactionType(AliTransactionType.MINAPP); + } + Map orderInfo = orderInfo(order); + Map body = new LinkedHashMap<>(); + body.put(BIZ_CONTENT, orderInfo.remove(BIZ_CONTENT)); + OrderParaStructure.loadParameters(body, APP_AUTH_TOKEN, (String) orderInfo.remove(APP_AUTH_TOKEN)); + //预订单 + JSONObject result = getHttpRequestTemplate().postForObject(getReqUrl() + "?" + UriVariables.getMapToParameters(orderInfo), body, JSONObject.class); + JSONObject response = result.getJSONObject("alipay_trade_create_response"); + if (!SUCCESS_CODE.equals(response.getString(CODE))) { + LOG.warn("下单失败:{}", response); + } + + return response; + } + /** * pos主动扫码付款(条码付) * @@ -344,9 +488,10 @@ public class AliPayService extends BasePayService { */ @Override public Map microPay(PayOrder order) { - if (null == order.getTransactionType()){ + 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){ + } + 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())); } @@ -355,7 +500,7 @@ public class AliPayService extends BasePayService { JSONObject result = getHttpRequestTemplate().postForObject(getReqUrl() + "?" + UriVariables.getMapToParameters(orderInfo), null, JSONObject.class); JSONObject response = result.getJSONObject("alipay_trade_pay_response"); if (!SUCCESS_CODE.equals(response.getString(CODE))) { - LOG.info("收款失败"); + LOG.info("收款失败{}", response); } return result; } @@ -363,10 +508,11 @@ public class AliPayService extends BasePayService { /** * 统一收单交易结算接口 + * * @param order 交易结算信息 * @return 结算结果 */ - public Map settle(OrderSettle order){ + public Map settle(OrderSettle order) { //获取公共参数 Map parameters = getPublicParameters(AliTransactionType.SETTLE); setAppAuthToken(parameters, order.getAttrs()); @@ -391,6 +537,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); + } + /** * 交易关闭接口 @@ -404,6 +573,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); + } + /** * 支付交易返回失败或支付系统超时,调用该接口撤销交易。 * 如果此订单用户支付失败,支付宝系统会将此订单关闭;如果用户支付成功,支付宝系统会将此订单资金退还给用户。 @@ -421,41 +601,63 @@ public class AliPayService extends BasePayService { /** * 设置支付宝授权Token + * * @param parameters 参数 - * @param attrs 订单属性 - * @return 参数 + * @param attrs 订单属性 */ - private void setAppAuthToken(Map parameters, Map attrs) { - setParameters(parameters, APP_AUTH_TOKEN, (String) attrs.remove(APP_AUTH_TOKEN)); + 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())); - bizContent.putAll(refundOrder.getAttrs()); + 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; } - /** * 查询退款 * @@ -468,10 +670,9 @@ public class AliPayService extends BasePayService { 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()); - } - bizContent.putAll(refundOrder.getAttrs()); + 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)); //设置签名 @@ -483,27 +684,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 支付平台订单号或者账单类型, 具体请 @@ -512,8 +728,6 @@ public class AliPayService extends BasePayService { * @param transactionType 交易类型 * @return 返回支付方对应接口的结果 */ - - @Override public Map secondaryInterface(Object tradeNoOrBillDate, String outTradeNoBillType, TransactionType transactionType) { if (transactionType == AliTransactionType.REFUND) { @@ -522,7 +736,7 @@ 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())); } @@ -535,7 +749,7 @@ public class AliPayService extends BasePayService { //设置签名 setSign(parameters); - return requestTemplate.getForObject(getReqUrl() + "?" + UriVariables.getMapToParameters(parameters), JSONObject.class); + return requestTemplate.getForObject(getReqUrl(transactionType) + "?" + UriVariables.getMapToParameters(parameters), JSONObject.class); } /** @@ -555,11 +769,11 @@ public class AliPayService extends BasePayService { bizContent.put("out_biz_no", order.getOutNo()); bizContent.put("trans_amount", order.getAmount()); transferType.setAttr(bizContent, order); - setParameters(bizContent, "order_title", order); - setParameters(bizContent, "original_order_id", order); + OrderParaStructure.loadParameters(bizContent, "order_title", order); + OrderParaStructure.loadParameters(bizContent, "original_order_id", order); setPayeeInfo(bizContent, order); bizContent.put("remark", order.getRemark()); - setParameters(bizContent, "business_params", order); + OrderParaStructure.loadParameters(bizContent, "business_params", order); //设置请求参数的集合 parameters.put(BIZ_CONTENT, JSON.toJSONString(bizContent)); @@ -568,24 +782,49 @@ public class AliPayService extends BasePayService { return getHttpRequestTemplate().postForObject(getReqUrl() + "?" + UriVariables.getMapToParameters(parameters), null, JSONObject.class); } - private Map setPayeeInfo(Map bizContent, Order order){ + /** + * 转账查询 + * + * @param assistOrder 辅助交易订单 + * @return 对应的转账订单 + */ + @Override + public Map transferQuery(AssistOrder assistOrder) { + //获取公共参数 + Map parameters = getPublicParameters(AliTransferType.TRANS_QUERY); + + Map bizContent = new TreeMap(); + 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){ + if (attr instanceof String) { bizContent.put(PAYEE_INFO, attr); } - if (attr instanceof TreeMap){ + if (attr instanceof TreeMap) { bizContent.put(PAYEE_INFO, attr); } - if (attr instanceof Map){ - Map payeeInfo = new TreeMap((Map)attr); + if (attr instanceof Map) { + Map payeeInfo = new TreeMap((Map) attr); bizContent.put(PAYEE_INFO, payeeInfo); } return bizContent; } - /** * 转账查询 * @@ -595,20 +834,8 @@ public class AliPayService extends BasePayService { */ @Override public Map transferQuery(String outNo, String tradeNo) { - //获取公共参数 - 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); - } - //设置请求参数的集合 - parameters.put(BIZ_CONTENT, JSON.toJSONString(bizContent)); - //设置签名 - setSign(parameters); - return getHttpRequestTemplate().postForObject(getReqUrl() + "?" + UriVariables.getMapToParameters(parameters), null, JSONObject.class); + return transferQuery(new AssistOrder(tradeNo, outNo)); } @@ -655,4 +882,49 @@ public class AliPayService extends BasePayService { 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..22dd82f1993f628434313848bb54903d859fb4db --- /dev/null +++ b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliPayConst.java @@ -0,0 +1,88 @@ +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 BUSINESS_PARAMS = "business_params"; + public static final String DISCOUNTABLE_AMOUNT = "discountable_amount"; + public static final String UNDISCOUNTABLE_AMOUNT = "undiscountable_amount"; + public static final String STORE_ID = "store_id"; + public static final String ENABLE_PAY_CHANNELS = "enable_pay_channels"; + public static final String DISABLE_PAY_CHANNELS = "disable_pay_channels"; + public static final String QUERY_OPTIONS = "query_options"; + public static final String AGREEMENT_SIGN_PARAMS = "agreement_sign_params"; + public static final String ALIPAY_STORE_ID = "alipay_store_id"; + public static final String BIZ_TYPE = "biz_type"; + public static final String REFUND_REASON = "refund_reason"; + 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/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 13e4f12b006459c10d694799900de601cab9cd34..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 @@ -84,6 +84,11 @@ public enum AliTransactionType implements TransactionType { * 退款查询 */ REFUNDQUERY("alipay.trade.fastpay.refund.query"), + /** + * 收单退款冲退完成通知 + * 退款存在退到银行卡场景下时,收单会根据银行回执消息发送退款完成信息 + */ + REFUND_DEPOSITBACK_COMPLETED ("alipay.trade.refund.depositback.completed"), /** * 下载对账单 */ 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/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/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 b266376b63a73c354a386e8bd540e69a6ab381b0..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("支付宝公钥"); + aliPayConfigStorage.setAppId("应用id"); + //普通公钥方式与证书公钥方式为两者取其一的方式 + 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); //获取扫码付的二维码 diff --git a/pay-java-baidu/pom.xml b/pay-java-baidu/pom.xml index 5e39ceb1ad1d37095ddc07711857297126ed53b8..650c6ccea4763c0a3b1f533b59c1efda4dda02ee 100644 --- a/pay-java-baidu/pom.xml +++ b/pay-java-baidu/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.3-SNAPSHOT + 2.14.8 4.0.0 pay-java-baidu @@ -17,11 +17,7 @@ pay-java-common - - org.junit.jupiter - junit-jupiter - test - + 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 index f1496e7592377f2cd360b81845999851ac75afb4..61ee1cdba16620e4f62a0fc2ac4f044c7a238f0d 100644 --- 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 @@ -3,51 +3,64 @@ 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() { - throw new UnsupportedOperationException("不支持"); + return getDealId(); } - + public String getDealId() { return dealId; } - + public void setDealId(String dealId) { this.dealId = dealId; } - + public String getAppKey() { - return this.getKeyPrivate(); + return this.appid; } - + public void setAppKey(String appKey) { - setKeyPrivate(appKey); + this.setAppid(appKey); } - + @Override public String getKeyPublic() { - return super.getKeyPrivate(); + return keyPublic; } - + @Override public void setKeyPublic(String keyPublic) { - super.setKeyPublic(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 index 87e4c1cc13c501628b40e63ec0f96a302ab74c85..42c064cb565e71b89d5a3b33e24b530e8878f3b3 100644 --- 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 @@ -1,26 +1,48 @@ 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.BaiduRefundOrder; 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.*; +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; -import java.math.BigDecimal; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - public class BaiduPayService extends BasePayService { public static final String APP_KEY = "appKey"; @@ -43,6 +65,12 @@ public class BaiduPayService extends BasePayService { 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); } @@ -58,32 +86,115 @@ public class BaiduPayService extends BasePayService { * @param params 回调回来的参数集 * @return 结果 */ + @Deprecated @Override public boolean verify(Map params) { - if (!RESPONSE_SUCCESS.equals(params.get(RESPONSE_STATUS))) { + + 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; } - return signVerify(params, String.valueOf(params.get(RSA_SIGN))) && verifySource(String.valueOf(params.get(TP_ORDER_ID))); + 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 params 参数集 - * @param sign 签名原文 - * @return 结果 + * @param publicKey 公钥原始字符串 + * @return X509标准公钥 + * @throws InvalidKeySpecException InvalidKeySpecException + * @throws NoSuchAlgorithmException NoSuchAlgorithmException */ - @Override - public boolean signVerify(Map params, String sign) { - String rsaSign = String.valueOf(params.get(RSA_SIGN)); - String targetRsaSign = getRsaSign(params, RSA_SIGN); - LOG.debug("百度返回的签名: " + rsaSign + " 本地产生的签名: " + targetRsaSign); - return StringUtils.equals(rsaSign, targetRsaSign); + 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)); } - @Override - public boolean verifySource(String id) { - return true; + /** + * 对输入参数进行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); } /** @@ -94,8 +205,9 @@ public class BaiduPayService extends BasePayService { */ @Override public Map orderInfo(PayOrder order) { - Map params = getUseOrderInfoParams(order); - String rsaSign = getRsaSign(params, RSA_SIGN); + LOG.info("百度支付配置:" + JSON.toJSONString(payConfigStorage)); + Map params = this.getUseOrderInfoParams(order); + String rsaSign = this.getRsaSign(params, RSA_SIGN); params.put(RSA_SIGN, rsaSign); return params; } @@ -109,7 +221,7 @@ public class BaiduPayService extends BasePayService { String appKey = payConfigStorage.getAppKey(); Map result = new HashMap<>(); result.put(APP_KEY, appKey); - result.put(APP_ID, payConfigStorage.getAppid()); + result.put(APP_ID, payConfigStorage.getAppId()); return result; } @@ -125,12 +237,15 @@ public class BaiduPayService extends BasePayService { String appKey = payConfigStorage.getAppKey(); String dealId = payConfigStorage.getDealId(); result.put(APP_KEY, appKey); - result.put(TP_ORDER_ID, payOrder.getTradeNo()); 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())); - result.put(TOTAL_AMOUNT, String.valueOf(Util.conversionAmount(order.getPrice()))); + + LOG.info("百度支付 getUseOrderInfoParams:" + JSON.toJSONString(result)); return result; } @@ -296,6 +411,17 @@ public class BaiduPayService extends BasePayService { 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); + } + /** * 百度不支持该操作 * @@ -307,7 +433,16 @@ public class BaiduPayService extends BasePayService { public Map close(String tradeNo, String outTradeNo) { throw new UnsupportedOperationException("不支持该操作"); } - + /** + * 交易关闭接口 + * + * @param assistOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 + */ + @Override + public Map close(AssistOrder assistOrder){ + throw new UnsupportedOperationException("不支持该操作"); + } /** * 退款 @@ -316,7 +451,7 @@ public class BaiduPayService extends BasePayService { * @return 退款结果 */ @Override - public Map refund(RefundOrder refundOrder) { + public BaseRefundResult refund(RefundOrder refundOrder) { Map parameters = getUseQueryPay(); BaiduTransactionType transactionType = BaiduTransactionType.APPLY_REFUND; parameters.put(METHOD, transactionType.getMethod()); @@ -329,7 +464,53 @@ public class BaiduPayService extends BasePayService { parameters.put("bizRefundBatchId", refundOrder.getRefundNo()); 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); + 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; + } + }; } @@ -355,34 +536,48 @@ public class BaiduPayService extends BasePayService { } /** - * 下载资金账单 + * 下载订单对账单 * * @param billDate 账单时间:日账单格式为yyyy-MM-dd * @param accessToken 用户token * @return 对账单 */ @Override - public Map downloadbill(Date billDate, String accessToken) { + 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", accessToken); - parameters.put("billTime", DateUtils.formatDay(billDate)); - return requestTemplate.getForObject(String.format("%s?%s", getReqUrl(BaiduTransactionType.DOWNLOAD_BILL), + 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 账单结果 */ - public Map downloadOrderBill(Date billDate, String accessToken) { - Map parameters = new HashMap<>(); - parameters.put("access_token", accessToken); - parameters.put("billTime", DateUtils.formatDay(billDate)); - return requestTemplate.getForObject(String.format("%s?%s", getReqUrl(BaiduTransactionType.DOWNLOAD_ORDER_BILL), - UriVariables.getMapToParameters(parameters)), JSONObject.class); + @Deprecated + public Map downloadMoneyBill(Date billDate, String accessToken) { + return downloadBill(billDate, new BaiduBillType(accessToken, BaiduTransactionType.DOWNLOAD_BILL.name())); } /** @@ -393,7 +588,6 @@ public class BaiduPayService extends BasePayService { * @param transactionType 交易类型 * @return 结果 */ - @Override public Map secondaryInterface(Object orderId, String siteId, TransactionType transactionType) { @@ -427,7 +621,17 @@ public class BaiduPayService extends BasePayService { * @return 签名结果 */ private String getRsaSign(Map params, String... ignoreKeys) { - String waitSignVal = SignUtils.parameterText(params, "&", false, ignoreKeys); - return SignUtils.valueOf(payConfigStorage.getSignType()).createSign(waitSignVal, payConfigStorage.getKeyPrivate(), payConfigStorage.getInputCharset()); + 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/BaiduRefundOrder.java b/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/bean/BaiduRefundOrder.java index 91d6510a0a96c38d9d4ca83f452f6f1c5286b2f8..50974eb0fee555dd83f8c8ce115facef2b596031 100644 --- 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 @@ -54,6 +54,7 @@ public class BaiduRefundOrder extends RefundOrder { /** * 业务方退款批次id,退款业务流水唯一编号,发起部分退款时必传 + * @param bizRefundBatchId 业务方退款批次id */ public void setBizRefundBatchId(String bizRefundBatchId) { setRefundNo(bizRefundBatchId); diff --git a/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/package-info.java b/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/package-info.java deleted file mode 100644 index e9370e2948a64a52039a149a6744fcedaf91a477..0000000000000000000000000000000000000000 --- a/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package com.egzosn.pay.baidu; \ No newline at end of file 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 index 6f9b5c2ba45dd49c400c2b2622dace93c91d6ec6..43f31a129c4ee4ccb59afdede357743da743e212 100644 --- 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 @@ -1,7 +1,5 @@ package com.egzosn.pay.baidu.api; -import org.junit.jupiter.api.Test; - /** * Created by hocgin on 2019/11/24. * email: hocgin@gmail.com @@ -9,16 +7,16 @@ import org.junit.jupiter.api.Test; * @author hocgin */ public class BaiduPayServiceTest { - - @Test - public void orderInfo() { + + + 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); - // payService.refund() } + } diff --git a/pay-java-common/pom.xml b/pay-java-common/pom.xml index 1c79c0029bf7486ba9322383dc65374337584045..93de96ee5f72b4945fbce141c742d531b509a8f4 100644 --- a/pay-java-common/pom.xml +++ b/pay-java-common/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.3-SNAPSHOT + 2.14.8 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 39641396637e17db6a8abf7982585e06d566dd68..b559fd5732ed5b4daeab3890fe9f4bbda394af4b 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,16 +1,13 @@ package com.egzosn.pay.common.api; -import com.egzosn.pay.common.bean.CertStoreType; -import com.egzosn.pay.common.bean.MsgType; -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
@@ -24,10 +21,7 @@ public abstract class BasePayConfigStorage implements PayConfigStorage {
      * 应用私钥,rsa_private pkcs8格式 生成签名时使用
      */
     private String keyPrivate;
-    /**
-     * 应用私钥证书,rsa_private pkcs8格式 生成签名时使用
-     */
-    private String keyPrivateCertPwd;
+
     /**
      * 支付平台公钥(签名校验使用)
      */
@@ -47,7 +41,7 @@ public abstract class BasePayConfigStorage implements PayConfigStorage {
     /**
      * 字符类型
      */
-    private String inputCharset;
+    private String inputCharset = "utf-8";
 
 
     /**
@@ -55,24 +49,19 @@ public abstract class BasePayConfigStorage implements PayConfigStorage {
      */
     private String payType;
 
-    /**
-     * 消息来源类型
-     */
-    private MsgType msgType;
-
 
     /**
      * 访问令牌 每次请求其他方法都要传入的值
      */
-    private String accessToken;
+    private volatile String accessToken;
     /**
      * access token 到期时间时间戳
      */
-    private long expiresTime;
+    private volatile long expiresTime;
     /**
      * 授权码锁
      */
-    private Lock accessTokenLock = new ReentrantLock();
+    private Lock accessTokenLock;
     /**
      * 是否为沙箱环境,默认为正式环境
      */
@@ -81,8 +70,12 @@ public abstract class BasePayConfigStorage implements PayConfigStorage {
     /**
      * 是否为证书签名
      */
-    private boolean isCertSign = false;
+    private boolean certSign = false;
 
+    /**
+     * 配置附加信息,可用于预设未提供的参数,这里会覆盖以上所有的配置信息,
+     */
+    private volatile Map attr;
 
     @Override
     public Object getAttach() {
@@ -102,14 +95,6 @@ public abstract class BasePayConfigStorage implements PayConfigStorage {
         this.keyPrivate = keyPrivate;
     }
 
-    @Override
-    public String getKeyPrivateCertPwd() {
-        return keyPrivateCertPwd;
-    }
-
-    public void setKeyPrivateCertPwd(String keyPrivateCertPwd) {
-        this.keyPrivateCertPwd = keyPrivateCertPwd;
-    }
 
     @Override
     public String getKeyPublic() {
@@ -165,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;
     }
@@ -197,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
@@ -207,7 +198,10 @@ public abstract class BasePayConfigStorage implements PayConfigStorage {
         this.expiresTime = expiresTime;
     }
 
-    @Override
+
+    /**
+     * 强制将access token过期掉
+     */
     public void expireAccessToken() {
         this.expiresTime = 0;
     }
@@ -239,12 +233,34 @@ public abstract class BasePayConfigStorage implements PayConfigStorage {
     }
 
     public boolean isCertSign() {
-        return isCertSign;
+        return certSign;
     }
 
     public void setCertSign(boolean certSign) {
-        isCertSign = certSign;
+        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 261c7942dd76955cab29f6ee9ddb43978175ed4e..c36cd89e74b7090de08c17fdb8175986d43f3a84 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,32 +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.awt.image.BufferedImage;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-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()); + protected final Logger LOG = LoggerFactory.getLogger(getClass()); protected PC payConfigStorage; protected HttpRequestTemplate requestTemplate; @@ -40,8 +59,9 @@ public abstract class BasePayService implements Pay /** * 支付消息拦截器 */ - protected List interceptors = new ArrayList(); - ; + protected List> interceptors = new ArrayList>(); + + private Charset inputCharset = Consts.UTF_8; /** * 设置支付配置 @@ -51,6 +71,10 @@ public abstract class BasePayService implements Pay @Override public BasePayService setPayConfigStorage(PC payConfigStorage) { this.payConfigStorage = payConfigStorage; + + if (StringUtils.isNotEmpty(payConfigStorage.getInputCharset())) { + this.inputCharset = Charset.forName(payConfigStorage.getInputCharset()); + } return this; } @@ -84,6 +108,7 @@ public abstract class BasePayService implements Pay public BasePayService(PC payConfigStorage, HttpConfigStorage configStorage) { setPayConfigStorage(payConfigStorage); setRequestTemplateConfigStorage(configStorage); + } @@ -98,13 +123,15 @@ public abstract class BasePayService implements Pay 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); + } + catch (UnsupportedEncodingException e) { + LOG.error("", e); } return base64ClientID; } + /** * 创建签名 * @@ -137,13 +164,21 @@ public abstract class BasePayService implements Pay */ @Override public String toPay(O order) { - Map orderInfo = orderInfo(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 预订单类型 + * @param 预订单类型 * @return 对应app所需参数信息 */ @Override @@ -159,7 +194,18 @@ public abstract class BasePayService implements Pay */ @Override public BufferedImage genQrPay(O order) { - return MatrixToImageWriter.writeInfoToJpgBuff(getQrPay(order)); + return MatrixToImageWriter.writeInfoToJpgBuff(getQrPay(order)); + } + + /** + * 小程序支付,返回小程序所需的订单构建信息 + * + * @param order 发起支付的订单信息 + * @return 返回支付结果 + */ + @Override + public Map jsApi(O order) { + return Collections.emptyMap(); } /** @@ -171,30 +217,38 @@ public abstract class BasePayService implements Pay */ @Override 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(); + Map params = new TreeMap<>(); for (Map.Entry entry : parameterMap.entrySet()) { String name = entry.getKey(); String[] values = entry.getValue(); - String valueStr = ""; + StringBuilder sb = new StringBuilder(); for (int i = 0, len = values.length; i < len; i++) { - valueStr += (i == len - 1) ? values[i] : values[i] + ","; + sb.append(values[i]).append((i == len - 1) ? "" : ','); } + String valueStr = sb.toString(); 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); + 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); } - /** * 交易查询接口,带处理器 * @@ -219,6 +273,7 @@ public abstract class BasePayService implements Pay * @param 返回类型 * @return 返回支付方交易关闭后的结果 */ + @Deprecated @Override public T close(String tradeNo, String outTradeNo, Callback callback) { return callback.perform(close(tradeNo, outTradeNo)); @@ -235,7 +290,7 @@ public abstract class BasePayService implements Pay */ @Override public T cancel(String tradeNo, String outTradeNo, Callback callback) { - return callback.perform(close(tradeNo, outTradeNo)); + return callback.perform(cancel(tradeNo, outTradeNo)); } /** @@ -247,11 +302,10 @@ public abstract class BasePayService implements Pay */ @Override public Map cancel(String tradeNo, String outTradeNo) { - return Collections.EMPTY_MAP; + return Collections.emptyMap(); } - /** * 申请退款接口 * @@ -263,11 +317,10 @@ public abstract class BasePayService implements Pay @Override public T refund(RefundOrder refundOrder, Callback callback) { - return callback.perform(refund(refundOrder)); + return callback.perform(refund(refundOrder).getAttrs()); } - /** * 查询退款 * @@ -282,30 +335,15 @@ public abstract class BasePayService implements Pay } /** - * 目前只支持日账单 + * 下载对账单 * - * @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(); } /** @@ -329,7 +367,7 @@ public abstract class BasePayService implements Pay */ @Override public Map transfer(TransferOrder order) { - return new HashMap<>(0); + return Collections.emptyMap(); } /** @@ -341,7 +379,7 @@ public abstract class BasePayService implements Pay */ @Override public Map transferQuery(String outNo, String tradeNo) { - return new HashMap<>(0); + return Collections.emptyMap(); } /** @@ -396,6 +434,7 @@ public abstract class BasePayService implements Pay interceptors.add(interceptor); } + /** * 将请求参数或者请求流转化为 Map * @@ -403,17 +442,29 @@ 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 = this.createMessage(data); - Map context = new HashMap(); + PayMessage payMessage = this.createMessage(noticeParams.getBody()); + Map context = new HashMap<>(); for (PayMessageInterceptor interceptor : interceptors) { if (!interceptor.intercept(payMessage, context, this)) { return successPayOutMessage(payMessage); @@ -436,28 +487,41 @@ public abstract class BasePayService implements Pay /** * 预订单回调处理器,用于订单信息的扩展 * 签名之前使用 - * 如果需要进行扩展请重写该方法即可 + * 如果需要进行扩展请重写该方法即可 + * * @param orderInfo 预订单信息 * @param orderInfo 订单信息 * @return 处理后订单信息 */ - public Map preOrderHandler(Map orderInfo, O payOrder){ + @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) { - if (StringUtils.isNotEmpty(value)) { - parameters.put(key, value); - } - return parameters; + 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) { - Object attr = order.getAttr(key); - if (null != attr && !"".equals(attr)) { - parameters.put(key, attr); - } - return parameters; + 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 dbb537b5ca41be609a54f4862f15c3e92f3e9b9f..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 @@ -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
index cb119a42bbecd3fdb0e1c7035a70324ed1b856e3..dde73fb1880b24d699cda8cc91f150e48aade87e 100644
--- 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
@@ -5,9 +5,10 @@ import java.io.InputStream;
 
 /**
  * 证书存储方式
+ *
  * @author egan
- *         email egzosn@gmail.com
- *         date 2019/10/13.23:09
+ * email egzosn@gmail.com
+ * date 2019/10/13.23:09
  */
 public interface CertStore {
 
@@ -18,5 +19,5 @@ public interface CertStore {
      * @return 输入流
      * @throws IOException 找不到文件异常
      */
-    public abstract InputStream getInputStream(Object cert) 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 496b7a3a76192af61d46bae403b43cfefd54ff05..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;
 
 /**
  * 默认处理支付回调消息的处理器接口
@@ -21,7 +22,7 @@ import java.util.Map;
  */
 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 8abdb230e57e650b3a20c21818687c9b2203051e..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,142 +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(); + 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 过期时间,时间戳 */ @@ -144,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/PayMessageRouter.java b/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayMessageRouter.java index 24e849b310ee866697e9c1fc28238fcdc25f104b..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,11 +1,5 @@ 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; @@ -14,6 +8,13 @@ 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处理
@@ -43,8 +44,8 @@ import java.util.concurrent.Future;
  * @author egan
  */
 public class PayMessageRouter {
+    protected final Logger LOG = LoggerFactory.getLogger(PayMessageRouter.class);
 
-    protected final Log LOG = LogFactory.getLog(PayMessageRouter.class);
     /**
      * 异步线程大小
      */
@@ -124,15 +125,13 @@ public class PayMessageRouter {
      * 处理支付消息
      *
      * @param payMessage 支付消息
-     * @param storage 支付配置
+     * @param storage    支付配置
      * @return 支付输出结果
      */
     public PayOutMessage route(Map payMessage, PayConfigStorage storage) {
         PayMessage message = payService.createMessage(payMessage);
         message.setPayType(storage.getPayType());
-        if (null != storage.getMsgType()){
-            message.setMsgType(storage.getMsgType().name());
-        }
+
         return route(message);
     }
 
@@ -172,7 +171,8 @@ public class PayMessageRouter {
                             }
                         })
                 );
-            } else {
+            }
+            else {
                 res = rule.service(payMessage, payService, exceptionHandler);
                 // 在同步操作结束,session访问结束
                 if (LOG.isDebugEnabled()) {
@@ -190,9 +190,11 @@ public class PayMessageRouter {
                             future.get();
                             LOG.debug("End session access: async=true, fromPay=" + payMessage.getFromPay());
 
-                        } catch (InterruptedException e) {
+                        }
+                        catch (InterruptedException e) {
                             LOG.error("Error happened when wait task finish", e);
-                        } catch (ExecutionException e) {
+                        }
+                        catch (ExecutionException e) {
                             LOG.error("Error happened when wait task finish", e);
                         }
                     }
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 910fc7e80acbbad7bcb51438e362989110a2d92a..f01b9b84e05b81942f35046c488cd17d7ce297e3 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,20 +1,30 @@
 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.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
  *         
@@ -54,59 +64,50 @@ public interface PayService { /** * 回调校验 - * + * 已过时方法,详情{@link #verify(NoticeParams)} * @param params 回调回来的参数集 * @return 签名校验 true通过 - */ - - boolean verify(Map params); - /** - * 签名校验 - * 后面版本废弃 - * @param params 参数集 - * @param sign 签名原文 - * @return 签名校验 true通过 + * @see #verify(NoticeParams) */ @Deprecated - boolean signVerify(Map params, String sign); - + boolean verify(Map params); /** - * 支付宝需要,微信是否也需要再次校验来源,进行订单查询 - * 校验数据来源 - * 后面版本废弃 - * @param id 业务id, 数据的真实性. - * @return true通过 + * 回调校验 + * + * @param params 回调回来的参数集 + * @return 签名校验 true通过 */ - @Deprecated - boolean verifySource(String id); + boolean verify(NoticeParams params); /** * 返回创建的订单信息 * * @param order 支付订单 + * @param 预订单类型 * @return 订单信息 - * @param 预订单类型 * @see PayOrder 支付订单信息 */ - Map orderInfo(O order); + Map orderInfo(O order); /** * 页面转跳支付, 返回对应页面重定向信息 * * @param order 订单信息 - * @param 预订单类型 + * @param 预订单类型 * @return 对应页面重定向信息 */ - String toPay(O order); + String toPay(O order); + /** * app支付 + * * @param order 订单信息 - * @param 预订单类型 + * @param 预订单类型 * @return 对应app所需参数信息 */ - Map app(O order); + Map app(O order); /** * 创建签名 @@ -118,16 +119,25 @@ public interface PayService { String createSign(String content, String characterEncoding); - /** * 将请求参数或者请求流转化为 Map * * @param parameterMap 请求参数 * @param is 请求流 * @return 获得回调的请求参数 + * @see #getNoticeParams(NoticeRequest) */ + @Deprecated Map getParameter2Map(Map parameterMap, InputStream is); + /** + * 将请求参数或者请求流转化为 Map + * + * @param request 通知请求 + * @return 获得回调的请求参数 + */ + NoticeParams getNoticeParams(NoticeRequest request); + /** * 获取输出消息,用户返回给支付端 * @@ -161,27 +171,37 @@ public interface PayService { * 获取输出二维码,用户返回给支付端, * * @param order 发起支付的订单信息 - * @param 预订单类型 + * @param 预订单类型 * @return 返回图片信息,支付时需要的 */ - BufferedImage genQrPay(O order); + BufferedImage genQrPay(O order); + /** * 获取输出二维码信息, * * @param order 发起支付的订单信息 - * @param 预订单类型 + * @param 预订单类型 * @return 返回二维码信息,,支付时需要的 */ - String getQrPay(O order); + String getQrPay(O order); + /** + * 小程序支付,返回小程序所需的订单构建信息 + * + * @param order 发起支付的订单信息 + * @param 预订单类型 + * @return 返回支付结果 + */ + Map jsApi(O order); /** * 刷卡付,pos主动扫码付款(条码付) * 刷脸付 + * * @param order 发起支付的订单信息 - * @param 预订单类型 + * @param 预订单类型 * @return 返回支付结果 */ - Map microPay(O order); + Map microPay(O order); /** * 交易查询接口 @@ -189,7 +209,9 @@ public interface PayService { * @param tradeNo 支付平台订单号 * @param outTradeNo 商户单号 * @return 返回查询回来的结果集,支付方原值返回 + * @see #query(AssistOrder) */ + @Deprecated Map query(String tradeNo, String outTradeNo); /** @@ -201,17 +223,38 @@ public interface PayService { * @param 返回类型 * @return 返回查询回来的结果集 */ + @Deprecated T query(String tradeNo, String outTradeNo, Callback callback); + + /** + * 交易查询接口 + * + * @param assistOrder 查询条件 + * @return 返回查询回来的结果集,支付方原值返回 + */ + Map query(AssistOrder assistOrder); + + /** * 交易关闭接口 * * @param tradeNo 支付平台订单号 * @param outTradeNo 商户单号 * @return 返回支付方交易关闭后的结果 + * @see #close(AssistOrder) */ + @Deprecated Map close(String tradeNo, String outTradeNo); + /** + * 交易关闭接口 + * + * @param assistOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 + */ + Map close(AssistOrder assistOrder); + /** * 交易关闭接口 @@ -222,8 +265,10 @@ public interface PayService { * @param 返回类型 * @return 返回支付方交易关闭后的结果 */ + @Deprecated T close(String tradeNo, String outTradeNo, Callback callback); + /** * 交易交易撤销 * @@ -242,17 +287,17 @@ public interface PayService { * @param 返回类型 * @return 返回支付方交易撤销后的结果 */ + @Deprecated T cancel(String tradeNo, String outTradeNo, Callback callback); - /** * 申请退款接口 * * @param refundOrder 退款订单信息 * @return 返回支付方申请退款后的结果 */ - Map refund(RefundOrder refundOrder); + RefundResult refund(RefundOrder refundOrder); /** * 申请退款接口 @@ -262,10 +307,10 @@ public interface PayService { * @param 返回类型 * @return 返回支付方申请退款后的结果 */ + @Deprecated T refund(RefundOrder refundOrder, Callback callback); - /** * 查询退款 * @@ -282,53 +327,26 @@ public interface PayService { * @param 返回类型 * @return 返回支付方查询退款后的结果 */ + @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 返回支付方对应接口的结果 - */ - @Deprecated - Map secondaryInterface(Object tradeNoOrBillDate, String outTradeNoBillType, TransactionType transactionType); - - /** - * 通用查询接口 - * 接下来移除此方法 - * @param tradeNoOrBillDate 支付平台订单号或者账单日期, 具体请 类型为{@link String }或者 {@link Date },类型须强制限制,类型不对应则抛出异常{@link PayErrorException} - * @param outTradeNoBillType 商户单号或者 账单类型 - * @param transactionType 交易类型 - * @param callback 处理器 - * @param 返回类型 - * @return 返回支付方对应接口的结果 - */ - @Deprecated - T secondaryInterface(Object tradeNoOrBillDate, String outTradeNoBillType, TransactionType transactionType, Callback callback); + Map downloadBill(Date billDate, BillType billType); /** @@ -347,6 +365,7 @@ public interface PayService { * @param 返回类型 * @return 对应的转账结果 */ + @Deprecated T transfer(TransferOrder order, Callback callback); @@ -356,7 +375,9 @@ public interface PayService { * @param outNo 商户转账订单号 * @param tradeNo 支付平台转账订单号 * @return 对应的转账订单 + * @deprecated 替代{@link TransferService#transferQuery(com.egzosn.pay.common.bean.AssistOrder)} */ + @Deprecated Map transferQuery(String outNo, String tradeNo); /** @@ -368,22 +389,34 @@ public interface PayService { * @param 返回类型 * @return 对应的转账订单 */ + @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 request 请求参数 + * @return 获得回调响应信息 + */ + PayOutMessage payBack(NoticeRequest request); + + /** * 设置支付消息处理器,这里用于处理具体的支付业务 * * @param handler 消息处理器 - * 配合{@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 }进行实现 */ @@ -393,7 +426,7 @@ public interface PayService { * 设置支付消息处理器,这里用于处理具体的支付业务 * * @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 }进行实现 */ @@ -409,6 +442,7 @@ public interface PayService { /** * 创建消息 + * * @param message 支付平台返回的消息 * @return 支付消息对象 */ @@ -417,12 +451,14 @@ public interface PayService { /** * 预订单回调处理器,用于订单信息的扩展 * 签名之前使用 - * 如果需要进行扩展请重写该方法即可 + * 如果需要进行扩展请重写该方法即可 + * * @param orderInfo 商户平台预订单信息 - * @param payOrder 订单信息 - * @param 预订单类型 + * @param payOrder 订单信息 + * @param 预订单类型 * @return 处理后订单信息 */ - Map preOrderHandler(Map orderInfo, O payOrder); + @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 index d7b6895bbb1c4e7f9e6b1f8c2f4e83125a3e4dc8..4517b83207189e060c82ef0f1d71855ffea7c76d 100644 --- 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 @@ -1,10 +1,16 @@ 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; -import java.io.*; - /** * 证书存储类型 * @@ -15,7 +21,23 @@ import java.io.*; public enum CertStoreType implements CertStore { /** - * 路径,建议绝对路径 + * 无存储类型,表示无需要转换为输入流 + */ + NONE{ + /** + * 证书信息转化为对应的输入流 + * + * @param cert 证书信息 + * @return 输入流 + * @throws IOException 找不到文件异常 + */ + @Override + public InputStream getInputStream(Object cert) throws IOException { + return null; + } + }, + /** + * 文件路径,建议绝对路径 */ PATH { /** @@ -30,6 +52,22 @@ public enum CertStoreType implements CertStore { 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); + } + }, /** * 文件流转化成字符串存储至文件或者数据库中 */ @@ -98,14 +136,10 @@ public enum CertStoreType implements CertStore { Class clazz = Class.forName((String) beanClazz); CertStore certStore = (CertStore)clazz.newInstance(); return certStore.getInputStream(beanClazz); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InstantiationException e) { - e.printStackTrace(); + } catch (ReflectiveOperationException e) { + throw new PayErrorException(new PayException("证书获取异常", e.getMessage())); } - return null; + } }; 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/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 8806133470af377cc158c310bca3a3d4e2096078..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 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.
- * 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 index fd16e1c6df318f81b9fb5853d9d1c386d46c6688..0c06f367ab48c99a37c408b4fb709de398fd1a19 100644 --- 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 @@ -1,7 +1,5 @@ package com.egzosn.pay.common.bean; -import java.util.Map; - /** * 支付订单信息 * @@ -11,22 +9,7 @@ import java.util.Map; * date 2020/01/05 13:34 *
*/ -public interface Order { - - /** - * 获取订单属性 这里可用做覆盖已设置的订单信息属性,订单信息在签名前进行覆盖。 - * - * @return 属性 - */ - Map getAttrs(); - - /** - * 获取订单属性 这里可用做覆盖已设置的订单信息属性,订单信息在签名前进行覆盖。 - * - * @param key 属性名 - * @return 属性 - */ - Object getAttr(String key); +public interface Order extends Attrs { /** 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 1836758fab32d03a42fa9865ee1feeb30a476d8d..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,7 +16,6 @@ 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;
@@ -32,16 +29,14 @@ public class PayMessage implements Serializable {
         this.payMessage = payMessage;
     }
 
-    public PayMessage(Map payMessage, String payType, String msgType) {
+    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;
     }
@@ -50,14 +45,6 @@ public class PayMessage implements Serializable {
         this.payMessage = payMessage;
     }
 
-    public String getMsgType() {
-        return msgType;
-    }
-
-    public void setMsgType(String msgType) {
-        this.msgType = msgType;
-    }
-
 
     public String getPayType() {
         return payType;
@@ -72,7 +59,7 @@ public class PayMessage implements Serializable {
     }
 
     public void setTransactionType(String transactionType) {
-            this.transactionType = transactionType;
+        this.transactionType = transactionType;
     }
 
     public String getFromPay() {
@@ -90,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;
@@ -120,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();
@@ -137,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 35987abbae5698331c77d3f9c9b49f2a8f08df7b..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
@@ -1,11 +1,9 @@
 package com.egzosn.pay.common.bean;
 
-import com.egzosn.pay.common.util.str.StringUtils;
-
 import java.math.BigDecimal;
 import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
+
+import com.egzosn.pay.common.util.str.StringUtils;
 
 /**
  * 支付订单信息
@@ -16,7 +14,7 @@ import java.util.Map;
  *      date 2016/10/19 22:34
  *  
*/ -public class PayOrder implements Order { +public class PayOrder extends AssistOrder { /** * 商品名称 */ @@ -33,14 +31,7 @@ public class PayOrder implements Order { * 价格 */ private BigDecimal price; - /** - * 支付平台订单号,交易号 - */ - private String tradeNo; - /** - * 商户订单号 - */ - private String outTradeNo; + /** * 银行卡类型 */ @@ -48,10 +39,12 @@ public class PayOrder implements Order { /** * 设备信息 */ + @Deprecated private String deviceInfo; /** * 支付创建ip */ + @Deprecated private String spbillCreateIp; /** * 付款条码串,人脸凭证,有关支付代码相关的, @@ -61,12 +54,13 @@ public class PayOrder implements Order { * 微信专用,,,, * WAP支付链接 */ + @Deprecated private String wapUrl; /** * 微信专用,,,, * WAP支付网页名称 */ - + @Deprecated private String wapName; /** * 用户唯一标识 @@ -74,10 +68,7 @@ public class PayOrder implements Order { * 支付宝 buyer_id */ private String openid; - /** - * 交易类型 - */ - private TransactionType transactionType; + /** * 支付币种 */ @@ -87,11 +78,6 @@ public class PayOrder implements Order { */ private Date expirationTime; - /** - * 订单附加信息,可用于预设未提供的参数,这里会覆盖以上所有的订单信息, - */ - private Map attr; - public PayOrder() { } @@ -105,8 +91,8 @@ public class PayOrder implements Order { this.subject = StringUtils.tryTrim(subject); this.body = StringUtils.tryTrim(body); this.price = price; - this.outTradeNo = StringUtils.tryTrim(outTradeNo); - this.transactionType = transactionType; + setOutTradeNo(StringUtils.tryTrim(outTradeNo)); + setTransactionType(transactionType); } @@ -150,49 +136,6 @@ public class PayOrder implements Order { this.price = price; } - /** - * 支付平台订单号,交易号 - * - * @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; - } public String getBankType() { return bankType; @@ -258,48 +201,4 @@ public class PayOrder implements Order { this.expirationTime = expirationTime; } - @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); - } - - - - @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 30e5959d1fb6ed769a4b1a9f90022f40c6a23dd6..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
@@ -2,8 +2,6 @@ package com.egzosn.pay.common.bean;
 
 import java.math.BigDecimal;
 import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
 
 /**
  * 退款订单信息
@@ -14,19 +12,11 @@ import java.util.Map;
  *      date 2018/1/15 21:40
  *   
*/ -public class RefundOrder implements Order { +public class RefundOrder extends AssistOrder { /** * 退款单号,每次进行退款的单号,此处唯一 */ private String refundNo; - /** - * 支付平台订单号,交易号 - */ - private String tradeNo; - /** - * 商户单号 - */ - private String outTradeNo; /** * 退款金额 */ @@ -55,9 +45,9 @@ public class RefundOrder implements Order { private String userId; /** - * 订单附加信息,可用于预设未提供的参数,这里会覆盖以上所有的订单信息, + * 退款URL */ - private Map attr; + private String refundUrl; public String getRefundNo() { return refundNo; @@ -67,22 +57,6 @@ public class RefundOrder implements Order { 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; } @@ -136,47 +110,30 @@ public class RefundOrder implements Order { 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; } - @Override - public Map getAttrs() { - if (null == attr) { - attr = new HashMap<>(); - } - return attr; - } - - @Override - public Object getAttr(String key) { - return getAttrs().get(key); + public String getRefundUrl() { + return refundUrl; } - - /** - * 添加订单信息 - * - * @param key key - * @param value 值 - */ - @Override - public void addAttr(String key, Object value) { - getAttrs().put(key, value); + 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 index 11230e8241daa6826d72a373743171f55530eb38..4f9c159e549e767a1823077d0744444b5082bd4e 100644 --- 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 @@ -6,7 +6,7 @@ import java.util.*; /** * 签名类型 * - * @author: egan + * @author egan *
  * email egzosn@gmail.com
  * date 2019/12/08 13:30
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 e54abc6a24c7930cdfd2d284e41378cd43500bad..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 @@ -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 bfc799b1a1c1326a5329e87fa62859b500085a28..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,16 +1,22 @@
 package com.egzosn.pay.common.http;
 
-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;
-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;
@@ -19,25 +25,30 @@ import org.apache.http.entity.ContentType;
 import org.apache.http.entity.StringEntity;
 import org.apache.http.util.EntityUtils;
 
-import java.io.*;
-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); + 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); /** @@ -273,6 +284,9 @@ 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()) { value = new String[]{"application/x-www-form-urlencoded"}; @@ -306,6 +320,38 @@ public class ClientHttpRequest extends HttpEntityEnclosingRequestBase impleme * @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"; @@ -348,35 +394,6 @@ public class ClientHttpRequest extends HttpEntityEnclosingRequestBase impleme throw new PayErrorException(new PayException("failure", "类型转化异常,contentType:" + entity.getContentType().getValue(), result)); } - //是否为 输入流 - 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())); - } - } throw new PayErrorException(new PayException("failure", "类型转化异常,contentType:" + entity.getContentType().getValue())); } 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 274b9d9a9e8c437c3fd1408234845e32d45c5ce8..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 @@ -7,7 +7,7 @@ import java.io.*; /** * HTTP 配置 - * @author: egan + * @author egan *
  * email egzosn@gmail.com
  * date 2017/3/3 20:48
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 ceaf45bf10ba087ebd8e195fc5b4ed81b18675e8..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,11 +1,17 @@
 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;
@@ -23,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 static final Log LOG = LogFactory.getLog(HttpRequestTemplate.class); + protected static final Logger LOG = LoggerFactory.getLogger(HttpRequestTemplate.class); protected CloseableHttpClient httpClient; @@ -55,8 +59,10 @@ public class HttpRequestTemplate { protected HttpConfigStorage configStorage; private SSLConnectionSocketFactory sslsf; + /** - * 获取代理带代理地址的 HttpHost + * 获取代理带代理地址的 HttpHost + * * @return 获取代理带代理地址的 HttpHost */ public HttpHost getHttpProxy() { @@ -98,7 +104,8 @@ public class HttpRequestTemplate { } /** - * 初始化 + * 初始化 + * * @param configStorage 请求配置 */ public HttpRequestTemplate(HttpConfigStorage configStorage) { @@ -111,24 +118,26 @@ public class HttpRequestTemplate { /** - * 创建ssl配置 + * 创建ssl配置 + * * @param configStorage 请求配置 * @return SSLConnectionSocketFactory Layered socket factory for TLS/SSL connections. */ - public SSLConnectionSocketFactory createSSL( HttpConfigStorage configStorage){ - if (null != sslsf){ + public SSLConnectionSocketFactory createSSL(HttpConfigStorage configStorage) { + if (null != sslsf) { return sslsf; } - if (null == configStorage.getKeystore()){ + if (null == configStorage.getKeystore()) { try { return sslsf = new SSLConnectionSocketFactory(SSLContext.getDefault()); - } catch (NoSuchAlgorithmException e) { - LOG.error(e); + } + catch (NoSuchAlgorithmException e) { + LOG.error("", e); } } //读取本机存放的PKCS12证书文件 - try(InputStream instream = configStorage.getKeystoreInputStream()){ + try (InputStream instream = configStorage.getKeystoreInputStream()) { //指定读取证书格式为PKCS12 KeyStore keyStore = KeyStore.getInstance("PKCS12"); @@ -144,14 +153,16 @@ public class HttpRequestTemplate { //指定TLS版本 sslsf = new SSLConnectionSocketFactory( - sslcontext, new String[]{"TLSv1"}, null, + sslcontext, new String[]{"TLSv1", "TLSv1.2"}, null, new DefaultHostnameVerifier()); return sslsf; - } catch (IOException e) { - LOG.error(e); - } catch (GeneralSecurityException e) { - LOG.error(e); + } + catch (IOException e) { + LOG.error("", e); + } + catch (GeneralSecurityException e) { + LOG.error("", e); } return null; @@ -159,10 +170,11 @@ public class HttpRequestTemplate { /** * 创建凭据提供程序 + * * @param configStorage 请求配置 * @return 凭据提供程序 */ - public CredentialsProvider createCredentialsProvider(HttpConfigStorage configStorage){ + public CredentialsProvider createCredentialsProvider(HttpConfigStorage configStorage) { if (StringUtils.isBlank(configStorage.getAuthUsername())) { @@ -181,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(); @@ -216,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); } @@ -243,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); } @@ -255,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); } @@ -285,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>();
@@ -327,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){ + 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 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 63c2d6438298e84bdcb996d12f18ee59140cd784..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,13 +1,16 @@ package com.egzosn.pay.common.util; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -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; /** * 日期转换运算工具 @@ -19,11 +22,11 @@ import java.util.*; *
*/ public final class DateUtils { + private static final Logger LOG = LoggerFactory.getLogger(DateUtils.class); + private DateUtils() { } - private static final Log LOG = LogFactory.getLog(DateUtils.class); - static final class DateFormatHolder { private static final ThreadLocal>> THREADLOCAL_FORMATS = new ThreadLocal>>(); @@ -55,11 +58,14 @@ 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_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) { @@ -75,8 +81,9 @@ public final class DateUtils { SimpleDateFormat formatFor = DateFormatHolder.formatFor(pattern); try { return formatFor.parse(date); - } catch (ParseException e) { - LOG.error(e); + } + catch (ParseException e) { + LOG.error("", e); } return null; } @@ -85,15 +92,15 @@ public final class DateUtils { return parseDate(date, YYYY_MM_DD_HH_MM_SS); } - public static final String format(Date date) { + public static String format(Date date) { return formatDate(date, YYYY_MM_DD_HH_MM_SS); } - public static final Date parseDay(String date) { + public static Date parseDay(String date) { return parseDate(date, YYYY_MM_DD); } - public static final String formatDay(Date date) { + public static String formatDay(Date date) { return formatDate(date, YYYY_MM_DD); } @@ -103,8 +110,8 @@ public final class DateUtils { * @param date 结束点日期 * @return 分钟数 */ - public static final long minutesRemaining(Date date) { - return (date.getTime() / 1000 / 60 - System.currentTimeMillis() / 1000 / 60); + public static long minutesRemaining(Date date) { + return (date.getTime() / 1000 / 60 - DateUtils.toEpochSecond() / 60); } /** @@ -113,7 +120,7 @@ public final class DateUtils { * @param date 结束点日期 * @return 小时数 */ - public static final long remainingHours(Date date) { + public static long remainingHours(Date date) { return minutesRemaining(date) / 60; } @@ -123,8 +130,18 @@ public final class DateUtils { * @param date 结束点日期 * @return 天数 */ - public static final long remainingDays(Date date) { + 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 a323e8adc4093352c7bfde8019a78d1c4e3eae3f..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,16 +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.Element;
-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;
@@ -22,20 +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.List;
-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
  *         
@@ -57,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())); } @@ -76,6 +79,7 @@ public class XML { } + /** * 解析xml并转化为Json值 * @@ -90,6 +94,7 @@ public class XML { } return toJSONObject(content.getBytes(charset)); } + /** * 解析xml并转化为Json值 * @@ -103,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())); } @@ -113,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())); } @@ -154,7 +161,8 @@ public class XML { JSONArray array = new JSONArray(); array.add(json); json = array; - } else { + } + else { j.put(node.getNodeName(), getChildren(nodeList)); } } @@ -164,7 +172,8 @@ public class XML { c.put(node.getNodeName(), getChildren(nodeList)); ((JSONArray) json).add(c); } - } else if (node.getNodeType() == Node.ELEMENT_NODE ) { + } + else if (node.getNodeType() == Node.ELEMENT_NODE) { if (null == json) { json = new JSONObject(); } @@ -198,9 +207,8 @@ public class XML { * @param clazz 需要转化的类 * @param 类型 * @return 对应的对象 - * @throws IOException xml io转化异常 */ - public static T inputStream2Bean(InputStream in, Class clazz) throws IOException { + public static T inputStream2Bean(InputStream in, Class clazz) { JSON json = toJSONObject(in); return json.toJavaObject(clazz); } @@ -211,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(); } @@ -224,22 +232,24 @@ public class XML { Node node = children.item(idx); NodeList nodeList = node.getChildNodes(); int length = nodeList.getLength(); - if (node.getNodeType() == Node.ELEMENT_NODE && (length >1 || length==1 && nodeList.item(0).hasChildNodes())) { + 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 ) { + } + else if (node.getNodeType() == Node.ELEMENT_NODE) { m.put(node.getNodeName(), node.getTextContent()); } } - } catch (Exception e) { + } + catch (ParserConfigurationException | SAXException e) { throw new PayErrorException(new PayException("XML failure", "XML解析失败\n" + e.getMessage())); - } finally { + } + finally { in.close(); } return m; } - /** * 将Map转换为XML格式的字符串 * @@ -254,16 +264,17 @@ public class XML { /** * 将Map转换为XML格式的字符串 * - * @param data Map类型数据 + * @param data Map类型数据 * @param rootElementName 最外层节点名称 - * @param encoding 字符编码 + * @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(rootElementName); @@ -292,18 +303,18 @@ public class XML { transformer.transform(source, result); 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())); } - return ""; } /** * 将Map转换为XML格式的字符串 * - * @param data Map类型数据 + * @param data Map类型数据 * @param document 文档 * @param element 节点 */ @@ -333,17 +344,19 @@ public class XML { } } - private static void object2Xml(Object value, Document document, org.w3c.dom.Element element){ + 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 ){ + 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 { + } + else { value = value.toString().trim(); element.appendChild(document.createTextNode(value.toString())); } @@ -352,9 +365,4 @@ public class XML { } - public static void main(String[] args) { - String text = "01张三2张4"; - System.out.println( getMap2Xml(toJSONObject(text), "datas", "utf-8")); - - } } 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 3577a1139f917e8f17ec8ff06c573dfd217cfce0..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 @@ -14,20 +14,25 @@ */ 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.*; +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 com.egzosn.pay.common.util.str.StringUtils; + /** * acpsdk证书工具类,主要用于对证书的加载和使用 @@ -35,7 +40,7 @@ import java.util.Enumeration; * 声明:以下代码只是为了方便接入方测试而提供的样例代码,商户可以根据自己需要,按照技术文档编写。该代码仅供参考,不提供编码,性能,规范性等方面的保障 */ public class CertDescriptor { - protected static final Log LOG = LogFactory.getLog(CertDescriptor.class); + protected static final Logger LOG = LoggerFactory.getLogger(CertDescriptor.class); /** * 证书容器,存储对商户请求报文签名私钥证书. */ @@ -69,13 +74,16 @@ public class CertDescriptor { if (LOG.isWarnEnabled()) { LOG.warn("[CertId=" + encryptCertTemp.getSerialNumber().toString() + "]"); } - } catch (CertificateException e) { + } + catch (CertificateException e) { LOG.error("InitCert Error", e); - } finally { + } + finally { if (null != certIn) { try { certIn.close(); - } catch (IOException e) { + } + catch (IOException e) { LOG.error(e.toString()); } } @@ -96,7 +104,8 @@ public class CertDescriptor { try { in = new FileInputStream(path); encryptCertTemp = initCert(in); - } catch (FileNotFoundException e) { + } + catch (FileNotFoundException e) { LOG.error("InitCert Error File Not Found", e); } return encryptCertTemp; @@ -115,16 +124,18 @@ public class CertDescriptor { if (aliasenum.hasMoreElements()) { keyAlias = aliasenum.nextElement(); } - PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, - pwd.toCharArray()); + PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, pwd.toCharArray()); return privateKey; - } catch (KeyStoreException e) { + } + catch (KeyStoreException e) { LOG.error("getSignCertPrivateKey Error", e); return null; - } catch (UnrecoverableKeyException e) { + } + catch (UnrecoverableKeyException e) { LOG.error("getSignCertPrivateKey Error", e); return null; - } catch (NoSuchAlgorithmException e) { + } + catch (NoSuchAlgorithmException e) { LOG.error("getSignCertPrivateKey Error", e); return null; } @@ -145,7 +156,8 @@ public class CertDescriptor { } X509Certificate cert = (X509Certificate) keyStore.getCertificate(keyAlias); return cert.getSerialNumber().toString(); - } catch (Exception e) { + } + catch (Exception e) { LOG.error("getSignCertId Error", e); return null; } @@ -168,7 +180,8 @@ public class CertDescriptor { if (LOG.isInfoEnabled()) { LOG.info("InitSignCert Successful. CertId=[" + getSignCertId() + "]"); } - } catch (IOException e) { + } + catch (IOException e) { LOG.error("InitSignCert Error", e); } } @@ -231,14 +244,17 @@ public class CertDescriptor { ks.load(fxKeyFile, nPassword); } return ks; - } catch (Exception e) { + } + catch (Exception e) { LOG.error("getKeyInfo Error", e); return null; - } finally { + } + finally { if (null != fxKeyFile) { try { fxKeyFile.close(); - } catch (IOException e) { + } + catch (IOException e) { LOG.error("getKeyInfo Error", e); } } @@ -263,7 +279,8 @@ public class CertDescriptor { X509Certificate cert = (X509Certificate) keyStore .getCertificate(keyAlias); return cert.getSerialNumber().toString(); - } catch (KeyStoreException e) { + } + catch (KeyStoreException e) { LOG.error("getCertIdIdByStore Error", e); return null; } @@ -281,7 +298,8 @@ public class CertDescriptor { if (LOG.isInfoEnabled()) { LOG.info("Load PublicKeyCert Successful"); } - } else if (LOG.isInfoEnabled()) { + } + else if (LOG.isInfoEnabled()) { LOG.info("PublicKeyCert is empty"); } } @@ -297,7 +315,8 @@ public class CertDescriptor { if (LOG.isInfoEnabled()) { LOG.info("Load PublicKeyCert Successful"); } - } else if (LOG.isInfoEnabled()) { + } + else if (LOG.isInfoEnabled()) { LOG.info("PublicKeyCert is empty"); } } @@ -311,14 +330,17 @@ public class CertDescriptor { if (!StringUtils.isEmpty(certPath)) { try { initRootCert(new FileInputStream(certPath)); - } catch (FileNotFoundException e) { + } + catch (FileNotFoundException e) { LOG.info("RootCert is empty"); } - } else if (LOG.isInfoEnabled()) { + } + else if (LOG.isInfoEnabled()) { LOG.info("RootCert is empty"); } } + /** * 加载根证书 * @@ -330,7 +352,8 @@ public class CertDescriptor { if (LOG.isInfoEnabled()) { LOG.info("Load RootCert Successful"); } - } else if (LOG.isInfoEnabled()) { + } + else if (LOG.isInfoEnabled()) { LOG.info("RootCert is empty"); } } 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 74ade5f242b9be3f8e50a6753ede334883654ab6..a73bab78238849a4603fa5f09aa4b446173ecaab 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,23 +1,20 @@ package com.egzosn.pay.common.util.sign; -import com.egzosn.pay.common.bean.SignType; -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 org.bouncycastle.jce.provider.BouncyCastleProvider; -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 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
@@ -51,7 +48,7 @@ public enum SignUtils implements SignType {
         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";
@@ -68,26 +65,7 @@ public enum SignUtils implements SignType {
          */
         @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);
         }
 
         /**
@@ -163,140 +141,6 @@ public enum SignUtils implements SignType {
         }
     };
 
-    /**
-     *
-     * 把数组所有元素排序,并按照“参数=参数值”的模式用“@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 "";
-        }
-        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 (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 (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() {
@@ -306,24 +150,28 @@ public enum SignUtils implements SignType {
     /**
      * 签名
      *
-     * @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);
 
@@ -333,17 +181,31 @@ public enum SignUtils implements SignType {
     /**
      * 签名字符串
      *
-     * @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);
     }
 
+    /**
+     * 初始化BC
+     */
+    public static void initBc() {
+        String javaVersion = System.getProperty("java.version");
+        if (javaVersion.contains("1.8") || javaVersion.startsWith("8")) {
+            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/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 d75d1c89a875f4fb6b71ef3ac0eac6adc6f0ba1b..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,297 +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 GeneralSecurityException 解密异常 - * @throws IOException IOException - */ - public static String decrypt(String content, String privateKey, String characterEncoding) throws GeneralSecurityException, IOException { + /** + * 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 GeneralSecurityException 加密异常 - * @return 私钥 - */ - 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 密钥类型 - * @throws GeneralSecurityException 加密异常 - * @throws IOException 加密异常 - * @return 公钥 - */ - 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 GeneralSecurityException 加密异常 - * @throws IOException 加密异常 - * @return 公钥 - */ - 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)); - } + /** + * 得到公钥 + * + * @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 46cae1c6b5415d0f90bcafa7a5ee96228336ea51..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 @@ -5,93 +5,109 @@ 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"; - - - - public static String sign(String content, String 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, PublicKey publicKey, String characterEncoding){ - return RSA.verify(content, sign, publicKey, SIGN_SHA256RSA_ALGORITHMS, 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 { + private static final String SIGN_SHA256RSA_ALGORITHMS = "SHA256WithRSA"; + + + public static String sign(String content, String 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, 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); } - /** - * 得到私钥 - * @param key 密钥字符串(经过base64编码) - * @throws GeneralSecurityException 加密异常 - * @return 私钥 - */ - public static PrivateKey getPrivateKey(String key) throws GeneralSecurityException { - return RSA.getPrivateKey(key); - } - - /** - * - * @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)); - } + /** + * 得到私钥 + * + * @param key 密钥字符串(经过base64编码) + * @return 私钥 + * @throws GeneralSecurityException 加密异常 + */ + public static PrivateKey getPrivateKey(String key) throws GeneralSecurityException { + return RSA.getPrivateKey(key); + } + + /** + * @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 79a0636709a12609e04ac83bc0b1e4aeca1a9cfe..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,13 +125,16 @@ public class StringUtils { } try { return content.getBytes(charset); - } catch (UnsupportedEncodingException e) { + } + catch (UnsupportedEncodingException e) { throw new RuntimeException("转码过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset); } } + /** * 对 subject body 进行 trim 运算, * 以防止在签名是可能造成的签名错误问题 + * * @param str 字符 * @return 去除空格之后的字符 */ @@ -135,4 +142,34 @@ public class StringUtils { 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 0f008869c9ba30fea17a310092ba5a88faee6ebe..d4f530138bcdcacddba78aeae2465b489cc22a9c 100644 --- a/pay-java-demo/README.md +++ b/pay-java-demo/README.md @@ -7,8 +7,8 @@ /** * 支付类型 * @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 { @@ -22,7 +22,7 @@ public enum PayType implements BasePayType { public PayService getPayService(ApyAccount apyAccount) { AliPayConfigStorage aliPayConfigStorage = new AliPayConfigStorage(); aliPayConfigStorage.setPid(apyAccount.getPartner()); - aliPayConfigStorage.setAppid(apyAccount.getAppid()); + aliPayConfigStorage.setAppId(apyAccount.getAppid()); aliPayConfigStorage.setKeyPublic(apyAccount.getPublicKey()); aliPayConfigStorage.setKeyPrivate(apyAccount.getPrivateKey()); aliPayConfigStorage.setNotifyUrl(apyAccount.getNotifyUrl()); @@ -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 @@ -244,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 { /** @@ -334,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)){ @@ -355,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); @@ -376,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 9cec5a291884b84292303eb29afc66472ff21999..dc143c4ce8e94b13b17e6f13419016ee3cc23f3c 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.3-SNAPSHOT + 2.14.8 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 @@ -81,14 +72,30 @@ compile - + + jakarta.annotation + jakarta.annotation-api + 1.3.5 + + + + + javax.servlet + javax.servlet-api + provided + ${servlet-api.version} + com.fasterxml.jackson.core jackson-databind - 2.9.10.1 + 2.9.10.6 + + + org.slf4j + slf4j-log4j12 + 1.7.30 - @@ -98,8 +105,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.7 - 1.7 + 1.8 + 1.8 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 7ee3fbf9c31ec5dbc759ef871fb1fe4d27b6c67e..7dac125f52bbbb827ab39f92855a57f7f82e8463 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,40 +2,49 @@ 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.alibaba.fastjson.JSONObject; 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.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.MapGen; 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 + * @author egan * email egzosn@gmail.com * date 2016/11/18 0:25 */ @@ -47,16 +56,46 @@ public class AliPayController { @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.setAppId("2016080400165436"); +// 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"); //是否为测试账号,沙箱环境 @@ -68,7 +107,7 @@ public class AliPayController { httpConfigStorage.setMaxTotal(20); //默认的每个路由的最大连接数 httpConfigStorage.setDefaultMaxPerRoute(10); - service = new AliPayService(aliPayConfigStorage, httpConfigStorage); + service = new AliPayService(aliPayConfigStorage, httpConfigStorage); //增加支付回调消息拦截器 service.addPayMessageInterceptor(new AliPayMessageInterceptor()); //设置回调消息处理 @@ -76,20 +115,19 @@ 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.PAGE); + 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); +// 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); @@ -98,8 +136,6 @@ public class AliPayController { } - - /** * 获取支付预订单信息 * @@ -109,7 +145,7 @@ 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.app(order))); @@ -119,50 +155,77 @@ public class AliPayController { /** * 获取二维码图像 * 二维码支付 - * @param price 金额 + * + * @param price 金额 * @return 二维码图像 * @throws IOException IOException */ @RequestMapping(value = "toQrPay.jpg", produces = "image/jpeg;charset=UTF-8") - public byte[] toQrPay( 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 金额 + * + * @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 ? new BigDecimal(0.01) : price, System.currentTimeMillis()+"", AliTransactionType.SWEEPPAY)); + return service.getQrPay(new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, System.currentTimeMillis() + "", AliTransactionType.SWEEPPAY)); } /** * 刷卡付,pos主动扫码付款(条码付) - * @param authCode 授权码,条码等 - * @param price 金额 + * + * @param openid 授权码,条码等 + * @param price 金额 + * @return 支付结果 + */ + @RequestMapping(value = "minapp") + public Map minapp(BigDecimal price, String openid) { + //获取对应的支付账户操作工具(可根据账户id) + //条码付 + 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.setOpenid(openid); + order.addAttr("op_app_id", "小程序支付中,商户实际经营主体的小程序应用的appid,也即最终唤起收银台支付所在的小程序的应用id"); + //预订单结果 + Map params = service.jsApi(order); + //这里开发者自行处理 + return params; + } + + /** + * 刷卡付,pos主动扫码付款(条码付) + * + * @param authCode 授权码,条码等 + * @param price 金额 * @return 支付结果 */ @RequestMapping(value = "microPay") public Map microPay(BigDecimal price, String authCode) { //获取对应的支付账户操作工具(可根据账户id) //条码付 - PayOrder order = new PayOrder("egan order", "egan order", null == price ? new BigDecimal(0.01) : price, UUID.randomUUID().toString().replace("-", ""), AliTransactionType.BAR_CODE); - //声波付 -// PayOrder order = new PayOrder("egan order", "egan 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))) { //支付校验通过后的处理 //......业务逻辑处理块........ @@ -172,31 +235,50 @@ public class AliPayController { //这里开发者自行处理 return params; } + /** + * 刷卡付,pos主动扫码付款(条码付) + * + * @param openid 授权码,条码等 + * @param price 金额 + * @return 支付结果 + */ + @RequestMapping(value = "jsapi") + public Map jsapi(BigDecimal price, String openid) { + //获取对应的支付账户操作工具(可根据账户id) + //条码付 + PayOrder order = new PayOrder("egan order", "egan order", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), AliTransactionType.BAR_CODE); + + order.setOpenid(openid); + //支付结果 + Map orderInfo = service.orderInfo(order); + Map bizContent = new MapGen<>("biz_content", orderInfo.get("biz_content")).getAttr(); + JSONObject result = service.getHttpRequestTemplate().postForObject(service.getReqUrl() + "?" + UriVariables.getMapToParameters(orderInfo), bizContent, JSONObject.class); + //这里开发者自行处理 + return result; + } /** * 支付回调地址 方式一 - * + *

* 方式二,{@link #payBack(HttpServletRequest)} 是属于简化方式, 试用与简单的业务场景 * - * * @param request 请求 - * * @return 返回对应的响应码 - * @see #payBack(HttpServletRequest) * @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) { + 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(); @@ -204,24 +286,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(); + } /** * 查询 @@ -231,7 +330,7 @@ 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())); } /** @@ -259,8 +358,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撤销接口 * @@ -279,18 +379,24 @@ 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(RefundOrder order) { + public Map refundquery() { + RefundOrder order = new RefundOrder(); + order.setOutTradeNo("我方系统商户单号"); + order.setTradeNo("支付宝单号"); + //退款金额 + order.setRefundAmount(new BigDecimal(1)); + order.setRefundNo("退款单号"); + order.setDescription(""); return service.refundquery(order); } @@ -301,8 +407,8 @@ 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()); } @@ -310,7 +416,6 @@ public class AliPayController { * 转账 * * @param order 转账订单 - * * @return 对应的转账结果 */ @RequestMapping("transfer") @@ -334,11 +439,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 index 06d111487fd33d8419e982526bb57c5f8f4cb572..0ea09e5bc94083fd71b50bef48d978a1a4a4469c 100644 --- 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 @@ -7,6 +7,8 @@ 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; @@ -20,7 +22,7 @@ import java.util.UUID; /** * 发起支付入口 * - * @author: egan + * @author egan * email egzosn@gmail.com * date 2016/11/18 0:25 */ @@ -65,7 +67,7 @@ public class FuiouPayController { @RequestMapping(value = "toPay.html", produces = "text/html;charset=UTF-8") public String toPay( BigDecimal price) { //支付订单基础信息 - PayOrder order = new PayOrder("订单title", "摘要", new BigDecimal(0.01) , UUID.randomUUID().toString().replace("-", "").substring(2)); + PayOrder order = new PayOrder("订单title", "摘要", BigDecimal.valueOf(0.01) , UUID.randomUUID().toString().replace("-", "").substring(2)); order.setTransactionType(FuiouTransactionType.B2C); //获取支付所需的信息 // Map directOrderInfo = service.orderInfo(order); @@ -112,15 +114,32 @@ public class FuiouPayController { * * @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 = "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(); + } } 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 c7adb425f71f8e295329be0ae47d4ec7e573599b..49b267aa85cf76fbc490d9432ffdc4d23c7cbf45 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,13 +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.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; @@ -17,28 +43,13 @@ 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.web.support.HttpRequestNoticeParams; import com.egzosn.pay.wx.bean.WxTransactionType; -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 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 static com.egzosn.pay.demo.dao.ApyAccountRepository.apyAccounts; /** * 发起支付入口 * - * @author: egan + * @author egan * email egzosn@gmail.com * date 2016/11/18 0:25 */ @@ -76,7 +87,7 @@ public class PayController { * 跳到支付页面 * 针对实时支付,即时付款 * - * @param request 请求 + * @param request 请求 * @param payId 账户id * @param transactionType 交易类型, 这个针对于每一个 支付类型的对应的几种交易方式 * @param bankType 针对刷卡支付,卡的类型,类型值 @@ -88,7 +99,7 @@ public class PayController { //获取对应的支付账户操作工具(可根据账户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(); @@ -115,6 +126,7 @@ public class PayController { /** * 跳到支付页面 * 针对实时支付,即时付款 + * * @param request 请求 * @return 跳到支付页面 */ @@ -123,7 +135,7 @@ 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(); //设置网页地址 @@ -150,10 +162,10 @@ public class PayController { //获取对应的支付账户操作工具(可根据账户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); + Map orderInfo = payResponse.getService().jsApi(order); orderInfo.put("code", 0); return orderInfo; @@ -164,7 +176,7 @@ public class PayController { * * @param payId 支付账户id * @param transactionType 交易类型 - * @param price 金额 + * @param price 金额 * @return 支付预订单信息 */ @RequestMapping("app") @@ -173,7 +185,7 @@ public class PayController { 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().app(order)); return data; } @@ -188,20 +200,20 @@ public class PayController { * @return 支付结果 */ @RequestMapping(value = "microPay") - public Map microPay(Integer payId, String transactionType, BigDecimal price, String authCode) { + public Map microPay(Integer payId, String transactionType, BigDecimal price, String authCode) { //获取对应的支付账户操作工具(可根据账户id) PayResponse payResponse = service.getPayResponse(payId); - PayOrder order = new PayOrder("egan order", "egan 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); //支付结果 Map params = payResponse.getService().microPay(order); PayConfigStorage storage = payResponse.getService().getPayConfigStorage(); //校验 - if (payResponse.getService().verify(params)) { - PayMessage message = new PayMessage(params, storage.getPayType(), storage.getMsgType().name()); - //支付校验通过后的处理 + if (payResponse.getService().verify(new NoticeParams(params))) { + PayMessage message = new PayMessage(params, storage.getPayType()); + //支付校验通过后的处理,,路由的方式已经不建议使用了, payResponse.getRouter().route(message); } //这里开发者自行处理 @@ -215,7 +227,6 @@ public class PayController { * @param payId 账户id * @param transactionType 交易类型, 这个针对于每一个 支付类型的对应的几种交易方式 * @param price 金额 - * * @return 二维码图像 * @throws IOException IOException */ @@ -225,13 +236,15 @@ 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 金额 + * + * @param price 金额 * @return 二维码图像 * @throws IOException IOException */ @@ -240,8 +253,9 @@ public class PayController { //获取对应的支付账户操作工具(可根据账户id) //获取对应的支付账户操作工具(可根据账户id) PayResponse payResponse = service.getPayResponse(payId); - return payResponse.getService().getQrPay( new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price, System.currentTimeMillis() + "", PayType.valueOf(payResponse.getStorage().getPayType()).getTransactionType(transactionType))); + return payResponse.getService().getQrPay(new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, System.currentTimeMillis() + "", PayType.valueOf(payResponse.getStorage().getPayType()).getTransactionType(transactionType))); } + /** * 获取一码付二维码图像 * 二维码支付 @@ -249,7 +263,7 @@ public class PayController { * @param wxPayId 微信账户id * @param aliPayId 支付宝id * @param price 金额 - * @param request 请求 + * @param request 请求 * @return 二维码图像 * @throws IOException IOException */ @@ -279,7 +293,7 @@ public class PayController { * @param wxPayId 微信账户id * @param aliPayId 支付宝id * @param price 金额 - * @param request 请求 + * @param request 请求 * @return 支付宝与微信平台的判断 * @throws IOException IOException */ @@ -288,7 +302,7 @@ public class PayController { 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")) { payOrder.setTransactionType(WxTransactionType.NATIVE); @@ -308,34 +322,52 @@ public class PayController { } - - + /** + * 支付回调地址 + * 方式三 + * + * @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 { + //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() + PayResponse payResponse = service.getPayResponse(payId); + return payResponse.getService().payBack(new HttpRequestNoticeParams(request)).toMessage(); + } /** * 支付回调地址 方式一 *

- * 方式二,{@link #payBack(HttpServletRequest, Integer)} 是属于简化方式, 试用与简单的业务场景 + * 建议使用 方式三,{@link #payBack(HttpServletRequest, Integer)} 是属于简化方式, 试用与简单的业务场景 * * @param request 请求 - * @param payId 账户id + * @param payId 账户id * @return 支付是否成功 - * @throws IOException IOException */ @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)) { + 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);*/ @@ -361,17 +393,18 @@ public class PayController { * 方式二 * * @param request 请求 - * @param payId 账户id + * @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} + * 拦截器相关增加, 详情查看{@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(); @@ -387,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())); } /** * 查询 @@ -412,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); } /** @@ -449,30 +484,17 @@ 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 对应的转账结果 @@ -485,7 +507,8 @@ public class PayController { /** * 转账查询 - * @param payId 账户id + * + * @param payId 账户id * @param outNo 商户转账订单号 * @param tradeNo 支付平台转账订单号 * @return 对应的转账订单 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 f9a7873e0174ed5b8436e2056309972f4c5c7a56..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,30 +1,33 @@ package com.egzosn.pay.demo.controller; +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.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 + * @author egan * email egzosn@gmail.com * date 2018/05/06 10:30 */ @@ -38,13 +41,15 @@ public class PayPalPayController { @PostConstruct public void init() { PayPalConfigStorage storage = new PayPalConfigStorage(); - storage.setClientID("AZ7HTcvrEAxYbzYx_iDZAi06GdqbjhqqQzFgPBFLxm2VUMzwlmiNUBk_y_5QNP4zWKblTuM6ZBAmxScd"); - storage.setClientSecret("EBMIjAag6NiRdXZxteTv0amEsmKN345xJv3bN7f_HRXSqcRJlW7PXhYXjI9sk5I4nKYOHgeqzhXCXKFo"); + 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://www.egzosn.com/payPal/payBack.json"); //取消按钮转跳地址,这里用异步通知地址的兼容的做法 - storage.setNotifyUrl("http://www.egzosn.com/pay/cancel"); + storage.setCancelUrl("http://www.egzosn.com/pay/cancel"); service = new PayPalPayService(storage); //请求连接池配置 @@ -67,7 +72,7 @@ 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); @@ -75,32 +80,36 @@ public class PayPalPayController { String toPayHtml = service.toPay(order); //某些支付下单时无法设置单号,通过下单后返回对应单号,如 paypal,友店。 - String outTradeNo = order.getOutTradeNo(); - System.out.println("支付订单号:" + outTradeNo + " 这里可以进行回存"); + 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(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 @@ -117,22 +126,40 @@ public class PayPalPayController { return "failure"; } + /* */ + /** * 支付回调地址 * - * @param request 请求 + * @param request 请求 + * + * @return 是否成功 * - * @return 结果 - * @throws IOException IOException * 业务处理在对应的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 0fca69d17f81de02f945575cd4c5a4d42b02a9c5..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,23 +1,31 @@ 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 @@ -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 用户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 获取授权用户信息 */ @RequestMapping("getAuthorizationUser.json") - public Map getAuthorizationUser( String payeeId ){ + public Map getAuthorizationUser(String payeeId) { Map data = new LinkedHashMap<>(); data.put("code", 0); @@ -92,36 +101,36 @@ public class PayoneerPayController { /** * 主动收款 - * @param price 金额 - * @param userId 付款用户 + * + * @param price 金额 + * @param userId 付款用户 * @return 支付结果 */ @ResponseBody @RequestMapping(value = "microPay.json") - public Map microPay(BigDecimal price, String userId){ + public Map microPay(BigDecimal price, String userId) { PayOrder order = new PayOrder("Order_payment:", "Order payment", price, UUID.randomUUID().toString().replace("-", ""), PayoneerTransactionType.CHARGE); //币种 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 是否成功 * @throws IOException IOException */ @@ -152,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())); } @@ -164,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())); } + /** * 申请退款接口 * @@ -173,7 +183,7 @@ public class PayoneerPayController { * @return 返回支付方申请退款后的结果 */ @RequestMapping("refund") - public Map refund(RefundOrder order) { + public RefundResult refund(RefundOrder order) { return service.refund(order); } @@ -195,7 +205,6 @@ public class PayoneerPayController { * 转账 * * @param order 转账订单 - * * @return 对应的转账结果 */ @RequestMapping("transfer") @@ -213,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 6ced1dcb1d8d5bd8b5225cb4bd7971b46bbfea73..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,36 +2,39 @@ package com.egzosn.pay.demo.controller; -import com.egzosn.pay.common.api.PayService; +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.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.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.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 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; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import static com.egzosn.pay.union.bean.UnionTransactionType.WEB; +import com.egzosn.pay.web.support.HttpRequestNoticeParams; /** * 银联相关 * - * @author: egan + * @author egan * email egzosn@gmail.com * date 2016/11/18 0:25 */ @@ -41,7 +44,7 @@ public class UnionPayController { private UnionPayService service = null; - @PostConstruct +// @PostConstruct public void init() { UnionPayConfigStorage unionPayConfigStorage = new UnionPayConfigStorage(); unionPayConfigStorage.setMerId("700000000000001"); @@ -58,9 +61,6 @@ public class UnionPayController { //设置证书对应的存储方式,这里默认为文件地址 unionPayConfigStorage.setCertStoreType(CertStoreType.URL); - - - //前台通知网址 即SDKConstants.param_frontUrl unionPayConfigStorage.setReturnUrl("http://www.pay.egzosn.com/payBack.json"); //后台通知地址 即SDKConstants.param_backUrl @@ -96,10 +96,10 @@ 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); @@ -116,7 +116,7 @@ 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.app(order); } @@ -132,7 +132,7 @@ 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); @@ -154,7 +154,7 @@ public class UnionPayController { 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(); } /** @@ -167,7 +167,7 @@ public class UnionPayController { @RequestMapping(value = "getQrPay.json") public String getQrPay(BigDecimal price) throws IOException { //获取对应的支付账户操作工具(可根据账户id) - return service.getQrPay( new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price, System.currentTimeMillis()+"", UnionTransactionType.APPLY_QR_CODE)); + return service.getQrPay( new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, System.currentTimeMillis()+"", UnionTransactionType.APPLY_QR_CODE)); } /** @@ -180,7 +180,7 @@ public class UnionPayController { public Map microPay(BigDecimal price, String authCode) { //获取对应的支付账户操作工具(可根据账户id) //条码付 - PayOrder order = new PayOrder("egan order", "egan 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); //支付结果 @@ -232,18 +232,33 @@ public class UnionPayController { * * @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(); } @@ -255,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())); } @@ -266,7 +281,7 @@ public class UnionPayController { * @return 返回支付方申请退款后的结果 */ @RequestMapping("refund") - public Map refund(RefundOrder order) { + public UnionRefundResult refund(RefundOrder order) { return service.refund(order); } @@ -277,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 096f6091ec91422c15e316f7c48d380fe200ab55..0670ab9ad87b25fab44c1326f2f3525077d289e8 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 @@ -5,6 +5,7 @@ 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.web.support.HttpRequestNoticeParams; import com.egzosn.pay.wx.api.WxPayConfigStorage; import com.egzosn.pay.wx.api.WxPayService; import com.egzosn.pay.wx.bean.*; @@ -24,7 +25,7 @@ import java.util.UUID; /** * 发起支付入口 * - * @author: egan + * @author egan * email egzosn@gmail.com * date 2016/11/18 0:25 */ @@ -38,12 +39,11 @@ public class WxPayController { //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(商户号)"); //以下两个参数在 服务商版模式中必填-------- @@ -67,7 +67,7 @@ public class WxPayController { //TODO 这里也支持输入流的入参。 // httpConfigStorage.setKeystore(WxPayController.class.getResourceAsStream("/证书文件")); httpConfigStorage.setKeystore(KEYSTORE); - httpConfigStorage.setStorePassword(STORE_PASSWORD); + httpConfigStorage.setStorePassword("ssl 证书对应的密码, 默认为商户号"); //设置ssl证书对应的存储方式,这里默认为文件地址 httpConfigStorage.setCertStoreType(CertStoreType.PATH); } @@ -97,7 +97,7 @@ public class WxPayController { */ @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(); //设置网页地址 @@ -121,10 +121,10 @@ 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); + Map orderInfo = service.jsApi(order); orderInfo.put("code", 0); return orderInfo; @@ -141,7 +141,7 @@ 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.app(order)); @@ -159,7 +159,7 @@ public class WxPayController { 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(); } @@ -173,7 +173,7 @@ public class WxPayController { @RequestMapping(value = "getQrPay.json") public String getQrPay(BigDecimal price) throws IOException { //获取对应的支付账户操作工具(可根据账户id) - return service.getQrPay( new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price, System.currentTimeMillis()+"", WxTransactionType.NATIVE)); + return service.getQrPay( new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, System.currentTimeMillis()+"", WxTransactionType.NATIVE)); } /** * 刷卡付,pos主动扫码付款(条码付) @@ -185,7 +185,7 @@ public class WxPayController { public Map microPay( BigDecimal price, String authCode) { //获取对应的支付账户操作工具(可根据账户id) //条码付 - PayOrder order = new PayOrder("egan order", "egan 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); //支付结果 @@ -212,7 +212,7 @@ public class WxPayController { @RequestMapping(value = "facePay") public Map facePay(BigDecimal price, String authCode, String openid) { //获取对应的支付账户操作工具(可根据账户id) - PayOrder order = new PayOrder("egan order", "egan order", null == price ? new BigDecimal(0.01) : price, UUID.randomUUID().toString().replace("-", ""), WxTransactionType.FACEPAY); + 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下的唯一标识 @@ -246,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(); @@ -272,11 +272,27 @@ public class WxPayController { * 如果未设置 {@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 { + @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(); + } /** @@ -287,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())); } @@ -299,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())); } /** @@ -309,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退款证书"); } @@ -334,8 +350,8 @@ 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()); } 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..7274f1d1dd52aed0f3f88b88bf9ec2f7b9d70c8c --- /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.jsApi(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..c23bf417f98cd5b58f1d3c5918eb62c5e5266ac3 --- /dev/null +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxV3PayController.java @@ -0,0 +1,376 @@ + +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"); + //验签、转账等接口使用,9月份开始不允许获取证书方式了,直接通过公钥字符来做, + wxPayConfigStorage.setKeyPublic("支付平台公钥(原为自动获取的证书)"); + 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("http://pay.egzosn.com/yifenli_mall.p12"); + wxPayConfigStorage.setCertStoreType(CertStoreType.URL); + 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("-", ""), com.egzosn.pay.wx.bean.WxTransactionType.JSAPI); + order.setOpenid(openid); + + Map orderInfo = service.jsApi(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 04aef65679e276db85a45585d18b0073f059663a..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,17 +3,17 @@ 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 + * + * @author egan * email egzosn@gmail.com * date 2016/11/18 1:21 */ @@ -21,16 +21,15 @@ import java.util.Map; 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=="); @@ -41,7 +40,6 @@ public class ApyAccountRepository { 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,7 +91,6 @@ 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); @@ -106,17 +101,15 @@ public class ApyAccountRepository { 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.setAppId("1AZ7HTcvrEAxYbzYx_iDZAi06GdqbjhqqQzFgPBFLxm2VUMzwlmiNUBk_y_5QNP4zWKblTuM6ZBAmxScd");//Program ID apyAccount6.setPrivateKey("1EBMIjAag6NiRdXZxteTv0amEsmKN345xJv3bN7f_HRXSqcRJlW7PXhYXjI9sk5I4nKYOHgeqzhXCXKFo");//API password apyAccount6.setInputCharset("UTF-8"); apyAccount6.setPayType(PayType.payPal); - apyAccount6.setMsgType(MsgType.json); apyAccount6.setTest(true); apyAccounts.put(apyAccount6.getPayId(), apyAccount6); } @@ -125,10 +118,11 @@ public class ApyAccountRepository { /** * 根据id获取对应的账户信息 + * * @param payId 账户id * @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 2ac2dd68a91f1bee5da39bc11cd8315b021e71a1..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,13 +2,12 @@ package com.egzosn.pay.demo.entity; -import com.egzosn.pay.common.bean.MsgType; - //import javax.persistence.*; /** * 支付账户 - * @author: egan + * + * @author egan * email egzosn@gmail.com * date 2016/11/18 0:36 */ @@ -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 85b0b919a196c8753831b1d761d7c65e986ec4fc..3bfc236a4358676df47243ce56cc5c4565357953 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 @@ -6,10 +6,14 @@ 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.CertStoreType; -import com.egzosn.pay.common.bean.MsgType; 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.FuiouPayMessageHandler; +import com.egzosn.pay.demo.service.handler.PayPalPayMessageHandler; +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.fuiou.api.FuiouPayConfigStorage; import com.egzosn.pay.fuiou.api.FuiouPayService; import com.egzosn.pay.fuiou.bean.FuiouTransactionType; @@ -30,7 +34,6 @@ import com.egzosn.pay.wx.youdian.api.WxYouDianPayService; import com.egzosn.pay.wx.youdian.bean.YoudianTransactionType; - /** * 支付类型 * @@ -41,7 +44,7 @@ import com.egzosn.pay.wx.youdian.bean.YoudianTransactionType; public enum PayType implements BasePayType { - aliPay{ + aliPay { /** * @see com.egzosn.pay.ali.api.AliPayService * @param apyAccount @@ -49,28 +52,26 @@ public enum PayType implements BasePayType { */ @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); + AliPayService aliPayService = new AliPayService(aliPayConfigStorage); + aliPayService.setPayMessageHandler(new WxPayMessageHandler(apyAccount.getPayId())); + return aliPayService; } @Override @@ -82,12 +83,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()); @@ -95,7 +96,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()); @@ -109,7 +109,7 @@ public enum PayType implements BasePayType { httpConfigStorage.setCertStoreType(CertStoreType.PATH); return new WxPayService(wxPayConfigStorage, httpConfigStorage);*/ WxPayService wxPayService = new WxPayService(wxPayConfigStorage); - wxPayService.setPayMessageHandler(new WxPayMessageHandler(1)); + wxPayService.setPayMessageHandler(new WxPayMessageHandler(apyAccount.getPayId())); return wxPayService; } @@ -124,7 +124,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。暂未测试集群环境 @@ -135,11 +135,12 @@ 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); + final WxYouDianPayService wxYouDianPayService = new WxYouDianPayService(wxPayConfigStorage); + wxYouDianPayService.setPayMessageHandler(new YouDianPayMessageHandler(apyAccount.getPayId())); + return wxYouDianPayService; } /** @@ -153,8 +154,7 @@ public enum PayType implements BasePayType { return YoudianTransactionType.valueOf(transactionType); } - },fuiou{ - + }, fuiou { @Override public PayService getPayService(ApyAccount apyAccount) { FuiouPayConfigStorage fuiouPayConfigStorage = new FuiouPayConfigStorage(); @@ -164,10 +164,11 @@ 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); + final FuiouPayService fuiouPayService = new FuiouPayService(fuiouPayConfigStorage); + fuiouPayService.setPayMessageHandler(new FuiouPayMessageHandler(apyAccount.getPayId())); + return fuiouPayService; } @Override @@ -176,8 +177,7 @@ public enum PayType implements BasePayType { } - },unionPay{ - + }, unionPay { @Override public PayService getPayService(ApyAccount apyAccount) { UnionPayConfigStorage unionPayConfigStorage = new UnionPayConfigStorage(); @@ -201,10 +201,11 @@ public enum PayType implements BasePayType { 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); + final UnionPayService unionPayService = new UnionPayService(unionPayConfigStorage); + unionPayService.setPayMessageHandler(new UnionPayMessageHandler(apyAccount.getPayId())); + return unionPayService; } @Override @@ -213,13 +214,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,7 +227,9 @@ public enum PayType implements BasePayType { configStorage.setApiPassword(apyAccount.getPrivateKey()); //是否为沙箱 configStorage.setTest(true); - return new PayoneerPayService(configStorage); + final PayoneerPayService payoneerPayService = new PayoneerPayService(configStorage); + payoneerPayService.setPayMessageHandler(new PayoneerMessageHandler(apyAccount.getPayId())); + return payoneerPayService; //以下不建议进行使用,会引起两次请求的问题 //Basic Auth @@ -243,20 +245,22 @@ 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); //发起付款后的页面转跳地址 storage.setReturnUrl(apyAccount.getReturnUrl()); //取消按钮转跳地址,这里兼容的做法 storage.setNotifyUrl(apyAccount.getNotifyUrl()); - return new PayPalPayService(storage); + final PayPalPayService payPalPayService = new PayPalPayService(storage); + payPalPayService.setPayMessageHandler(new PayPalPayMessageHandler(apyAccount.getPayId())); + return payPalPayService; } @Override 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 38d814d164f951895698c396dcdd91a8eab86f16..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,7 +5,7 @@ import java.util.Date; /** * 订单辅助接口 - * @author: egan + * @author egan * email egzosn@gmail.com * date 2017/3/12 14:50 */ 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 a55bde13db3b3b68483d5fa8b2b9f5347d9414c4..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,7 +10,7 @@ import java.util.HashMap; import java.util.Map; /** - * @author: egan + * @author egan * email egzosn@gmail.com * date 2016/11/18 1:11 */ 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 1ebfbafff64a4e42f8ab2c971edecddbcc301717..f461bc5044c153d8f92b57a78494d6f3a74e9939 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,23 +1,30 @@ package com.egzosn.pay.demo.service; +import javax.annotation.Resource; + +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; + +import com.egzosn.pay.common.api.BasePayService; 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 + * + * @author egan * email egzosn@gmail.com * date 2016/11/18 0:34 */ @@ -38,6 +45,7 @@ public class PayResponse { /** * 初始化支付配置 + * * @param apyAccount 账户信息 * @see ApyAccount 对应表结构详情--》 /pay-java-demo/resources/apy_account.sql */ @@ -45,20 +53,25 @@ public class PayResponse { //根据不同的账户类型 初始化支付配置 this.service = apyAccount.getPayType().getPayService(apyAccount); this.storage = service.getPayConfigStorage(); + + //这里设置http请求配置 // service.setRequestTemplateConfigStorage(getHttpConfigStorage()); buildRouter(apyAccount.getPayId()); + + } /** * 获取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"); // //代理端口 @@ -77,14 +90,16 @@ public class PayResponse { /** * 配置路由 + * * @param payId 指定账户id,用户多微信支付多支付宝支付 + * @deprecated 不再推荐使用路由方式,回调或拦截器,直接在payService中设置并获取使用,回调拦截器已提供对应的实现方式:{@link BasePayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} 与{@link BasePayService#addPayMessageInterceptor(com.egzosn.pay.common.api.PayMessageInterceptor)} */ + @Deprecated private void buildRouter(Integer payId) { router = new PayMessageRouter(this.service); router .rule() //消息类型 - .msgType(MsgType.text.name()) //支付账户事件类型 .payType(PayType.aliPay.name()) //拦截器 @@ -93,33 +108,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() @@ -140,6 +149,12 @@ public class PayResponse { return service; } + /** + * 不建议使用, 回调拦截器已提供对应的实现方式:{@link BasePayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} 与{@link BasePayService#addPayMessageInterceptor(com.egzosn.pay.common.api.PayMessageInterceptor)} + * + * @return + */ + @Deprecated public PayMessageRouter getRouter() { return router; } 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 912df2e626fdb48e4ee0580f79839d376a122a71..4fe35d398d9c5af6683dea192343fccd48b9b52e 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 @@ -1,27 +1,23 @@ package com.egzosn.pay.demo.service.handler; -import com.egzosn.pay.common.api.PayMessageHandler; -import com.egzosn.pay.common.api.PayService; +import java.util.Map; + 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; -import java.util.Map; /** * PayPal支付回调处理器 * Created by ZaoSheng on 2016/6/1. - * */ -@Component -public class PayPalPayMessageHandler implements PayMessageHandler { - +public class PayPalPayMessageHandler extends BasePayMessageHandler { + public PayPalPayMessageHandler(Integer payId) { + super(payId); + } @Override public PayOutMessage handle(PayMessage payMessage, Map context, PayPalPayService payService) throws PayErrorException { 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/interceptor/AliPayMessageInterceptor.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/interceptor/AliPayMessageInterceptor.java index 3d35c88af51a339691d0e7e39073d1f4eddad375..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 @@ -13,7 +13,7 @@ import java.util.Map; /** * 支付宝回调信息拦截器 - * @author: egan + * @author egan * email egzosn@gmail.com * date 2017/1/18 19:28 */ 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 efb8a07fef1e1e592cd0c772a5d837bdb0dff884..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 @@ -12,7 +12,7 @@ import java.util.Map; /** * 回调信息拦截器 - * @author: egan + * @author egan * email egzosn@gmail.com * date 2017/1/18 19:28 */ 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/webapp/gzh.png b/pay-java-demo/src/main/webapp/gzh.png new file mode 100644 index 0000000000000000000000000000000000000000..3ca140e3e1265387fb060c7d318614a94316784b Binary files /dev/null and b/pay-java-demo/src/main/webapp/gzh.png differ 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 7d1b18ed9e2481e8c459c118abbfd57c33fd7e73..107215bca08f2b8518bb883266e869b9e8f6f56c 100644 --- a/pay-java-fuiou/pom.xml +++ b/pay-java-fuiou/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.3-SNAPSHOT + 2.14.8 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 2dd49a8b5ad7d40517823ffde55ecd48d58aa6a5..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,9 +1,10 @@ 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
  * 
@@ -15,7 +16,8 @@ public class FuiouPayConfigStorage extends BasePayConfigStorage { private String mchntCd; /** - * 应用id + * 应用id + * * @return 空 */ @Override @@ -23,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 8f6b095b374512351af4a6acbcabfef2e43885c9..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,25 +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 com.egzosn.pay.fuiou.bean.FuiouRefundResult; import com.egzosn.pay.fuiou.bean.FuiouTransactionType; -import java.awt.image.BufferedImage; -import java.io.InputStream; -import java.math.BigDecimal; -import java.util.*; - /** * @author Actinia - *
- *email hayesfu@qq.com
+ * 
+ * email hayesfu@qq.com
  * create 2017 2017/1/16 0016
  * 
*/ @@ -37,7 +52,7 @@ public class FuiouPayService extends BasePayService { /** * B2C/B2B支付 */ - public static final String URL_FuiouSmpGate = "smpGate.do"; + public static final String URL_FuiouSmpGate = "smpGate.do"; /** * B2C/B2B支付(跨境支付) */ @@ -58,48 +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){ + public String getReqUrl(TransactionType transactionType) { return payConfigStorage.isTest() ? DEV_URL_FUIOU_BASE_DOMAIN : URL_FUIOU_BASE_DOMAIN; } + /** * 获取对应的请求地址 + * * @return 请求地址 */ - public String getReqUrl(){ + 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; @@ -107,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; } @@ -116,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); } @@ -134,14 +174,13 @@ public class FuiouPayService extends BasePayService { * @param orderId 业务id, 数据的真实性. * @return 返回校验结果 */ - @Override public boolean verifySource(String orderId) { LinkedHashMap params = new LinkedHashMap(3); params.put("mchnt_cd", payConfigStorage.getPid()); params.put("order_id", orderId); - params.put("md5", createSign(SignUtils.parameters2MD5Str(params, "|"), payConfigStorage.getInputCharset())); + 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")); @@ -150,23 +189,30 @@ public class FuiouPayService extends BasePayService { /** * 将支付请求参数加密成md5 + * * @param order 支付订单 * @return 返回支付请求参数集合 */ @Override public Map orderInfo(PayOrder order) { - if (null == order.getTransactionType()){ + if (null == order.getTransactionType()) { order.setTransactionType(FuiouTransactionType.B2C); } Map parameters = getOrderInfo(order); - String sign = createSign(SignUtils.parameters2MD5Str(parameters, "|"), payConfigStorage.getInputCharset()); + 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 返回支付请求参数集合 */ @@ -186,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"); } @@ -209,6 +255,7 @@ public class FuiouPayService extends BasePayService { /** * 对内容进行加密 + * * @param content 需要加密的内容 * @param characterEncoding 字符编码 * @return 加密后的字符串 @@ -218,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 消息实体 */ @@ -267,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 返回输出消息 */ @@ -280,6 +333,7 @@ public class FuiouPayService extends BasePayService { /** * 发送支付请求(form表单) + * * @param orderInfo 发起支付的订单信息 * @param method 请求方式 "post" "get", * @return form表单提交的html字符串 @@ -287,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 String getQrPay (PayOrder order) { + public String getQrPay(PayOrder order) { throw new UnsupportedOperationException(); } /** * 暂未实现或无此功能 + * * @param order 发起支付的订单信息 * @return 不支持的操作异常 */ @@ -313,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(); @@ -340,6 +397,7 @@ public class FuiouPayService extends BasePayService { /** * 交易查询接口 + * * @param tradeNo 支付平台订单号 * @param outTradeNo 商户单号 * @return 空 @@ -347,39 +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 assistOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 + */ + @Override + 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()); @@ -392,9 +468,9 @@ public class FuiouPayService extends BasePayService { //备注 params.put("rem", ""); params.putAll(refundOrder.getAttrs()); - params.put("md5", createSign(SignUtils.parameters2MD5Str(params, "|"), payConfigStorage.getInputCharset())); + 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); } @@ -412,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 06e14f361bf91df718e8adb204a7dd270ddf921b..c8094678979a4d752d0d6a5a24615632cdfca984 100644 --- a/pay-java-payoneer/README.md +++ b/pay-java-payoneer/README.md @@ -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 7a79e0c26fcfe3ae71a2a0920a7f8ae0b96774ca..7f208e3e4bec4aabba0616632f107687a8e0dbb1 100644 --- a/pay-java-payoneer/pom.xml +++ b/pay-java-payoneer/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.3-SNAPSHOT + 2.14.8 4.0.0 pay-java-payoneer 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 a598c74c11a379bc3c61b6f7d8dd4b74224aa35b..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
@@ -16,27 +18,38 @@ public class PayoneerConfigStorage extends BasePayConfigStorage {
      */
     private String programId;
     /**
-     *  PayoneerPay 用户名
+     * PayoneerPay 用户名
      */
     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 8596b7bdbc83cecddfec6b34c41d908d2f11049b..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,24 +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.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 { /** * 测试地址 */ @@ -60,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 @@ -96,7 +118,6 @@ public class PayoneerPayService extends BasePayService im * 授权状态 * * @param payeeId 用户id - * * @return 返回是否认证 true 已认证 */ @Override @@ -109,7 +130,6 @@ public class PayoneerPayService extends BasePayService im * 获取授权用户信息 * * @param payeeId 用户id - * * @return 获取授权用户信息,包含用户状态,注册时间,联系人信息,地址信息等等 */ @Override @@ -122,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; } /** @@ -154,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")); @@ -167,7 +185,6 @@ public class PayoneerPayService extends BasePayService im * 返回创建的订单信息 * * @param order 支付订单 - * * @return 订单信息 * @see PayOrder 支付订单信息 */ @@ -191,7 +208,6 @@ public class PayoneerPayService extends BasePayService im * * @param content 需要签名的内容 * @param characterEncoding 字符编码 - * * @return 签名 */ @Override @@ -205,7 +221,6 @@ public class PayoneerPayService extends BasePayService im * * @param code 状态 * @param message 消息 - * * @return 返回输出消息 */ @Override @@ -218,7 +233,6 @@ public class PayoneerPayService extends BasePayService im * 主要用于拦截器中返回 * * @param payMessage 支付回调消息 - * * @return 返回输出消息 */ @Override @@ -231,7 +245,6 @@ public class PayoneerPayService extends BasePayService im * * @param orderInfo 发起支付的订单信息 * @param method 请求方式 "post" "get", - * * @return 获取输出消息,用户返回给支付端, 针对于web端 * @see MethodType 请求类型 */ @@ -244,7 +257,6 @@ public class PayoneerPayService extends BasePayService im * 获取输出二维码,用户返回给支付端, * * @param order 发起支付的订单信息 - * * @return 返回图片信息,支付时需要的 */ @Override @@ -256,7 +268,6 @@ public class PayoneerPayService extends BasePayService im * 刷卡付,pos主动扫码付款(条码付) * * @param order 发起支付的订单信息 - * * @return 返回支付结果 */ @Override @@ -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 @@ -300,6 +320,17 @@ public class PayoneerPayService extends BasePayService im return secondaryInterface(tradeNo, outTradeNo, PayoneerTransactionType.CHARGE_CANCEL); } + /** + * 交易关闭接口 + * + * @param assistOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 + */ + @Override + public Map close(AssistOrder assistOrder) { + return secondaryInterface(assistOrder.getTradeNo(), assistOrder.getOutTradeNo(), PayoneerTransactionType.CHARGE_CANCEL); + } + /** * 交易交易撤销 * @@ -313,18 +344,60 @@ 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); + } + + @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; + } + }; } @@ -339,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(); } @@ -358,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; } @@ -377,7 +448,6 @@ public class PayoneerPayService extends BasePayService im * 转账 * * @param order 转账订单 - * * @return 对应的转账结果 */ @Override @@ -398,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 @@ -416,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 b59187654923037072251003751aea22638df7fb..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 { 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 0fba2f8f7588ccb1c1e2ff57f70c5c47a3e4154d..83a906633c5419ec19f5d5659c4cc396d63a1371 100644 --- a/pay-java-paypal/pom.xml +++ b/pay-java-paypal/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.3-SNAPSHOT + 2.14.8 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 8169fa7b7f55aced096baa4b88efd8443de5863c..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,42 +1,71 @@ package com.egzosn.pay.paypal.api; +import java.util.concurrent.locks.ReentrantLock; + import com.egzosn.pay.common.api.BasePayConfigStorage; /** * 贝宝支付配置存储 * * @author egan - *

- * email egzosn@gmail.com - * date 2018-4-8 22:11:42 + *

+ * email egzosn@gmail.com + * date 2018-4-8 22:11:42 */ public class PayPalConfigStorage extends BasePayConfigStorage { - private 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(); } @@ -48,6 +77,10 @@ public class PayPalConfigStorage extends BasePayConfigStorage { /** * 设置取消页面的url + *

+     * 注意:这里不是异步回调的通知
+     * IPN 地址设置的路径:https://developer.paypal.com/developer/ipnSimulator/
+     * 
* * @param cancelUrl 取消页面的url */ @@ -57,10 +90,25 @@ public class PayPalConfigStorage extends BasePayConfigStorage { /** * 获取取消页面的url + *
+     * 注意:这里不是异步回调的通知
+     * 
+ * + * @return 取消页面的url */ 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 ea120e78508eb3ca0727047042c6252af22d24ba..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.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,10 +67,11 @@ public class PayPalPayService extends BasePayService{ /** * 获取对应的请求地址 + * * @return 请求地址 */ @Override - public String getReqUrl(TransactionType transactionType){ + public String getReqUrl(TransactionType transactionType) { return (payConfigStorage.isTest() ? SANDBOX_REQ_URL : REQ_URL) + transactionType.getMethod(); } @@ -55,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 支付异常 @@ -84,52 +112,53 @@ public class PayPalPayService extends BasePayService{ } 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())); - } + 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())); @@ -137,6 +166,7 @@ public class PayPalPayService extends BasePayService{ return new HttpHeader(headers); } + /** * 返回创建的订单信息 * @@ -146,25 +176,26 @@ public class PayPalPayService extends BasePayService{ */ @Override public Map orderInfo(PayOrder order) { - if (null == order.getTransactionType()){ + if (null == order.getTransactionType()) { order.setTransactionType(PayPalTransactionType.sale); } Amount amount = new Amount(); - if (null == order.getCurType()){ + if (null == order.getCurType()) { order.setCurType(DefaultCurType.USD); } 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); @@ -181,11 +212,11 @@ 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); - if ("created".equals(resp.getString("state")) && StringUtils.isNotEmpty(resp.getString("id"))){ - order.setOutTradeNo(resp.getString("id")); + if ("created".equals(resp.getString("state")) && StringUtils.isNotEmpty(resp.getString("id"))) { + order.setTradeNo(resp.getString("id")); } return preOrderHandler(resp, order); } @@ -202,15 +233,15 @@ 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 @@ -222,6 +253,7 @@ public class PayPalPayService extends BasePayService{ public Map microPay(PayOrder order) { return null; } + /** * 交易查询接口 * @@ -231,8 +263,19 @@ 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 @@ -240,21 +283,30 @@ public class PayPalPayService extends BasePayService{ return null; } - + /** + * 交易关闭接口 + * + * @param assistOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 + */ + @Override + 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()) == -1){ + if (null != refundOrder.getRefundAmount() && BigDecimal.ZERO.compareTo(refundOrder.getRefundAmount()) == -1) { Amount amount = new Amount(); - if(null == refundOrder.getCurType()){ + if (null == refundOrder.getCurType()) { refundOrder.setCurType(DefaultCurType.USD); } @@ -266,8 +318,53 @@ public class PayPalPayService extends BasePayService{ 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; + 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; + } + }; } /** @@ -278,17 +375,12 @@ public class PayPalPayService extends BasePayService{ */ @Override public Map refundquery(RefundOrder refundOrder) { - JSONObject resp = getHttpRequestTemplate().getForObject(getReqUrl(PayPalTransactionType.REFUND_QUERY), authHeader(), JSONObject.class, refundOrder.getTradeNo()); - return resp; + 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-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/PayPalTransactionType.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/PayPalTransactionType.java new file mode 100644 index 0000000000000000000000000000000000000000..2cde37de6f22a87cc260594793e33f35b5762fb5 --- /dev/null +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/PayPalTransactionType.java @@ -0,0 +1,74 @@ +package com.egzosn.pay.paypal.v2.bean; + +import com.egzosn.pay.common.bean.TransactionType; + +/** + * 贝宝交易类型 + *
+ * 说明交易类型主要用于支付接口调用参数所需
+ *
+ *
+ *
+ * 
+ * + * @author egan + *

+ * email egzosn@gmail.com + * date 2018/04/28 11:10 + */ +public enum PayPalTransactionType implements TransactionType { + /** + * 获取token + */ + AUTHORIZE("v1/oauth2/token"), + /** + * 付款 网页支付 + */ + CHECKOUT("v2/checkout/orders"), + /** + * 获取订单信息 + */ + ORDERS_GET("/v2/checkout/orders/{order_id}"), + /** + * 确认订单并返回确认后订单信息 + */ + ORDERS_CAPTURE("/v2/checkout/orders/{order_id}/capture"), + /** + * 获取确认后订单信息 + */ + GET_CAPTURE("/v2/payments/captures/{capture_id}"), + /** + * 退款 + */ + REFUND("/v2/payments/captures/{capture_id}/refund"), + + /** + * 退款查询 + */ + REFUND_GET("/v2/payments/refunds/{refund_id}"), + + ; + + + private String method; + + private PayPalTransactionType(String method) { + this.method = method; + } + + @Override + public String getType() { + return this.name(); + } + + /** + * 获取接口名称 + * + * @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 40951dfc60471a81aef4b254f783be670dfd9e14..7d604a80d7021ca89487b591c77804fd11a64fed 100644 --- a/pay-java-union/README.md +++ b/pay-java-union/README.md @@ -80,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("-", "")); ``` @@ -190,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 abff084abaccb811336f07c0e010992433f09b88..00721c6a98b03a8b065725177a4770feabccb1a3 100644 --- a/pay-java-union/pom.xml +++ b/pay-java-union/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.3-SNAPSHOT + 2.14.8 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 24864d3fb35682e3d5edeba3626445875b7c61f9..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,16 +1,15 @@ package com.egzosn.pay.union.api; -import com.egzosn.pay.common.api.BasePayConfigStorage; -import com.egzosn.pay.common.bean.CertStoreType; - 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
  *         
@@ -41,6 +40,10 @@ public class UnionPayConfigStorage extends BasePayConfigStorage { * 应用私钥证书 */ private Object keyPrivateCert; + /** + * 应用私钥证书,rsa_private pkcs8格式 生成签名时使用 + */ + private String keyPrivateCertPwd; /** * 中级证书 @@ -55,21 +58,23 @@ public class UnionPayConfigStorage extends BasePayConfigStorage { * 证书存储类型 */ private CertStoreType certStoreType; + /** * 设置私钥证书 * * @param certificate 私钥证书地址 或者证书内容字符串 - * 私钥证书密码 {@link #setKeyPrivateCertPwd(String)} + * 私钥证书密码 {@link #setKeyPrivateCertPwd(String)} */ public void setKeyPrivateCert(String certificate) { super.setKeyPrivate(certificate); this.keyPrivateCert = certificate; } + /** * 设置私钥证书 * * @param keyPrivateCert 私钥证书信息流 - * 私钥证书密码 {@link #setKeyPrivateCertPwd(String)} + * 私钥证书密码 {@link #setKeyPrivateCertPwd(String)} */ public void setKeyPrivateCert(InputStream keyPrivateCert) { this.keyPrivateCert = keyPrivateCert; @@ -87,6 +92,7 @@ public class UnionPayConfigStorage extends BasePayConfigStorage { public void setAcpMiddleCert(String acpMiddleCert) { this.acpMiddleCert = acpMiddleCert; } + /** * 设置中级证书 * @@ -104,6 +110,7 @@ public class UnionPayConfigStorage extends BasePayConfigStorage { public void setAcpRootCert(String acpRootCert) { this.acpRootCert = acpRootCert; } + /** * 设置根证书 * @@ -120,6 +127,7 @@ public class UnionPayConfigStorage extends BasePayConfigStorage { public String getAcpRootCert() { return (String) acpRootCert; } + public InputStream getAcpMiddleCertInputStream() throws IOException { return certStoreType.getInputStream(acpMiddleCert); } @@ -128,14 +136,35 @@ public class UnionPayConfigStorage extends BasePayConfigStorage { return certStoreType.getInputStream(acpRootCert); } + /** + * 获取私钥证书密码 + * + * @return 私钥证书密码 + */ + public String getKeyPrivateCertPwd() { + return keyPrivateCertPwd; + } - + public void setKeyPrivateCertPwd(String keyPrivateCertPwd) { + this.keyPrivateCertPwd = keyPrivateCertPwd; + } @Override public String getAppid() { return null; } + /** + * 应用id + * 纠正名称 + * + * @return 应用id + */ + @Override + public String getAppId() { + return null; + } + /** * @return 合作者id * @see #getPid() @@ -201,6 +230,7 @@ public class UnionPayConfigStorage extends BasePayConfigStorage { /** * 证书存储类型 + * * @return 证书存储类型 */ public CertStoreType getCertStoreType() { 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 24d64dada871fc4e30366c38da1a5c6d216fd331..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,8 +1,42 @@ 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; @@ -11,27 +45,17 @@ 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.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.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.math.BigDecimal; -import java.security.GeneralSecurityException; -import java.security.InvalidAlgorithmParameterException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.*; -import java.sql.Timestamp; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.*; - /** * @author Actinia *
@@ -61,7 +85,7 @@ public class UnionPayService extends BasePayService {
     /**
      * 证书解释器
      */
-    private CertDescriptor certDescriptor;
+    private volatile CertDescriptor certDescriptor;
 
     /**
      * 构造函数
@@ -69,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) {
@@ -84,18 +108,18 @@ public class UnionPayService extends BasePayService {
      */
     @Override
     public UnionPayService setPayConfigStorage(UnionPayConfigStorage payConfigStorage) {
-        super.setPayConfigStorage(payConfigStorage);
-        if (!payConfigStorage.isCertSign() || null != certDescriptor) {
+        this.payConfigStorage = payConfigStorage;
+        if (null != certDescriptor) {
             return this;
         }
-
-        certDescriptor = new CertDescriptor();
         try {
+            certDescriptor = new CertDescriptor();
             certDescriptor.initPrivateSignCert(payConfigStorage.getKeyPrivateCertInputStream(), payConfigStorage.getKeyPrivateCertPwd(), "PKCS12");
             certDescriptor.initPublicCert(payConfigStorage.getAcpMiddleCertInputStream());
             certDescriptor.initRootCert(payConfigStorage.getAcpRootCertInputStream());
-        } catch (IOException e) {
-            LOG.error(e);
+        }
+        catch (IOException e) {
+            LOG.error("", e);
         }
 
 
@@ -130,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());
     }
@@ -139,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自行选择外其他不需修改
@@ -155,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:平台商户)
@@ -174,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;
@@ -191,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());
@@ -213,17 +269,6 @@ public class UnionPayService extends BasePayService {
         }
     }
 
-    /**
-     * 支付宝需要,微信是否也需要再次校验来源,进行订单查询
-     * 校验数据来源
-     *
-     * @param id 业务id, 数据的真实性.
-     * @return true通过
-     */
-    @Override
-    public boolean verifySource(String id) {
-        return false;
-    }
 
     /**
      * 订单超时时间。
@@ -254,7 +299,7 @@ public class UnionPayService extends BasePayService {
         Map params = this.getCommonParam();
 
         UnionTransactionType type = (UnionTransactionType) order.getTransactionType();
-
+        initNotifyUrl(params, order);
 
         //设置交易类型相关的参数
         type.convertMap(params);
@@ -311,20 +356,20 @@ public class UnionPayService extends BasePayService {
             case RSA:
                 parameters.put(SDKConstants.param_signMethod, SDKConstants.SIGNMETHOD_RSA);
                 parameters.put(SDKConstants.param_certId, certDescriptor.getSignCertId());
-                signStr = SignUtils.SHA1.createSign(SignUtils.parameterText(parameters, "&", "signature"), "", payConfigStorage.getInputCharset());
+                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, certDescriptor.getSignCertId());
-                signStr = SignUtils.SHA256.createSign(SignUtils.parameterText(parameters, "&", "signature"), "", payConfigStorage.getInputCharset());
+                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;
@@ -370,12 +415,15 @@ public class UnionPayService extends BasePayService {
             /*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 (GeneralSecurityException e) {
-            LOG.error(e);
+        }
+        catch (CertificateExpiredException e) {
+            LOG.error("", e);
+        }
+        catch (GeneralSecurityException e) {
+            LOG.error("", e);
         }
         return null;
     }
@@ -387,9 +435,9 @@ public class UnionPayService extends BasePayService {
      * @return 返回支付结果
      */
 
-    public JSONObject postOrder(PayOrder order) {
+    public JSONObject postOrder(PayOrder order, String url) {
         Map params = orderInfo(order);
-        String responseStr = getHttpRequestTemplate().postForObject(this.getBackTransUrl(), params, String.class);
+        String responseStr = getHttpRequestTemplate().postForObject(url, params, String.class);
         JSONObject response = UriVariables.getParametersToMap(responseStr);
         if (response.isEmpty()) {
             throw new PayErrorException(new PayException("failure", "响应内容有误!", responseStr));
@@ -402,7 +450,8 @@ public class UnionPayService extends BasePayService {
 
         if (null == order.getTransactionType()) {
             order.setTransactionType(UnionTransactionType.WEB);
-        } else if (UnionTransactionType.WEB != order.getTransactionType() && UnionTransactionType.WAP != order.getTransactionType() && UnionTransactionType.B2B != order.getTransactionType()) {
+        }
+        else if (UnionTransactionType.WEB != order.getTransactionType() && UnionTransactionType.WAP != order.getTransactionType() && UnionTransactionType.B2B != order.getTransactionType()) {
             throw new PayErrorException(new PayException("-1", "错误的交易类型:" + order.getTransactionType()));
         }
 
@@ -418,7 +467,7 @@ public class UnionPayService extends BasePayService {
     @Override
     public String getQrPay(PayOrder order) {
         order.setTransactionType(UnionTransactionType.APPLY_QR_CODE);
-        JSONObject response = postOrder(order);
+        JSONObject response = postOrder(order, getBackTransUrl());
         if (this.verify(response)) {
             if (SDKConstants.OK_RESP_CODE.equals(response.get(SDKConstants.param_respCode))) {
                 //成功
@@ -438,7 +487,7 @@ public class UnionPayService extends BasePayService {
     @Override
     public Map microPay(PayOrder order) {
         order.setTransactionType(UnionTransactionType.CONSUME);
-        JSONObject response = postOrder(order);
+        JSONObject response = postOrder(order, getBackTransUrl());
         return response;
     }
 
@@ -455,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;
@@ -526,7 +576,7 @@ public class UnionPayService extends BasePayService {
         if (null == order.getTransactionType()) {
             order.setTransactionType(UnionTransactionType.APP);
         }
-        JSONObject response = postOrder(order);
+        JSONObject response = postOrder(order, getAppTransUrl());
         if (this.verify(response)) {
             if (SDKConstants.OK_RESP_CODE.equals(response.get(SDKConstants.param_respCode))) {
 //                //成功,获取tn号
@@ -548,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()));
-
     }
 
 
@@ -579,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);
 
     }
@@ -591,7 +651,7 @@ 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());
@@ -601,12 +661,11 @@ public class UnionPayService extends BasePayService {
         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()));
@@ -626,9 +685,19 @@ public class UnionPayService extends BasePayService {
         return Collections.emptyMap();
     }
 
+    /**
+     * 交易关闭接口
+     *
+     * @param assistOrder 关闭订单
+     * @return 返回支付方交易关闭后的结果
+     */
+    @Override
+    public Map close(AssistOrder assistOrder) {
+        return Collections.emptyMap();
+    }
 
     @Override
-    public Map refund(RefundOrder refundOrder) {
+    public UnionRefundResult refund(RefundOrder refundOrder) {
         return unionRefundOrConsumeUndo(refundOrder, UnionTransactionType.REFUND);
     }
 
@@ -644,6 +713,18 @@ public class UnionPayService extends BasePayService {
         return Collections.emptyMap();
     }
 
+    /**
+     * 下载对账单
+     *
+     * @param billDate 账单时间
+     * @param fileType 文件类型 文件类型,一般商户填写00即可
+     * @return 返回fileContent 请自行将数据落地
+     */
+    @Override
+    public Map downloadBill(Date billDate, String fileType) {
+        return downloadBill(billDate, new UnionPayBillType(fileType));
+    }
+
     /**
      * 下载对账单
      *
@@ -652,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);
@@ -675,19 +757,6 @@ public class UnionPayService extends BasePayService {
     }
 
 
-    /**
-     * @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-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/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 ff2f114f6a5de93eeb6bae1fce59e524596bea84..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(new RefundOrder("原交易查询流水号", "订单号", 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..2ddceda009e768a29635382afd9987c750157a77 --- /dev/null +++ b/pay-java-web-support/pom.xml @@ -0,0 +1,34 @@ + + + + pay-java-parent + com.egzosn + 2.14.8 + + 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 89445d1e7b8353a5b4caf43d62d2a63739dda32d..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("-", "")); ``` diff --git a/pay-java-wx-youdian/pom.xml b/pay-java-wx-youdian/pom.xml index d43cfda4480f53601dbce814195d20708afa2e53..5b78c0c201e9d4ac08a23f2239fa8f42065a61d5 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.13.3-SNAPSHOT + 2.14.8 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 7548278a44c98d37810e077691ca449baba1291a..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,14 +1,16 @@ package com.egzosn.pay.wx.youdian.api; +import java.util.concurrent.locks.ReentrantLock; + import com.egzosn.pay.common.api.BasePayConfigStorage; /** * 支付客户端配置存储 * * @author egan - *

- * email egzosn@gmail.com - * date 2017/01/12 22:58 + *

+ * email egzosn@gmail.com + * date 2017/01/12 22:58 */ public class WxYouDianPayConfigStorage extends BasePayConfigStorage { @@ -24,6 +26,17 @@ public class WxYouDianPayConfigStorage extends BasePayConfigStorage { return null; } + /** + * 应用id + * 纠正名称 + * + * @return 应用id + */ + @Override + public String getAppId() { + return null; + } + @Override public String getPid() { @@ -49,5 +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 e28cb1079f74bb1f7a35ac973f159ab36bf8b220..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.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.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(getReqUrl(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()); + data.put(ACCESS_TOKEN, getAccessToken()); data.put("paymoney", Util.conversionAmount(order.getPrice()).toString()); data.putAll(order.getAttrs()); - data = preOrderHandler(data, order); - String apbNonce = SignUtils.randomStr(); - String sign = createSign(SignUtils.parameterText(data, "") + apbNonce, payConfigStorage.getInputCharset()); + 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(getReqUrl(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; - - } /** * 具体需要返回的数据为 @@ -295,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 请求类型 */ @@ -342,15 +344,15 @@ public class WxYouDianPayService extends BasePayService microPay(PayOrder order) { order.setTransactionType(YoudianTransactionType.MICROPAY); - JSONObject orderInfo = orderInfo(order); - return orderInfo; + return orderInfo(order); } /** @@ -362,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(getReqUrl(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); } @@ -383,8 +396,16 @@ public class WxYouDianPayService extends BasePayService close(AssistOrder assistOrder) { + return Collections.emptyMap(); + } /** * 申请退款接口 @@ -393,23 +414,69 @@ public class WxYouDianPayService extends BasePayService 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(getReqUrl(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 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,12 @@ public class WxYouDianPayService extends BasePayService pay-java-parent com.egzosn - 2.13.3-SNAPSHOT + 2.14.8 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 index bf01bbdc3a79bc14d13a802ff703c50f96e1003c..09232dfa974b19dfd9015a28dbb4b9e22af59bef 100644 --- 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 @@ -3,13 +3,18 @@ package com.egzosn.pay.wx.api; import java.util.Date; import java.util.Map; +import com.egzosn.pay.common.bean.BillType; + /** - * @description:账单接口 - * @author: faymanwang - * @email: 1057438332@qq.com - * @time: 2020/7/31 11:21 + * 账单接口 + * + * @author faymanwang + * email: 1057438332@qq.com + * time: 2020/7/31 11:21 */ public interface WxBillService { - public Map downloadbill(Date billDate, String billType, String path); + @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 index ac656b4509c740ce73090b6e6fb17bb3eabad50d..07a290b14e6a53c8ddd753b49132758e30926c5c 100644 --- 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 @@ -16,7 +16,7 @@ public interface WxConst { /** * 沙箱 */ - String SANDBOXNEW = "sandboxnew/"; + String SANDBOXNEW = "xdc/apiv2sandbox/"; String SUCCESS = "SUCCESS"; String FAIL = "FAIL"; @@ -32,6 +32,9 @@ public interface WxConst { 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 009edf44f3fcb708d48abf499c2139b20d41719e..93a87f4b743a838ddab9f05164305a7405a3886b 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,30 +1,82 @@
 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.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.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.*;
-
-import java.io.*;
-import java.net.URLEncoder;
-import java.security.GeneralSecurityException;
-import java.util.*;
-import java.util.zip.GZIPInputStream;
-import java.util.zip.GZIPOutputStream;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-import static com.egzosn.pay.wx.api.WxConst.*;
-import static com.egzosn.pay.wx.bean.WxTransferType.*;
+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.wx.bean.WxTransferType;
 
 /**
  * 微信支付服务
@@ -35,7 +87,7 @@ import static com.egzosn.pay.wx.bean.WxTransferType.*;
  * date 2016-5-18 14:09:01
  * 
*/ -public class WxPayService extends BasePayService implements WxRedPackService,WxBillService { +public class WxPayService extends BasePayService implements WxRedPackService, WxBillService, TransferService { /** @@ -90,41 +142,37 @@ public class WxPayService extends BasePayService implements * @param params 回调回来的参数集 * @return 签名校验 true通过 */ + @Deprecated @Override public boolean verify(Map params) { - if (!(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; - } - - if (null == params.get(SIGN)) { - if (LOG.isDebugEnabled()) { - LOG.debug(String.format("微信支付异常:签名为空!%s=%s", 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)); + } @@ -135,8 +183,7 @@ public class WxPayService extends BasePayService implements * @param sign 比对的签名结果 * @return 生成的签名结果 */ - @Override - public boolean signVerify(Map params, String sign) { + private boolean signVerify(Map params, String sign) { return signVerify(params, sign, payConfigStorage.isTest()); } @@ -146,7 +193,7 @@ public class WxPayService extends BasePayService implements if (isTest) { keyPrivate = getKeyPrivate(); } - String content = SignUtils.parameterText(params, "&", SIGN, "appId") + "&key=" + (signUtils == SignUtils.MD5 ? "" : keyPrivate); + String content = SignTextUtils.parameterText(params, "&", SIGN, "appId") + "&key=" + (signUtils == SignUtils.MD5 ? "" : keyPrivate); return signUtils.verify(content, sign, keyPrivate, payConfigStorage.getInputCharset()); } @@ -157,13 +204,13 @@ public class WxPayService extends BasePayService implements */ private Map getPublicParameters() { - Map parameters = new TreeMap(); - parameters.put(APPID, payConfigStorage.getAppid()); + Map parameters = new TreeMap<>(); + parameters.put(APPID, payConfigStorage.getAppId()); parameters.put(MCH_ID, payConfigStorage.getMchId()); //判断如果是服务商模式信息则加入 - setParameters(parameters, "sub_mch_id", payConfigStorage.getSubMchId()); - setParameters(parameters, "sub_appid", payConfigStorage.getSubAppid()); - 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; @@ -183,14 +230,14 @@ public class WxPayService extends BasePayService implements // 购买支付信息 parameters.put("body", order.getSubject()); // 购买支付信息 - setParameters(parameters, "detail", order); + 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("total_fee", Util.conversionCentAmount(order.getPrice())); - setParameters(parameters, "attach", order.getAddition()); - parameters.put("notify_url", payConfigStorage.getNotifyUrl()); + OrderParaStructure.loadParameters(parameters, "attach", order.getAddition()); + initNotifyUrl(parameters, order); parameters.put("trade_type", order.getTransactionType().getType()); if (null != order.getExpirationTime()) { parameters.put("time_start", DateUtils.formatDate(new Date(), DateUtils.YYYYMMDDHHMMSS)); @@ -203,11 +250,11 @@ public class WxPayService extends BasePayService implements ((WxTransactionType) order.getTransactionType()).setAttribute(parameters, order); //可覆盖参数 -/* setParameters(parameters, "notify_url", order); - setParameters(parameters, "goods_tag", order); - setParameters(parameters, "limit_pay", order); - setParameters(parameters, "receipt", order); - setParameters(parameters, "product_id", 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); @@ -217,8 +264,10 @@ public class WxPayService extends BasePayService implements LOG.debug("requestXML:" + requestXML); } + HttpStringEntity entity = new HttpStringEntity(requestXML, ClientHttpRequest.APPLICATION_XML_UTF_8); + //调起支付的参数列表 - JSONObject result = requestTemplate.postForObject(getReqUrl(order.getTransactionType()), requestXML, JSONObject.class); + JSONObject result = requestTemplate.postForObject(getReqUrl(order.getTransactionType()), entity, JSONObject.class); 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())); @@ -240,30 +289,31 @@ public class WxPayService extends BasePayService implements ////统一下单 JSONObject result = unifiedOrder(order); // 对微信返回的数据进行校验 - if (verify(preOrderHandler(result, order))) { + if (verify(new NoticeParams(preOrderHandler(result, order)))) { //如果是扫码支付或者刷卡付无需处理,直接返回 if (((WxTransactionType) order.getTransactionType()).isReturn()) { return result; } - Map 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("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("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")); @@ -283,7 +333,7 @@ public class WxPayService extends BasePayService implements signTypeStr = SignUtils.HMACSHA256.getName(); } parameters.put("sign_type", signTypeStr); - String sign = createSign(SignUtils.parameterText(parameters, "&", SIGN, "appId"), payConfigStorage.getInputCharset()); + String sign = createSign(SignTextUtils.parameterText(parameters, "&", SIGN, "appId"), payConfigStorage.getInputCharset()); parameters.put(SIGN, sign); return parameters; } @@ -300,12 +350,13 @@ public class WxPayService extends BasePayService implements } SortedMap parameters = new TreeMap(); parameters.put(MCH_ID, payConfigStorage.getMchId()); - parameters.put(NONCE_STR, SignUtils.randomStr()); + parameters.put(NONCE_STR, SignTextUtils.randomStr()); - String sign = createSign(SignUtils.parameterText(parameters, "&", SIGN, "appId"), payConfigStorage.getInputCharset(), false); + String sign = createSign(SignTextUtils.parameterText(parameters, "&", SIGN, "appId"), payConfigStorage.getInputCharset(), false); parameters.put(SIGN, sign); - JSONObject result = requestTemplate.postForObject(getReqUrl(WxTransactionType.GETSIGNKEY), XML.getMap2Xml(parameters), JSONObject.class); + 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"); } @@ -343,19 +394,46 @@ public class WxPayService extends BasePayService implements 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())); } @@ -383,7 +461,7 @@ public class WxPayService extends BasePayService implements */ @Override public PayOutMessage successPayOutMessage(PayMessage payMessage) { - return PayOutMessage.XML().code("Success").content("成功").build(); + return PayOutMessage.XML().code("SUCCESS").content("成功").build(); } @@ -424,6 +502,20 @@ public class WxPayService extends BasePayService implements return (String) orderInfo.get("code_url"); } + /** + * 小程序支付,返回小程序所需的订单构建信息 + * + * @param order 发起支付的订单信息 + * @return 返回支付结果 + */ + @Override + public Map jsApi(PayOrder order) { + if (null == order.getTransactionType()) { + order.setTransactionType(WxTransactionType.JSAPI); + } + return orderInfo(order); + } + /** * 刷卡付,pos主动扫码付款 * @@ -435,7 +527,8 @@ public class WxPayService extends BasePayService implements if (null == order.getTransactionType()) { order.setTransactionType(WxTransactionType.MICROPAY); - } else if (WxTransactionType.MICROPAY != order.getTransactionType() && WxTransactionType.FACEPAY != order.getTransactionType()) { + } + else if (WxTransactionType.MICROPAY != order.getTransactionType() && WxTransactionType.FACEPAY != order.getTransactionType()) { throw new PayErrorException(new PayException("-1", "错误的交易类型:" + order.getTransactionType())); } return orderInfo(order); @@ -454,6 +547,17 @@ public class WxPayService extends BasePayService implements return secondaryInterface(transactionId, outTradeNo, WxTransactionType.QUERY); } + /** + * 交易查询接口 + * + * @param assistOrder 查询条件 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(AssistOrder assistOrder) { + return secondaryInterface(assistOrder.getTradeNo(), assistOrder.getOutTradeNo(), WxTransactionType.QUERY); + } + /** * 交易关闭接口 @@ -468,6 +572,18 @@ public class WxPayService extends BasePayService implements return secondaryInterface(transactionId, outTradeNo, WxTransactionType.CLOSE); } + /** + * 交易关闭接口 + * + * @param assistOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 + */ + @Override + public Map close(AssistOrder assistOrder) { + return secondaryInterface(assistOrder.getTradeNo(), assistOrder.getOutTradeNo(), WxTransactionType.CLOSE); + } + + /** * 交易交易撤销 * @@ -481,6 +597,13 @@ public class WxPayService extends BasePayService implements } + 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; + } + /** * 申请退款接口 * @@ -488,25 +611,25 @@ public class WxPayService extends BasePayService implements * @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())); - setParameters(parameters, "notify_url", payConfigStorage.getNotifyUrl()); + initNotifyUrl(parameters, refundOrder); if (null != refundOrder.getCurType()) { parameters.put("refund_fee_type", refundOrder.getCurType().getType()); } - setParameters(parameters, "refund_desc", refundOrder.getDescription()); + OrderParaStructure.loadParameters(parameters, "refund_desc", refundOrder.getDescription()); //附加参数,这里可进行覆盖前面所有参数 parameters.putAll(refundOrder.getAttrs()); //设置签名 setSign(parameters); - return requestTemplate.postForObject(getReqUrl(WxTransactionType.REFUND), XML.getMap2Xml(parameters), JSONObject.class); + return WxRefundResult.create(requestTemplate.postForObject(getReqUrl(WxTransactionType.REFUND), XML.getMap2Xml(parameters), JSONObject.class)); } @@ -521,9 +644,9 @@ public class WxPayService extends BasePayService implements //获取公共参数 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(getReqUrl(WxTransactionType.REFUNDQUERY), XML.getMap2Xml(parameters), JSONObject.class); @@ -533,98 +656,129 @@ public class WxPayService extends BasePayService implements /** * 目前只支持日账单 * - * @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) { - Map parameters = getDownloadBillParam(billDate, billType,false); + 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.getType()); + //目前只支持日账单 + parameters.put(WxConst.BILL_DATE, DateUtils.formatDate(billDate, DateUtils.YYYYMMDD)); + String fileType = billType.getFileType(); + OrderParaStructure.loadParameters(parameters, "tar_type", fileType); //设置签名 setSign(parameters); - String respStr = requestTemplate.postForObject(getReqUrl(WxTransactionType.DOWNLOADBILL), XML.getMap2Xml(parameters), String.class); - if (respStr.indexOf("<") == 0) { - return XML.toJSONObject(respStr); - } - Map ret = new HashMap(3); ret.put(RETURN_CODE, SUCCESS); ret.put(RETURN_MSG_CODE, "ok"); - ret.put("data", respStr); + 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; } + /** * 目前只支持日账单,增加账单返回格式 * * @param billDate 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; * @param billType 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 - * @param path 账单返回格式 账单存储的基础路径,按月切割 + * @param path 账单返回格式 账单存储的基础路径,按月切割 * @return 返回支付方下载对账单的结果 */ + @Deprecated @Override public Map downloadbill(Date billDate, String billType, String path) { - Map parameters = getDownloadBillParam(billDate, billType,true); - //设置签名 + Map parameters = getDownloadBillParam(billDate, billType, true); + //设置签名 setSign(parameters); InputStream inputStream = requestTemplate.postForObject(getReqUrl(WxTransactionType.DOWNLOADBILL), XML.getMap2Xml(parameters), InputStream.class); - try { - //解压流 - inputStream = uncompress(inputStream); - writeToLocal(path+DateUtils.formatDate(new Date(), DateUtils.YYYYMM)+"/"+DateUtils.formatDate(new Date(), DateUtils.YYYYMMDDHHMMSS)+".txt", inputStream); - Map ret = new HashMap(3); + //解压流 + 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 (Exception e) { - e.printStackTrace(); - Map ret = new HashMap(3); - ret.put(RETURN_CODE, FAIL); - ret.put(RETURN_MSG_CODE, "fail"); - ret.put("data", e.getMessage()); - return ret; } + catch (IOException e) { + throw new PayErrorException(new WxPayError(FAIL, e.getMessage()), e); + } + } /** * GZIP解压缩 * - * @param input - * @return + * @param input 输入流账单 + * @return 解压后输入流 + * @throws IOException IOException */ + @Deprecated public static InputStream uncompress(InputStream input) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - GZIPInputStream ungzip = new GZIPInputStream(input); - byte[] buffer = new byte[1024]; - int n; - while ((n = ungzip.read(buffer)) >= 0) { - out.write(buffer, 0, n); + 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 is = new ByteArrayInputStream(out.toByteArray()); - return is; + } /** * 将InputStream写入本地文件 + * * @param destination 写入本地目录 * @param inputStream 输入流 * @throws IOException IOException */ - private void writeToLocal(String destination, InputStream inputStream) - throws IOException { - - // 判断字节大小 - if (inputStream.available() != 0) { - System.out.println("结果大小:" + inputStream.available()); - File file = new File(destination); - if (!file.getParentFile().exists()) { - boolean result = file.getParentFile().mkdirs(); - if (!result) { - System.out.println("创建失败"); - } + @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("创建失败"); } - OutputStream out = new FileOutputStream(file); + } + try (OutputStream out = new FileOutputStream(file)) { int size = 0; int len = 0; byte[] buf = new byte[1024]; @@ -632,41 +786,41 @@ public class WxPayService extends BasePayService implements len += size; out.write(buf, 0, size); } - System.out.println("最终写入字节数大小:" + len); - inputStream.close(); - out.close(); + LOG.debug("最终写入字节数大小:{}", len); } + + } } /** * 下载账单公共参数 + * * @param billDate 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; * @param billType 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 - * @param tarType 账单返回格式 默认返回流false ,gzip 时候true + * @param tarType 账单返回格式 默认返回流false ,gzip 时候true * @return */ - private Map getDownloadBillParam(Date billDate, String billType,boolean tarType) { + @Deprecated + private Map getDownloadBillParam(Date billDate, String billType, boolean tarType) { //获取公共参数 Map parameters = getPublicParameters(); parameters.put("bill_type", billType); //目前只支持日账单 - parameters.put("bill_date", DateUtils.formatDate(billDate, DateUtils.YYYYMMDD)); - if(tarType){ + 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 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)); @@ -674,7 +828,7 @@ public class WxPayService extends BasePayService implements if (transactionType == WxTransactionType.DOWNLOADBILL) { if (transactionIdOrBillDate instanceof Date) { - return downloadbill((Date) transactionIdOrBillDate, outTradeNoBillType); + return downloadBill((Date) transactionIdOrBillDate, WxPayBillType.forType(outTradeNoBillType)); } throw new PayErrorException(new PayException(FAILURE, "非法类型异常:" + transactionIdOrBillDate.getClass())); } @@ -685,8 +839,8 @@ public class WxPayService extends BasePayService implements //获取公共参数 Map parameters = getPublicParameters(); - setParameters(parameters, OUT_TRADE_NO, outTradeNoBillType); - setParameters(parameters, "transaction_id", (String) transactionIdOrBillDate); + OrderParaStructure.loadParameters(parameters, OUT_TRADE_NO, outTradeNoBillType); + OrderParaStructure.loadParameters(parameters, "transaction_id", (String) transactionIdOrBillDate); //设置签名 setSign(parameters); return requestTemplate.postForObject(getReqUrl(transactionType), XML.getMap2Xml(parameters), JSONObject.class); @@ -696,19 +850,11 @@ public class WxPayService extends BasePayService implements * 转账 * * @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("partner_trade_no", order.getOutNo()); @@ -716,20 +862,22 @@ public class WxPayService extends BasePayService implements if (!StringUtils.isEmpty(order.getRemark())) { parameters.put("desc", order.getRemark()); } - parameters.put(NONCE_STR, SignUtils.randomStr()); + parameters.put(NONCE_STR, SignTextUtils.randomStr()); if (null != order.getTransferType() && TRANSFERS == order.getTransferType()) { transfers(parameters, order); parameters.put("mchid", payConfigStorage.getPid()); - } else { + } + 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(getReqUrl(order.getTransferType()), XML.getMap2Xml(parameters), JSONObject.class); } + /** * 转账到余额所需要参数 * @@ -741,9 +889,9 @@ public class WxPayService extends BasePayService implements * 商户企业付款到银行卡 *

*/ - 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()); //默认不校验真实姓名 @@ -763,7 +911,7 @@ public class WxPayService extends BasePayService implements * @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())); @@ -782,30 +930,51 @@ public class WxPayService extends BasePayService implements * 商户企业付款到银行卡 *

* @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("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)) { + 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(getReqUrl(QUERY_BANK), XML.getMap2Xml(parameters), JSONObject.class); } - - public String keyPublic(String content) { + private String keyPublic(String content) { try { return RSA2.encrypt(content, payConfigStorage.getKeyPublic(), CIPHER_ALGORITHM, payConfigStorage.getInputCharset()); - } catch (GeneralSecurityException | IOException e) { + } + catch (GeneralSecurityException | IOException e) { throw new PayErrorException(new WxPayError(FAILURE, e.getLocalizedMessage())); } } @@ -826,22 +995,48 @@ public class WxPayService extends BasePayService implements * * @param redpackOrder 红包实体 * @return 返回发红包实体后的结果 - * @author: faymanwang 1057438332@qq.com + * @author faymanwang 1057438332@qq.com */ @Override public Map sendredpack(RedpackOrder redpackOrder) { - Map parameters = new TreeMap(); + return sendRedPack(redpackOrder); + } + + /** + * 微信发红包 + * + * @param redpackOrder 红包实体 + * @return 返回发红包实体后的结果 + * @author faymanwang 1057438332@qq.com + */ + @Override + public Map sendRedPack(RedpackOrder redpackOrder) { + Map parameters = new TreeMap<>(); redPackParam(redpackOrder, parameters); - if (WxSendredpackType.SENDGROUPREDPACK == redpackOrder.getTransferType()) { + 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 == redpackOrder.getTransferType()) { + } + else if (WxSendredpackType.SENDMINIPROGRAMHB == transferType) { parameters.put("notify_way", "MINI_PROGRAM_JSAPI"); } - parameters.put(SIGN, createSign(SignUtils.parameterText(parameters, "&", SIGN), payConfigStorage.getInputCharset())); - return requestTemplate.postForObject(getReqUrl(redpackOrder.getTransferType()), XML.getMap2Xml(parameters), JSONObject.class); + 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; } @@ -852,14 +1047,28 @@ public class WxPayService extends BasePayService implements * * @param mchBillno 商户发放红包的商户订单号 * @return 返回查询结果 - * @author: faymanwang 1057438332@qq.com + * @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("mch_billno", mchBillNo); parameters.put("bill_type", "MCHT"); - parameters.put(SIGN, createSign(SignUtils.parameterText(parameters, "&", SIGN), payConfigStorage.getInputCharset())); + parameters.put(SIGN, createSign(SignTextUtils.parameterText(parameters, "&", SIGN), payConfigStorage.getInputCharset())); return requestTemplate.postForObject(getReqUrl(WxSendredpackType.GETHBINFO), XML.getMap2Xml(parameters), JSONObject.class); } @@ -870,12 +1079,15 @@ public class WxPayService extends BasePayService implements * @param parameters 接收参数 */ private void redPackParam(RedpackOrder redpackOrder, Map parameters) { - parameters.put(NONCE_STR, SignUtils.randomStr()); + parameters.put(NONCE_STR, SignTextUtils.randomStr()); parameters.put(MCH_ID, payConfigStorage.getPid()); - parameters.put("wxappid", payConfigStorage.getAppid()); + 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("mch_billno", redpackOrder.getMchBillNo()); parameters.put("total_amount", Util.conversionCentAmount(redpackOrder.getTotalAmount())); parameters.put("total_num", 1); parameters.put("wishing", redpackOrder.getWishing()); 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 index 341f3a8315507a8ef19590c823fca89b7e264d64..a18cff5500575edbd30a995f99d91fc9b177b0c0 100644 --- 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 @@ -18,8 +18,17 @@ public interface WxRedPackService { * * @param redpackOrder 红包实体 * @return 返回发红包实体后的结果 + * @see #sendRedPack(RedpackOrder) */ + @Deprecated Map sendredpack(RedpackOrder redpackOrder); + /** + * 微信发红包 + * + * @param redpackOrder 红包实体 + * @return 返回发红包实体后的结果 + */ + Map sendRedPack(RedpackOrder redpackOrder); /** * 查询红包记录 @@ -28,6 +37,17 @@ public interface WxRedPackService { * * @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 index 8354f3c614e5539322aa9eb665d97024f44607ae..2587431ea7f35d526b58bce1c61ef92d0c4dbfac 100644 --- 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 @@ -1,32 +1,57 @@ package com.egzosn.pay.wx.bean; -import com.egzosn.pay.common.bean.TransferOrder; - 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 getOutNo(); + return getMchBillNo(); } + @Deprecated public void setMchBillno(String mchBillno) { - setOutNo(mchBillno); + setMchBillNo(mchBillno); + } + + public String getMchBillNo() { + return mchBillNo; + } + + public void setMchBillNo(String mchBillNo) { + setOutNo(mchBillNo); + this.mchBillNo = mchBillNo; } /** * 商户名称:红包发送者名称 + * * @return 红包发送者名称 */ public String getSendName() { @@ -34,11 +59,12 @@ public class RedpackOrder extends TransferOrder { } public void setSendName(String sendName) { - super.setPayerName(sendName); + super.setPayerName(sendName); } /** * 用户openid + * * @return 用户openid */ public String getReOpenid() { @@ -46,11 +72,12 @@ public class RedpackOrder extends TransferOrder { } public void setReOpenid(String reOpenid) { - super.setPayeeAccount(reOpenid); + super.setPayeeAccount(reOpenid); } /** * 付款金额 每个红包金额必须在默认额度内(默认大于1元,小于200元,可在产品设置中自行申请调高额度) + * * @return 付款金额 */ public BigDecimal getTotalAmount() { @@ -60,22 +87,26 @@ public class RedpackOrder extends TransferOrder { 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; + return null == totalNum ? 1 : (Integer) totalNum; } public void setTotalNum(int totalNum) { addAttr("total_num", totalNum); } + /** * 红包祝福语 + * * @return 红包祝福语 */ public String getWishing() { @@ -90,6 +121,7 @@ public class RedpackOrder extends TransferOrder { /** * 活动名称 + * * @return 活动名称 */ public String getActName() { @@ -103,6 +135,7 @@ public class RedpackOrder extends TransferOrder { public String getSceneId() { return (String) getAttr("scene_id"); } + /** * 发放红包使用场景,红包金额大于200或者小于1元时必传 * PRODUCT_1:商品促销 @@ -113,7 +146,8 @@ public class RedpackOrder extends TransferOrder { * PRODUCT_6:保险回馈 * PRODUCT_7:彩票派奖 * PRODUCT_8:税务刮奖 - * @param sceneId 红包使用场景 + * + * @param sceneId 红包使用场景 */ public void setSceneId(String sceneId) { addAttr("scene_id", sceneId); @@ -123,4 +157,13 @@ public class RedpackOrder extends TransferOrder { 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/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/WxTransactionType.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxTransactionType.java index 740f9b5dd42a9fbef15d0cea41ca85fda2fd250f..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 { /** * 公众号支付 */ @@ -50,7 +51,7 @@ public enum WxTransactionType implements TransactionType { /** * 刷脸支付 */ - FACEPAY("pay/facepay"){ + FACEPAY("pay/facepay") { @Override public void setAttribute(Map parameters, PayOrder order) { parameters.put("openid", order.getOpenid()); @@ -60,11 +61,11 @@ public enum WxTransactionType implements TransactionType { /** * 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()); @@ -74,6 +75,7 @@ public enum WxTransactionType implements TransactionType { sceneInfo.put("h5_info", value); parameters.put("scene_info", sceneInfo.toJSONString()); } + /** * 是否直接返回 * @@ -87,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"); } + /** * 是否直接返回 * @@ -146,6 +149,7 @@ public enum WxTransactionType implements TransactionType { public String getType() { return this.name(); } + @Override public String getMethod() { return this.method; @@ -153,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/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..7db14646ed10b03bbbf9582bdef98a27a8ab54af --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxCombinePayService.java @@ -0,0 +1,182 @@ +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 order 发起支付的订单信息 + * @return 返回支付结果 + */ + @Override + public Map jsApi(PayOrder order) { + if (null == order.getTransactionType()) { + order.setTransactionType(WxTransactionType.COMBINE_JSAPI); + } + return super.jsApi(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..9984e6283ef21165473af5fb77e2026221898a62 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayService.java @@ -0,0 +1,795 @@ +package com.egzosn.pay.wx.v3.api; + +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.PrivateKey; +import java.security.PublicKey; +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 org.apache.http.message.BasicHeader; + +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.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.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.RSA; +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); + } + if (StringUtils.isEmpty(payConfigStorage.getKeyPublic())) { + //在这预先进行初始化 + 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"); + + + //这里为微信回调时的请求内容体,原值数据 + String body = noticeParams.getBodyStr(); + //签名信息 + String signText = StringUtils.joining("\n", timestamp, nonce, body); + if (StringUtils.isNotEmpty(payConfigStorage.getKeyPublic())) { + return RSA2.verify(signText, signature, payConfigStorage.getKeyPublic(), payConfigStorage.getInputCharset()); + } + Certificate certificate = getAssistService().getCertificate(serial); + 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); + } + + /** + * http 实体 钩子 + * + * @param entity 实体 + * @return 返回处理后的实体 + */ + @Override + public HttpStringEntity hookHttpEntity(HttpStringEntity entity) { + entity.addHeader(new BasicHeader(WxConst.WECHATPAY_SERIAL, payConfigStorage.getCertEnvironment().getPlatformSerialNumber())); + return entity; + } + + /** + * 将请求参数或者请求流转化为 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"); + } + + /** + * 小程序支付,返回小程序所需的订单构建信息 + * + * @param order 发起支付的订单信息 + * @return 返回支付结果 + */ + @Override + public Map jsApi(PayOrder order) { + if (null == order.getTransactionType()) { + order.setTransactionType(WxTransactionType.JSAPI); + } + return orderInfo(order); + } + + /** + * 刷卡付,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; + } + + PublicKey publicKeyTmp = null; + if (StringUtils.isEmpty(payConfigStorage.getKeyPublic())) { + // 商户上送敏感信息时使用`微信支付平台公钥`加密 + String serialNumber = payConfigStorage.getCertEnvironment().getPlatformSerialNumber(); + Certificate certificate = getAssistService().getCertificate(serialNumber); + publicKeyTmp = certificate.getPublicKey(); + } + else { + try { + publicKeyTmp = RSA.getPublicKey(payConfigStorage.getKeyPublic()); + } + catch (IOException | GeneralSecurityException e) { + throw new PayErrorException(new WxPayError("", e.getMessage())); + } + } + PublicKey publicKey = publicKeyTmp; + return transferDetails.stream() + .peek(transferDetailListItem -> { + String userName = transferDetailListItem.getUserName(); + if (StringUtils.isNotEmpty(userName)) { + String encryptedUserName = AntCertificationUtil.encryptToString(userName, publicKey); + transferDetailListItem.setUserName(encryptedUserName); + } + String userIdCard = transferDetailListItem.getUserIdCard(); + if (StringUtils.isNotEmpty(userIdCard)) { + String encryptedUserIdCard = AntCertificationUtil.encryptToString(userIdCard, publicKey); + 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..71d6abb905ebb3d002d87fbd2f2bdd4c66febfa5 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxProfitSharingService.java @@ -0,0 +1,452 @@ +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); + } + + + + /** + * 返回创建的订单信息 + * + * @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..5e6a58fc94d550cbc095a495763c752766d4d05f --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/utils/AntCertificationUtil.java @@ -0,0 +1,199 @@ +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) { + return encryptToString(message, certificate.getPublicKey()); + } + + /** + * 对请求敏感字段进行加密 + * + * @param message the message + * @param publicKey the certificate + * @return 加密后的内容 + */ + public static String encryptToString(String message, PublicKey publicKey) { + try { + Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding", WxConst.BC_PROVIDER); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + + 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..f8d274dec1170c144beb82fdef80dcd52afb4af5 --- /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 = "relation_type"; + 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 e0a604b39c0e463ff3b48439079839beb1ad291f..4b42a608cdcedcb46db7664515118e15635810ad 100644 --- a/pay-java-wx/src/test/java/PayTest.java +++ b/pay-java-wx/src/test/java/PayTest.java @@ -19,14 +19,14 @@ 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.setAppId("公众账号ID"); wxPayConfigStorage.setMchId("合作者id(商户号)"); //以下两个参数在 服务商版模式中必填-------- @@ -44,7 +44,7 @@ public class PayTest { //支付服务 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); //获取扫码付的二维码 diff --git a/pay-java-yiji/pom.xml b/pay-java-yiji/pom.xml index 45ce907e0c05908a3eee477540dc22e96bb657fc..bdf6cae7a040eb3ef4707b5daa19f6c201594f46 100644 --- a/pay-java-yiji/pom.xml +++ b/pay-java-yiji/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.3-SNAPSHOT + 2.14.8 4.0.0 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 index 492ac1f4b280e22b8ded75bedaf24c3b7ccce97c..8484c27fa0d505f00712c46d003b754553674360 100644 --- 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 @@ -4,7 +4,8 @@ import com.egzosn.pay.common.api.BasePayConfigStorage; /** * 易极付配置存储 - * @author egan + * + * @author egan * *
  * email egzosn@gmail.com
@@ -15,14 +16,14 @@ public class YiJiPayConfigStorage extends BasePayConfigStorage {
 
 
     /**
-     *  	易极付分配的商户号 合作者id
+     * 易极付分配的商户号 合作者id
      */
     private String partnerId;
 
     /**
      * 卖家id
      */
-    private String  sellerUserId;
+    private String sellerUserId;
 
     public String getPartnerId() {
         return partnerId;
@@ -37,6 +38,17 @@ public class YiJiPayConfigStorage extends BasePayConfigStorage {
         return null;
     }
 
+    /**
+     * 应用id
+     * 纠正名称
+     *
+     * @return 应用id
+     */
+    @Override
+    public String getAppId() {
+        return null;
+    }
+
 
     /**
      * 合作商唯一标识
@@ -47,8 +59,6 @@ public class YiJiPayConfigStorage extends BasePayConfigStorage {
     }
 
 
-
-
     @Override
     public String getSeller() {
         return sellerUserId;
@@ -63,7 +73,8 @@ public class YiJiPayConfigStorage extends BasePayConfigStorage {
     }
 
     /**
-     *  为商户平台设置的密钥key
+     * 为商户平台设置的密钥key
+     *
      * @return 密钥
      */
     public String getSecretKey() {
@@ -71,7 +82,7 @@ public class YiJiPayConfigStorage extends BasePayConfigStorage {
     }
 
     public void setSecretKey(String secretKey) {
-         setKeyPrivate(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
index 232d126510596f85e6861561b7ed6b8e64d81c40..7a93c0db31eb4268b620d1bd87a254a4f0196c73 100644
--- 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
@@ -1,29 +1,43 @@
 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.*;
-import com.egzosn.pay.common.exception.PayErrorException;
+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;
 
-import java.util.Collections;
-import java.util.Date;
-import java.util.Map;
-import java.util.TreeMap;
-
 
 /**
  * 易极付支付服务
  *
  * @author egan
- *         

- * email egzosn@gmail.com - * * date 2019/04/15 22:51 + *

+ * email egzosn@gmail.com + * * date 2019/04/15 22:51 */ public class YiJiPayService extends BasePayService { @@ -42,9 +56,6 @@ public class YiJiPayService extends BasePayService { public static final String SIGN = "sign"; - public static final String SUCCESS_CODE = "10000"; - - public static final String CODE = "code"; /** * 获取对应的请求地址 @@ -53,11 +64,13 @@ public class YiJiPayService extends BasePayService { */ @Override public String getReqUrl(TransactionType transactionType) { - if (payConfigStorage.isTest()){ + if (payConfigStorage.isTest()) { return DEV_REQ_URL; - }else if (/*YiJiTransactionType.corderRemittanceSynOrder == transactionType ||*/ YiJiTransactionType.applyRemittranceWithSynOrder == transactionType){ + } + else if (/*YiJiTransactionType.corderRemittanceSynOrder == transactionType ||*/ YiJiTransactionType.applyRemittranceWithSynOrder == transactionType) { return HTTPS_GLOBAL_REQ_URL; - }else { + } + else { return HTTPS_REQ_URL; } } @@ -71,7 +84,6 @@ public class YiJiPayService extends BasePayService { } - /** * 回调校验 * @@ -81,13 +93,25 @@ public class YiJiPayService extends BasePayService { @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)); - } /** @@ -97,25 +121,12 @@ public class YiJiPayService extends BasePayService { * @param sign 比对的签名结果 * @return 生成的签名结果 */ - @Override public boolean signVerify(Map params, String sign) { return SignUtils.valueOf(payConfigStorage.getSignType()).verify(params, sign, payConfigStorage.getKeyPublic(), payConfigStorage.getInputCharset()); } - /** - * 校验数据来源 - * - * @param id 业务id, 数据的真实性. - * @return true通过 - */ - @Override - public boolean verifySource(String id) { - return true; - } - - /** * 生成并设置签名 * @@ -124,7 +135,7 @@ public class YiJiPayService extends BasePayService { */ private Map setSign(Map parameters) { parameters.put("signType", 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; @@ -159,18 +170,18 @@ public class YiJiPayService extends BasePayService { orderInfo.put("orderNo", order.getOutTradeNo()); orderInfo.put("outOrderNo", order.getOutTradeNo()); - if (StringUtils.isNotEmpty(payConfigStorage.getSeller())){ + if (StringUtils.isNotEmpty(payConfigStorage.getSeller())) { orderInfo.put("sellerUserId", payConfigStorage.getSeller()); } - ((YiJiTransactionType)order.getTransactionType()).setAttribute(orderInfo, order); + ((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()){ + if (null != order.getCurType()) { orderInfo.put("currency", order.getCurType()); } orderInfo.putAll(order.getAttrs()); @@ -277,6 +288,17 @@ public class YiJiPayService extends BasePayService { return Collections.emptyMap(); } + /** + * 交易查询接口 + * + * @param assistOrder 查询条件 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(AssistOrder assistOrder) { + return Collections.emptyMap(); + } + /** * 交易关闭接口 @@ -290,8 +312,16 @@ public class YiJiPayService extends BasePayService { return Collections.emptyMap(); } - - + /** + * 交易关闭接口 + * + * @param assistOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 + */ + @Override + public Map close(AssistOrder assistOrder) { + return Collections.emptyMap(); + } /** * 申请退款接口 @@ -300,7 +330,7 @@ public class YiJiPayService extends BasePayService { * @return 返回支付方申请退款后的结果 */ @Override - public Map refund(RefundOrder refundOrder) { + public RefundResult refund(RefundOrder refundOrder) { Map orderInfo = getPublicParameters(YiJiTransactionType.tradeRefund); orderInfo.put("orderNo", refundOrder.getOutTradeNo()); orderInfo.put("outOrderNo", refundOrder.getOutTradeNo()); @@ -308,7 +338,52 @@ public class YiJiPayService extends BasePayService { orderInfo.put("refundTime", DateUtils.formatDay(refundOrder.getOrderDate())); orderInfo.put("refundReason", refundOrder.getDescription()); setSign(orderInfo); - return getHttpRequestTemplate().postForObject(getReqUrl(YiJiTransactionType.tradeRefund), orderInfo, JSONObject.class); + 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; + } + }; } /** @@ -324,34 +399,19 @@ public class YiJiPayService extends BasePayService { } + /** - * 目前只支持日账单 - * * @param billDate 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于易极付交易收单的业务账单;signcustomer是指基于商户易极付余额收入及支出等资金变动的帐务账单; * @param billType 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 * @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(); - } - /** * 转账 这里外部进行调用{@link #buildRequest(Map, MethodType)} * @@ -363,10 +423,10 @@ public class YiJiPayService extends BasePayService { Map data = getPublicParameters(YiJiTransactionType.applyRemittranceWithSynOrder); data.put("remittranceBatchNo", order.getBatchNo()); data.put("outOrderNo", order.getOutNo()); - data.put("payAmount", Util.conversionAmount(order.getAmount()) ); + 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("payMemo", order.getRemark()); data.put("toCountryCode", order.getCountryCode().getCode()); data.put("tradeUseCode", "326"); data.put("payeeName", order.getPayeeName()); diff --git a/pom.xml b/pom.xml index 952c79daaf3fa0bc45226f88844c548b9acbc545..49afca89e88c27608372479f4b93196a12561279 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.egzosn pay-java-parent pom - 2.13.3-SNAPSHOT + 2.14.8 Pay Java - Parent Pay Java Parent @@ -49,6 +49,7 @@ pay-java-common + pay-java-web-support pay-java-ali pay-java-wx pay-java-wx-youdian @@ -59,17 +60,17 @@ pay-java-yiji pay-java-baidu pay-java-demo - - 2.13.3-SNAPSHOT + 2.14.8 4.5.4 1.2.17 - 1.2.58 + 1.2.83 3.3.1 - 5.5.1 + 4.0.1 + 1.59 @@ -81,6 +82,11 @@ pay-java-common ${pay.version} + + com.egzosn + pay-java-web-support + ${pay.version} + @@ -94,13 +100,17 @@ fastjson ${fastjson.version} - + + org.bouncycastle + bcprov-jdk15on + ${bcprov-jdk15on.version} + - log4j - log4j - ${log4j.version} + org.slf4j + slf4j-api + 1.7.30 @@ -110,10 +120,12 @@ core ${zxing.version} + - org.junit.jupiter - junit-jupiter - ${junit.version} + javax.servlet + javax.servlet-api + provided + ${servlet-api.version} @@ -135,8 +147,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.7 - 1.7 + 1.8 + 1.8 utf-8 @@ -184,6 +196,7 @@ attach-javadocs + install jar @@ -197,6 +210,7 @@ attach-sources + install jar-no-fork