From 545bc0c466b465703ab27804eaeb180f5b7c263a Mon Sep 17 00:00:00 2001 From: egzosn Date: Sun, 16 Aug 2020 22:51:15 +0800 Subject: [PATCH 001/165] =?UTF-8?q?=E9=80=80=E6=AC=BE=E7=BB=93=E6=9E=9C?= =?UTF-8?q?=E9=9B=86=E7=BB=9F=E4=B8=80=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pay-java-ali/README.md | 2 +- .../com/egzosn/pay/ali/api/AliPayService.java | 7 +- .../egzosn/pay/ali/bean/AliRefundResult.java | 377 +++++++++++++ .../pay/ali/bean/PresetPayToolInfo.java | 44 ++ .../egzosn/pay/ali/bean/TradeFundBill.java | 82 +++ .../egzosn/pay/baidu/api/BaiduPayService.java | 50 +- .../egzosn/pay/common/api/BasePayService.java | 2 +- .../com/egzosn/pay/common/api/PayService.java | 2 +- .../pay/common/bean/BaseRefundResult.java | 337 ++++++++++++ .../egzosn/pay/common/bean/MethodType.java | 2 +- .../com/egzosn/pay/common/bean/Order.java | 3 +- .../egzosn/pay/common/bean/RefundResult.java | 103 ++++ .../java/com/egzosn/pay/common/util/XML.java | 5 - pay-java-demo/pom.xml | 2 +- .../pay/demo/controller/AliPayController.java | 3 +- .../pay/demo/controller/PayController.java | 2 +- .../demo/controller/PayPalPayController.java | 3 +- .../demo/controller/UnionPayController.java | 3 +- .../pay/demo/controller/WxPayController.java | 2 +- .../egzosn/pay/fuiou/api/FuiouPayService.java | 5 +- .../pay/fuiou/bean/FuiouRefundResult.java | 166 ++++++ pay-java-payoneer/README.md | 2 +- .../pay/payoneer/api/PayoneerPayService.java | 50 +- pay-java-paypal/README.md | 2 +- .../pay/paypal/api/PayPalPayService.java | 49 +- pay-java-union/README.md | 2 +- .../egzosn/pay/union/api/UnionPayService.java | 14 +- .../pay/union/bean/UnionRefundResult.java | 326 ++++++++++++ .../wx/youdian/api/WxYouDianPayService.java | 51 +- pay-java-wx/README.md | 2 +- .../com/egzosn/pay/wx/api/WxPayService.java | 4 +- .../egzosn/pay/wx/bean/WxRefundResult.java | 496 ++++++++++++++++++ .../egzosn/pay/yiji/api/YiJiPayService.java | 50 +- pom.xml | 5 +- 34 files changed, 2209 insertions(+), 46 deletions(-) create mode 100644 pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliRefundResult.java create mode 100644 pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/PresetPayToolInfo.java create mode 100644 pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/TradeFundBill.java create mode 100644 pay-java-common/src/main/java/com/egzosn/pay/common/bean/BaseRefundResult.java create mode 100644 pay-java-common/src/main/java/com/egzosn/pay/common/bean/RefundResult.java create mode 100644 pay-java-fuiou/src/main/java/com/egzosn/pay/fuiou/bean/FuiouRefundResult.java create mode 100644 pay-java-union/src/main/java/com/egzosn/pay/union/bean/UnionRefundResult.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxRefundResult.java diff --git a/pay-java-ali/README.md b/pay-java-ali/README.md index e7a2f5e..824213f 100644 --- a/pay-java-ali/README.md +++ b/pay-java-ali/README.md @@ -236,7 +236,7 @@ RefundOrder order = new RefundOrder("支付宝单号", "我方系统单号", "退款金额", "订单总金额"); //非必填, 根据业务需求而定,可用于多次退款 order.setRefundNo("退款单号") - Map result = service.refund(order); + AliRefundResult result = service.refund(order); ``` 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 9b7aa7d..4101dc3 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 @@ -3,6 +3,7 @@ package com.egzosn.pay.ali.api; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; 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.OrderSettle; @@ -437,7 +438,7 @@ public class AliPayService extends BasePayService { * @return 返回支付方申请退款后的结果 */ @Override - public Map refund(RefundOrder refundOrder) { + public AliRefundResult refund(RefundOrder refundOrder) { //获取公共参数 Map parameters = getPublicParameters(AliTransactionType.REFUND); setAppAuthToken(parameters, refundOrder.getAttrs()); @@ -451,7 +452,9 @@ public class AliPayService extends BasePayService { parameters.put(BIZ_CONTENT, JSON.toJSONString(bizContent)); //设置签名 setSign(parameters); - return requestTemplate.getForObject(getReqUrl() + "?" + UriVariables.getMapToParameters(parameters), JSONObject.class); + final AliRefundResult refundResult = AliRefundResult.create(requestTemplate.getForObject(getReqUrl() + "?" + UriVariables.getMapToParameters(parameters), JSONObject.class)); + refundResult.setOutRequestNo(refundOrder.getRefundNo()); + return refundResult; } 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 0000000..6fbf661 --- /dev/null +++ b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliRefundResult.java @@ -0,0 +1,377 @@ +package com.egzosn.pay.ali.bean; + +import java.math.BigDecimal; +import java.util.Date; +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 TradeFundBill 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 TradeFundBill getRefundDetailItemList() { + return refundDetailItemList; + } + + public void setRefundDetailItemList(TradeFundBill 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/PresetPayToolInfo.java b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/PresetPayToolInfo.java new file mode 100644 index 0000000..d7617c4 --- /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/TradeFundBill.java b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/TradeFundBill.java new file mode 100644 index 0000000..f2862ff --- /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-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 87e4c1c..febafbf 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 @@ -316,7 +316,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 +329,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; + } + }; } 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 261c794..fbc5b59 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 @@ -263,7 +263,7 @@ 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()); } 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 910fc7e..0c1b6ac 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 @@ -252,7 +252,7 @@ public interface PayService { * @param refundOrder 退款订单信息 * @return 返回支付方申请退款后的结果 */ - Map refund(RefundOrder refundOrder); + RefundResult refund(RefundOrder refundOrder); /** * 申请退款接口 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 0000000..b92763a --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/BaseRefundResult.java @@ -0,0 +1,337 @@ +package com.egzosn.pay.common.bean; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * 基础的退款结果对象 + * + * @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)); + } + + /** + * Returns the number of key-value mappings in this map. If the + * map contains more than Integer.MAX_VALUE elements, returns + * Integer.MAX_VALUE. + * + * @return the number of key-value mappings in this map + */ + @Override + public int size() { + return attrs.size(); + } + + /** + * Returns true if this map contains no key-value mappings. + * + * @return true if this map contains no key-value mappings + */ + @Override + public boolean isEmpty() { + return attrs.isEmpty(); + } + + /** + * Returns true if this map contains a mapping for the specified + * key. More formally, returns true if and only if + * this map contains a mapping for a key k such that + * (key==null ? k==null : key.equals(k)). (There can be + * at most one such mapping.) + * + * @param key key whose presence in this map is to be tested + * @return true if this map contains a mapping for the specified + * key + * @throws ClassCastException if the key is of an inappropriate type for + * this map + * (optional) + * @throws NullPointerException if the specified key is null and this map + * does not permit null keys + * (optional) + */ + @Override + public boolean containsKey(Object key) { + return attrs.containsKey(key); + } + + /** + * Returns true if this map maps one or more keys to the + * specified value. More formally, returns true if and only if + * this map contains at least one mapping to a value v such that + * (value==null ? v==null : value.equals(v)). This operation + * will probably require time linear in the map size for most + * implementations of the Map interface. + * + * @param value value whose presence in this map is to be tested + * @return true if this map maps one or more keys to the + * specified value + * @throws ClassCastException if the value is of an inappropriate type for + * this map + * (optional) + * @throws NullPointerException if the specified value is null and this + * map does not permit null values + * (optional) + */ + @Override + public boolean containsValue(Object value) { + return attrs.containsValue(value); + } + + /** + * Returns the value to which the specified key is mapped, + * or {@code null} if this map contains no mapping for the key. + * + *

More formally, if this map contains a mapping from a key + * {@code k} to a value {@code v} such that {@code (key==null ? k==null : + * key.equals(k))}, then this method returns {@code v}; otherwise + * it returns {@code null}. (There can be at most one such mapping.) + * + *

If this map permits null values, then a return value of + * {@code null} does not necessarily indicate that the map + * contains no mapping for the key; it's also possible that the map + * explicitly maps the key to {@code null}. The {@link #containsKey + * containsKey} operation may be used to distinguish these two cases. + * + * @param key the key whose associated value is to be returned + * @return the value to which the specified key is mapped, or + * {@code null} if this map contains no mapping for the key + * @throws ClassCastException if the key is of an inappropriate type for + * this map + * (optional) + * @throws NullPointerException if the specified key is null and this map + * does not permit null keys + * (optional) + */ + @Override + public Object get(Object key) { + return attrs.get(key); + } + + /** + * Associates the specified value with the specified key in this map + * (optional operation). If the map previously contained a mapping for + * the key, the old value is replaced by the specified value. (A map + * m is said to contain a mapping for a key k if and only + * if {@link #containsKey(Object) m.containsKey(k)} would return + * true.) + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * @return the previous value associated with key, or + * null if there was no mapping for key. + * (A null return can also indicate that the map + * previously associated null with key, + * if the implementation supports null values.) + * @throws UnsupportedOperationException if the put operation + * is not supported by this map + * @throws ClassCastException if the class of the specified key or value + * prevents it from being stored in this map + * @throws NullPointerException if the specified key or value is null + * and this map does not permit null keys or values + * @throws IllegalArgumentException if some property of the specified key + * or value prevents it from being stored in this map + */ + @Override + public Object put(String key, Object value) { + return attrs.put(key, value); + } + + /** + * Removes the mapping for a key from this map if it is present + * (optional operation). More formally, if this map contains a mapping + * from key k to value v such that + * (key==null ? k==null : key.equals(k)), that mapping + * is removed. (The map can contain at most one such mapping.) + * + *

Returns the value to which this map previously associated the key, + * or null if the map contained no mapping for the key. + * + *

If this map permits null values, then a return value of + * null does not necessarily indicate that the map + * contained no mapping for the key; it's also possible that the map + * explicitly mapped the key to null. + * + *

The map will not contain a mapping for the specified key once the + * call returns. + * + * @param key key whose mapping is to be removed from the map + * @return the previous value associated with key, or + * null if there was no mapping for key. + * @throws UnsupportedOperationException if the remove operation + * is not supported by this map + * @throws ClassCastException if the key is of an inappropriate type for + * this map + * (optional) + * @throws NullPointerException if the specified key is null and this + * map does not permit null keys + * (optional) + */ + @Override + public Object remove(Object key) { + return attrs.remove(key); + } + + /** + * Copies all of the mappings from the specified map to this map + * (optional operation). The effect of this call is equivalent to that + * of calling {@link #put(Object, Object) put(k, v)} on this map once + * for each mapping from key k to value v in the + * specified map. The behavior of this operation is undefined if the + * specified map is modified while the operation is in progress. + * + * @param m mappings to be stored in this map + * @throws UnsupportedOperationException if the putAll operation + * is not supported by this map + * @throws ClassCastException if the class of a key or value in the + * specified map prevents it from being stored in this map + * @throws NullPointerException if the specified map is null, or if + * this map does not permit null keys or values, and the + * specified map contains null keys or values + * @throws IllegalArgumentException if some property of a key or value in + * the specified map prevents it from being stored in this map + */ + @Override + public void putAll(Map m) { + attrs.putAll(m); + } + + /** + * Removes all of the mappings from this map (optional operation). + * The map will be empty after this call returns. + * + * @throws UnsupportedOperationException if the clear operation + * is not supported by this map + */ + @Override + public void clear() { + attrs.clear(); + } + + /** + * Returns a {@link Set} view of the keys contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. If the map is modified + * while an iteration over the set is in progress (except through + * the iterator's own remove operation), the results of + * the iteration are undefined. The set supports element removal, + * which removes the corresponding mapping from the map, via the + * Iterator.remove, Set.remove, + * removeAll, retainAll, and clear + * operations. It does not support the add or addAll + * operations. + * + * @return a set view of the keys contained in this map + */ + @Override + public Set keySet() { + return attrs.keySet(); + } + + /** + * Returns a {@link Collection} view of the values contained in this map. + * The collection is backed by the map, so changes to the map are + * reflected in the collection, and vice-versa. If the map is + * modified while an iteration over the collection is in progress + * (except through the iterator's own remove operation), + * the results of the iteration are undefined. The collection + * supports element removal, which removes the corresponding + * mapping from the map, via the Iterator.remove, + * Collection.remove, removeAll, + * retainAll and clear operations. It does not + * support the add or addAll operations. + * + * @return a collection view of the values contained in this map + */ + @Override + public Collection values() { + return attrs.values(); + } + + /** + * Returns a {@link Set} view of the mappings contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. If the map is modified + * while an iteration over the set is in progress (except through + * the iterator's own remove operation, or through the + * setValue operation on a map entry returned by the + * iterator) the results of the iteration are undefined. The set + * supports element removal, which removes the corresponding + * mapping from the map, via the Iterator.remove, + * Set.remove, removeAll, retainAll and + * clear operations. It does not support the + * add or addAll operations. + * + * @return a set view of the mappings contained in this map + */ + @Override + public Set> entrySet() { + return attrs.entrySet(); + } +} 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 36b02f5..dd9f31b 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. 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 fd16e1c..9ba9820 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,5 +1,6 @@ package com.egzosn.pay.common.bean; +import java.io.Serializable; import java.util.Map; /** @@ -11,7 +12,7 @@ import java.util.Map; * date 2020/01/05 13:34 * */ -public interface Order { +public interface Order extends Serializable { /** * 获取订单属性 这里可用做覆盖已设置的订单信息属性,订单信息在签名前进行覆盖。 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 0000000..50ca0c1 --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/RefundResult.java @@ -0,0 +1,103 @@ +package com.egzosn.pay.common.bean; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Map; + +/** + * 退款结果 + * + * 这里继承Map为兼容方案,后续版本进行删除 + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2020/8/16 9:55
+ * 
+ */ +public interface RefundResult extends Map, 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/util/XML.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/XML.java index a323e8a..1ee67ec 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 @@ -352,9 +352,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-demo/pom.xml b/pay-java-demo/pom.xml index 9cec5a2..79c6805 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -86,7 +86,7 @@ com.fasterxml.jackson.core jackson-databind - 2.9.10.1 + 2.11.2 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 7ee3fbf..2f528e7 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 @@ -4,6 +4,7 @@ package com.egzosn.pay.demo.controller; 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; @@ -279,7 +280,7 @@ public class AliPayController { * @return 返回支付方申请退款后的结果 */ @RequestMapping("refund") - public Map refund(RefundOrder order) { + public AliRefundResult refund(RefundOrder order) { return service.refund(order); } 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 c7adb42..bd998bc 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 @@ -422,7 +422,7 @@ public class PayController { * @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()); 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 f9a7873..fe84f00 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 @@ -5,6 +5,7 @@ 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; @@ -86,7 +87,7 @@ public class PayPalPayController { * @return 返回支付方申请退款后的结果 */ @RequestMapping("refund") - public Map refund() { + public RefundResult refund() { // TODO 这里需要 refundAmount, curType, description, tradeNo RefundOrder order = new RefundOrder(); order.setCurType(DefaultCurType.USD); 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 6ced1dc..8c1e6e5 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 @@ -12,6 +12,7 @@ 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; @@ -266,7 +267,7 @@ public class UnionPayController { * @return 返回支付方申请退款后的结果 */ @RequestMapping("refund") - public Map refund(RefundOrder order) { + public UnionRefundResult refund(RefundOrder order) { return service.refund(order); } 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 096f609..a554c73 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 @@ -309,7 +309,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退款证书"); } 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 8f6b095..43f3348 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 @@ -9,6 +9,7 @@ import com.egzosn.pay.common.util.DateUtils; import com.egzosn.pay.common.util.Util; import com.egzosn.pay.common.util.sign.SignUtils; import com.egzosn.pay.common.util.str.StringUtils; +import com.egzosn.pay.fuiou.bean.FuiouRefundResult; import com.egzosn.pay.fuiou.bean.FuiouTransactionType; import java.awt.image.BufferedImage; @@ -379,7 +380,7 @@ public class FuiouPayService extends BasePayService { * @return 退款返回结果集 */ @Override - public Map refund(RefundOrder refundOrder) { + public RefundResult refund(RefundOrder refundOrder) { Map params = new HashMap<>(); //商户代码 params.put("mchnt_cd", payConfigStorage.getPid()); @@ -394,7 +395,7 @@ public class FuiouPayService extends BasePayService { params.putAll(refundOrder.getAttrs()); params.put("md5", createSign(SignUtils.parameters2MD5Str(params, "|"), payConfigStorage.getInputCharset())); JSONObject resultJson = getHttpRequestTemplate().postForObject(getReqUrl() + URL_FuiouSmpRefundGate, params, JSONObject.class); - return resultJson; + return FuiouRefundResult.create(resultJson); } 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 0000000..e85dfc1 --- /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-payoneer/README.md b/pay-java-payoneer/README.md index 06e14f3..c809467 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/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 8596b7b..617d79a 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 @@ -18,6 +18,7 @@ import org.apache.http.Header; import org.apache.http.entity.ContentType; import org.apache.http.message.BasicHeader; +import java.math.BigDecimal; import java.util.*; /** @@ -323,8 +324,53 @@ public class PayoneerPayService extends BasePayService im * @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; + } + }; } diff --git a/pay-java-paypal/README.md b/pay-java-paypal/README.md index 590af71..5730273 100644 --- a/pay-java-paypal/README.md +++ b/pay-java-paypal/README.md @@ -95,6 +95,6 @@ order.setDescription(" description "); order.setTradeNo("paypal 平台的单号"); order.setRefundAmount(new BigDecimal(0.01)); - Map result = service.refund(order); + RefundResult result = service.refund(order); ``` 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 ea120e7..b1447ed 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 @@ -249,7 +249,7 @@ public class PayPalPayService extends BasePayService{ * @return 返回支付方申请退款后的结果 */ @Override - public Map refund(RefundOrder refundOrder) { + public RefundResult refund(RefundOrder refundOrder) { JSONObject request = new JSONObject(); if (null != refundOrder.getRefundAmount() && BigDecimal.ZERO.compareTo( refundOrder.getRefundAmount()) == -1){ @@ -267,7 +267,52 @@ 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; + 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; + } + }; } /** diff --git a/pay-java-union/README.md b/pay-java-union/README.md index 40951df..4d370b1 100644 --- a/pay-java-union/README.md +++ b/pay-java-union/README.md @@ -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/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 24d64da..953c645 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 @@ -17,6 +17,7 @@ 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.UnionPayMessage; +import com.egzosn.pay.union.bean.UnionRefundResult; import com.egzosn.pay.union.bean.UnionTransactionType; import java.io.ByteArrayInputStream; @@ -591,7 +592,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 +602,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; + 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())); @@ -628,7 +628,7 @@ public class UnionPayService extends BasePayService { @Override - public Map refund(RefundOrder refundOrder) { + public UnionRefundResult refund(RefundOrder refundOrder) { return unionRefundOrConsumeUndo(refundOrder, UnionTransactionType.REFUND); } 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 0000000..36990ca --- /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-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 e28cb10..82f6c8a 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 @@ -393,7 +393,7 @@ public class WxYouDianPayService extends BasePayService refund(RefundOrder refundOrder) { + public RefundResult refund(RefundOrder refundOrder) { String apbNonce = SignUtils.randomStr(); TreeMap data = new TreeMap<>(); data.put("access_token", payConfigStorage.getAccessToken()); @@ -408,8 +408,53 @@ public class WxYouDianPayService extends BasePayService implements * @return 返回支付方申请退款后的结果 */ @Override - public Map refund(RefundOrder refundOrder) { + public WxRefundResult refund(RefundOrder refundOrder) { //获取公共参数 Map parameters = getPublicParameters(); @@ -506,7 +506,7 @@ public class WxPayService extends BasePayService implements 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)); } 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 0000000..e17c385 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxRefundResult.java @@ -0,0 +1,496 @@ +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 Integer 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 Integer getCouponRefundId0() {
+        return couponRefundId0;
+    }
+
+    public void setCouponRefundId0(Integer 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-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 232d126..ccc553a 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
@@ -11,6 +11,7 @@ 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.math.BigDecimal;
 import java.util.Collections;
 import java.util.Date;
 import java.util.Map;
@@ -300,7 +301,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 +309,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;
+            }
+        };
     }
 
     /**
diff --git a/pom.xml b/pom.xml
index 952c79d..4a5dee5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -67,7 +67,7 @@
         2.13.3-SNAPSHOT
         4.5.4
         1.2.17
-        1.2.58
+        1.2.73
         3.3.1
         5.5.1
     
@@ -92,10 +92,11 @@
             
                 com.alibaba
                 fastjson
-                ${fastjson.version}
+                1.2.73
             
 
 
+
             
             
                 log4j
-- 
Gitee


From c5604f5d3a9319bcbd10dfe8bb05af8cf910ace8 Mon Sep 17 00:00:00 2001
From: egzosn 
Date: Mon, 12 Oct 2020 22:51:52 +0800
Subject: [PATCH 002/165] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=95=B4=E7=90=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../pay/demo/controller/AliPayController.java | 29 ++++++++++++++++++-
 .../pay/paypal/api/PayPalConfigStorage.java   |  6 +++-
 .../pay/union/api/UnionPayConfigStorage.java  | 17 +++++++++--
 .../egzosn/pay/union/api/UnionPayService.java |  2 +-
 .../api/WxYouDianPayConfigStorage.java        | 12 +++++---
 5 files changed, 57 insertions(+), 9 deletions(-)

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 2f528e7..b71161f 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
@@ -9,6 +9,7 @@ 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.CertStoreType;
 import com.egzosn.pay.common.bean.PayOrder;
 import com.egzosn.pay.common.bean.RefundOrder;
 import com.egzosn.pay.common.http.HttpConfigStorage;
@@ -47,13 +48,39 @@ public class AliPayController {
     private AliPayService service = null;
     @Resource
     private AutowireCapableBeanFactory spring;
+    /**
+     * 设置普通公钥的方式
+     * 普通公钥方式与证书公钥方式为两者取其一的方式
+     * @param aliPayConfigStorage 支付宝配置信息
+     *
+     */
+    private static void keyPublic(AliPayConfigStorage aliPayConfigStorage){
+        aliPayConfigStorage.setKeyPublic("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIgHnOn7LLILlKETd6BFRJ0GqgS2Y3mn1wMQmyh9zEyWlz5p1zrahRahbXAfCfSqshSNfqOmAQzSHRVjCqjsAw1jyqrXaPdKBmr90DIpIxmIyKXv4GGAkPyJ/6FTFY99uhpiq0qadD/uSzQsefWo0aTvP/65zi3eof7TcZ32oWpwIDAQAB");
+    }
+
+    /**
+     * 设置证书公钥信息
+     * 普通公钥方式与证书公钥方式为两者取其一的方式
+     * @param aliPayConfigStorage 支付宝配置信息
+     */
+    private static void certKeyPublic(AliPayConfigStorage aliPayConfigStorage){
+        //设置为证书方式
+        aliPayConfigStorage.setCertSign(true);
+        //设置证书存储方式,这里为路径
+        aliPayConfigStorage.setCertStoreType(CertStoreType.PATH);
+        aliPayConfigStorage.setMerchantCert("请填写您的应用公钥证书文件路径,例如:d:/appCertPublicKey_2019051064521003.crt");
+        aliPayConfigStorage.setAliPayCert("请填写您的支付宝公钥证书文件路径,例如:d:/alipayCertPublicKey_RSA2.crt");
+        aliPayConfigStorage.setAliPayRootCert("请填写您的支付宝根证书文件路径,例如:d:/alipayRootCert.crt");
+    }
 
     @PostConstruct
     public void init() {
         AliPayConfigStorage aliPayConfigStorage = new AliPayConfigStorage();
         aliPayConfigStorage.setPid("2088102169916436");
         aliPayConfigStorage.setAppid("2016080400165436");
-        aliPayConfigStorage.setKeyPublic("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIgHnOn7LLILlKETd6BFRJ0GqgS2Y3mn1wMQmyh9zEyWlz5p1zrahRahbXAfCfSqshSNfqOmAQzSHRVjCqjsAw1jyqrXaPdKBmr90DIpIxmIyKXv4GGAkPyJ/6FTFY99uhpiq0qadD/uSzQsefWo0aTvP/65zi3eof7TcZ32oWpwIDAQAB");
+        //普通公钥方式与证书公钥方式为两者取其一的方式
+        keyPublic(aliPayConfigStorage);
+//        certKeyPublic(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==");
         aliPayConfigStorage.setNotifyUrl("http://pay.egzosn.com/payBack.json");
         aliPayConfigStorage.setReturnUrl("http://pay.egzosn.com/payBack.html");
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 8169fa7..ccffe2e 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,5 +1,7 @@
 package com.egzosn.pay.paypal.api;
 
+import java.util.concurrent.locks.ReentrantLock;
+
 import com.egzosn.pay.common.api.BasePayConfigStorage;
 
 /**
@@ -62,5 +64,7 @@ public class PayPalConfigStorage extends BasePayConfigStorage {
         return getNotifyUrl();
     }
 
-
+    public PayPalConfigStorage() {
+        setAccessTokenLock(new ReentrantLock());
+    }
 }
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 24864d3..5497b07 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
@@ -41,6 +41,10 @@ public class UnionPayConfigStorage extends BasePayConfigStorage {
      * 应用私钥证书
      */
     private Object keyPrivateCert;
+    /**
+     * 应用私钥证书,rsa_private pkcs8格式 生成签名时使用
+     */
+    private String keyPrivateCertPwd;
 
     /**
      * 中级证书
@@ -55,6 +59,7 @@ public class UnionPayConfigStorage extends BasePayConfigStorage {
      * 证书存储类型
      */
     private CertStoreType certStoreType;
+
     /**
      * 设置私钥证书
      *
@@ -128,9 +133,17 @@ 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;
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 953c645..d1ef681 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
@@ -70,7 +70,7 @@ public class UnionPayService extends BasePayService {
      * @param payConfigStorage 支付配置
      */
     public UnionPayService(UnionPayConfigStorage payConfigStorage) {
-        super(payConfigStorage);
+        this(payConfigStorage, null);
     }
 
     public UnionPayService(UnionPayConfigStorage payConfigStorage, HttpConfigStorage configStorage) {
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 7548278..be789e9 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 { @@ -49,5 +51,7 @@ public class WxYouDianPayConfigStorage extends BasePayConfigStorage { return getAccessToken(); } - + public WxYouDianPayConfigStorage() { + setAccessTokenLock(new ReentrantLock()); + } } -- Gitee From 708a7b16b60ee5f421e2ad3a3852e5fb8e0f895e Mon Sep 17 00:00:00 2001 From: egzosn Date: Mon, 12 Oct 2020 22:52:22 +0800 Subject: [PATCH 003/165] =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=AE=9D=E8=AF=81?= =?UTF-8?q?=E4=B9=A6=E5=85=AC=E9=92=A5=E5=AE=9E=E7=8E=B0=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pay-java-ali/README.md | 30 +- .../pay/ali/api/AliPayConfigStorage.java | 112 +- .../com/egzosn/pay/ali/api/AliPayService.java | 181 +- .../com/egzosn/pay/ali/bean/AliPayConst.java | 59 + .../egzosn/pay/ali/bean/CertEnvironment.java | 93 + .../pay/ali/utils/AntCertificationUtil.java | 389 +++ pay-java-ali/src/test/java/PayTest.java | 30 +- .../pay/common/api/BasePayConfigStorage.java | 92 +- .../com/egzosn/pay/common/api/CertStore.java | 2 +- .../pay/common/api/PayConfigStorage.java | 35 +- .../com/egzosn/pay/common/bean/Attrs.java | 31 + .../egzosn/pay/common/bean/CertStoreType.java | 46 +- .../com/egzosn/pay/common/bean/Order.java | 17 +- .../egzosn/pay/common/bean/PayMessage.java | 3 + .../com/egzosn/pay/common/bean/PayOrder.java | 2 +- .../com/egzosn/pay/common/util/IOUtils.java | 2415 +++++++++++++++++ .../pay/common/util/sign/SecureUtil.java | 4 +- 17 files changed, 3371 insertions(+), 170 deletions(-) create mode 100644 pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliPayConst.java create mode 100644 pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/CertEnvironment.java create mode 100644 pay-java-ali/src/main/java/com/egzosn/pay/ali/utils/AntCertificationUtil.java create mode 100644 pay-java-common/src/main/java/com/egzosn/pay/common/bean/Attrs.java create mode 100644 pay-java-common/src/main/java/com/egzosn/pay/common/util/IOUtils.java diff --git a/pay-java-ali/README.md b/pay-java-ali/README.md index 824213f..aebefc7 100644 --- a/pay-java-ali/README.md +++ b/pay-java-ali/README.md @@ -3,10 +3,10 @@ ## 支付宝支付简单例子 #### 支付配置 - +##### 普通公钥 ```java - - + + AliPayConfigStorage aliPayConfigStorage = new AliPayConfigStorage(); aliPayConfigStorage.setPid("合作者id"); aliPayConfigStorage.setAppid("应用id"); @@ -21,6 +21,30 @@ aliPayConfigStorage.setTest(true); ``` +##### 证书公钥 +```java + + + AliPayConfigStorage aliPayConfigStorage = new AliPayConfigStorage(); + aliPayConfigStorage.setPid("合作者id"); + aliPayConfigStorage.setAppid("应用id"); + 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); + +``` #### 网络请求配置 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 3eef8c4..cc2028e 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,17 +1,31 @@ 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 */ @@ -27,6 +41,40 @@ public class AliPayConfigStorage extends BasePayConfigStorage { private String seller; + + /** + * 应用公钥证书 + */ + 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; } @@ -55,5 +103,63 @@ 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 4101dc3..a1a6515 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,14 +1,40 @@ package com.egzosn.pay.ali.api; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.TreeMap; + +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.HTTPS_REQ_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.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.bean.MethodType; +import com.egzosn.pay.common.bean.Order; +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; @@ -18,57 +44,17 @@ import com.egzosn.pay.common.util.Util; import com.egzosn.pay.common.util.sign.SignUtils; import com.egzosn.pay.common.util.str.StringUtils; -import java.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"; - 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"; - /** - * 应用授权概述 - */ - private static final String APP_AUTH_TOKEN = "app_auth_token"; - /** - * 收款方信息 - */ - private static final String PAYEE_INFO = "payee_info"; /** * 获取对应的请求地址 @@ -77,8 +63,9 @@ public class AliPayService extends BasePayService { */ @Override public String getReqUrl(TransactionType transactionType) { - return payConfigStorage.isTest() ? DEV_REQ_URL : HTTPS_REQ_URL; + return payConfigStorage.isTest() ? AliPayConst.DEV_REQ_URL : HTTPS_REQ_URL; } + /** * 获取对应的请求地址 * @@ -89,12 +76,25 @@ 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); } @@ -131,18 +131,37 @@ public class AliPayService extends BasePayService { if (SIGN.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 公钥信息 + */ + private String getKeyPublic(Map params){ + if (!payConfigStorage.isCertSign()){ + return payConfigStorage.getKeyPublic(); + } + return payConfigStorage.getCertEnvironment().getAliPayPublicKey(getAliPayCertSN(params)); + } + /** + * 从响应Map中提取支付宝公钥证书序列号 + * + * @param respMap 响应Map + * @return 支付宝公钥证书序列号 + */ + public String getAliPayCertSN(java.util.Map respMap){ + return (String) respMap.get(AliPayConst.ALIPAY_CERT_SN_FIELD); + } /** * 校验数据来源 @@ -200,6 +219,7 @@ public class AliPayService extends BasePayService { orderInfo.put("notify_url", payConfigStorage.getNotifyUrl()); orderInfo.put("format", "json"); + setAppAuthToken(orderInfo, order.getAttrs()); Map bizContent = new TreeMap<>(); @@ -240,9 +260,11 @@ public class AliPayService extends BasePayService { 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()); orderInfo.put(BIZ_CONTENT, JSON.toJSONString(bizContent)); - return preOrderHandler(orderInfo, order); + return preOrderHandler(orderInfo, order); } /** @@ -258,9 +280,22 @@ public class AliPayService extends BasePayService { orderInfo.put("charset", payConfigStorage.getInputCharset()); orderInfo.put("timestamp", DateUtils.format(new Date())); orderInfo.put("version", "1.0"); + loadCertSn(orderInfo); return orderInfo; } + /** + * 加载证书序列 + * @param orderInfo 订单信息 + */ + private void loadCertSn(Map orderInfo){ + if (payConfigStorage.isCertSign()){ + final CertEnvironment certEnvironment = payConfigStorage.getCertEnvironment(); + setParameters(orderInfo, "app_cert_sn", certEnvironment.getMerchantCertSN()); + setParameters(orderInfo, "alipay_root_cert_sn", certEnvironment.getRootCertSN()); + } + } + /** * 获取输出消息,用户返回给支付端 @@ -308,7 +343,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(""); @@ -316,7 +351,6 @@ public class AliPayService extends BasePayService { } - /** * 获取输出二维码信息, * @@ -324,7 +358,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); //预订单 @@ -345,9 +379,9 @@ 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())); } @@ -364,10 +398,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()); @@ -422,13 +457,24 @@ public class AliPayService extends BasePayService { /** * 设置支付宝授权Token + * * @param parameters 参数 - * @param attrs 订单属性 + * @param attrs 订单属性 * @return 参数 */ private void setAppAuthToken(Map parameters, Map attrs) { + setAppAuthToken(parameters); setParameters(parameters, APP_AUTH_TOKEN, (String) attrs.remove(APP_AUTH_TOKEN)); } + /** + * 设置支付宝授权Token + * + * @param parameters 参数 + * @return 参数 + */ + private void setAppAuthToken(Map parameters) { + setParameters(parameters, APP_AUTH_TOKEN, payConfigStorage.getAppAuthToken()); + } /** @@ -458,7 +504,6 @@ public class AliPayService extends BasePayService { } - /** * 查询退款 * @@ -507,7 +552,6 @@ public class AliPayService extends BasePayService { } - /** * @param tradeNoOrBillDate 支付平台订单号或者账单类型, 具体请 * 类型为{@link String }或者 {@link Date },类型须强制限制,类型不对应则抛出异常{@link PayErrorException} @@ -558,11 +602,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); + setParameters(bizContent, "order_title", order); + setParameters(bizContent, "original_order_id", order); setPayeeInfo(bizContent, order); bizContent.put("remark", order.getRemark()); - setParameters(bizContent, "business_params", order); + setParameters(bizContent, "business_params", order); //设置请求参数的集合 parameters.put(BIZ_CONTENT, JSON.toJSONString(bizContent)); @@ -571,24 +615,23 @@ public class AliPayService extends BasePayService { return getHttpRequestTemplate().postForObject(getReqUrl() + "?" + UriVariables.getMapToParameters(parameters), null, JSONObject.class); } - private Map setPayeeInfo(Map bizContent, Order order){ + 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; } - /** * 转账查询 * 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 0000000..801e548 --- /dev/null +++ b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliPayConst.java @@ -0,0 +1,59 @@ +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.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 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"; +} 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 0000000..bc71470 --- /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 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; + +import java.io.InputStream; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 证书模式运行时环境 + * + * @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 Map cachedAliPayPublicKey = 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); + cachedAliPayPublicKey.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 cachedAliPayPublicKey.values().iterator().next(); + } + + if (cachedAliPayPublicKey.containsKey(sn)) { + return cachedAliPayPublicKey.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/utils/AntCertificationUtil.java b/pay-java-ali/src/main/java/com/egzosn/pay/ali/utils/AntCertificationUtil.java new file mode 100644 index 0000000..b4b94d4 --- /dev/null +++ b/pay-java-ali/src/main/java/com/egzosn/pay/ali/utils/AntCertificationUtil.java @@ -0,0 +1,389 @@ +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.CertificateException; +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.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +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.encrypt.Base64; +import com.egzosn.pay.common.util.str.StringUtils; + +/** + * 证书文件可信校验 + * + * @author junying.wjy + * @version $Id: AntCertificationUtil.java, v 0.1 2019-07-29 下午04:46 junying.wjy Exp $ + * + * @author egan update 2020/10/12 + * + */ +public class AntCertificationUtil { + private static final Log LOGGER = LogFactory.getLog(AntCertificationUtil.class); + + /** + * 验证证书是否可信 + * + * @param certContent 需要验证的目标证书或者证书链 + * @param rootCertContent 可信根证书列表 + */ + 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()); + CertificateFactory factory = null; + try { + factory = CertificateFactory.getInstance("X.509"); + Collection certificates = factory.generateCertificates(inputStream); + return certificates.toArray(new X509Certificate[certificates.size()]); + } catch (CertificateException 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 b266376..244297e 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; @@ -20,12 +21,39 @@ import java.util.UUID; */ public class PayTest { + /** + * 设置普通公钥的方式 + * 普通公钥方式与证书公钥方式为两者取其一的方式 + * @param aliPayConfigStorage 支付宝配置信息 + * + */ + private static void keyPublic(AliPayConfigStorage aliPayConfigStorage){ + aliPayConfigStorage.setKeyPublic("支付宝公钥"); + } + + /** + * 设置证书公钥信息 + * 普通公钥方式与证书公钥方式为两者取其一的方式 + * @param aliPayConfigStorage 支付宝配置信息 + */ + private static void certKeyPublic(AliPayConfigStorage aliPayConfigStorage){ + //设置为证书方式 + aliPayConfigStorage.setCertSign(true); + //设置证书存储方式,这里为路径 + aliPayConfigStorage.setCertStoreType(CertStoreType.PATH); + aliPayConfigStorage.setMerchantCert("请填写您的应用公钥证书文件路径,例如:d:/appCertPublicKey_2019051064521003.crt"); + aliPayConfigStorage.setAliPayCert("请填写您的支付宝公钥证书文件路径,例如:d:/alipayCertPublicKey_RSA2.crt"); + aliPayConfigStorage.setAliPayRootCert("请填写您的支付宝根证书文件路径,例如:d:/alipayRootCert.crt"); + } + public static void main(String[] args) { AliPayConfigStorage aliPayConfigStorage = new AliPayConfigStorage(); aliPayConfigStorage.setPid("合作者id"); aliPayConfigStorage.setAppid("应用id"); - aliPayConfigStorage.setKeyPublic("支付宝公钥"); + //普通公钥方式与证书公钥方式为两者取其一的方式 + keyPublic(aliPayConfigStorage); +// certKeyPublic(aliPayConfigStorage); aliPayConfigStorage.setKeyPrivate("应用私钥"); aliPayConfigStorage.setNotifyUrl("异步回调地址"); aliPayConfigStorage.setReturnUrl("同步回调地址"); 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 3964139..24b7af8 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,11 +1,10 @@ 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; + +import com.egzosn.pay.common.bean.MsgType; /** * 支付基础配置存储 @@ -24,10 +23,7 @@ public abstract class BasePayConfigStorage implements PayConfigStorage { * 应用私钥,rsa_private pkcs8格式 生成签名时使用 */ private String keyPrivate; - /** - * 应用私钥证书,rsa_private pkcs8格式 生成签名时使用 - */ - private String keyPrivateCertPwd; + /** * 支付平台公钥(签名校验使用) */ @@ -58,21 +54,22 @@ public abstract class BasePayConfigStorage implements PayConfigStorage { /** * 消息来源类型 */ + @Deprecated 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 +78,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 +103,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() { @@ -173,23 +166,31 @@ public abstract class BasePayConfigStorage implements PayConfigStorage { 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 +198,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 +207,10 @@ public abstract class BasePayConfigStorage implements PayConfigStorage { this.expiresTime = expiresTime; } - @Override + + /** + * 强制将access token过期掉 + */ public void expireAccessToken() { this.expiresTime = 0; } @@ -239,12 +242,33 @@ 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/CertStore.java b/pay-java-common/src/main/java/com/egzosn/pay/common/api/CertStore.java index cb119a4..bbbb082 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 @@ -18,5 +18,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/PayConfigStorage.java b/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayConfigStorage.java index 8abdb23..df85e6c 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,5 +1,6 @@ package com.egzosn.pay.common.api; +import com.egzosn.pay.common.bean.Attrs; import com.egzosn.pay.common.bean.MsgType; import com.egzosn.pay.common.util.sign.CertDescriptor; @@ -13,7 +14,7 @@ import java.util.concurrent.locks.Lock; * date 2016-5-18 14:09:01 * */ - public interface PayConfigStorage { + public interface PayConfigStorage extends Attrs { /** * 附加支付配置 @@ -21,11 +22,6 @@ import java.util.concurrent.locks.Lock; */ Object getAttach(); - /** - * 获取私钥证书密码 - * @return 私钥证书密码 - */ - String getKeyPrivateCertPwd(); /** * 应用id * @return 应用id @@ -98,35 +94,11 @@ import java.util.concurrent.locks.Lock; * @see MsgType * @return "text" 或者 "xml",json */ + @Deprecated 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(); /** * 应该是线程安全的 @@ -150,5 +122,4 @@ import java.util.concurrent.locks.Lock; - } 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 0000000..cd7d883 --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/Attrs.java @@ -0,0 +1,31 @@ +package com.egzosn.pay.common.bean; + +import java.io.Serializable; +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 属性 + */ + Object getAttr(String key); +} 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 d7b6895..20488f9 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,6 +1,8 @@ package com.egzosn.pay.common.bean; 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 +17,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 +48,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 +132,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/Order.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/Order.java index 9ba9820..62a2612 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 @@ -12,22 +12,7 @@ import java.util.Map; * date 2020/01/05 13:34 * */ -public interface Order extends Serializable { - - /** - * 获取订单属性 这里可用做覆盖已设置的订单信息属性,订单信息在签名前进行覆盖。 - * - * @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/PayMessage.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/PayMessage.java index 1836758..cf09fc1 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 @@ -18,6 +18,7 @@ import java.util.Map; */ public class PayMessage implements Serializable { private Map payMessage = null; + @Deprecated private String msgType; private String payType; private String transactionType; @@ -50,10 +51,12 @@ public class PayMessage implements Serializable { this.payMessage = payMessage; } + @Deprecated public String getMsgType() { return msgType; } + @Deprecated public void setMsgType(String msgType) { this.msgType = msgType; } 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 35987ab..92995ab 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 @@ -90,7 +90,7 @@ public class PayOrder implements Order { /** * 订单附加信息,可用于预设未提供的参数,这里会覆盖以上所有的订单信息, */ - private Map attr; + private volatile Map attr; public PayOrder() { 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 0000000..25c6761 --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/util/IOUtils.java @@ -0,0 +1,2415 @@ +/* + * 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 java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.CharArrayWriter; +import java.io.Closeable; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.net.HttpURLConnection; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; +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; + +import org.apache.commons.codec.Charsets; + +/** + * 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. + * @throws IOException if an I/O error occurs + * @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 be >= 0 + * @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, must be >= 0 + * @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 be >= 0 + * @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, must be >= 0 + * @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/sign/SecureUtil.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/SecureUtil.java index 096442b..63cbc1b 100644 --- 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 @@ -60,7 +60,7 @@ public class SecureUtil { */ public static byte[] sha1X16 (String data, String encoding) { try { - byte[] bytes = digestByData(data.getBytes(encoding),ALGORITHM_SHA1); + 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) { @@ -91,7 +91,7 @@ public class SecureUtil { public static String sha256X16Str(String data, String encoding) { byte[] bytes =null; try { - bytes = digestByData(data.getBytes(encoding),ALGORITHM_SHA1); + bytes = digestByData(data.getBytes(encoding), ALGORITHM_SHA1); } catch (UnsupportedEncodingException e) { throw new PayErrorException(new PayException("error", e.getLocalizedMessage())); } -- Gitee From a1be9c0dfd08d2d124ae41389823d01498d6cba4 Mon Sep 17 00:00:00 2001 From: egzosn Date: Mon, 26 Oct 2020 22:58:28 +0800 Subject: [PATCH 004/165] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E6=B2=99=E7=9B=92?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E6=B5=81=E7=A8=8B=E4=B8=AD=EF=BC=8C=E5=8F=91?= =?UTF-8?q?=E9=80=81xml=E6=A0=BC=E5=BC=8F=E7=9A=84=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E8=AF=B7=E6=B1=82application/xml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pay/common/http/ClientHttpRequest.java | 1 + .../com/egzosn/pay/wx/api/WxPayService.java | 70 +++++++++++++++---- 2 files changed, 56 insertions(+), 15 deletions(-) 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 bfc799b..f1b0aba 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 @@ -38,6 +38,7 @@ import static com.egzosn.pay.common.http.UriVariables.getMapToParameters; public class ClientHttpRequest extends HttpEntityEnclosingRequestBase implements org.apache.http.client.ResponseHandler { protected static final Log LOG = LogFactory.getLog(ClientHttpRequest.class); public static final ContentType APPLICATION_FORM_URLENCODED_UTF_8 = ContentType.create("application/x-www-form-urlencoded", Consts.UTF_8); + public static final ContentType APPLICATION_XML_UTF_8 = ContentType.create("application/xml", Consts.UTF_8); /** 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 9d86e36..ce72660 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,69 @@ 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.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.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.bean.MethodType; +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.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.util.DateUtils; import com.egzosn.pay.common.util.Util; import com.egzosn.pay.common.util.XML; import com.egzosn.pay.common.util.sign.SignUtils; import com.egzosn.pay.common.util.sign.encrypt.RSA2; import com.egzosn.pay.common.util.str.StringUtils; -import com.egzosn.pay.wx.bean.*; - -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.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; /** * 微信支付服务 @@ -305,7 +344,8 @@ public class WxPayService extends BasePayService implements String sign = createSign(SignUtils.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"); } -- Gitee From dea01ea02054797be7baf5ded34d28004663ee7e Mon Sep 17 00:00:00 2001 From: wangshirui Date: Wed, 28 Oct 2020 18:52:33 +0800 Subject: [PATCH 005/165] =?UTF-8?q?Fix:=20=E5=BE=AE=E4=BF=A1=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=E4=B8=8B=E5=8D=95=E6=8E=A5=E5=8F=A3=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E6=A0=BC=E5=BC=8F=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/egzosn/pay/wx/api/WxPayService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 ce72660..661ebb5 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 @@ -256,8 +256,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())); -- Gitee From a3f5fe80164054d33fc3db7b7295301fd38c62cf Mon Sep 17 00:00:00 2001 From: egzosn Date: Thu, 12 Nov 2020 22:15:14 +0800 Subject: [PATCH 006/165] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=AF=81=E4=B9=A6IO?= =?UTF-8?q?=E8=AF=BB=E5=8F=96=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/ali/api/AliPayConfigStorage.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) 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 cc2028e..cad2cfe 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 @@ -41,7 +41,6 @@ public class AliPayConfigStorage extends BasePayConfigStorage { private String seller; - /** * 应用公钥证书 */ @@ -147,16 +146,14 @@ public class AliPayConfigStorage extends BasePayConfigStorage { * 初始化证书信息 */ public void loadCertEnvironment() { - if (isCertSign() && null != this.certEnvironment){ + if (!isCertSign() || null == this.certEnvironment){ return; } - try (InputStream merchantCertStream = certStoreType.getInputStream(merchantCert); - InputStream aliPayCertStream = certStoreType.getInputStream(aliPayCert); - InputStream aliPayRootCertStream = certStoreType.getInputStream(aliPayRootCert); - ){ + 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) { + } catch (IOException e) { throw new PayErrorException(new PayException("读取证书异常", e.getMessage())); } } -- Gitee From 012b71739de1dd41bbd3ec552c999939d9fc32d6 Mon Sep 17 00:00:00 2001 From: egzosn Date: Fri, 13 Nov 2020 22:29:47 +0800 Subject: [PATCH 007/165] .. --- pay-java-ali/README.md | 2 +- pay-java-ali/src/test/java/PayTest.java | 2 +- .../java/com/egzosn/pay/common/util/Util.java | 2 +- pay-java-demo/README.md | 6 +++--- .../pay/demo/controller/AliPayController.java | 14 +++++++------- .../pay/demo/controller/FuiouPayController.java | 2 +- .../pay/demo/controller/PayController.java | 16 ++++++++-------- .../demo/controller/PayPalPayController.java | 4 ++-- .../pay/demo/controller/UnionPayController.java | 14 +++++++------- .../pay/demo/controller/WxPayController.java | 17 ++++++++--------- pay-java-fuiou/README.md | 2 +- pay-java-fuiou/src/test/java/PayTest.java | 2 +- pay-java-union/README.md | 2 +- pay-java-union/src/test/java/PayTest.java | 2 +- pay-java-wx-youdian/README.md | 2 +- pay-java-wx-youdian/src/test/java/PayTest.java | 2 +- pay-java-wx/README.md | 4 ++-- pay-java-wx/src/test/java/PayTest.java | 2 +- 18 files changed, 48 insertions(+), 49 deletions(-) diff --git a/pay-java-ali/README.md b/pay-java-ali/README.md index aebefc7..0f27875 100644 --- a/pay-java-ali/README.md +++ b/pay-java-ali/README.md @@ -109,7 +109,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-ali/src/test/java/PayTest.java b/pay-java-ali/src/test/java/PayTest.java index 244297e..c098259 100644 --- a/pay-java-ali/src/test/java/PayTest.java +++ b/pay-java-ali/src/test/java/PayTest.java @@ -65,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-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 e67ce7e..e8c2b14 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 @@ -451,7 +451,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); diff --git a/pay-java-demo/README.md b/pay-java-demo/README.md index 0f00886..d6b9a7e 100644 --- a/pay-java-demo/README.md +++ b/pay-java-demo/README.md @@ -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/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 b71161f..c0a918a 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 @@ -115,9 +115,9 @@ public class AliPayController { @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("-", ""), 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); @@ -137,7 +137,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))); @@ -155,7 +155,7 @@ public class AliPayController { 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(); } /** @@ -168,7 +168,7 @@ public class AliPayController { @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)); } @@ -182,9 +182,9 @@ public class AliPayController { 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 ? BigDecimal.valueOf(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.WAVE_CODE); //设置授权码,条码等 order.setAuthCode(authCode); //支付结果 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 06d1114..e3e79a0 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 @@ -65,7 +65,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); 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 bd998bc..8c4f2fc 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 @@ -88,7 +88,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(); @@ -123,7 +123,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,7 +150,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("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); @@ -173,7 +173,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; } @@ -192,7 +192,7 @@ public class PayController { //获取对应的支付账户操作工具(可根据账户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); //支付结果 @@ -225,7 +225,7 @@ 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(); } /** @@ -240,7 +240,7 @@ 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))); } /** * 获取一码付二维码图像 @@ -288,7 +288,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); 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 fe84f00..bd4e344 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 @@ -68,7 +68,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); @@ -93,7 +93,7 @@ public class PayPalPayController { 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); } 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 8c1e6e5..978c90c 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 @@ -97,10 +97,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); @@ -117,7 +117,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); } @@ -133,7 +133,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), SignUtils.randomStr()); //App支付 order.setTransactionType(UnionTransactionType.APP); @@ -155,7 +155,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(); } /** @@ -168,7 +168,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)); } /** @@ -181,7 +181,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, SignUtils.randomStr(), UnionTransactionType.CONSUME); //设置授权码,条码等 order.setAuthCode(authCode); //支付结果 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 a554c73..1269ebc 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 @@ -38,7 +38,6 @@ public class WxPayController { //ssl 退款证书相关 不使用可注释 private static String KEYSTORE = "ssl 退款证书"; - private static String STORE_PASSWORD = "ssl 证书对应的密码, 默认为商户号"; @PostConstruct public void init() { @@ -67,7 +66,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 +96,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,7 +120,7 @@ public class WxPayController { @RequestMapping(value = "jsapi" ) public Map toPay(String openid, BigDecimal price) { - PayOrder order = new PayOrder("订单title", "摘要", null == price ? new BigDecimal(0.01) : price, UUID.randomUUID().toString().replace("-", ""), WxTransactionType.JSAPI); + PayOrder order = new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), WxTransactionType.JSAPI); order.setOpenid(openid); Map orderInfo = service.orderInfo(order); @@ -141,7 +140,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 +158,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 +172,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 +184,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 +211,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下的唯一标识 diff --git a/pay-java-fuiou/README.md b/pay-java-fuiou/README.md index deb86df..ea87ca4 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/src/test/java/PayTest.java b/pay-java-fuiou/src/test/java/PayTest.java index a59cca0..53dfd25 100644 --- a/pay-java-fuiou/src/test/java/PayTest.java +++ b/pay-java-fuiou/src/test/java/PayTest.java @@ -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-union/README.md b/pay-java-union/README.md index 4d370b1..7d604a8 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("-", "")); ``` diff --git a/pay-java-union/src/test/java/PayTest.java b/pay-java-union/src/test/java/PayTest.java index ff2f114..93ef3d5 100644 --- a/pay-java-union/src/test/java/PayTest.java +++ b/pay-java-union/src/test/java/PayTest.java @@ -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())); /*----------- 网页支付-------------------*/ diff --git a/pay-java-wx-youdian/README.md b/pay-java-wx-youdian/README.md index 89445d1..e7f855c 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/src/test/java/PayTest.java b/pay-java-wx-youdian/src/test/java/PayTest.java index 069cca4..e4d3089 100644 --- a/pay-java-wx-youdian/src/test/java/PayTest.java +++ b/pay-java-wx-youdian/src/test/java/PayTest.java @@ -37,7 +37,7 @@ public class PayTest { //支付服务 PayService service = new WxYouDianPayService(wxPayConfigStorage); //支付订单基础信息 - PayOrder payOrder = new PayOrder("订单title", "摘要", new BigDecimal(0.01) , UUID.randomUUID().toString().replace("-", "")); + PayOrder payOrder = new PayOrder("订单title", "摘要", BigDecimal.valueOf(0.01) , UUID.randomUUID().toString().replace("-", "")); /*-----------扫码付-------------------*/ payOrder.setTransactionType(YoudianTransactionType.NATIVE); //获取扫码付的二维码 diff --git a/pay-java-wx/README.md b/pay-java-wx/README.md index ba8783b..488a177 100644 --- a/pay-java-wx/README.md +++ b/pay-java-wx/README.md @@ -81,7 +81,7 @@ ```java //支付订单基础信息 - PayOrder payOrder = new PayOrder("订单title", "摘要", new BigDecimal(0.01) , UUID.randomUUID().toString().replace("-", "")); + PayOrder payOrder = new PayOrder("订单title", "摘要", BigDecimal.valueOf(0.01) , UUID.randomUUID().toString().replace("-", "")); ``` @@ -146,7 +146,7 @@ /*-----------刷脸付-------------------*/ //获取对应的支付账户操作工具(可根据账户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下的唯一标识 diff --git a/pay-java-wx/src/test/java/PayTest.java b/pay-java-wx/src/test/java/PayTest.java index e0a604b..4588157 100644 --- a/pay-java-wx/src/test/java/PayTest.java +++ b/pay-java-wx/src/test/java/PayTest.java @@ -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); //获取扫码付的二维码 -- Gitee From 85015a0131819cb52a43a86b5fc011210bff7def Mon Sep 17 00:00:00 2001 From: egzosn Date: Fri, 13 Nov 2020 22:45:10 +0800 Subject: [PATCH 008/165] .. --- pay-java-paypal/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pay-java-paypal/README.md b/pay-java-paypal/README.md index 5730273..5ca93d0 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)); + order.setRefundAmount(BigDecimal.valueOf(0.01)); RefundResult result = service.refund(order); ``` -- Gitee From d6b6819324f28bea1f5b241f8f4308586e51e68a Mon Sep 17 00:00:00 2001 From: egzosn Date: Thu, 12 Nov 2020 23:32:59 +0800 Subject: [PATCH 009/165] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=95=B4=E7=90=86?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egzosn/pay/ali/bean/CertEnvironment.java | 8 +- .../egzosn/pay/common/api/BasePayService.java | 31 +++++-- .../com/egzosn/pay/common/api/CertStore.java | 5 +- .../pay/common/api/PayConfigStorage.java | 83 +++++++++++-------- .../egzosn/pay/common/bean/CertStoreType.java | 8 +- .../com/egzosn/pay/common/bean/Order.java | 3 - .../egzosn/pay/common/bean/PayMessage.java | 3 - .../pay/common/http/ClientHttpRequest.java | 38 +++++---- 8 files changed, 108 insertions(+), 71 deletions(-) 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 index bc71470..f8b262c 100644 --- 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 @@ -3,15 +3,15 @@ */ 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; -import java.io.InputStream; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - /** * 证书模式运行时环境 * 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 fbc5b59..605ed7d 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,20 +1,34 @@ package com.egzosn.pay.common.api; +import java.awt.image.BufferedImage; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +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.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import com.alibaba.fastjson.JSON; -import com.egzosn.pay.common.bean.*; +import com.egzosn.pay.common.bean.MethodType; +import com.egzosn.pay.common.bean.Order; +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.exception.PayErrorException; 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.*; /** * 支付基础服务 @@ -455,6 +469,7 @@ public abstract class BasePayService implements Pay protected Map setParameters(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; 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 bbbb082..dde73fb 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 { 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 df85e6c..3885601 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 @@ -2,113 +2,126 @@ package com.egzosn.pay.common.api; import com.egzosn.pay.common.bean.Attrs; import com.egzosn.pay.common.bean.MsgType; -import com.egzosn.pay.common.util.sign.CertDescriptor; - -import java.util.concurrent.locks.Lock; /** * 支付客户端配置存储 - * @author egan + * + * @author egan *

  *     email egzosn@gmail.com
  *     date 2016-5-18 14:09:01
  * 
*/ - public interface PayConfigStorage extends Attrs { +public interface PayConfigStorage extends Attrs { /** * 附加支付配置 + * * @return 附加信息 */ - Object getAttach(); + Object getAttach(); /** - * 应用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(); + String getPayType(); /** * 消息类型 - * @see #getMsgType - * @see MsgType + * * @return "text" 或者 "xml",json + * @see #getMsgType + * @see MsgType */ @Deprecated - MsgType getMsgType(); - - + MsgType getMsgType(); /** * 应该是线程安全的 - * @param accessToken 新的accessToken值 + * + * @param accessToken 新的accessToken值 * @param expiresInSeconds 过期时间,以秒为单位 多少秒 */ void updateAccessToken(String accessToken, int expiresInSeconds); /** * 应该是线程安全的 + * * @param accessToken 新的accessToken值 * @param expiresTime 过期时间,时间戳 */ @@ -116,10 +129,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/bean/CertStoreType.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/CertStoreType.java index 20488f9..4517b83 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,12 +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.*; - /** * 证书存储类型 * 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 62a2612..0c06f36 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,8 +1,5 @@ package com.egzosn.pay.common.bean; -import java.io.Serializable; -import java.util.Map; - /** * 支付订单信息 * 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 cf09fc1..11c247a 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,9 +2,6 @@ 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; /** 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 f1b0aba..f58b25f 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 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.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.http.*; +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,13 +25,17 @@ 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请求的客户端 * -- Gitee From def4e71a1842bab242b30c38330fe75afef39b48 Mon Sep 17 00:00:00 2001 From: egzosn Date: Thu, 12 Nov 2020 23:33:25 +0800 Subject: [PATCH 010/165] =?UTF-8?q?=E5=8A=A0=E5=85=A5=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/ali/api/AliPayService.java | 65 ++++++++++++++----- .../com/egzosn/pay/ali/bean/AliPayConst.java | 4 ++ .../com/egzosn/pay/wx/api/WxPayService.java | 10 +-- 3 files changed, 53 insertions(+), 26 deletions(-) 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 a1a6515..93d859b 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 @@ -9,6 +9,7 @@ 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.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; @@ -55,7 +56,6 @@ import com.egzosn.pay.common.util.str.StringUtils; public class AliPayService extends BasePayService { - /** * 获取对应的请求地址 * @@ -144,22 +144,24 @@ public class AliPayService extends BasePayService { /** * 获取公钥信息 + * * @param params 响应参数 * @return 公钥信息 */ - private String getKeyPublic(Map params){ - if (!payConfigStorage.isCertSign()){ + private String getKeyPublic(Map params) { + if (!payConfigStorage.isCertSign()) { return payConfigStorage.getKeyPublic(); } return payConfigStorage.getCertEnvironment().getAliPayPublicKey(getAliPayCertSN(params)); } + /** * 从响应Map中提取支付宝公钥证书序列号 * * @param respMap 响应Map * @return 支付宝公钥证书序列号 */ - public String getAliPayCertSN(java.util.Map respMap){ + public String getAliPayCertSN(java.util.Map respMap) { return (String) respMap.get(AliPayConst.ALIPAY_CERT_SN_FIELD); } @@ -203,6 +205,15 @@ public class AliPayService extends BasePayService { return setSign(getOrder(order)); } + private void setNotifyUrl(Map orderInfo, PayOrder order) { + orderInfo.put(NOTIFY_URL, payConfigStorage.getNotifyUrl()); + setParameters(orderInfo, NOTIFY_URL, order); + } + + private void setReturnUrl(Map orderInfo, PayOrder order) { + orderInfo.put(RETURN_URL, payConfigStorage.getReturnUrl()); + setParameters(orderInfo, RETURN_URL, order); + } /** * 支付宝创建订单信息 @@ -216,10 +227,8 @@ public class AliPayService extends BasePayService { 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<>(); @@ -232,12 +241,12 @@ 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()); + setReturnUrl(orderInfo, order); break; case WAP: bizContent.put(PASSBACK_PARAMS, order.getAddition()); bizContent.put(PRODUCT_CODE, "QUICK_WAP_PAY"); - orderInfo.put(RETURN_URL, payConfigStorage.getReturnUrl()); + setReturnUrl(orderInfo, order); break; case APP: bizContent.put(PASSBACK_PARAMS, order.getAddition()); @@ -257,16 +266,31 @@ 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"); - } - + setExpirationTime(bizContent, order); bizContent.putAll(order.getAttrs()); orderInfo.put(BIZ_CONTENT, JSON.toJSONString(bizContent)); return preOrderHandler(orderInfo, order); } + private Map setExpirationTime(Map bizContent, PayOrder order) { + if (null == order.getExpirationTime()) { + return bizContent; + } + bizContent.put("timeout_express", DateUtils.minutesRemaining(order.getExpirationTime()) + "m"); + switch ((AliTransactionType) order.getTransactionType()) { + case SWEEPPAY: + bizContent.put("qr_code_timeout_express", DateUtils.minutesRemaining(order.getExpirationTime()) + "m"); + case PAGE: + case WAP: + case APP: + bizContent.put("time_expire", DateUtils.formatDate(order.getExpirationTime(), "yyyy-MM-dd HH:mm")); + break; + default: + } + return bizContent; + } + /** * 获取公共请求参数 * @@ -286,10 +310,11 @@ public class AliPayService extends BasePayService { /** * 加载证书序列 + * * @param orderInfo 订单信息 */ - private void loadCertSn(Map orderInfo){ - if (payConfigStorage.isCertSign()){ + private void loadCertSn(Map orderInfo) { + if (payConfigStorage.isCertSign()) { final CertEnvironment certEnvironment = payConfigStorage.getCertEnvironment(); setParameters(orderInfo, "app_cert_sn", certEnvironment.getMerchantCertSN()); setParameters(orderInfo, "alipay_root_cert_sn", certEnvironment.getRootCertSN()); @@ -325,7 +350,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); @@ -381,7 +407,8 @@ public class AliPayService extends BasePayService { public Map microPay(PayOrder order) { if (null == order.getTransactionType()) { order.setTransactionType(AliTransactionType.BAR_CODE); - } else if (order.getTransactionType() != AliTransactionType.BAR_CODE && order.getTransactionType() != AliTransactionType.WAVE_CODE && order.getTransactionType() != AliTransactionType.SECURITY_CODE) { + } + 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())); } @@ -466,6 +493,7 @@ public class AliPayService extends BasePayService { setAppAuthToken(parameters); setParameters(parameters, APP_AUTH_TOKEN, (String) attrs.remove(APP_AUTH_TOKEN)); } + /** * 设置支付宝授权Token * @@ -647,7 +675,8 @@ public class AliPayService extends BasePayService { Map bizContent = new TreeMap(); if (StringUtils.isEmpty(outNo)) { bizContent.put("order_id", tradeNo); - } else { + } + else { bizContent.put("out_biz_no", outNo); } //设置请求参数的集合 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 index 801e548..a56db71 100644 --- 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 @@ -39,6 +39,10 @@ public final class AliPayConst { * 返回地址 */ public static final String RETURN_URL = "return_url"; + /** + * 回调地址 + */ + public static final String NOTIFY_URL = "notify_url"; /** * 请求内容 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 661ebb5..4d9769a 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 @@ -132,20 +132,13 @@ public class WxPayService extends BasePayService implements @Override public boolean verify(Map params) { - if (!(SUCCESS.equals(params.get(RETURN_CODE)) && SUCCESS.equals(params.get(RESULT_CODE)))) { + if (null == 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; } - 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) { @@ -230,6 +223,7 @@ public class WxPayService extends BasePayService implements parameters.put("total_fee", Util.conversionCentAmount(order.getPrice())); setParameters(parameters, "attach", order.getAddition()); parameters.put("notify_url", payConfigStorage.getNotifyUrl()); + setParameters(parameters, "notify_url", order); parameters.put("trade_type", order.getTransactionType().getType()); if (null != order.getExpirationTime()) { parameters.put("time_start", DateUtils.formatDate(new Date(), DateUtils.YYYYMMDDHHMMSS)); -- Gitee From 2d257798f4e557065a07167b805b8716a634def5 Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 18 Nov 2020 22:19:12 +0800 Subject: [PATCH 011/165] =?UTF-8?q?seller=5Fid=20=E9=9D=9E=E5=BF=85?= =?UTF-8?q?=E5=A1=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/ali/api/AliPayService.java | 2 +- pay-java-demo/src/main/webapp/gzh.png | Bin 0 -> 131545 bytes pay-java-demo/src/main/webapp/wx.jpg | Bin 0 -> 86036 bytes 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 pay-java-demo/src/main/webapp/gzh.png create mode 100644 pay-java-demo/src/main/webapp/wx.jpg 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 93d859b..879cdd0 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 @@ -233,7 +233,7 @@ public class AliPayService extends BasePayService { Map bizContent = new TreeMap<>(); bizContent.put("body", order.getBody()); - bizContent.put("seller_id", payConfigStorage.getSeller()); + setParameters(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()); 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..bb9a069e24afa708eabced9fcc2b43bd3481c989 GIT binary patch literal 131545 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 literal 0 HcmV?d00001 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 GIT binary patch literal 86036 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 literal 0 HcmV?d00001 -- Gitee From 519c278b54abad6b6323dcc9f8dcda93ef532af7 Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 18 Nov 2020 22:26:22 +0800 Subject: [PATCH 012/165] =?UTF-8?q?=E5=85=AC=E4=BC=97=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 20fc538..f482e40 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,8 @@ android 例子 [pay-java-android](https://gitee.com/egzosn/pay-java-android) 非常欢迎和感谢对本项目发起Pull Request的同学,不过本项目基于git flow开发流程,因此在发起Pull Request的时候请选择develop分支。 +作者公众号(每周输出)![公众号](https://egzosn.gitee.io/pay-java-parent/gzh.png "gzh.png") + E-Mail:egzosn@gmail.com QQ群:542193977 -- Gitee From 6b2b80b62ea8a5deda239e25c543eb0d695ad558 Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 18 Nov 2020 22:27:22 +0800 Subject: [PATCH 013/165] =?UTF-8?q?seller=5Fid=20=E9=9D=9E=E5=BF=85?= =?UTF-8?q?=E5=A1=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f482e40..adeac87 100644 --- a/README.md +++ b/README.md @@ -69,4 +69,4 @@ E-Mail:egzosn@gmail.com QQ群:542193977 -微信群: ![微信群](https://www.egzosn.com/images/wx.jpg "wx.jpg") +微信群: ![微信群](https://egzosn.gitee.io/pay-java-parent/wx.jpg "wx.jpg") -- Gitee From c117dc51ce007e77da8326a610221db92cc6bd83 Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 18 Nov 2020 22:30:15 +0800 Subject: [PATCH 014/165] md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index adeac87..576cce4 100644 --- a/README.md +++ b/README.md @@ -63,10 +63,12 @@ android 例子 [pay-java-android](https://gitee.com/egzosn/pay-java-android) 非常欢迎和感谢对本项目发起Pull Request的同学,不过本项目基于git flow开发流程,因此在发起Pull Request的时候请选择develop分支。 -作者公众号(每周输出)![公众号](https://egzosn.gitee.io/pay-java-parent/gzh.png "gzh.png") +作者公众号(每周输出) +![公众号](https://egzosn.gitee.io/pay-java-parent/gzh.png "gzh.png") E-Mail:egzosn@gmail.com QQ群:542193977 -微信群: ![微信群](https://egzosn.gitee.io/pay-java-parent/wx.jpg "wx.jpg") +微信群: +![微信群](https://egzosn.gitee.io/pay-java-parent/wx.jpg "wx.jpg") -- Gitee From ad8e592c90e7d24e72ea62fe52e6a9b2565e8531 Mon Sep 17 00:00:00 2001 From: egzosn Date: Mon, 30 Nov 2020 09:58:08 +0800 Subject: [PATCH 015/165] =?UTF-8?q?=E8=AF=81=E4=B9=A6=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=88=A4=E6=96=AD=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/egzosn/pay/ali/api/AliPayConfigStorage.java | 7 +++---- .../main/java/com/egzosn/pay/ali/api/AliPayService.java | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) 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 cad2cfe..d9d907d 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 @@ -16,9 +16,8 @@ import com.egzosn.pay.common.exception.PayErrorException; *

* email egzosn@gmail.com * date 2016-5-18 14:09:01 - * - * - * 以下证书签名相关触发前提是 {@link BasePayConfigStorage#isCertSign}等于true的情况。不然走的就是普通的方式 + *

+ * 以下证书签名相关触发前提是 {@link BasePayConfigStorage#isCertSign}等于true的情况。不然走的就是普通的方式 */ public class AliPayConfigStorage extends BasePayConfigStorage { @@ -146,7 +145,7 @@ public class AliPayConfigStorage extends BasePayConfigStorage { * 初始化证书信息 */ public void loadCertEnvironment() { - if (!isCertSign() || null == this.certEnvironment){ + if (!isCertSign() || null != this.certEnvironment) { return; } try (InputStream merchantCertStream = certStoreType.getInputStream(merchantCert); 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 879cdd0..c0dcb7d 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 @@ -90,7 +90,7 @@ public class AliPayService extends BasePayService { public AliPayService(AliPayConfigStorage payConfigStorage, HttpConfigStorage configStorage) { super(payConfigStorage, configStorage); - + payConfigStorage.loadCertEnvironment(); } public AliPayService(AliPayConfigStorage payConfigStorage) { -- Gitee From ec5dcffb91f394b2b91173dc793872332e12b531 Mon Sep 17 00:00:00 2001 From: egzosn Date: Sun, 29 Nov 2020 10:33:52 +0800 Subject: [PATCH 016/165] =?UTF-8?q?=E8=AF=81=E4=B9=A6=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pay-java-ali/pom.xml | 6 +++++- .../egzosn/pay/ali/utils/AntCertificationUtil.java | 7 ++++++- .../pay/demo/controller/AliPayController.java | 13 +++++++------ .../pay/demo/controller/UnionPayController.java | 5 +---- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index e279f8e..cda91f0 100644 --- a/pay-java-ali/pom.xml +++ b/pay-java-ali/pom.xml @@ -19,7 +19,11 @@ pay-java-common
- + + org.bouncycastle + bcprov-jdk15on + 1.59 + 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 index b4b94d4..7dae2ac 100644 --- 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 @@ -10,6 +10,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.PublicKey; +import java.security.Security; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; @@ -25,6 +26,7 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import com.egzosn.pay.common.bean.result.PayException; import com.egzosn.pay.common.exception.PayErrorException; @@ -43,7 +45,10 @@ import com.egzosn.pay.common.util.str.StringUtils; */ public class AntCertificationUtil { private static final Log LOGGER = LogFactory.getLog(AntCertificationUtil.class); - + static { + Security.removeProvider("SunEC"); + Security.addProvider(new BouncyCastleProvider()); + } /** * 验证证书是否可信 * 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 c0a918a..9eb3606 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 @@ -68,9 +68,9 @@ public class AliPayController { 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.setMerchantCert("E:\\Documents\\支付宝开放平台开发助手\\CSR\\appCertPublicKey_2016080400165436.crt"); + aliPayConfigStorage.setAliPayCert("E:\\Documents\\支付宝开放平台开发助手\\CSR\\alipayCertPublicKey_RSA2.crt"); + aliPayConfigStorage.setAliPayRootCert("E:\\Documents\\支付宝开放平台开发助手\\CSR\\alipayRootCert.crt"); } @PostConstruct @@ -79,9 +79,10 @@ public class AliPayController { aliPayConfigStorage.setPid("2088102169916436"); aliPayConfigStorage.setAppid("2016080400165436"); //普通公钥方式与证书公钥方式为两者取其一的方式 - keyPublic(aliPayConfigStorage); -// certKeyPublic(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=="); +// keyPublic(aliPayConfigStorage); + certKeyPublic(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=="); + aliPayConfigStorage.setKeyPrivate("MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCVEtN86O7ueJCaEWOg0cY/S5eXo8dViq/7iLCa9S4rLYJot665vhW2hnP9J5U8p1Zqa0PnTqYC5cH1Rs8pik5d8ebCtSbbvftN8lT1YHO88m1SAYGomQyDHbhwp+DEWor9rciAYNSKnuxV3Vjncv628E7Bf55/5dwP7SZBQox9Qjbjb6kkVWvghabCAsKDtRoc12weD3O1Opyf4iCFo/Ofl84ILH8PCS3W7CTBAhLaEYXGTcqDQGS8lTrPpMtDZyB1GWnBKdIAsfzFRbFkfzvNEJ1kqPvRfY6hcDA0lqgFdsr9Jlc4zGCABunKs+cviQLY4LtkZUXp76PCP1VrJhpJAgMBAAECggEBAJQKdSI3xIfoDVojaLgdeu4CqNG7oMovh2KZNSE92RKiCD7sIsVsou13ippeA9OPLr9SNMXxkeWQu9RT9IkBQ6ACCbNU6PkSKw4WWQ6xMV7ztWLaAiQnkYhoLHEcFpeIfz07psDu6EOdXYBL5+05gwCzltKMZSAIakkaroHij9gXL4w8LXDKfhL3HiUg0fupyQtOMYhTUbHwl3/WdNYpBFZB/peyzMNY5LDpRhWyz+3lyWjI+fPCnsCnC1QgqRRcYuX4MHSrmvAE3MrnGLAsRje9Muq8zGK1sLRtA2q4zoRQZ/dpT4yk08JD3pzRz1JhFPmhIRfAjCLaQGK5y/CFQvECgYEA5ZyMOrea4zycqh+YHyhwdo5A/oMHXPlcdeKK+i0MULVbfUNHCOEz/GabgadoB37JuDBpjhcN8x93RR3XSnGxxGF3vAcU+X9bZltstmUwOd1NkK73+u84uaThCQSEk5+WtEXXCkHkS09+m8cuBBpQ8mlIzgPN9JAIvDwIrjye6vUCgYEApjTAnSNr4ZjdP8Dwr3ItaL5s1y1TG3X5LyLErHpTec43ZznxL3aNNwhozuAXYYQIMrpLQ//eLf4v7oPtVfDP7tb6Qw11Up3LI9hZoQ3h1Vvd7Z275Qh6Xk5vUQJW7v+TJEZ8YMT/ZAfz3nl2KRzzV3mWgiT1J4J2SgKCapT6RYUCgYEAqMBjd4wVc8jKQ2YxAUVRUnC+Z8+ZC8XhJGq5RaDyGicP9IILpQUOlgQ7ahDFLi0KyCocied7wyFvIQcLgZUJ15Di1PhHfA8T96hdv7eiuARjy2AstOo4QIhjM6N0ZKK3ugpSPyGqTYdBP/Ala7KDp4WlknPNXF63bNCaeKPpEDkCgYAUYTuNCR7dVsUqYBojKTjwMwldiS07nMelQ/ohvA4oTWT1v2EXlM3UBa/0Tf4P8wSeU8GO4Pz5XsW51WtwskqM2sdtGWVdcAPMrWawHOeoJc+cp0620sdOcQetQ0AtpiEAvbPamc1HEsKQcQxQFkCwZTRTWPzGj3S5tn0PXJ0jkQKBgQDVkh2ySpk9361zpzSzi3nSKYQ2E0TRecE/98FQY5IXkm75BhT8o1t289pxJaXuhKkOBh4Pwjjc2P1zy0UK6Aih2nuH8FxKYPumNid5dz19AB0uTBR36jriWena0eDFhVRVwePb2u5N80bV8v4kZ0OTLxi4Y8DLggJns67IKuwB5A=="); aliPayConfigStorage.setNotifyUrl("http://pay.egzosn.com/payBack.json"); aliPayConfigStorage.setReturnUrl("http://pay.egzosn.com/payBack.html"); aliPayConfigStorage.setSignType(SignUtils.RSA.name()); 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 978c90c..15a6b40 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 @@ -42,7 +42,7 @@ public class UnionPayController { private UnionPayService service = null; - @PostConstruct +// @PostConstruct public void init() { UnionPayConfigStorage unionPayConfigStorage = new UnionPayConfigStorage(); unionPayConfigStorage.setMerId("700000000000001"); @@ -59,9 +59,6 @@ public class UnionPayController { //设置证书对应的存储方式,这里默认为文件地址 unionPayConfigStorage.setCertStoreType(CertStoreType.URL); - - - //前台通知网址 即SDKConstants.param_frontUrl unionPayConfigStorage.setReturnUrl("http://www.pay.egzosn.com/payBack.json"); //后台通知地址 即SDKConstants.param_backUrl -- Gitee From 7e05435b1fd5df91036283ab5db45c06d02fed29 Mon Sep 17 00:00:00 2001 From: egzosn Date: Sun, 29 Nov 2020 11:45:40 +0800 Subject: [PATCH 017/165] =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=AE=9D=E5=85=AC?= =?UTF-8?q?=E9=92=A5=E8=AF=81=E4=B9=A6=E6=A1=88=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pay/demo/controller/AliPayController.java | 10 +-- .../ali/alipayCertPublicKey_RSA2.crt | 22 +++++ .../src/main/resources/ali/alipayRootCert.crt | 88 +++++++++++++++++++ .../ali/appCertPublicKey_2016080400165436.crt | 19 ++++ ...w.egzosn.com_\347\247\201\351\222\245.txt" | 1 + 5 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 pay-java-demo/src/main/resources/ali/alipayCertPublicKey_RSA2.crt create mode 100644 pay-java-demo/src/main/resources/ali/alipayRootCert.crt create mode 100644 pay-java-demo/src/main/resources/ali/appCertPublicKey_2016080400165436.crt create mode 100644 "pay-java-demo/src/main/resources/ali/www.egzosn.com_\347\247\201\351\222\245.txt" 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 9eb3606..616636f 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 @@ -68,9 +68,9 @@ public class AliPayController { aliPayConfigStorage.setCertSign(true); //设置证书存储方式,这里为路径 aliPayConfigStorage.setCertStoreType(CertStoreType.PATH); - aliPayConfigStorage.setMerchantCert("E:\\Documents\\支付宝开放平台开发助手\\CSR\\appCertPublicKey_2016080400165436.crt"); - aliPayConfigStorage.setAliPayCert("E:\\Documents\\支付宝开放平台开发助手\\CSR\\alipayCertPublicKey_RSA2.crt"); - aliPayConfigStorage.setAliPayRootCert("E:\\Documents\\支付宝开放平台开发助手\\CSR\\alipayRootCert.crt"); + aliPayConfigStorage.setMerchantCert("E:\\pay-java-parent\\pay-java-demo\\src\\main\\resources\\ali\\alipayCertPublicKey_RSA2.crt"); + aliPayConfigStorage.setAliPayCert("E:\\pay-java-parent\\pay-java-demo\\src\\main\\resources\\ali\\alipayCertPublicKey_RSA2.crt"); + aliPayConfigStorage.setAliPayRootCert("E:\\pay-java-parent\\pay-java-demo\\src\\main\\resources\\ali\\alipayRootCert.crt"); } @PostConstruct @@ -82,10 +82,10 @@ public class AliPayController { // keyPublic(aliPayConfigStorage); certKeyPublic(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=="); - aliPayConfigStorage.setKeyPrivate("MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCVEtN86O7ueJCaEWOg0cY/S5eXo8dViq/7iLCa9S4rLYJot665vhW2hnP9J5U8p1Zqa0PnTqYC5cH1Rs8pik5d8ebCtSbbvftN8lT1YHO88m1SAYGomQyDHbhwp+DEWor9rciAYNSKnuxV3Vjncv628E7Bf55/5dwP7SZBQox9Qjbjb6kkVWvghabCAsKDtRoc12weD3O1Opyf4iCFo/Ofl84ILH8PCS3W7CTBAhLaEYXGTcqDQGS8lTrPpMtDZyB1GWnBKdIAsfzFRbFkfzvNEJ1kqPvRfY6hcDA0lqgFdsr9Jlc4zGCABunKs+cviQLY4LtkZUXp76PCP1VrJhpJAgMBAAECggEBAJQKdSI3xIfoDVojaLgdeu4CqNG7oMovh2KZNSE92RKiCD7sIsVsou13ippeA9OPLr9SNMXxkeWQu9RT9IkBQ6ACCbNU6PkSKw4WWQ6xMV7ztWLaAiQnkYhoLHEcFpeIfz07psDu6EOdXYBL5+05gwCzltKMZSAIakkaroHij9gXL4w8LXDKfhL3HiUg0fupyQtOMYhTUbHwl3/WdNYpBFZB/peyzMNY5LDpRhWyz+3lyWjI+fPCnsCnC1QgqRRcYuX4MHSrmvAE3MrnGLAsRje9Muq8zGK1sLRtA2q4zoRQZ/dpT4yk08JD3pzRz1JhFPmhIRfAjCLaQGK5y/CFQvECgYEA5ZyMOrea4zycqh+YHyhwdo5A/oMHXPlcdeKK+i0MULVbfUNHCOEz/GabgadoB37JuDBpjhcN8x93RR3XSnGxxGF3vAcU+X9bZltstmUwOd1NkK73+u84uaThCQSEk5+WtEXXCkHkS09+m8cuBBpQ8mlIzgPN9JAIvDwIrjye6vUCgYEApjTAnSNr4ZjdP8Dwr3ItaL5s1y1TG3X5LyLErHpTec43ZznxL3aNNwhozuAXYYQIMrpLQ//eLf4v7oPtVfDP7tb6Qw11Up3LI9hZoQ3h1Vvd7Z275Qh6Xk5vUQJW7v+TJEZ8YMT/ZAfz3nl2KRzzV3mWgiT1J4J2SgKCapT6RYUCgYEAqMBjd4wVc8jKQ2YxAUVRUnC+Z8+ZC8XhJGq5RaDyGicP9IILpQUOlgQ7ahDFLi0KyCocied7wyFvIQcLgZUJ15Di1PhHfA8T96hdv7eiuARjy2AstOo4QIhjM6N0ZKK3ugpSPyGqTYdBP/Ala7KDp4WlknPNXF63bNCaeKPpEDkCgYAUYTuNCR7dVsUqYBojKTjwMwldiS07nMelQ/ohvA4oTWT1v2EXlM3UBa/0Tf4P8wSeU8GO4Pz5XsW51WtwskqM2sdtGWVdcAPMrWawHOeoJc+cp0620sdOcQetQ0AtpiEAvbPamc1HEsKQcQxQFkCwZTRTWPzGj3S5tn0PXJ0jkQKBgQDVkh2ySpk9361zpzSzi3nSKYQ2E0TRecE/98FQY5IXkm75BhT8o1t289pxJaXuhKkOBh4Pwjjc2P1zy0UK6Aih2nuH8FxKYPumNid5dz19AB0uTBR36jriWena0eDFhVRVwePb2u5N80bV8v4kZ0OTLxi4Y8DLggJns67IKuwB5A=="); + 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"); //是否为测试账号,沙箱环境 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 0000000..75b39f2 --- /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 0000000..76417c5 --- /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 0000000..e5107d3 --- /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 0000000..33cfb63 --- /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 -- Gitee From fc60b4553ef2149c258ee3b3e531bcb7c2a4ac35 Mon Sep 17 00:00:00 2001 From: egzosn Date: Tue, 1 Dec 2020 22:42:52 +0800 Subject: [PATCH 018/165] 2.13.3 --- README.md | 2 +- pay-java-ali/pom.xml | 2 +- pay-java-baidu/pom.xml | 2 +- pay-java-common/pom.xml | 2 +- .../com/egzosn/pay/common/util/IOUtils.java | 60 +++++-------------- pay-java-demo/pom.xml | 2 +- pay-java-fuiou/pom.xml | 2 +- pay-java-payoneer/pom.xml | 2 +- pay-java-paypal/pom.xml | 2 +- pay-java-union/pom.xml | 2 +- pay-java-wx-youdian/pom.xml | 2 +- pay-java-wx/pom.xml | 2 +- .../com/egzosn/pay/wx/api/WxBillService.java | 8 +-- .../com/egzosn/pay/wx/api/WxPayService.java | 5 +- .../egzosn/pay/wx/bean/WxRefundResult.java | 11 ++-- pay-java-yiji/pom.xml | 2 +- pom.xml | 4 +- 17 files changed, 42 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 576cce4..d904512 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ com.egzosn {module-name} - 2.13.2 + 2.13.3 ``` diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index cda91f0..e48d462 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.13.3 4.0.0 pay-java-ali diff --git a/pay-java-baidu/pom.xml b/pay-java-baidu/pom.xml index 5e39ceb..4f4a174 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.13.3 4.0.0 pay-java-baidu diff --git a/pay-java-common/pom.xml b/pay-java-common/pom.xml index 1c79c00..0adcfa5 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.13.3 4.0.0 jar 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 index 25c6761..9b15a56 100644 --- 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 @@ -16,29 +16,10 @@ */ package com.egzosn.pay.common.util; -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.CharArrayWriter; -import java.io.Closeable; -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.Reader; -import java.io.StringWriter; -import java.io.UnsupportedEncodingException; -import java.io.Writer; -import java.net.HttpURLConnection; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.URI; -import java.net.URL; -import java.net.URLConnection; +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; @@ -46,8 +27,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import org.apache.commons.codec.Charsets; - /** * General IO stream manipulation utilities. *

@@ -80,7 +59,6 @@ import org.apache.commons.codec.Charsets; * 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 @@ -283,8 +261,7 @@ public class IOUtils { if (closeable != null) { closeable.close(); } - } - catch (IOException ioe) { + } catch (IOException ioe) { // ignore } } @@ -316,8 +293,7 @@ public class IOUtils { if (sock != null) { try { sock.close(); - } - catch (IOException ioe) { + } catch (IOException ioe) { // ignored } } @@ -350,8 +326,7 @@ public class IOUtils { if (selector != null) { try { selector.close(); - } - catch (IOException ioe) { + } catch (IOException ioe) { // ignored } } @@ -384,8 +359,7 @@ public class IOUtils { if (sock != null) { try { sock.close(); - } - catch (IOException ioe) { + } catch (IOException ioe) { // ignored } } @@ -409,7 +383,6 @@ public class IOUtils { * * @param input Stream to be fully buffered. * @return A fully buffered stream. - * @throws IOException if an I/O error occurs * @since 2.0 */ public static InputStream toBufferedInputStream(InputStream input) { @@ -612,8 +585,7 @@ public class IOUtils { URLConnection conn = url.openConnection(); try { return IOUtils.toByteArray(conn); - } - finally { + } finally { close(conn); } } @@ -631,8 +603,7 @@ public class IOUtils { InputStream inputStream = urlConn.getInputStream(); try { return IOUtils.toByteArray(inputStream); - } - finally { + } finally { inputStream.close(); } } @@ -864,8 +835,7 @@ public class IOUtils { InputStream inputStream = url.openStream(); try { return toString(inputStream, encoding); - } - finally { + } finally { inputStream.close(); } } @@ -2252,7 +2222,7 @@ public class IOUtils { * @param input where to read input from * @param buffer destination * @param offset inital offset into buffer - * @param length length to read, must be >= 0 + * @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 @@ -2298,7 +2268,7 @@ public class IOUtils { * @param input where to read input from * @param buffer destination * @param offset inital offset into buffer - * @param length length to read, must be >= 0 + * @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 @@ -2344,7 +2314,7 @@ public class IOUtils { * @param input where to read input from * @param buffer destination * @param offset inital offset into buffer - * @param length length to read, must be >= 0 + * @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 @@ -2383,7 +2353,7 @@ public class IOUtils { * @param input where to read input from * @param buffer destination * @param offset inital offset into buffer - * @param length length to read, must be >= 0 + * @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 diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index 79c6805..e844498 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.13.3 4.0.0 war diff --git a/pay-java-fuiou/pom.xml b/pay-java-fuiou/pom.xml index 7d1b18e..32e6b7f 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.13.3 4.0.0 pay-java-fuiou diff --git a/pay-java-payoneer/pom.xml b/pay-java-payoneer/pom.xml index 7a79e0c..dcc61df 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.13.3 4.0.0 pay-java-payoneer diff --git a/pay-java-paypal/pom.xml b/pay-java-paypal/pom.xml index 0fba2f8..f330606 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.13.3 4.0.0 diff --git a/pay-java-union/pom.xml b/pay-java-union/pom.xml index abff084..e154945 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.13.3 4.0.0 diff --git a/pay-java-wx-youdian/pom.xml b/pay-java-wx-youdian/pom.xml index d43cfda..2b218bb 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.13.3 4.0.0 pay-java-wx-youdian diff --git a/pay-java-wx/pom.xml b/pay-java-wx/pom.xml index f2dca76..9877cd3 100644 --- a/pay-java-wx/pom.xml +++ b/pay-java-wx/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.3-SNAPSHOT + 2.13.3 4.0.0 pay-java-wx 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 bf01bbd..eeed364 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 @@ -4,12 +4,12 @@ import java.util.Date; import java.util.Map; /** - * @description:账单接口 + * 账单接口 * @author: faymanwang - * @email: 1057438332@qq.com - * @time: 2020/7/31 11:21 + * email: 1057438332@qq.com + * time: 2020/7/31 11:21 */ public interface WxBillService { - public Map downloadbill(Date billDate, String billType, String path); + Map downloadbill(Date billDate, String billType, String path); } 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 4d9769a..84d3e4c 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 @@ -626,8 +626,9 @@ public class WxPayService extends BasePayService implements /** * GZIP解压缩 * - * @param input - * @return + * @param input 输入流账单 + * @return 解压后输入流 + * @throws IOException IOException */ public static InputStream uncompress(InputStream input) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); 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 index e17c385..ce3ade7 100644 --- 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 @@ -11,9 +11,10 @@ import com.egzosn.pay.common.bean.CurType; /** * 微信退款结果 * @author Egan - *


+ * 
  * email egzosn@gmail.com
  * date 2020/8/16 21:29
+ * 
*/ public class WxRefundResult extends BaseRefundResult { @@ -123,7 +124,7 @@ public class WxRefundResult extends BaseRefundResult { private BigDecimal refundFee; /** * 应结退款金额 - * 去掉非充值代金券退款金额后的退款金额,退款金额=申请退款金额-非充值代金券退款金额,退款金额<=申请退款金额 + * 去掉非充值代金券退款金额后的退款金额,退款金额=申请退款金额-非充值代金券退款金额,退款金额<=申请退款金额 */ @JSONField(name = "settlement_refund_fee") private BigDecimal settlementRefundFee; @@ -135,7 +136,7 @@ public class WxRefundResult extends BaseRefundResult { private BigDecimal totalFee; /** * 应结订单金额 - * 去掉非充值代金券金额后的订单总金额,应结订单金额=订单金额-非充值代金券金额,应结订单金额<=订单金额。 + * 去掉非充值代金券金额后的订单总金额,应结订单金额=订单金额-非充值代金券金额,应结订单金额<=订单金额。 */ @JSONField(name = "settlement_total_fee") private BigDecimal settlementTotalFee; @@ -176,13 +177,13 @@ public class WxRefundResult extends BaseRefundResult { private String couponType0; /** * 代金券退款总金额 - * 代金券退款金额<=退款金额,退款金额-代金券或立减优惠退款金额为现金,说明详见代金券或立减优惠 + * 代金券退款金额<=退款金额,退款金额-代金券或立减优惠退款金额为现金,说明详见代金券或立减优惠 */ @JSONField(name = "coupon_refund_fee") private BigDecimal couponRefundFee; /** * 单个代金券退款金额 - * 代金券退款金额<=退款金额,退款金额-代金券或立减优惠退款金额为现金,说明详见代金券或立减优惠 + * 代金券退款金额<=退款金额,退款金额-代金券或立减优惠退款金额为现金,说明详见代金券或立减优惠 * 这里只接收0的,其余请自行获取 */ @JSONField(name = "coupon_refund_fee_0") diff --git a/pay-java-yiji/pom.xml b/pay-java-yiji/pom.xml index 45ce907..28b4f3d 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.13.3 4.0.0 diff --git a/pom.xml b/pom.xml index 4a5dee5..43d418c 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.egzosn pay-java-parent pom - 2.13.3-SNAPSHOT + 2.13.3 Pay Java - Parent Pay Java Parent @@ -64,7 +64,7 @@ - 2.13.3-SNAPSHOT + 2.13.3 4.5.4 1.2.17 1.2.73 -- Gitee From d5a46707e2ce1d3b37fd450b7d002e1caa2482a3 Mon Sep 17 00:00:00 2001 From: egzosn Date: Tue, 1 Dec 2020 22:50:29 +0800 Subject: [PATCH 019/165] =?UTF-8?q?2.13.4=E5=BC=80=E5=8F=91=E5=BC=80?= =?UTF-8?q?=E5=A7=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pay-java-ali/pom.xml | 2 +- .../src/main/java/com/egzosn/pay/ali/api/AliPayService.java | 2 +- pay-java-baidu/pom.xml | 2 +- pay-java-common/pom.xml | 2 +- pay-java-demo/pom.xml | 2 +- pay-java-fuiou/pom.xml | 2 +- pay-java-payoneer/pom.xml | 2 +- pay-java-paypal/pom.xml | 2 +- pay-java-union/pom.xml | 2 +- pay-java-wx-youdian/pom.xml | 2 +- pay-java-wx/pom.xml | 2 +- pay-java-yiji/pom.xml | 2 +- pom.xml | 4 ++-- 13 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index e48d462..f31a779 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 + 2.13.4-SNAPSHOT 4.0.0 pay-java-ali 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 c0dcb7d..31c7110 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 @@ -245,7 +245,7 @@ public class AliPayService extends BasePayService { break; case WAP: bizContent.put(PASSBACK_PARAMS, order.getAddition()); - bizContent.put(PRODUCT_CODE, "QUICK_WAP_PAY"); + bizContent.put(PRODUCT_CODE, "QUICK_WAP_WAY"); setReturnUrl(orderInfo, order); break; case APP: diff --git a/pay-java-baidu/pom.xml b/pay-java-baidu/pom.xml index 4f4a174..228a3d0 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 + 2.13.4-SNAPSHOT 4.0.0 pay-java-baidu diff --git a/pay-java-common/pom.xml b/pay-java-common/pom.xml index 0adcfa5..e0ad8d1 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 + 2.13.4-SNAPSHOT 4.0.0 jar diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index e844498..7c0de2a 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 + 2.13.4-SNAPSHOT 4.0.0 war diff --git a/pay-java-fuiou/pom.xml b/pay-java-fuiou/pom.xml index 32e6b7f..6a834c7 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 + 2.13.4-SNAPSHOT 4.0.0 pay-java-fuiou diff --git a/pay-java-payoneer/pom.xml b/pay-java-payoneer/pom.xml index dcc61df..9df1ded 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 + 2.13.4-SNAPSHOT 4.0.0 pay-java-payoneer diff --git a/pay-java-paypal/pom.xml b/pay-java-paypal/pom.xml index f330606..0abbb06 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 + 2.13.4-SNAPSHOT 4.0.0 diff --git a/pay-java-union/pom.xml b/pay-java-union/pom.xml index e154945..6c539c1 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 + 2.13.4-SNAPSHOT 4.0.0 diff --git a/pay-java-wx-youdian/pom.xml b/pay-java-wx-youdian/pom.xml index 2b218bb..2081227 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 + 2.13.4-SNAPSHOT 4.0.0 pay-java-wx-youdian diff --git a/pay-java-wx/pom.xml b/pay-java-wx/pom.xml index 9877cd3..dfe0e5d 100644 --- a/pay-java-wx/pom.xml +++ b/pay-java-wx/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.3 + 2.13.4-SNAPSHOT 4.0.0 pay-java-wx diff --git a/pay-java-yiji/pom.xml b/pay-java-yiji/pom.xml index 28b4f3d..0a3ac4e 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 + 2.13.4-SNAPSHOT 4.0.0 diff --git a/pom.xml b/pom.xml index 43d418c..fc3322b 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.egzosn pay-java-parent pom - 2.13.3 + 2.13.4-SNAPSHOT Pay Java - Parent Pay Java Parent @@ -64,7 +64,7 @@ - 2.13.3 + 2.13.4-SNAPSHOT 4.5.4 1.2.17 1.2.73 -- Gitee From 75d5ee32182064c2a22274ad824d206e64e9b1c4 Mon Sep 17 00:00:00 2001 From: wangshirui Date: Wed, 2 Dec 2020 18:03:28 +0800 Subject: [PATCH 020/165] =?UTF-8?q?[pay-common]=20Add:=20payBack=E7=9A=84?= =?UTF-8?q?=E6=96=B0=E9=87=8D=E8=BD=BD=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egzosn/pay/common/api/BasePayService.java | 24 +++++++++++++++++++ .../com/egzosn/pay/common/api/PayService.java | 8 +++++++ 2 files changed, 32 insertions(+) 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 605ed7d..a86cfc9 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 @@ -436,6 +436,30 @@ public abstract class BasePayService implements Pay return getPayMessageHandler().handle(payMessage, context, this); } + /** + * 使用转换过的参数进行回调处理 + * + * @param data 转化后的参数Map + * @return 获得回调响应信息 + */ + @Override + public PayOutMessage payBack(Map data) { + if (LOG.isDebugEnabled()) { + LOG.debug("回调响应:" + JSON.toJSONString(data)); + } + if (!verify(data)) { + return getPayOutMessage("fail", "失败"); + } + PayMessage payMessage = this.createMessage(data); + Map context = new HashMap(); + for (PayMessageInterceptor interceptor : interceptors) { + if (!interceptor.intercept(payMessage, context, this)) { + return successPayOutMessage(payMessage); + } + } + return getPayMessageHandler().handle(payMessage, context, this); + } + /** * 创建消息 * 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 0c1b6ac..fa4cb17 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 @@ -379,6 +379,14 @@ public interface PayService { */ PayOutMessage payBack(Map parameterMap, InputStream is); + /** + * 使用转换过的参数进行回调处理 + * + * @param data 转化后的参数Map + * @return 获得回调响应信息 + */ + PayOutMessage payBack(Map data); + /** * 设置支付消息处理器,这里用于处理具体的支付业务 * -- Gitee From ea03f94668c1dcbe0c5c9b40b17a98ae53c6e1b1 Mon Sep 17 00:00:00 2001 From: egan Date: Fri, 4 Dec 2020 14:56:43 +0800 Subject: [PATCH 021/165] update pay-java-wx/README.md. --- pay-java-wx/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pay-java-wx/README.md b/pay-java-wx/README.md index 488a177..da29557 100644 --- a/pay-java-wx/README.md +++ b/pay-java-wx/README.md @@ -112,6 +112,21 @@ ``` + +#### JSAPI支付 + +```java + + /*-----------JSAPI-------------------*/ + //公众号支付 + payOrder.setTransactionType(WxTransactionType.JSAPI); + //微信公众号对应微信付款用户的唯一标识 + payOrder.setOpenid(openid); + Map appOrderInfo = service.orderInfo(payOrder); + /*-----------/JSAPI-------------------*/ + +``` + #### 网页支付 ```java -- Gitee From 3805fcdc698c78ff37556f862cc1293116b05996 Mon Sep 17 00:00:00 2001 From: egzosn Date: Mon, 28 Dec 2020 10:23:30 +0800 Subject: [PATCH 022/165] =?UTF-8?q?=E9=80=80=E6=AC=BE=E5=BA=8F=E5=88=97?= =?UTF-8?q?=E5=8F=B7=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egzosn/pay/common/bean/RefundResult.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) 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 index 50ca0c1..1304419 100644 --- 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 @@ -6,15 +6,16 @@ import java.util.Map; /** * 退款结果 - * + *

* 这里继承Map为兼容方案,后续版本进行删除 + * * @author Egan *

  * email egzosn@gmail.com
  * date 2020/8/16 9:55
  * 
*/ -public interface RefundResult extends Map, Serializable { +public interface RefundResult extends Serializable { /** * 获取退款结果原信息集 * @@ -29,6 +30,7 @@ public interface RefundResult extends Map, Serializable { * @return 属性值 */ Object getAttr(String key); + /** * 获取退款结果属性值 * @@ -36,6 +38,7 @@ public interface RefundResult extends Map, Serializable { * @return 属性值 */ String getAttrString(String key); + /** * 获取退款结果属性值 * @@ -47,42 +50,49 @@ public interface RefundResult extends Map, Serializable { /** * 获取退款请求结果状态码 + * * @return 状态码 */ String getCode(); /** * 获取退款请求结果状态提示信息 + * * @return 提示信息 */ String getMsg(); /** * 返回业务结果状态码 + * * @return 业务结果状态码 */ String getResultCode(); /** * 返回业务结果状态提示信息 + * * @return 业务结果状态提示信息 */ String getResultMsg(); /** * 退款金额 + * * @return 退款金额 */ BigDecimal getRefundFee(); + /** * 退款币种信息 + * * @return 币种信息 */ CurType getRefundCurrency(); /** - * 支付平台交易号 - * 发起支付时 支付平台(如支付宝)返回的交易订单号 + * 支付平台交易号 + * 发起支付时 支付平台(如支付宝)返回的交易订单号 * * @return 支付平台交易号 */ @@ -91,12 +101,14 @@ public interface RefundResult extends Map, Serializable { /** * 支付订单号 * 发起支付时,用户系统的订单号 + * * @return 支付订单号 */ String getOutTradeNo(); /** * 商户退款单号 + * * @return 商户退款单号 */ String getRefundNo(); -- Gitee From 6a72cc0906b9ec5ab484a2b74a386d375b53f987 Mon Sep 17 00:00:00 2001 From: egzosn Date: Mon, 28 Dec 2020 10:40:54 +0800 Subject: [PATCH 023/165] =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=AE=9D=E5=85=AC?= =?UTF-8?q?=E9=92=A5=E8=AF=81=E4=B9=A6=E6=A1=88=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pay/common/bean/BaseRefundResult.java | 259 ------------------ 1 file changed, 259 deletions(-) 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 index b92763a..100a7b3 100644 --- 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 @@ -1,9 +1,7 @@ package com.egzosn.pay.common.bean; import java.math.BigDecimal; -import java.util.Collection; import java.util.Map; -import java.util.Set; /** * 基础的退款结果对象 @@ -77,261 +75,4 @@ public abstract class BaseRefundResult implements RefundResult { return new BigDecimal(getAttrString(key)); } - /** - * Returns the number of key-value mappings in this map. If the - * map contains more than Integer.MAX_VALUE elements, returns - * Integer.MAX_VALUE. - * - * @return the number of key-value mappings in this map - */ - @Override - public int size() { - return attrs.size(); - } - - /** - * Returns true if this map contains no key-value mappings. - * - * @return true if this map contains no key-value mappings - */ - @Override - public boolean isEmpty() { - return attrs.isEmpty(); - } - - /** - * Returns true if this map contains a mapping for the specified - * key. More formally, returns true if and only if - * this map contains a mapping for a key k such that - * (key==null ? k==null : key.equals(k)). (There can be - * at most one such mapping.) - * - * @param key key whose presence in this map is to be tested - * @return true if this map contains a mapping for the specified - * key - * @throws ClassCastException if the key is of an inappropriate type for - * this map - * (
optional) - * @throws NullPointerException if the specified key is null and this map - * does not permit null keys - * (optional) - */ - @Override - public boolean containsKey(Object key) { - return attrs.containsKey(key); - } - - /** - * Returns true if this map maps one or more keys to the - * specified value. More formally, returns true if and only if - * this map contains at least one mapping to a value v such that - * (value==null ? v==null : value.equals(v)). This operation - * will probably require time linear in the map size for most - * implementations of the Map interface. - * - * @param value value whose presence in this map is to be tested - * @return true if this map maps one or more keys to the - * specified value - * @throws ClassCastException if the value is of an inappropriate type for - * this map - * (optional) - * @throws NullPointerException if the specified value is null and this - * map does not permit null values - * (optional) - */ - @Override - public boolean containsValue(Object value) { - return attrs.containsValue(value); - } - - /** - * Returns the value to which the specified key is mapped, - * or {@code null} if this map contains no mapping for the key. - * - *

More formally, if this map contains a mapping from a key - * {@code k} to a value {@code v} such that {@code (key==null ? k==null : - * key.equals(k))}, then this method returns {@code v}; otherwise - * it returns {@code null}. (There can be at most one such mapping.) - * - *

If this map permits null values, then a return value of - * {@code null} does not necessarily indicate that the map - * contains no mapping for the key; it's also possible that the map - * explicitly maps the key to {@code null}. The {@link #containsKey - * containsKey} operation may be used to distinguish these two cases. - * - * @param key the key whose associated value is to be returned - * @return the value to which the specified key is mapped, or - * {@code null} if this map contains no mapping for the key - * @throws ClassCastException if the key is of an inappropriate type for - * this map - * (optional) - * @throws NullPointerException if the specified key is null and this map - * does not permit null keys - * (optional) - */ - @Override - public Object get(Object key) { - return attrs.get(key); - } - - /** - * Associates the specified value with the specified key in this map - * (optional operation). If the map previously contained a mapping for - * the key, the old value is replaced by the specified value. (A map - * m is said to contain a mapping for a key k if and only - * if {@link #containsKey(Object) m.containsKey(k)} would return - * true.) - * - * @param key key with which the specified value is to be associated - * @param value value to be associated with the specified key - * @return the previous value associated with key, or - * null if there was no mapping for key. - * (A null return can also indicate that the map - * previously associated null with key, - * if the implementation supports null values.) - * @throws UnsupportedOperationException if the put operation - * is not supported by this map - * @throws ClassCastException if the class of the specified key or value - * prevents it from being stored in this map - * @throws NullPointerException if the specified key or value is null - * and this map does not permit null keys or values - * @throws IllegalArgumentException if some property of the specified key - * or value prevents it from being stored in this map - */ - @Override - public Object put(String key, Object value) { - return attrs.put(key, value); - } - - /** - * Removes the mapping for a key from this map if it is present - * (optional operation). More formally, if this map contains a mapping - * from key k to value v such that - * (key==null ? k==null : key.equals(k)), that mapping - * is removed. (The map can contain at most one such mapping.) - * - *

Returns the value to which this map previously associated the key, - * or null if the map contained no mapping for the key. - * - *

If this map permits null values, then a return value of - * null does not necessarily indicate that the map - * contained no mapping for the key; it's also possible that the map - * explicitly mapped the key to null. - * - *

The map will not contain a mapping for the specified key once the - * call returns. - * - * @param key key whose mapping is to be removed from the map - * @return the previous value associated with key, or - * null if there was no mapping for key. - * @throws UnsupportedOperationException if the remove operation - * is not supported by this map - * @throws ClassCastException if the key is of an inappropriate type for - * this map - * (optional) - * @throws NullPointerException if the specified key is null and this - * map does not permit null keys - * (optional) - */ - @Override - public Object remove(Object key) { - return attrs.remove(key); - } - - /** - * Copies all of the mappings from the specified map to this map - * (optional operation). The effect of this call is equivalent to that - * of calling {@link #put(Object, Object) put(k, v)} on this map once - * for each mapping from key k to value v in the - * specified map. The behavior of this operation is undefined if the - * specified map is modified while the operation is in progress. - * - * @param m mappings to be stored in this map - * @throws UnsupportedOperationException if the putAll operation - * is not supported by this map - * @throws ClassCastException if the class of a key or value in the - * specified map prevents it from being stored in this map - * @throws NullPointerException if the specified map is null, or if - * this map does not permit null keys or values, and the - * specified map contains null keys or values - * @throws IllegalArgumentException if some property of a key or value in - * the specified map prevents it from being stored in this map - */ - @Override - public void putAll(Map m) { - attrs.putAll(m); - } - - /** - * Removes all of the mappings from this map (optional operation). - * The map will be empty after this call returns. - * - * @throws UnsupportedOperationException if the clear operation - * is not supported by this map - */ - @Override - public void clear() { - attrs.clear(); - } - - /** - * Returns a {@link Set} view of the keys contained in this map. - * The set is backed by the map, so changes to the map are - * reflected in the set, and vice-versa. If the map is modified - * while an iteration over the set is in progress (except through - * the iterator's own remove operation), the results of - * the iteration are undefined. The set supports element removal, - * which removes the corresponding mapping from the map, via the - * Iterator.remove, Set.remove, - * removeAll, retainAll, and clear - * operations. It does not support the add or addAll - * operations. - * - * @return a set view of the keys contained in this map - */ - @Override - public Set keySet() { - return attrs.keySet(); - } - - /** - * Returns a {@link Collection} view of the values contained in this map. - * The collection is backed by the map, so changes to the map are - * reflected in the collection, and vice-versa. If the map is - * modified while an iteration over the collection is in progress - * (except through the iterator's own remove operation), - * the results of the iteration are undefined. The collection - * supports element removal, which removes the corresponding - * mapping from the map, via the Iterator.remove, - * Collection.remove, removeAll, - * retainAll and clear operations. It does not - * support the add or addAll operations. - * - * @return a collection view of the values contained in this map - */ - @Override - public Collection values() { - return attrs.values(); - } - - /** - * Returns a {@link Set} view of the mappings contained in this map. - * The set is backed by the map, so changes to the map are - * reflected in the set, and vice-versa. If the map is modified - * while an iteration over the set is in progress (except through - * the iterator's own remove operation, or through the - * setValue operation on a map entry returned by the - * iterator) the results of the iteration are undefined. The set - * supports element removal, which removes the corresponding - * mapping from the map, via the Iterator.remove, - * Set.remove, removeAll, retainAll and - * clear operations. It does not support the - * add or addAll operations. - * - * @return a set view of the mappings contained in this map - */ - @Override - public Set> entrySet() { - return attrs.entrySet(); - } } -- Gitee From 0636608876ca593943e0d0f6794e275292c3d57a Mon Sep 17 00:00:00 2001 From: egzosn Date: Mon, 28 Dec 2020 10:43:30 +0800 Subject: [PATCH 024/165] =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=AE=9D=E5=85=AC?= =?UTF-8?q?=E9=92=A5=E8=AF=81=E4=B9=A6=E6=A1=88=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pay-java-ali/pom.xml | 2 +- pay-java-baidu/pom.xml | 2 +- pay-java-common/pom.xml | 2 +- pay-java-demo/pom.xml | 2 +- pay-java-fuiou/pom.xml | 2 +- pay-java-payoneer/pom.xml | 2 +- pay-java-paypal/pom.xml | 2 +- pay-java-union/pom.xml | 2 +- .../egzosn/pay/union/api/UnionPayService.java | 2 +- pay-java-union/src/test/java/PayTest.java | 30 +++++++++---------- pay-java-wx-youdian/pom.xml | 2 +- pay-java-wx/pom.xml | 2 +- pay-java-yiji/pom.xml | 2 +- pom.xml | 6 ++-- 14 files changed, 30 insertions(+), 30 deletions(-) diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index f31a779..f014c96 100644 --- a/pay-java-ali/pom.xml +++ b/pay-java-ali/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.4-SNAPSHOT + 2.13.3-b1 4.0.0 pay-java-ali diff --git a/pay-java-baidu/pom.xml b/pay-java-baidu/pom.xml index 228a3d0..d336160 100644 --- a/pay-java-baidu/pom.xml +++ b/pay-java-baidu/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.4-SNAPSHOT + 2.13.3-b1 4.0.0 pay-java-baidu diff --git a/pay-java-common/pom.xml b/pay-java-common/pom.xml index e0ad8d1..2ca60b8 100644 --- a/pay-java-common/pom.xml +++ b/pay-java-common/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.4-SNAPSHOT + 2.13.3-b1 4.0.0 jar diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index 7c0de2a..6858975 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.4-SNAPSHOT + 2.13.3-b1 4.0.0 war diff --git a/pay-java-fuiou/pom.xml b/pay-java-fuiou/pom.xml index 6a834c7..52f09fc 100644 --- a/pay-java-fuiou/pom.xml +++ b/pay-java-fuiou/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.4-SNAPSHOT + 2.13.3-b1 4.0.0 pay-java-fuiou diff --git a/pay-java-payoneer/pom.xml b/pay-java-payoneer/pom.xml index 9df1ded..d7e29fa 100644 --- a/pay-java-payoneer/pom.xml +++ b/pay-java-payoneer/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.4-SNAPSHOT + 2.13.3-b1 4.0.0 pay-java-payoneer diff --git a/pay-java-paypal/pom.xml b/pay-java-paypal/pom.xml index 0abbb06..3886fb3 100644 --- a/pay-java-paypal/pom.xml +++ b/pay-java-paypal/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.4-SNAPSHOT + 2.13.3-b1 4.0.0 diff --git a/pay-java-union/pom.xml b/pay-java-union/pom.xml index 6c539c1..a98787c 100644 --- a/pay-java-union/pom.xml +++ b/pay-java-union/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.4-SNAPSHOT + 2.13.3-b1 4.0.0 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 d1ef681..6e576fb 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 @@ -580,7 +580,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); } diff --git a/pay-java-union/src/test/java/PayTest.java b/pay-java-union/src/test/java/PayTest.java index 93ef3d5..659ea4c 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", "摘要", BigDecimal.valueOf(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-wx-youdian/pom.xml b/pay-java-wx-youdian/pom.xml index 2081227..c0db493 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.4-SNAPSHOT + 2.13.3-b1 4.0.0 pay-java-wx-youdian diff --git a/pay-java-wx/pom.xml b/pay-java-wx/pom.xml index dfe0e5d..b9d7a20 100644 --- a/pay-java-wx/pom.xml +++ b/pay-java-wx/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.4-SNAPSHOT + 2.13.3-b1 4.0.0 pay-java-wx diff --git a/pay-java-yiji/pom.xml b/pay-java-yiji/pom.xml index 0a3ac4e..a03c038 100644 --- a/pay-java-yiji/pom.xml +++ b/pay-java-yiji/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.4-SNAPSHOT + 2.13.3-b1 4.0.0 diff --git a/pom.xml b/pom.xml index fc3322b..cfa1e34 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.egzosn pay-java-parent pom - 2.13.4-SNAPSHOT + 2.13.3-b1 Pay Java - Parent Pay Java Parent @@ -58,13 +58,13 @@ pay-java-paypal pay-java-yiji pay-java-baidu - pay-java-demo + - 2.13.4-SNAPSHOT + 2.13.3-b1 4.5.4 1.2.17 1.2.73 -- Gitee From 8b3e1f67917cb3e02beac7ba5cf39efbe8007040 Mon Sep 17 00:00:00 2001 From: egzosn Date: Mon, 28 Dec 2020 18:45:46 +0800 Subject: [PATCH 025/165] =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=AE=9D=E5=85=AC?= =?UTF-8?q?=E9=92=A5=E8=AF=81=E4=B9=A6=E6=A1=88=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../egzosn/pay/common/bean/RefundResult.java | 1 - .../pay/paypal/api/PayPalConfigStorage.java | 1 + .../egzosn/pay/paypal/bean/order/Amount.java | 3 + .../pay/paypal/bean/order/CartBase.java | 45 +-- .../egzosn/pay/paypal/bean/order/Error.java | 279 ++++++++---------- .../pay/paypal/bean/order/ErrorDetails.java | 26 +- .../pay/paypal/bean/order/FmfDetails.java | 40 +-- .../egzosn/pay/paypal/bean/order/Order.java | 131 ++------ .../egzosn/pay/paypal/bean/order/Payee.java | 72 +---- .../egzosn/pay/paypal/bean/order/Phone.java | 40 +-- .../pay/paypal/bean/order/RedirectUrls.java | 26 +- .../egzosn/pay/paypal/bean/order/Refund.java | 121 ++------ pom.xml | 2 +- 14 files changed, 224 insertions(+), 565 deletions(-) diff --git a/README.md b/README.md index d904512..8675b94 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ com.egzosn {module-name} - 2.13.3 + 2.13.3-b1 ``` 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 index 1304419..db31a45 100644 --- 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 @@ -7,7 +7,6 @@ import java.util.Map; /** * 退款结果 *

- * 这里继承Map为兼容方案,后续版本进行删除 * * @author Egan *

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 ccffe2e..3b29d41 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
@@ -59,6 +59,7 @@ public class PayPalConfigStorage extends BasePayConfigStorage {
 
     /**
      * 获取取消页面的url
+     * @return 取消页面的url
      */
     public String getCancelUrl() {
         return getNotifyUrl();
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 34d645b..9d1ebb3 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 077aceb..060b114 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 3d9069f..8543046 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,121 @@ 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() {
+    }
+
+    /**
+     * 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;
+    }
+
+
+    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 d054eb1..31c6486 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 2ef402f..0341cc4 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/Order.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/bean/order/Order.java
index a9ecd54..afb681a 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 9c09ebd..10e3b8f 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 b820b1b..8276fe3 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 a98b498..05ea842 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 fa8fefe..72bc775 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/pom.xml b/pom.xml index cfa1e34..2c2105e 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ pay-java-paypal pay-java-yiji pay-java-baidu - + pay-java-demo -- Gitee From 11bfc9af63715de7f55877b181624c14f4a5e5cc Mon Sep 17 00:00:00 2001 From: egzosn Date: Mon, 28 Dec 2020 20:07:28 +0800 Subject: [PATCH 026/165] =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=AE=9D=E5=85=AC?= =?UTF-8?q?=E9=92=A5=E8=AF=81=E4=B9=A6=E6=A1=88=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pay-java-ali/pom.xml | 2 +- pay-java-baidu/pom.xml | 2 +- pay-java-common/pom.xml | 2 +- pay-java-demo/pom.xml | 2 +- pay-java-fuiou/pom.xml | 2 +- pay-java-payoneer/pom.xml | 2 +- pay-java-paypal/pom.xml | 2 +- pay-java-union/pom.xml | 2 +- pay-java-wx-youdian/pom.xml | 2 +- pay-java-wx/pom.xml | 2 +- pay-java-yiji/pom.xml | 2 +- pom.xml | 4 ++-- 12 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index f014c96..f31a779 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-b1 + 2.13.4-SNAPSHOT 4.0.0 pay-java-ali diff --git a/pay-java-baidu/pom.xml b/pay-java-baidu/pom.xml index d336160..228a3d0 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-b1 + 2.13.4-SNAPSHOT 4.0.0 pay-java-baidu diff --git a/pay-java-common/pom.xml b/pay-java-common/pom.xml index 2ca60b8..e0ad8d1 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-b1 + 2.13.4-SNAPSHOT 4.0.0 jar diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index 6858975..7c0de2a 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-b1 + 2.13.4-SNAPSHOT 4.0.0 war diff --git a/pay-java-fuiou/pom.xml b/pay-java-fuiou/pom.xml index 52f09fc..6a834c7 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-b1 + 2.13.4-SNAPSHOT 4.0.0 pay-java-fuiou diff --git a/pay-java-payoneer/pom.xml b/pay-java-payoneer/pom.xml index d7e29fa..9df1ded 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-b1 + 2.13.4-SNAPSHOT 4.0.0 pay-java-payoneer diff --git a/pay-java-paypal/pom.xml b/pay-java-paypal/pom.xml index 3886fb3..0abbb06 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-b1 + 2.13.4-SNAPSHOT 4.0.0 diff --git a/pay-java-union/pom.xml b/pay-java-union/pom.xml index a98787c..6c539c1 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-b1 + 2.13.4-SNAPSHOT 4.0.0 diff --git a/pay-java-wx-youdian/pom.xml b/pay-java-wx-youdian/pom.xml index c0db493..2081227 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-b1 + 2.13.4-SNAPSHOT 4.0.0 pay-java-wx-youdian diff --git a/pay-java-wx/pom.xml b/pay-java-wx/pom.xml index b9d7a20..dfe0e5d 100644 --- a/pay-java-wx/pom.xml +++ b/pay-java-wx/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.3-b1 + 2.13.4-SNAPSHOT 4.0.0 pay-java-wx diff --git a/pay-java-yiji/pom.xml b/pay-java-yiji/pom.xml index a03c038..0a3ac4e 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-b1 + 2.13.4-SNAPSHOT 4.0.0 diff --git a/pom.xml b/pom.xml index 2c2105e..fc3322b 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.egzosn pay-java-parent pom - 2.13.3-b1 + 2.13.4-SNAPSHOT Pay Java - Parent Pay Java Parent @@ -64,7 +64,7 @@ - 2.13.3-b1 + 2.13.4-SNAPSHOT 4.5.4 1.2.17 1.2.73 -- Gitee From 0f9439fd4eb1535d0303d916e0a0a88f7d2ce9c4 Mon Sep 17 00:00:00 2001 From: egzosn Date: Thu, 31 Dec 2020 17:10:09 +0800 Subject: [PATCH 027/165] xcx --- pay-java-demo/src/main/webapp/xcx.png | Bin 0 -> 6950538 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 pay-java-demo/src/main/webapp/xcx.png 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 GIT binary patch literal 6950538 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 Date: Sun, 17 Jan 2021 22:38:11 +0800 Subject: [PATCH 028/165] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=B7=B2=E5=BA=9F?= =?UTF-8?q?=E5=BC=83=E7=9A=84=E5=AD=97=E6=AE=B5=E4=B8=8E=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pay/ali/api/AliPayConfigStorage.java | 26 +- .../com/egzosn/pay/ali/api/AliPayService.java | 11 +- .../pay/baidu/api/BaiduPayConfigStorage.java | 38 ++- .../egzosn/pay/baidu/api/BaiduPayService.java | 28 +- .../pay/common/api/BasePayConfigStorage.java | 32 +-- .../egzosn/pay/common/api/BasePayService.java | 34 +-- .../pay/common/api/PayConfigStorage.java | 23 +- .../pay/common/api/PayMessageRouter.java | 28 +- .../pay/common/api/PayMessageRouterRule.java | 80 ++---- .../com/egzosn/pay/common/api/PayService.java | 105 +++---- .../com/egzosn/pay/common/bean/MsgType.java | 30 -- .../egzosn/pay/common/bean/PayMessage.java | 45 ++- .../egzosn/pay/common/bean/PayOutMessage.java | 24 +- .../bean/outbuilder/PayJsonOutMessage.java | 7 +- .../bean/outbuilder/PayTextOutMessage.java | 2 - .../bean/outbuilder/PayXmlOutMessage.java | 2 - .../egzosn/pay/common/http/UriVariables.java | 75 ++--- .../pay/demo/controller/AliPayController.java | 90 +++--- .../pay/demo/controller/PayController.java | 101 ++++--- .../demo/controller/PayPalPayController.java | 49 ++-- .../controller/PayoneerPayController.java | 61 +++-- .../pay/demo/dao/ApyAccountRepository.java | 22 +- .../egzosn/pay/demo/entity/ApyAccount.java | 17 +- .../com/egzosn/pay/demo/entity/PayType.java | 26 +- .../egzosn/pay/demo/service/PayResponse.java | 32 ++- .../pay/fuiou/api/FuiouPayConfigStorage.java | 24 +- .../egzosn/pay/fuiou/api/FuiouPayService.java | 155 ++++++----- .../payoneer/api/PayoneerConfigStorage.java | 46 +++- .../pay/payoneer/api/PayoneerPayService.java | 80 +++--- .../pay/paypal/api/PayPalConfigStorage.java | 50 +++- .../pay/paypal/api/PayPalPayService.java | 147 +++++----- .../pay/union/api/UnionPayConfigStorage.java | 32 ++- .../egzosn/pay/union/api/UnionPayService.java | 87 +++--- .../api/WxYouDianPayConfigStorage.java | 11 + .../wx/youdian/api/WxYouDianPayService.java | 257 +++++++++--------- .../egzosn/pay/wx/api/WxPayConfigStorage.java | 69 +++-- .../com/egzosn/pay/wx/api/WxPayService.java | 124 ++++----- .../pay/yiji/api/YiJiPayConfigStorage.java | 25 +- .../egzosn/pay/yiji/api/YiJiPayService.java | 79 ++---- 39 files changed, 1095 insertions(+), 1079 deletions(-) delete mode 100644 pay-java-common/src/main/java/com/egzosn/pay/common/bean/MsgType.java 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 d9d907d..2acb5a6 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 @@ -28,7 +28,7 @@ public class AliPayConfigStorage extends BasePayConfigStorage { /** * 商户应用id */ - private String appid; + private String appId; /** * 商户签约拿到的pid,partner_id的简称,合作伙伴身份等同于 partner */ @@ -73,15 +73,30 @@ public class AliPayConfigStorage extends BasePayConfigStorage { this.appAuthToken = appAuthToken; } - public void setAppid(String appid) { - this.appid = appid; + 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() { @@ -152,7 +167,8 @@ public class AliPayConfigStorage extends BasePayConfigStorage { InputStream aliPayCertStream = certStoreType.getInputStream(aliPayCert); InputStream aliPayRootCertStream = certStoreType.getInputStream(aliPayRootCert)) { this.certEnvironment = new CertEnvironment(merchantCertStream, aliPayCertStream, aliPayRootCertStream); - } catch (IOException e) { + } + 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 31c7110..4e15880 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 @@ -123,7 +123,6 @@ public class AliPayService extends BasePayService { * @param sign 比对的签名结果 * @return 生成的签名结果 */ - @Override public boolean signVerify(Map params, String sign) { if (params instanceof JSONObject) { @@ -171,7 +170,6 @@ public class AliPayService extends BasePayService { * @param id 业务id, 数据的真实性. * @return true通过 */ - @Override public boolean verifySource(String id) { return true; } @@ -281,10 +279,11 @@ public class AliPayService extends BasePayService { switch ((AliTransactionType) order.getTransactionType()) { case SWEEPPAY: bizContent.put("qr_code_timeout_express", DateUtils.minutesRemaining(order.getExpirationTime()) + "m"); + break; case PAGE: case WAP: case APP: - bizContent.put("time_expire", DateUtils.formatDate(order.getExpirationTime(), "yyyy-MM-dd HH:mm")); + bizContent.put("time_expire", DateUtils.formatDate(order.getExpirationTime(), DateUtils.YYYY_MM_DD_HH_MM_SS)); break; default: } @@ -526,7 +525,9 @@ public class AliPayService extends BasePayService { parameters.put(BIZ_CONTENT, JSON.toJSONString(bizContent)); //设置签名 setSign(parameters); - final AliRefundResult refundResult = AliRefundResult.create(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; } @@ -587,8 +588,6 @@ public class AliPayService extends BasePayService { * @param transactionType 交易类型 * @return 返回支付方对应接口的结果 */ - - @Override public Map secondaryInterface(Object tradeNoOrBillDate, String outTradeNoBillType, TransactionType transactionType) { if (transactionType == AliTransactionType.REFUND) { 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 f1496e7..7786b80 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,61 @@ package com.egzosn.pay.baidu.api; import com.egzosn.pay.common.api.BasePayConfigStorage; public class BaiduPayConfigStorage extends BasePayConfigStorage { - private String appid; + private String appId; private String dealId; - + @Override + @Deprecated public String getAppid() { - return this.appid; + return this.appId; } - + @Override public String getPid() { return getDealId(); } - + @Override public String getSeller() { throw new UnsupportedOperationException("不支持"); } - + public String getDealId() { return dealId; } - + public void setDealId(String dealId) { this.dealId = dealId; } - + public String getAppKey() { return this.getKeyPrivate(); } - + public void setAppKey(String appKey) { setKeyPrivate(appKey); } - + @Override public String getKeyPublic() { return super.getKeyPrivate(); } - + @Override public void setKeyPublic(String keyPublic) { super.setKeyPublic(keyPublic); } - - public void setAppid(String appid) { - this.appid = appid; + + public void setAppid(String appId) { + this.appId = appId; + } + + @Override + public String getAppId() { + return appId; + } + + 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 febafbf..8c2095b 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,14 +1,25 @@ package com.egzosn.pay.baidu.api; +import java.math.BigDecimal; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; 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.BaseRefundResult; +import com.egzosn.pay.common.bean.CurType; +import com.egzosn.pay.common.bean.MethodType; +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; @@ -16,11 +27,6 @@ import com.egzosn.pay.common.util.Util; import com.egzosn.pay.common.util.sign.SignUtils; import com.egzosn.pay.common.util.str.StringUtils; -import java.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"; @@ -63,7 +69,7 @@ public class BaiduPayService extends BasePayService { if (!RESPONSE_SUCCESS.equals(params.get(RESPONSE_STATUS))) { return false; } - return signVerify(params, String.valueOf(params.get(RSA_SIGN))) && verifySource(String.valueOf(params.get(TP_ORDER_ID))); + return signVerify(params, String.valueOf(params.get(RSA_SIGN))); } /** @@ -73,7 +79,6 @@ public class BaiduPayService extends BasePayService { * @param sign 签名原文 * @return 结果 */ - @Override public boolean signVerify(Map params, String sign) { String rsaSign = String.valueOf(params.get(RSA_SIGN)); String targetRsaSign = getRsaSign(params, RSA_SIGN); @@ -81,10 +86,6 @@ public class BaiduPayService extends BasePayService { return StringUtils.equals(rsaSign, targetRsaSign); } - @Override - public boolean verifySource(String id) { - return true; - } /** * 返回创建的订单信息 @@ -439,7 +440,6 @@ public class BaiduPayService extends BasePayService { * @param transactionType 交易类型 * @return 结果 */ - @Override public Map secondaryInterface(Object orderId, String siteId, TransactionType transactionType) { 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 24b7af8..b8b2b17 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 @@ -4,8 +4,6 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.Lock; -import com.egzosn.pay.common.bean.MsgType; - /** * 支付基础配置存储 * @@ -51,12 +49,6 @@ public abstract class BasePayConfigStorage implements PayConfigStorage { */ private String payType; - /** - * 消息来源类型 - */ - @Deprecated - private MsgType msgType; - /** * 访问令牌 每次请求其他方法都要传入的值 @@ -158,37 +150,36 @@ public abstract class BasePayConfigStorage implements PayConfigStorage { this.payType = payType; } - @Override - public MsgType getMsgType() { - return msgType; - } - - public void setMsgType(MsgType msgType) { - this.msgType = msgType; - } /** * 获取访问令牌 - * @return 访问令牌 + * + * @return 访问令牌 */ public String getAccessToken() { return this.accessToken; } + /** * 获取access token锁 + * * @return access token锁 */ public Lock getAccessTokenLock() { return this.accessTokenLock; } + /** * 强制将access token过期掉 - * @return 过期时间 + * + * @return 过期时间 */ public long getExpiresTime() { return expiresTime; } + /** * 访问令牌是否过期 + * * @return true过期 */ public boolean isAccessTokenExpired() { @@ -251,7 +242,7 @@ public abstract class BasePayConfigStorage implements PayConfigStorage { @Override public Map getAttrs() { - if (null == attr){ + if (null == attr) { attr = new HashMap<>(); } return attr; @@ -265,7 +256,8 @@ public abstract class BasePayConfigStorage implements PayConfigStorage { /** * 添加配置信息 - * @param key key + * + * @param key key * @param value 值 */ public void addAttr(String key, Object 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 605ed7d..2716266 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 @@ -21,9 +21,7 @@ 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.exception.PayErrorException; import com.egzosn.pay.common.http.HttpConfigStorage; import com.egzosn.pay.common.http.HttpRequestTemplate; import com.egzosn.pay.common.util.MatrixToImageWriter; @@ -112,7 +110,8 @@ 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) { + } + catch (UnsupportedEncodingException e) { LOG.error(e); } @@ -154,10 +153,12 @@ public abstract class BasePayService implements Pay Map orderInfo = orderInfo(order); return buildRequest(orderInfo, MethodType.POST); } + /** * app支付 + * * @param order 订单信息 - * @param 预订单类型 + * @param 预订单类型 * @return 对应app所需参数信息 */ @Override @@ -173,7 +174,7 @@ public abstract class BasePayService implements Pay */ @Override public BufferedImage genQrPay(O order) { - return MatrixToImageWriter.writeInfoToJpgBuff(getQrPay(order)); + return MatrixToImageWriter.writeInfoToJpgBuff(getQrPay(order)); } /** @@ -199,7 +200,8 @@ public abstract class BasePayService implements Pay if (valueStr.equals(new String(valueStr.getBytes("iso8859-1"), "iso8859-1"))) { valueStr = new String(valueStr.getBytes("iso8859-1"), payConfigStorage.getInputCharset()); } - } catch (UnsupportedEncodingException e) { + } + catch (UnsupportedEncodingException e) { LOG.error(e); } } @@ -265,7 +267,6 @@ public abstract class BasePayService implements Pay } - /** * 申请退款接口 * @@ -281,7 +282,6 @@ public abstract class BasePayService implements Pay } - /** * 查询退款 * @@ -309,19 +309,6 @@ public abstract class BasePayService implements Pay 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)); - } - /** * 转账 * @@ -450,12 +437,13 @@ public abstract class BasePayService implements Pay /** * 预订单回调处理器,用于订单信息的扩展 * 签名之前使用 - * 如果需要进行扩展请重写该方法即可 + * 如果需要进行扩展请重写该方法即可 + * * @param orderInfo 预订单信息 * @param orderInfo 订单信息 * @return 处理后订单信息 */ - public Map preOrderHandler(Map orderInfo, O payOrder){ + public Map preOrderHandler(Map orderInfo, O payOrder) { return orderInfo; } 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 3885601..b6bc600 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,7 +1,6 @@ package com.egzosn.pay.common.api; import com.egzosn.pay.common.bean.Attrs; -import com.egzosn.pay.common.bean.MsgType; /** * 支付客户端配置存储 @@ -23,11 +22,22 @@ public interface PayConfigStorage extends Attrs { /** * 应用id + * 纠正名称 * * @return 应用id + * @see #getAppId() */ + @Deprecated String getAppid(); + /** + * 应用id + * 纠正名称 + * + * @return 应用id + */ + String getAppId(); + /** * 合作商唯一标识 * @@ -100,17 +110,6 @@ public interface PayConfigStorage extends Attrs { */ String getPayType(); - /** - * 消息类型 - * - * @return "text" 或者 "xml",json - * @see #getMsgType - * @see MsgType - */ - @Deprecated - MsgType getMsgType(); - - /** * 应该是线程安全的 * 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 24e849b..2fe8825 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.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.egzosn.pay.common.bean.PayMessage; +import com.egzosn.pay.common.bean.PayOutMessage; +import com.egzosn.pay.common.util.LogExceptionHandler; + /** *
  * 支付消息路由器,通过代码化的配置,把来自支付的消息交给handler处理
@@ -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 97bd210..08477f5 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 0c1b6ac..7a66d42 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,26 @@
 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.MethodType;
+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
  *         
@@ -60,53 +66,35 @@ public interface PayService { */ boolean verify(Map params); - /** - * 签名校验 - * 后面版本废弃 - * @param params 参数集 - * @param sign 签名原文 - * @return 签名校验 true通过 - */ - @Deprecated - boolean signVerify(Map params, String sign); - - - /** - * 支付宝需要,微信是否也需要再次校验来源,进行订单查询 - * 校验数据来源 - * 后面版本废弃 - * @param id 业务id, 数据的真实性. - * @return true通过 - */ - @Deprecated - boolean verifySource(String id); /** * 返回创建的订单信息 * * @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,7 +106,6 @@ public interface PayService { String createSign(String content, String characterEncoding); - /** * 将请求参数或者请求流转化为 Map * @@ -161,27 +148,29 @@ 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); /** * 刷卡付,pos主动扫码付款(条码付) * 刷脸付 + * * @param order 发起支付的订单信息 - * @param 预订单类型 + * @param 预订单类型 * @return 返回支付结果 */ - Map microPay(O order); + Map microPay(O order); /** * 交易查询接口 @@ -245,7 +234,6 @@ public interface PayService { T cancel(String tradeNo, String outTradeNo, Callback callback); - /** * 申请退款接口 * @@ -265,7 +253,6 @@ public interface PayService { T refund(RefundOrder refundOrder, Callback callback); - /** * 查询退款 * @@ -305,32 +292,6 @@ public interface PayService { 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); - - /** * 转账 * @@ -409,6 +370,7 @@ public interface PayService { /** * 创建消息 + * * @param message 支付平台返回的消息 * @return 支付消息对象 */ @@ -417,12 +379,13 @@ public interface PayService { /** * 预订单回调处理器,用于订单信息的扩展 * 签名之前使用 - * 如果需要进行扩展请重写该方法即可 + * 如果需要进行扩展请重写该方法即可 + * * @param orderInfo 商户平台预订单信息 - * @param payOrder 订单信息 - * @param 预订单类型 + * @param payOrder 订单信息 + * @param 预订单类型 * @return 处理后订单信息 */ - Map preOrderHandler(Map orderInfo, O payOrder); + Map preOrderHandler(Map orderInfo, O payOrder); } 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 8806133..0000000 --- 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/PayMessage.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/PayMessage.java index 11c247a..888b787 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 @@ -7,6 +7,7 @@ import java.util.Map; /** * 支付回调消息 * 基础实现,具体可根据具体支付回调的消息去实现 + * * @author egan *
  *     email egzosn@gmail.com
@@ -15,8 +16,6 @@ import java.util.Map;
  */
 public class PayMessage implements Serializable {
     private Map payMessage = null;
-    @Deprecated
-    private String msgType;
     private String payType;
     private String transactionType;
     private String fromPay;
@@ -30,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;
     }
@@ -48,16 +45,6 @@ public class PayMessage implements Serializable {
         this.payMessage = payMessage;
     }
 
-    @Deprecated
-    public String getMsgType() {
-        return msgType;
-    }
-
-    @Deprecated
-    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/PayOutMessage.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/PayOutMessage.java
index 97f686f..2f50c10 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/outbuilder/PayJsonOutMessage.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/outbuilder/PayJsonOutMessage.java
index 576134b..bf5e49b 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 7d31196..dc99267 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 c4b96bd..576d1ba 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/http/UriVariables.java b/pay-java-common/src/main/java/com/egzosn/pay/common/http/UriVariables.java index f183b68..e7b6ae4 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,14 +1,13 @@ 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.URLEncoder; import java.util.List; import java.util.Map; -import java.util.Set; + +import com.alibaba.fastjson.JSONObject; +import com.egzosn.pay.common.bean.result.PayException; +import com.egzosn.pay.common.exception.PayErrorException; /** * URL表达式处理器 @@ -23,22 +22,22 @@ public class 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 +46,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 +78,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) { @@ -106,14 +105,15 @@ public class UriVariables { continue; } String value = os[i].toString().trim(); - valueStr += (i == len - 1) ? value : value + ","; + valueStr += (i == len - 1) ? value : value + ","; } builder.append(entry.getKey()).append("=").append(URLEncoder.encode(valueStr, "utf-8")).append("&"); continue; } - builder.append(entry.getKey()).append("=").append(URLEncoder.encode( entry.getValue().toString(), "utf-8")).append("&"); - } catch (UnsupportedEncodingException e) { + builder.append(entry.getKey()).append("=").append(URLEncoder.encode(entry.getValue().toString(), "utf-8")).append("&"); + } + catch (UnsupportedEncodingException e) { e.printStackTrace(); } } @@ -129,7 +129,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(); @@ -148,16 +148,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 = '}'; @@ -171,7 +174,8 @@ public class UriVariables { putKeyValueToMap(temp, isKey, key, map); temp.setLength(0); isKey = true; - } else { + } + else { temp.append(curChar); } } @@ -182,14 +186,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", "内容格式有误")); } 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 616636f..77a25b9 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,6 +2,22 @@ package com.egzosn.pay.demo.controller; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import javax.imageio.ImageIO; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + import com.egzosn.pay.ali.api.AliPayConfigStorage; import com.egzosn.pay.ali.api.AliPayService; import com.egzosn.pay.ali.bean.AliRefundResult; @@ -18,20 +34,6 @@ 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; /** @@ -48,22 +50,24 @@ public class AliPayController { private AliPayService service = null; @Resource private AutowireCapableBeanFactory spring; + /** * 设置普通公钥的方式 * 普通公钥方式与证书公钥方式为两者取其一的方式 - * @param aliPayConfigStorage 支付宝配置信息 * + * @param aliPayConfigStorage 支付宝配置信息 */ - private static void keyPublic(AliPayConfigStorage aliPayConfigStorage){ + private static void keyPublic(AliPayConfigStorage aliPayConfigStorage) { aliPayConfigStorage.setKeyPublic("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIgHnOn7LLILlKETd6BFRJ0GqgS2Y3mn1wMQmyh9zEyWlz5p1zrahRahbXAfCfSqshSNfqOmAQzSHRVjCqjsAw1jyqrXaPdKBmr90DIpIxmIyKXv4GGAkPyJ/6FTFY99uhpiq0qadD/uSzQsefWo0aTvP/65zi3eof7TcZ32oWpwIDAQAB"); } /** * 设置证书公钥信息 * 普通公钥方式与证书公钥方式为两者取其一的方式 + * * @param aliPayConfigStorage 支付宝配置信息 */ - private static void certKeyPublic(AliPayConfigStorage aliPayConfigStorage){ + private static void certKeyPublic(AliPayConfigStorage aliPayConfigStorage) { //设置为证书方式 aliPayConfigStorage.setCertSign(true); //设置证书存储方式,这里为路径 @@ -79,10 +83,10 @@ public class AliPayController { aliPayConfigStorage.setPid("2088102169916436"); aliPayConfigStorage.setAppid("2016080400165436"); //普通公钥方式与证书公钥方式为两者取其一的方式 -// keyPublic(aliPayConfigStorage); - certKeyPublic(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=="); - 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=="); + 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.RSA2.name()); @@ -97,7 +101,7 @@ public class AliPayController { httpConfigStorage.setMaxTotal(20); //默认的每个路由的最大连接数 httpConfigStorage.setDefaultMaxPerRoute(10); - service = new AliPayService(aliPayConfigStorage, httpConfigStorage); + service = new AliPayService(aliPayConfigStorage, httpConfigStorage); //增加支付回调消息拦截器 service.addPayMessageInterceptor(new AliPayMessageInterceptor()); //设置回调消息处理 @@ -105,16 +109,15 @@ 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 ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), AliTransactionType.PAGE); //WAP @@ -127,8 +130,6 @@ public class AliPayController { } - - /** * 获取支付预订单信息 * @@ -148,35 +149,39 @@ 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 ? BigDecimal.valueOf(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 ? BigDecimal.valueOf(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 authCode 授权码,条码等 + * @param price 金额 * @return 支付结果 */ @RequestMapping(value = "microPay") @@ -184,7 +189,7 @@ public class AliPayController { //获取对应的支付账户操作工具(可根据账户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.setAuthCode(authCode); @@ -204,15 +209,13 @@ public class AliPayController { /** * 支付回调地址 方式一 - * + *

* 方式二,{@link #payBack(HttpServletRequest)} 是属于简化方式, 试用与简单的业务场景 * - * * @param request 请求 - * * @return 返回对应的响应码 - * @see #payBack(HttpServletRequest) * @throws IOException IOException + * @see #payBack(HttpServletRequest) */ @Deprecated @RequestMapping(value = "payBackBefore.json") @@ -233,15 +236,15 @@ public class AliPayController { return service.getPayOutMessage("fail", "失败").toMessage(); } + /** * 支付回调地址 * * @param request 请求 - * * @return 返回对应的响应码 - * + *

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

* 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler} * @throws IOException IOException */ @@ -290,6 +293,7 @@ public class AliPayController { public Map close(QueryOrder order) { return service.close(order.getTradeNo(), order.getOutTradeNo()); } + /** * 交易c撤销接口 * @@ -339,7 +343,6 @@ public class AliPayController { * 转账 * * @param order 转账订单 - * * @return 对应的转账结果 */ @RequestMapping("transfer") @@ -363,7 +366,6 @@ public class AliPayController { * * @param outNo 商户转账订单号 * @param tradeNo 支付平台转账订单号 - * * @return 对应的转账订单 */ @RequestMapping("transferQuery") 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 8c4f2fc..7f1b916 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,37 @@ 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.MethodType; +import com.egzosn.pay.common.bean.PayMessage; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.PayOutMessage; +import com.egzosn.pay.common.bean.RefundOrder; +import com.egzosn.pay.common.bean.RefundResult; +import com.egzosn.pay.common.bean.TransferOrder; import com.egzosn.pay.common.http.UriVariables; import com.egzosn.pay.common.util.MatrixToImageWriter; import com.egzosn.pay.common.util.str.StringUtils; @@ -18,22 +42,6 @@ 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.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; /** * 发起支付入口 @@ -76,7 +84,7 @@ public class PayController { * 跳到支付页面 * 针对实时支付,即时付款 * - * @param request 请求 + * @param request 请求 * @param payId 账户id * @param transactionType 交易类型, 这个针对于每一个 支付类型的对应的几种交易方式 * @param bankType 针对刷卡支付,卡的类型,类型值 @@ -115,6 +123,7 @@ public class PayController { /** * 跳到支付页面 * 针对实时支付,即时付款 + * * @param request 请求 * @return 跳到支付页面 */ @@ -164,7 +173,7 @@ public class PayController { * * @param payId 支付账户id * @param transactionType 交易类型 - * @param price 金额 + * @param price 金额 * @return 支付预订单信息 */ @RequestMapping("app") @@ -188,7 +197,7 @@ 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); @@ -200,7 +209,7 @@ public class PayController { PayConfigStorage storage = payResponse.getService().getPayConfigStorage(); //校验 if (payResponse.getService().verify(params)) { - PayMessage message = new PayMessage(params, storage.getPayType(), storage.getMsgType().name()); + PayMessage message = new PayMessage(params, storage.getPayType()); //支付校验通过后的处理 payResponse.getRouter().route(message); } @@ -215,7 +224,6 @@ public class PayController { * @param payId 账户id * @param transactionType 交易类型, 这个针对于每一个 支付类型的对应的几种交易方式 * @param price 金额 - * * @return 二维码图像 * @throws IOException IOException */ @@ -228,10 +236,12 @@ public class PayController { 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 +250,9 @@ public class PayController { //获取对应的支付账户操作工具(可根据账户id) //获取对应的支付账户操作工具(可根据账户id) PayResponse payResponse = service.getPayResponse(payId); - return payResponse.getService().getQrPay( new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, System.currentTimeMillis() + "", PayType.valueOf(payResponse.getStorage().getPayType()).getTransactionType(transactionType))); + 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 +260,7 @@ public class PayController { * @param wxPayId 微信账户id * @param aliPayId 支付宝id * @param price 金额 - * @param request 请求 + * @param request 请求 * @return 二维码图像 * @throws IOException IOException */ @@ -279,7 +290,7 @@ public class PayController { * @param wxPayId 微信账户id * @param aliPayId 支付宝id * @param price 金额 - * @param request 请求 + * @param request 请求 * @return 支付宝与微信平台的判断 * @throws IOException IOException */ @@ -309,15 +320,13 @@ public class PayController { } - - /** * 支付回调地址 方式一 *

* 方式二,{@link #payBack(HttpServletRequest, Integer)} 是属于简化方式, 试用与简单的业务场景 * * @param request 请求 - * @param payId 账户id + * @param payId 账户id * @return 支付是否成功 * @throws IOException IOException */ @@ -361,14 +370,14 @@ 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 { @@ -417,6 +426,7 @@ public class PayController { /** * 申请退款接口 + * * @param payId 账户id * @param order 订单的请求体 * @return 返回支付方申请退款后的结果 @@ -426,7 +436,8 @@ public class PayController { 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); } /** @@ -457,22 +468,9 @@ public class PayController { } - /** - * 通用查询接口,根据 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); - } - - /** * 转账 + * * @param payId 账户id * @param order 转账订单 * @return 对应的转账结果 @@ -485,7 +483,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 bd4e344..a9b6a08 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,6 +1,18 @@ 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; @@ -10,17 +22,6 @@ 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; /** * 发起支付入口 @@ -39,13 +40,13 @@ 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"); 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); //请求连接池配置 @@ -76,11 +77,12 @@ 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; } + /** * 申请退款接口 * @@ -98,10 +100,13 @@ public class PayPalPayController { } + /* */ + /** * return url * PayPal确认付款调用的接口 * 用户确认付款后,paypal调用的这个方法执行付款 + * * @param request 请求 * @return 付款成功信息 * @throws IOException IOException @@ -118,17 +123,17 @@ public class PayPalPayController { return "failure"; } + /* */ + /** * 支付回调地址 * - * @param request 请求 - * + * @param request 请求 * @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} - * + * 业务处理在对应的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.json") public String payBack(HttpServletRequest request) throws IOException { 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 0fca69d..e6580b2 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,30 @@ 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.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 +41,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 +69,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 +82,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 +100,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 */ @@ -166,6 +174,7 @@ public class PayoneerPayController { public Map close(QueryOrder order) { return service.close(order.getTradeNo(), order.getOutTradeNo()); } + /** * 申请退款接口 * @@ -173,7 +182,7 @@ public class PayoneerPayController { * @return 返回支付方申请退款后的结果 */ @RequestMapping("refund") - public Map refund(RefundOrder order) { + public RefundResult refund(RefundOrder order) { return service.refund(order); } @@ -195,7 +204,6 @@ public class PayoneerPayController { * 转账 * * @param order 转账订单 - * * @return 对应的转账结果 */ @RequestMapping("transfer") @@ -213,7 +221,6 @@ public class PayoneerPayController { * * @param outNo 商户转账订单号 * @param tradeNo 支付平台转账订单号 - * * @return 对应的转账订单 */ @RequestMapping("transferQuery") 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 04aef65..58cf5e2 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,16 +3,16 @@ package com.egzosn.pay.demo.dao; -import com.egzosn.pay.common.bean.MsgType; +import java.util.HashMap; +import java.util.Map; + import com.egzosn.pay.common.util.sign.SignUtils; import com.egzosn.pay.demo.entity.ApyAccount; import com.egzosn.pay.demo.entity.PayType; -import java.util.HashMap; -import java.util.Map; - /** * 账户 + * * @author: egan * email egzosn@gmail.com * date 2016/11/18 1:21 @@ -21,12 +21,11 @@ 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"); @@ -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); @@ -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); @@ -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,7 +101,6 @@ 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); @@ -116,7 +110,6 @@ public class ApyAccountRepository { 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 2ac2dd6..ccc202b 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,12 +2,11 @@ package com.egzosn.pay.demo.entity; -import com.egzosn.pay.common.bean.MsgType; - //import javax.persistence.*; /** * 支付账户 + * * @author: egan * email egzosn@gmail.com * date 2016/11/18 0:36 @@ -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; } @@ -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; } @@ -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 85b0b91..7437aa4 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,7 +6,6 @@ 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.demo.service.handler.WxPayMessageHandler; @@ -30,7 +29,6 @@ import com.egzosn.pay.wx.youdian.api.WxYouDianPayService; import com.egzosn.pay.wx.youdian.bean.YoudianTransactionType; - /** * 支付类型 * @@ -41,7 +39,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 @@ -61,7 +59,6 @@ public enum PayType implements BasePayType { 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()); //请求连接池配置 @@ -82,7 +79,7 @@ public enum PayType implements BasePayType { } - },wxPay { + }, wxPay { @Override public PayService getPayService(ApyAccount apyAccount) { WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage(); @@ -95,7 +92,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()); @@ -124,7 +120,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 +131,10 @@ public enum PayType implements BasePayType { // wxPayConfigStorage.setReturnUrl(apyAccount.getReturnUrl()); wxPayConfigStorage.setSignType(apyAccount.getSignType()); wxPayConfigStorage.setPayType(apyAccount.getPayType().toString()); - wxPayConfigStorage.setMsgType(apyAccount.getMsgType()); wxPayConfigStorage.setSeller(apyAccount.getSeller()); wxPayConfigStorage.setInputCharset(apyAccount.getInputCharset()); wxPayConfigStorage.setTest(apyAccount.isTest()); - return new WxYouDianPayService(wxPayConfigStorage); + return new WxYouDianPayService(wxPayConfigStorage); } /** @@ -153,8 +148,7 @@ public enum PayType implements BasePayType { return YoudianTransactionType.valueOf(transactionType); } - },fuiou{ - + }, fuiou { @Override public PayService getPayService(ApyAccount apyAccount) { FuiouPayConfigStorage fuiouPayConfigStorage = new FuiouPayConfigStorage(); @@ -164,7 +158,6 @@ public enum PayType implements BasePayType { fuiouPayConfigStorage.setReturnUrl(apyAccount.getReturnUrl()); fuiouPayConfigStorage.setSignType(apyAccount.getSignType()); fuiouPayConfigStorage.setPayType(apyAccount.getPayType().toString()); - fuiouPayConfigStorage.setMsgType(apyAccount.getMsgType()); fuiouPayConfigStorage.setInputCharset(apyAccount.getInputCharset()); fuiouPayConfigStorage.setTest(apyAccount.isTest()); return new FuiouPayService(fuiouPayConfigStorage); @@ -176,8 +169,7 @@ public enum PayType implements BasePayType { } - },unionPay{ - + }, unionPay { @Override public PayService getPayService(ApyAccount apyAccount) { UnionPayConfigStorage unionPayConfigStorage = new UnionPayConfigStorage(); @@ -201,7 +193,6 @@ 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); @@ -213,13 +204,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()); @@ -243,7 +233,7 @@ public enum PayType implements BasePayType { } - },payPal{ + }, payPal { @Override public PayService getPayService(ApyAccount apyAccount) { PayPalConfigStorage storage = new PayPalConfigStorage(); 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 1ebfbaf..1be58b9 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,22 +1,28 @@ package com.egzosn.pay.demo.service; +import javax.annotation.Resource; + +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; + import com.egzosn.pay.common.api.PayConfigStorage; import com.egzosn.pay.common.api.PayMessageHandler; import com.egzosn.pay.common.api.PayMessageRouter; import com.egzosn.pay.common.api.PayService; -import com.egzosn.pay.common.bean.MsgType; import com.egzosn.pay.common.http.HttpConfigStorage; import com.egzosn.pay.demo.entity.ApyAccount; import com.egzosn.pay.demo.entity.PayType; -import com.egzosn.pay.demo.service.handler.*; +import com.egzosn.pay.demo.service.handler.AliPayMessageHandler; +import com.egzosn.pay.demo.service.handler.FuiouPayMessageHandler; +import com.egzosn.pay.demo.service.handler.PayoneerMessageHandler; +import com.egzosn.pay.demo.service.handler.UnionPayMessageHandler; +import com.egzosn.pay.demo.service.handler.WxPayMessageHandler; +import com.egzosn.pay.demo.service.handler.YouDianPayMessageHandler; import com.egzosn.pay.demo.service.interceptor.AliPayMessageInterceptor; import com.egzosn.pay.demo.service.interceptor.YoudianPayMessageInterceptor; -import org.springframework.beans.factory.config.AutowireCapableBeanFactory; - -import javax.annotation.Resource; /** * 支付响应对象 + * * @author: egan * email egzosn@gmail.com * date 2016/11/18 0:34 @@ -38,6 +44,7 @@ public class PayResponse { /** * 初始化支付配置 + * * @param apyAccount 账户信息 * @see ApyAccount 对应表结构详情--》 /pay-java-demo/resources/apy_account.sql */ @@ -52,13 +59,14 @@ public class PayResponse { /** * 获取http配置,如果配置为null则为默认配置,无代理,无证书的请求方式。 - * 此处非必需 + * 此处非必需 + * * @param apyAccount 账户信息 * @return 请求配置 */ - public HttpConfigStorage getHttpConfigStorage(ApyAccount apyAccount){ + public HttpConfigStorage getHttpConfigStorage(ApyAccount apyAccount) { HttpConfigStorage httpConfigStorage = new HttpConfigStorage(); - /* 网路代理配置 根据需求进行设置*/ + /* 网路代理配置 根据需求进行设置*/ // //http代理地址 // httpConfigStorage.setHttpProxyHost("192.168.1.69"); // //代理端口 @@ -77,6 +85,7 @@ public class PayResponse { /** * 配置路由 + * * @param payId 指定账户id,用户多微信支付多支付宝支付 */ private void buildRouter(Integer payId) { @@ -84,7 +93,6 @@ public class PayResponse { router .rule() //消息类型 - .msgType(MsgType.text.name()) //支付账户事件类型 .payType(PayType.aliPay.name()) //拦截器 @@ -93,33 +101,27 @@ public class PayResponse { .handler(spring.getBean(AliPayMessageHandler.class)) .end() .rule() - .msgType(MsgType.xml.name()) .payType(PayType.wxPay.name()) .handler(autowire(new WxPayMessageHandler(payId))) .end() .rule() - .msgType(MsgType.json.name()) .payType(PayType.youdianPay.name()) .interceptor(new YoudianPayMessageInterceptor()) //拦截器 .handler(autowire(new YouDianPayMessageHandler(payId))) .end() .rule() - .msgType(MsgType.xml.name()) .payType(PayType.fuiou.name()) .handler(autowire(new FuiouPayMessageHandler(payId))) .end() .rule() - .msgType(MsgType.json.name()) .payType(PayType.unionPay.name()) .handler(autowire(new UnionPayMessageHandler(payId))) .end() .rule() - .msgType(MsgType.json.name()) .payType(PayType.payoneer.name()) .handler(autowire(new PayoneerMessageHandler(payId))) .end() .rule() - .msgType(MsgType.text.name()) .payType(PayType.payPal.name()) .handler(spring.getBean(AliPayMessageHandler.class)) .end() diff --git a/pay-java-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 2dd49a8..af8f37c 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 43f3348..418af4b 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,7 +1,21 @@ package com.egzosn.pay.fuiou.api; + +import java.io.InputStream; +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.MethodType; +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; @@ -12,15 +26,10 @@ 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
  * 
*/ @@ -38,7 +47,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支付(跨境支付) */ @@ -63,39 +72,46 @@ public class FuiouPayService extends BasePayService { /** * 获取对应的请求地址 + * * @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 成功 其他失败 */ @@ -108,7 +124,8 @@ public class FuiouPayService extends BasePayService { try { //返回参数校验 和 重新请求订单检查数据是否合法 return (signVerify(params, (String) params.remove("md5")) && verifySource((String) params.get("order_id"))); - } catch (PayErrorException e) { + } + catch (PayErrorException e) { e.printStackTrace(); } return false; @@ -117,11 +134,10 @@ 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()); @@ -135,14 +151,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())); 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")); @@ -151,12 +166,13 @@ 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); } @@ -168,6 +184,7 @@ public class FuiouPayService extends BasePayService { /** * 按序添加请求参数 + * * @param order 支付订单 * @return 返回支付请求参数集合 */ @@ -189,9 +206,10 @@ public class FuiouPayService extends BasePayService { //商户接受的支付结果后台通知地址 //非必填 parameters.put("back_notify_url", StringUtils.isBlank(payConfigStorage.getNotifyUrl()) ? "" : payConfigStorage.getNotifyUrl()); - if (null != order.getExpirationTime()){ + 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"); } @@ -210,6 +228,7 @@ public class FuiouPayService extends BasePayService { /** * 对内容进行加密 + * * @param content 需要加密的内容 * @param characterEncoding 字符编码 * @return 加密后的字符串 @@ -221,45 +240,48 @@ public class FuiouPayService extends BasePayService { /** * 将请求参数或者请求流转化为 Map + * * @param parameterMap 请求参数 * @param is 请求流 * @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"); + 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; } /** - * 将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 消息实体 */ @@ -268,9 +290,11 @@ public class FuiouPayService extends BasePayService { public PayOutMessage getPayOutMessage(String code, String message) { return PayOutMessage.TEXT().content(code.toLowerCase()).build(); } + /** * 获取成功输出消息,用户返回给支付端 * 主要用于拦截器中返回 + * * @param payMessage 支付回调消息 * @return 返回输出消息 */ @@ -281,6 +305,7 @@ public class FuiouPayService extends BasePayService { /** * 发送支付请求(form表单) + * * @param orderInfo 发起支付的订单信息 * @param method 请求方式 "post" "get", * @return form表单提交的html字符串 @@ -288,22 +313,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 不支持的操作异常 */ @@ -314,20 +341,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(); @@ -341,6 +369,7 @@ public class FuiouPayService extends BasePayService { /** * 交易查询接口 + * * @param tradeNo 支付平台订单号 * @param outTradeNo 商户单号 * @return 空 @@ -357,9 +386,9 @@ public class FuiouPayService extends BasePayService { } - /** * 交易关闭接口 + * * @param tradeNo 支付平台订单号 * @param outTradeNo 商户单号 * @return 空 @@ -370,13 +399,10 @@ public class FuiouPayService extends BasePayService { } - - - /** * 申请退款接口 * - * @param refundOrder 退款订单信息 + * @param refundOrder 退款订单信息 * @return 退款返回结果集 */ @Override @@ -413,6 +439,7 @@ public class FuiouPayService extends BasePayService { /** * 下载对账单 + * * @param billDate 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 * @param billType 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; * @return 空 @@ -422,19 +449,5 @@ public class FuiouPayService extends BasePayService { 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-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 a598c74..c93ec95 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,18 @@ 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
      */
-    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 617d79a..2b4645c 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,31 @@
 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.bean.BaseRefundResult;
+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.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,19 +36,13 @@ 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.math.BigDecimal;
-import java.util.*;
 
 /**
  * payoneer业务逻辑
  *
  * @author Actinia
  * @author egan
- *         
+ * 
  *         email: egzosn@gmail.com
  *         email: hayesfu@qq.com
  *         create 2018-01-19
@@ -61,20 +77,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 @@ -97,7 +114,6 @@ public class PayoneerPayService extends BasePayService im * 授权状态 * * @param payeeId 用户id - * * @return 返回是否认证 true 已认证 */ @Override @@ -110,7 +126,6 @@ public class PayoneerPayService extends BasePayService im * 获取授权用户信息 * * @param payeeId 用户id - * * @return 获取授权用户信息,包含用户状态,注册时间,联系人信息,地址信息等等 */ @Override @@ -123,7 +138,6 @@ public class PayoneerPayService extends BasePayService im * 回调校验 * * @param params 回调回来的参数集 - * * @return 签名校验 true通过 */ @Override @@ -137,28 +151,14 @@ public class PayoneerPayService extends BasePayService im return false; } - /** - * 签名校验 - * - * @param params 参数集 - * @param sign 签名原文 - * - * @return 签名校验 true通过 - */ - @Override - public boolean signVerify(Map params, String sign) { - return true; - } /** * 支付宝需要,微信是否也需要再次校验来源,进行订单查询 * 校验数据来源 * * @param outTradeNo 业务id, 数据的真实性. - * * @return true通过 */ - @Override public boolean verifySource(String outTradeNo) { Map data = query(null, outTradeNo); return "DONE".equals(data.get("status")); @@ -168,7 +168,6 @@ public class PayoneerPayService extends BasePayService im * 返回创建的订单信息 * * @param order 支付订单 - * * @return 订单信息 * @see PayOrder 支付订单信息 */ @@ -192,7 +191,6 @@ public class PayoneerPayService extends BasePayService im * * @param content 需要签名的内容 * @param characterEncoding 字符编码 - * * @return 签名 */ @Override @@ -206,7 +204,6 @@ public class PayoneerPayService extends BasePayService im * * @param code 状态 * @param message 消息 - * * @return 返回输出消息 */ @Override @@ -219,7 +216,6 @@ public class PayoneerPayService extends BasePayService im * 主要用于拦截器中返回 * * @param payMessage 支付回调消息 - * * @return 返回输出消息 */ @Override @@ -232,7 +228,6 @@ public class PayoneerPayService extends BasePayService im * * @param orderInfo 发起支付的订单信息 * @param method 请求方式 "post" "get", - * * @return 获取输出消息,用户返回给支付端, 针对于web端 * @see MethodType 请求类型 */ @@ -245,7 +240,6 @@ public class PayoneerPayService extends BasePayService im * 获取输出二维码,用户返回给支付端, * * @param order 发起支付的订单信息 - * * @return 返回图片信息,支付时需要的 */ @Override @@ -257,7 +251,6 @@ public class PayoneerPayService extends BasePayService im * 刷卡付,pos主动扫码付款(条码付) * * @param order 发起支付的订单信息 - * * @return 返回支付结果 */ @Override @@ -271,7 +264,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申请收款失败,原因:未有返回值")); } /** @@ -279,7 +272,6 @@ public class PayoneerPayService extends BasePayService im * * @param tradeNo 支付平台订单号 * @param outTradeNo 商户单号 - * * @return 返回查询回来的结果集,支付方原值返回 */ @Override @@ -293,7 +285,6 @@ public class PayoneerPayService extends BasePayService im * * @param tradeNo 支付平台订单号 * @param outTradeNo 商户单号 - * * @return 返回支付方交易关闭后的结果 */ @Override @@ -314,13 +305,10 @@ public class PayoneerPayService extends BasePayService im } - - /** * 申请退款接口 * * @param refundOrder 退款订单信息 - * * @return 返回支付方申请退款后的结果 */ @Override @@ -390,7 +378,6 @@ public class PayoneerPayService extends BasePayService im * * @param billDate 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 * @param billType 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; - * * @return 返回支付方下载对账单的结果 */ @Override @@ -404,18 +391,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; } @@ -423,7 +409,6 @@ public class PayoneerPayService extends BasePayService im * 转账 * * @param order 转账订单 - * * @return 对应的转账结果 */ @Override @@ -449,7 +434,6 @@ public class PayoneerPayService extends BasePayService im * * @param outNo 商户转账订单号 * @param tradeNo 支付平台转账订单号 - * * @return 对应的转账订单 */ @Override 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 3b29d41..7c267dc 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 @@ -8,37 +8,58 @@ 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; @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(); } @@ -50,6 +71,10 @@ public class PayPalConfigStorage extends BasePayConfigStorage { /** * 设置取消页面的url + *

+     * 注意:这里不是异步回调的通知
+     * IPN 地址设置的路径:https://developer.paypal.com/developer/ipnSimulator/
+     * 
* * @param cancelUrl 取消页面的url */ @@ -59,6 +84,11 @@ public class PayPalConfigStorage extends BasePayConfigStorage { /** * 获取取消页面的url + *
+     * 注意:这里不是异步回调的通知
+     * IPN 地址设置的路径:https://developer.paypal.com/developer/ipnSimulator/
+     * 
+ * * @return 取消页面的url */ public String getCancelUrl() { 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 b1447ed..e80c8a0 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,34 @@ 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.BaseRefundResult; +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.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 +36,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 +64,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 +78,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,21 +109,23 @@ 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(); @@ -108,28 +135,19 @@ public class PayPalPayService extends BasePayService{ @Override public boolean verify(Map params) { - HttpStringEntity httpEntity = new HttpStringEntity("{\"payer_id\":\""+(String)params.get("PayerID")+"\"}", ContentType.APPLICATION_JSON); + 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 "approved".equals(resp.getString("state")); - @Override - public boolean signVerify(Map params, String sign) { - return true; - } - - @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 +155,7 @@ public class PayPalPayService extends BasePayService{ return new HttpHeader(headers); } + /** * 返回创建的订单信息 * @@ -146,21 +165,22 @@ 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); @@ -181,11 +201,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 +222,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 +242,7 @@ public class PayPalPayService extends BasePayService{ public Map microPay(PayOrder order) { return null; } + /** * 交易查询接口 * @@ -241,20 +262,19 @@ public class PayPalPayService extends BasePayService{ } - /** * 申请退款接口 * - * @param refundOrder 退款订单信息 + * @param refundOrder 退款订单信息 * @return 返回支付方申请退款后的结果 */ @Override public RefundResult refund(RefundOrder refundOrder) { - JSONObject request = new JSONObject(); + 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,7 +286,7 @@ 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()); + JSONObject resp = getHttpRequestTemplate().postForObject(getReqUrl(PayPalTransactionType.REFUND), httpEntity, JSONObject.class, refundOrder.getTradeNo()); return new BaseRefundResult(resp) { @Override public String getCode() { @@ -332,10 +352,5 @@ public class PayPalPayService extends BasePayService{ return Collections.emptyMap(); } - @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/api/UnionPayConfigStorage.java b/pay-java-union/src/main/java/com/egzosn/pay/union/api/UnionPayConfigStorage.java index 5497b07..5c2f2e6 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,16 @@ 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
  *         
@@ -64,17 +64,18 @@ public class UnionPayConfigStorage extends BasePayConfigStorage { * 设置私钥证书 * * @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; @@ -92,6 +93,7 @@ public class UnionPayConfigStorage extends BasePayConfigStorage { public void setAcpMiddleCert(String acpMiddleCert) { this.acpMiddleCert = acpMiddleCert; } + /** * 设置中级证书 * @@ -109,6 +111,7 @@ public class UnionPayConfigStorage extends BasePayConfigStorage { public void setAcpRootCert(String acpRootCert) { this.acpRootCert = acpRootCert; } + /** * 设置根证书 * @@ -125,6 +128,7 @@ public class UnionPayConfigStorage extends BasePayConfigStorage { public String getAcpRootCert() { return (String) acpRootCert; } + public InputStream getAcpMiddleCertInputStream() throws IOException { return certStoreType.getInputStream(acpMiddleCert); } @@ -135,6 +139,7 @@ public class UnionPayConfigStorage extends BasePayConfigStorage { /** * 获取私钥证书密码 + * * @return 私钥证书密码 */ public String getKeyPrivateCertPwd() { @@ -144,11 +149,23 @@ public class UnionPayConfigStorage extends BasePayConfigStorage { 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() @@ -214,6 +231,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 6e576fb..5a68c8e 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,37 @@ 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.MethodType; +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; @@ -20,19 +49,6 @@ 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 *
@@ -95,7 +111,8 @@ public class UnionPayService extends BasePayService {
             certDescriptor.initPrivateSignCert(payConfigStorage.getKeyPrivateCertInputStream(), payConfigStorage.getKeyPrivateCertPwd(), "PKCS12");
             certDescriptor.initPublicCert(payConfigStorage.getAcpMiddleCertInputStream());
             certDescriptor.initRootCert(payConfigStorage.getAcpRootCertInputStream());
-        } catch (IOException e) {
+        }
+        catch (IOException e) {
             LOG.error(e);
         }
 
@@ -192,7 +209,6 @@ public class UnionPayService extends BasePayService {
      * @param sign   签名原文
      * @return 签名校验 true通过
      */
-    @Override
     public boolean signVerify(Map params, String sign) {
         SignUtils signUtils = SignUtils.valueOf(payConfigStorage.getSignType());
 
@@ -214,17 +230,6 @@ public class UnionPayService extends BasePayService {
         }
     }
 
-    /**
-     * 支付宝需要,微信是否也需要再次校验来源,进行订单查询
-     * 校验数据来源
-     *
-     * @param id 业务id, 数据的真实性.
-     * @return true通过
-     */
-    @Override
-    public boolean verifySource(String id) {
-        return false;
-    }
 
     /**
      * 订单超时时间。
@@ -371,11 +376,14 @@ 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) {
+        }
+        catch (CertificateExpiredException e) {
             LOG.error(e);
-        } catch (GeneralSecurityException e) {
+        }
+        catch (GeneralSecurityException e) {
             LOG.error(e);
         }
         return null;
@@ -403,7 +411,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()));
         }
 
@@ -456,7 +465,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;
@@ -675,19 +685,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-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 be789e9..8888db6 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
@@ -26,6 +26,17 @@ public class WxYouDianPayConfigStorage extends BasePayConfigStorage {
         return null;
     }
 
+    /**
+     * 应用id
+     * 纠正名称
+     *
+     * @return 应用id
+     */
+    @Override
+    public String getAppId() {
+        return null;
+    }
+
 
     @Override
     public String getPid() {
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 82f6c8a..2dcdfff 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,9 +1,26 @@
 package com.egzosn.pay.wx.youdian.api;
 
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Iterator;
+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.BaseRefundResult;
+import com.egzosn.pay.common.bean.CurType;
+import com.egzosn.pay.common.bean.MethodType;
+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;
@@ -14,15 +31,11 @@ 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 */ @@ -33,18 +46,21 @@ public class WxYouDianPayService extends BasePayService data = new TreeMap<>(); - data.put("username", payConfigStorage.getSeller()); - data.put("password", payConfigStorage.getKeyPrivate()); - String apbNonce = SignUtils.randomStr(); + public JSONObject login() throws PayErrorException { + TreeMap data = new TreeMap<>(); + data.put("username", payConfigStorage.getSeller()); + data.put("password", payConfigStorage.getKeyPrivate()); + String apbNonce = SignUtils.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(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; + } /** @@ -115,15 +132,18 @@ public class WxYouDianPayService extends BasePayService params) { - if (!"SUCCESS".equals(params.get("return_code"))){ + if (!"SUCCESS".equals(params.get("return_code"))) { LOG.debug(String.format("友店微信支付异常:return_code=%s,参数集=%s", params.get("return_code"), params)); return false; } - if(params.get("sign") == null) {LOG.debug("友店微信支付异常:签名为空!out_trade_no=" + params.get("out_trade_no"));} + 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) { + return signVerify(params, (String) params.get("sign")) && verifySource((String) params.get("out_trade_no")); + } + catch (PayErrorException e) { LOG.error(e.getMessage()); } return false; @@ -132,32 +152,32 @@ public class WxYouDianPayService extends BasePayService 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) { 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){ + } + catch (PayErrorException e) { + if (Integer.parseInt(e.getPayError().getErrorCode()) >= 400) { throw e; } return false; @@ -166,46 +186,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); + data = preOrderHandler(data, order); String apbNonce = SignUtils.randomStr(); String sign = createSign(SignUtils.parameterText(data, "") + apbNonce, payConfigStorage.getInputCharset()); data.put("PayMoney", data.remove("paymoney")); - String params = SignUtils.parameterText(data) + "&apb_nonce=" + apbNonce + "&sign=" + sign; + String params = SignUtils.parameterText(data) + "&apb_nonce=" + apbNonce + "&sign=" + sign; try { - JSONObject json = execute(getReqUrl(order.getTransactionType())+ "?" + params, MethodType.GET, null); + JSONObject json = execute(getReqUrl(order.getTransactionType()) + "?" + params, MethodType.GET, null); //友店比较特殊,需要在下完预订单后,自己存储 order_sn 对应 微信官方文档 out_trade_no - order.setOutTradeNo(json.getString("order_sn")); + order.setTradeNo(json.getString("order_sn")); return json; - } catch (PayErrorException e) { - throw e; + } + catch (PayErrorException e) { + throw e; } } - /** * 签名 - * @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); } /** @@ -270,7 +291,7 @@ public class WxYouDianPayService extends BasePayService getParameter2Map(Map parameterMap, InputStream is) { Map params = new TreeMap(); - for (Iterator iter = parameterMap.keySet().iterator(); iter.hasNext();) { + for (Iterator iter = parameterMap.keySet().iterator(); iter.hasNext(); ) { String name = (String) iter.next(); String[] values = parameterMap.get(name); String valueStr = ""; @@ -295,7 +316,6 @@ public class WxYouDianPayService extends BasePayService query(String tradeNo, String outTradeNo) { String apbNonce = SignUtils.randomStr(); TreeMap data = new TreeMap<>(); - data.put("access_token", payConfigStorage.getAccessToken()); + data.put("access_token", payConfigStorage.getAccessToken()); - if (StringUtils.isEmpty(tradeNo)){ + if (StringUtils.isEmpty(tradeNo)) { data.put("order_sn", outTradeNo); - }else { + } + else { data.put("order_sn", tradeNo); } 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); + String queryParam = SignUtils.parameterText(data) + "&apb_nonce=" + apbNonce + "&sign=" + sign; + JSONObject jsonObject = execute(getReqUrl(YoudianTransactionType.NATIVE_STATUS) + "?" + queryParam, MethodType.GET, null); return jsonObject; } @@ -384,8 +408,6 @@ public class WxYouDianPayService extends BasePayService data = new TreeMap<>(); - data.put("access_token", payConfigStorage.getAccessToken()); + data.put("access_token", payConfigStorage.getAccessToken()); - if (StringUtils.isEmpty(refundOrder.getOutTradeNo())){ + if (StringUtils.isEmpty(refundOrder.getOutTradeNo())) { data.put("order_sn", refundOrder.getOutTradeNo()); - }else { + } + 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.REFUND) + "?" + queryParam, MethodType.GET, null); - return new BaseRefundResult() { + String queryParam = SignUtils.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("errorcode"); @@ -471,26 +494,11 @@ public class WxYouDianPayService extends BasePayService downloadbill(Date billDate, String billType) { + public Map 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) { - return Collections.emptyMap(); - } - - - public WxYouDianPayService(WxYouDianPayConfigStorage payConfigStorage) { super(payConfigStorage); } @@ -501,11 +509,12 @@ public class WxYouDianPayService extends BasePayService * 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 84d3e4c..200279f 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 @@ -74,7 +74,7 @@ import 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 { /** @@ -132,7 +132,7 @@ public class WxPayService extends BasePayService implements @Override public boolean verify(Map params) { - if (null == params.get(SIGN) || !(SUCCESS.equals(params.get(RETURN_CODE)) && SUCCESS.equals(params.get(RESULT_CODE))) ) { + if (null == 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)); } @@ -140,26 +140,15 @@ public class WxPayService extends BasePayService implements } try { - return signVerify(params, (String) params.get(SIGN)) && verifySource((String) params.get(OUT_TRADE_NO)); - } catch (PayErrorException e) { + return signVerify(params, (String) params.get(SIGN)); + } + catch (PayErrorException e) { LOG.error(e); } return false; } - /** - * 微信是否也需要再次校验来源,进行订单查询 - * - * @param id 商户单号 - * @return true通过 - */ - @Override - public boolean verifySource(String id) { - return true; - } - - /** * 根据反馈回来的信息,生成签名结果 * @@ -167,7 +156,6 @@ public class WxPayService extends BasePayService implements * @param sign 比对的签名结果 * @return 生成的签名结果 */ - @Override public boolean signVerify(Map params, String sign) { return signVerify(params, sign, payConfigStorage.isTest()); } @@ -190,11 +178,11 @@ public class WxPayService extends BasePayService implements private Map getPublicParameters() { Map parameters = new TreeMap(); - parameters.put(APPID, payConfigStorage.getAppid()); + parameters.put(APPID, payConfigStorage.getAppId()); parameters.put(MCH_ID, payConfigStorage.getMchId()); //判断如果是服务商模式信息则加入 setParameters(parameters, "sub_mch_id", payConfigStorage.getSubMchId()); - setParameters(parameters, "sub_appid", payConfigStorage.getSubAppid()); + setParameters(parameters, "sub_appid", payConfigStorage.getSubAppId()); parameters.put(NONCE_STR, SignUtils.randomStr()); return parameters; @@ -289,7 +277,8 @@ public class WxPayService extends BasePayService implements 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("prepayid", result.get("prepay_id")); @@ -391,7 +380,8 @@ public class WxPayService extends BasePayService implements TreeMap map = new TreeMap(); try { return XML.inputStream2Map(is, map); - } catch (IOException e) { + } + catch (IOException e) { throw new PayErrorException(new PayException("IOException", e.getMessage())); } @@ -419,7 +409,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(); } @@ -471,7 +461,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); @@ -575,7 +566,7 @@ public class WxPayService extends BasePayService implements */ @Override public Map downloadbill(Date billDate, String billType) { - Map parameters = getDownloadBillParam(billDate, billType,false); + Map parameters = getDownloadBillParam(billDate, billType, false); //设置签名 setSign(parameters); String respStr = requestTemplate.postForObject(getReqUrl(WxTransactionType.DOWNLOADBILL), XML.getMap2Xml(parameters), String.class); @@ -595,25 +586,26 @@ public class WxPayService extends BasePayService implements * * @param billDate 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; * @param billType 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 - * @param path 账单返回格式 账单存储的基础路径,按月切割 + * @param path 账单返回格式 账单存储的基础路径,按月切割 * @return 返回支付方下载对账单的结果 */ @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); + writeToLocal(path + DateUtils.formatDate(new Date(), DateUtils.YYYYMM) + "/" + DateUtils.formatDate(new Date(), DateUtils.YYYYMMDDHHMMSS) + ".txt", inputStream); 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) { + } + catch (Exception e) { e.printStackTrace(); Map ret = new HashMap(3); ret.put(RETURN_CODE, FAIL); @@ -627,8 +619,8 @@ public class WxPayService extends BasePayService implements * GZIP解压缩 * * @param input 输入流账单 - * @return 解压后输入流 - * @throws IOException IOException + * @return 解压后输入流 + * @throws IOException IOException */ public static InputStream uncompress(InputStream input) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -644,6 +636,7 @@ public class WxPayService extends BasePayService implements /** * 将InputStream写入本地文件 + * * @param destination 写入本地目录 * @param inputStream 输入流 * @throws IOException IOException @@ -651,45 +644,46 @@ public class WxPayService extends BasePayService implements 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("创建失败"); - } - } - OutputStream out = new FileOutputStream(file); - int size = 0; - int len = 0; - byte[] buf = new byte[1024]; - while ((size = inputStream.read(buf)) != -1) { - len += size; - out.write(buf, 0, size); + // 判断字节大小 + 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("创建失败"); } - System.out.println("最终写入字节数大小:" + len); - inputStream.close(); - out.close(); } + OutputStream out = new FileOutputStream(file); + int size = 0; + int len = 0; + byte[] buf = new byte[1024]; + while ((size = inputStream.read(buf)) != -1) { + len += size; + out.write(buf, 0, size); + } + System.out.println("最终写入字节数大小:" + len); + inputStream.close(); + out.close(); + } } /** * 下载账单公共参数 + * * @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) { + 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){ + if (tarType) { parameters.put("tar_type", "GZIP"); } return parameters; @@ -702,7 +696,6 @@ public class WxPayService extends BasePayService implements * @param transactionType 交易类型 * @return 返回支付方对应接口的结果 */ - @Override public Map secondaryInterface(Object transactionIdOrBillDate, String outTradeNoBillType, TransactionType transactionType) { if (transactionType == WxTransactionType.REFUND) { @@ -735,12 +728,12 @@ public class WxPayService extends BasePayService implements * @param order 转账订单 *
      *
-     *              注意事项:
-     *              ◆ 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。
-     *              ◆ XML具有可扩展性,因此返回参数可能会有新增,而且顺序可能不完全遵循此文档规范,如果在解析回包的时候发生错误,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新回包字段,会更新到此API文档中。
-     *              ◆ 因为错误代码字段err_code的值后续可能会增加,所以商户如果遇到回包返回新的错误码,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新的错误码,会更新到此API文档中。
-     *              ◆ 错误代码描述字段err_code_des只供人工定位问题时做参考,系统实现时请不要依赖这个字段来做自动化处理。
-     *              
+ * 注意事项: + * ◆ 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。 + * ◆ XML具有可扩展性,因此返回参数可能会有新增,而且顺序可能不完全遵循此文档规范,如果在解析回包的时候发生错误,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新回包字段,会更新到此API文档中。 + * ◆ 因为错误代码字段err_code的值后续可能会增加,所以商户如果遇到回包返回新的错误码,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新的错误码,会更新到此API文档中。 + * ◆ 错误代码描述字段err_code_des只供人工定位问题时做参考,系统实现时请不要依赖这个字段来做自动化处理。 + *
* @return 对应的转账结果 */ @Override @@ -757,7 +750,8 @@ public class WxPayService extends BasePayService implements 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); @@ -842,7 +836,8 @@ public class WxPayService extends BasePayService implements public 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())); } } @@ -873,7 +868,8 @@ public class WxPayService extends BasePayService implements //现金红包,小程序红包默认传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 == redpackOrder.getTransferType()) { parameters.put("notify_way", "MINI_PROGRAM_JSAPI"); } 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 492ac1f..8484c27 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 ccc553a..1ef9daa 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,9 +1,24 @@
 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.BaseRefundResult;
+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.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;
@@ -11,20 +26,14 @@ 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.math.BigDecimal;
-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 { @@ -54,11 +63,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; } } @@ -72,7 +83,6 @@ public class YiJiPayService extends BasePayService { } - /** * 回调校验 * @@ -98,25 +108,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; - } - - /** * 生成并设置签名 * @@ -160,18 +157,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()); @@ -292,8 +289,6 @@ public class YiJiPayService extends BasePayService { } - - /** * 申请退款接口 * @@ -384,20 +379,6 @@ public class YiJiPayService 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(); - } - /** * 转账 这里外部进行调用{@link #buildRequest(Map, MethodType)} * @@ -409,10 +390,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()); -- Gitee From b14bdf0d980e5547a3fb39f2b1c017a8691bcb83 Mon Sep 17 00:00:00 2001 From: egzosn Date: Sun, 17 Jan 2021 22:38:42 +0800 Subject: [PATCH 029/165] paypal v2 --- .../controller/PayPalV2PayController.java | 147 ++++++ .../v2/api/PayPalOutMessageBuilder.java | 26 ++ .../pay/paypal/v2/api/PayPalPayService.java | 437 ++++++++++++++++++ .../pay/paypal/v2/bean/PayPalOrder.java | 189 ++++++++ .../paypal/v2/bean/PayPalRefundResult.java | 119 +++++ .../paypal/v2/bean/PayPalTransactionType.java | 74 +++ .../paypal/v2/bean/order/AddressPortable.java | 220 +++++++++ .../v2/bean/order/ApplicationContext.java | 147 ++++++ .../pay/paypal/v2/bean/order/Money.java | 37 ++ .../egzosn/pay/paypal/v2/bean/order/Name.java | 174 +++++++ .../paypal/v2/bean/order/OrderRequest.java | 43 ++ .../v2/bean/order/PurchaseUnitRequest.java | 141 ++++++ .../paypal/v2/bean/order/ShippingDetail.java | 59 +++ 13 files changed, 1813 insertions(+) create mode 100644 pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/PayPalV2PayController.java create mode 100644 pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/api/PayPalOutMessageBuilder.java create mode 100644 pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/api/PayPalPayService.java create mode 100644 pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/PayPalOrder.java create mode 100644 pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/PayPalRefundResult.java create mode 100644 pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/PayPalTransactionType.java create mode 100644 pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/AddressPortable.java create mode 100644 pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/ApplicationContext.java create mode 100644 pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/Money.java create mode 100644 pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/Name.java create mode 100644 pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/OrderRequest.java create mode 100644 pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/PurchaseUnitRequest.java create mode 100644 pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/order/ShippingDetail.java 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 0000000..125c215 --- /dev/null +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/PayPalV2PayController.java @@ -0,0 +1,147 @@ +package com.egzosn.pay.demo.controller; + + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; + +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.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; + +/** + * 发起支付入口 + * + * @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)); + return service.refund(order); + } + + + /** + * 注意:这里不是异步回调的通知 IPN 地址设置的路径:https://developer.paypal.com/developer/ipnSimulator/ + * PayPal确认付款调用的接口 + * 用户确认付款后,paypal调用的这个方法执行付款 + * + * @param request 请求 + * @return 付款成功信息 + * @throws IOException IOException + */ + @GetMapping(value = "payBackBefore.json") + public String payBackBefore(HttpServletRequest request) throws IOException { + try (InputStream is = request.getInputStream()) { + if (service.verify(service.getParameter2Map(request.getParameterMap(), is))) { + // TODO 这里进行成功后的订单业务处理 + // TODO 返回成功付款页面,这个到时候再做一个漂亮的页面显示,并使用前后端分离的模式 + return service.successPayOutMessage(null).toMessage(); + } + } + return "failure"; + } + + /* */ + + /** + * 支付回调地址 + * 注意:这里不是异步回调的通知 IPN 地址设置的路径:https://developer.paypal.com/developer/ipnSimulator/ + * + * @param request 请求 + * @return 结果 + * @throws IOException IOException + * 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link 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.json") + public String payBack(HttpServletRequest request) throws IOException { + //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() + return service.payBack(request.getParameterMap(), request.getInputStream()).toMessage(); + } + + +} diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/api/PayPalOutMessageBuilder.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/api/PayPalOutMessageBuilder.java new file mode 100644 index 0000000..71e19c4 --- /dev/null +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/api/PayPalOutMessageBuilder.java @@ -0,0 +1,26 @@ +package com.egzosn.pay.paypal.v2.api; + +import java.util.Map; + +import com.egzosn.pay.common.bean.outbuilder.TextBuilder; + +/** + * @author Egan + *

+ * email egzosn@gmail.com
+ * date 2021/1/17
+ * 
+ */ +public class PayPalOutMessageBuilder extends TextBuilder { + + + public PayPalOutMessageBuilder(Map message) { + StringBuilder out = new StringBuilder(); + for (Map.Entry entry : message.entrySet()) { + out.append(entry.getKey()).append('=').append(entry.getValue()).append("
"); + } + super.content(out.toString()); + } + + +} 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 0000000..a6c72ed --- /dev/null +++ b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/api/PayPalPayService.java @@ -0,0 +1,437 @@ +package com.egzosn.pay.paypal.v2.api; + + +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; +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.CurType; +import com.egzosn.pay.common.bean.DefaultCurType; +import com.egzosn.pay.common.bean.MethodType; +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.UriVariables; +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.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; + + +/** + * 贝宝支付配置存储 + * + * @author egan + *

+ * email egzosn@gmail.com + * date 2021-1-16 ‏‎22:15:09 + */ +public class PayPalPayService extends BasePayService { + + /** + * 沙箱环境 + */ + 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/ + * 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 + */ + @Override + public boolean verify(Map params) { + 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); + + } + + + /** + * 获取授权请求头 + * + * @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; + } + + /** + * 返回创建的订单信息 + * + * @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".equals(resp.getString("state")) && StringUtils.isNotEmpty(resp.getString("id"))) { + order.setTradeNo(resp.getString("id")); + } + return preOrderHandler(resp, order); + } + + @Override + public PayOutMessage getPayOutMessage(String code, String message) { + String out = "The response from IPN was: " + code + ""; + return PayOutMessage.TEXT().content(out).build(); + } + + @Override + public PayOutMessage successPayOutMessage(PayMessage payMessage) { + Map message = payMessage.getPayMessage(); + return new PayPalOutMessageBuilder(message).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) { + JSONObject resp = getHttpRequestTemplate().getForObject(getReqUrl(PayPalTransactionType.ORDERS_GET), authHeader(), JSONObject.class, tradeNo); + return resp; + } + + @Override + public Map close(String tradeNo, String outTradeNo) { + return null; + } + + + /** + * 申请退款接口 + * + * @param refundOrder 退款订单信息 + * @return 返回支付方申请退款后的结果 + */ + @Override + public RefundResult refund(RefundOrder refundOrder) { + + JSONObject ordersCaptureInfo = getHttpRequestTemplate().getForObject(getReqUrl(PayPalTransactionType.ORDERS_CAPTURE), authHeader(), JSONObject.class, refundOrder.getTradeNo()); + if (!"COMPLETED".equals(ordersCaptureInfo.getString("status"))) { + return new PayPalRefundResult(ordersCaptureInfo, refundOrder.getTradeNo()); + } + + String captureId = ordersCaptureInfo.getJSONArray("purchaseUnits").getJSONObject(0).getJSONObject("payments").getJSONArray("captures").getJSONObject(0).getString("id"); + 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()); + JSONObject resp = getHttpRequestTemplate().postForObject(getReqUrl(PayPalTransactionType.REFUND), httpEntity, JSONObject.class, captureId); + PayPalRefundResult payPalRefundResult = new PayPalRefundResult(resp, refundOrder.getTradeNo()); + refundOrder.setRefundNo(payPalRefundResult.getRefundNo()); + return payPalRefundResult; + } + + /** + * 查询退款 + * + * @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, String billType) { + return Collections.emptyMap(); + } + + + /** + * 将请求参数或者请求流转化为 Map + * + * @param parameterMap 请求参数 + * @param is 请求流 + * @return 获得回调的请求参数 + */ + @Override + public Map getParameter2Map(Map parameterMap, InputStream is) { + + Map params = new LinkedHashMap<>(); + for (Map.Entry entry : parameterMap.entrySet()) { + String name = entry.getKey(); + String[] values = entry.getValue(); + String valueStr = ""; + for (int i = 0, len = values.length; i < len; i++) { + valueStr += (i == len - 1) ? values[i] : values[i] + ","; + } + 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); + } + } + params.put(name, valueStr); + } + return params; + } + + +} 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 0000000..24093e1 --- /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 0000000..551b8ca --- /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 0000000..30e8aa6 --- /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"), + /** + * 获取订单信息 + */ + 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 0000000..3abf421 --- /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 0000000..53118d7 --- /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 0000000..1ed4274 --- /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 0000000..f86d543 --- /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 0000000..7b75492 --- /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 0000000..591e884 --- /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 0000000..6561fc1 --- /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; + } +} -- Gitee From 5d46be5829eccbbaff57d4d1e56978fffa05a0d0 Mon Sep 17 00:00:00 2001 From: egzosn Date: Sun, 17 Jan 2021 23:23:43 +0800 Subject: [PATCH 030/165] paypal v2 --- .../controller/PayPalV2PayController.java | 20 +++++++++++++++++-- .../pay/paypal/v2/api/PayPalPayService.java | 7 +++++-- 2 files changed, 23 insertions(+), 4 deletions(-) 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 index 125c215..95abfd1 100644 --- 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 @@ -4,6 +4,7 @@ package com.egzosn.pay.demo.controller; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; +import java.util.Map; import javax.annotation.PostConstruct; import javax.servlet.http.HttpServletRequest; @@ -99,10 +100,22 @@ public class PayPalV2PayController { order.setDescription(" description "); order.setTradeNo("paypal 平台的单号, 支付下单返回的单号"); order.setRefundAmount(BigDecimal.valueOf(0.01)); - return service.refund(order); + 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); + } /** * 注意:这里不是异步回调的通知 IPN 地址设置的路径:https://developer.paypal.com/developer/ipnSimulator/ * PayPal确认付款调用的接口 @@ -115,6 +128,7 @@ public class PayPalV2PayController { @GetMapping(value = "payBackBefore.json") public String payBackBefore(HttpServletRequest request) throws IOException { try (InputStream is = request.getInputStream()) { + // 参数解析与校验 https://developer.paypal.com/docs/api-basics/notifications/ipn/IPNIntro/#id08CKFJ00JYK if (service.verify(service.getParameter2Map(request.getParameterMap(), is))) { // TODO 这里进行成功后的订单业务处理 // TODO 返回成功付款页面,这个到时候再做一个漂亮的页面显示,并使用前后端分离的模式 @@ -129,6 +143,7 @@ public class PayPalV2PayController { /** * 支付回调地址 * 注意:这里不是异步回调的通知 IPN 地址设置的路径:https://developer.paypal.com/developer/ipnSimulator/ + * 参数解析与校验 https://developer.paypal.com/docs/api-basics/notifications/ipn/IPNIntro/#id08CKFJ00JYK * * @param request 请求 * @return 结果 @@ -140,6 +155,7 @@ public class PayPalV2PayController { @RequestMapping(value = "payBack.json") public String payBack(HttpServletRequest request) throws IOException { //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() + // 参数解析与校验 https://developer.paypal.com/docs/api-basics/notifications/ipn/IPNIntro/#id08CKFJ00JYK return service.payBack(request.getParameterMap(), request.getInputStream()).toMessage(); } 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 index a6c72ed..a35aa73 100644 --- 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 @@ -145,6 +145,7 @@ public class PayPalPayService extends BasePayService { /** * 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. @@ -240,7 +241,7 @@ public class PayPalPayService extends BasePayService { /** * 返回创建的订单信息 - * + * 订单信息与接口地址 https://developer.paypal.com/docs/api/orders/v2 * @param order 支付订单 * @return 订单信息 * @see PayOrder 支付订单信息 @@ -350,6 +351,8 @@ public class PayPalPayService extends BasePayService { /** * 申请退款接口 * + * 1.需要通过支付单号获取captureId 详情: https://developer.paypal.com/docs/api/payments/v2/#captures + * 2.通过captureId发起退款 详情: https://developer.paypal.com/docs/api/payments/v2/#captures_refund * @param refundOrder 退款订单信息 * @return 返回支付方申请退款后的结果 */ @@ -384,7 +387,7 @@ public class PayPalPayService extends BasePayService { /** * 查询退款 - * + * 通过退款id获取退款信息 详情:https://developer.paypal.com/docs/api/payments/v2/#refunds * @param refundOrder 退款订单单号信息 * @return 返回支付方查询退款后的结果 */ -- Gitee From 6055833c6cc4d5472d75ccbfef813e9ca04e7066 Mon Sep 17 00:00:00 2001 From: wangshirui Date: Wed, 27 Jan 2021 14:27:20 +0800 Subject: [PATCH 031/165] =?UTF-8?q?[pay-java-wx]=20Update:=20jsapi?= =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=8F=82=E6=95=B0=E6=9B=B4=E6=96=B0=201.=20t?= =?UTF-8?q?imestamp=E9=9C=80=E8=A6=81=E6=98=AF=E5=AD=97=E7=AC=A6=E4=B8=B2?= =?UTF-8?q?=202.=20=E7=AD=BE=E5=90=8D=E5=AD=97=E6=AE=B5=E5=90=8D=E4=B8=BAp?= =?UTF-8?q?aySign;=20ref:=20https://pay.weixin.qq.com/wiki/doc/api/jsapi.p?= =?UTF-8?q?hp=3Fchapter=3D7=5F7&index=3D6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/egzosn/pay/wx/api/WxPayService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 200279f..e407211 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 @@ -274,7 +274,7 @@ public class WxPayService extends BasePayService implements if (WxTransactionType.JSAPI == order.getTransactionType()) { params.put("signType", payConfigStorage.getSignType()); params.put("appId", payConfigStorage.getAppid()); - params.put("timeStamp", System.currentTimeMillis() / 1000); + params.put("timeStamp", System.currentTimeMillis() / 1000 + ""); params.put("nonceStr", result.get(NONCE_STR)); params.put("package", "prepay_id=" + result.get("prepay_id")); } @@ -287,7 +287,7 @@ public class WxPayService extends BasePayService implements params.put("package", "Sign=WXPay"); } String paySign = createSign(SignUtils.parameterText(params), payConfigStorage.getInputCharset()); - params.put(SIGN, paySign); + 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")); -- Gitee From a63eefd78d1251d7ef5099751959bf618c49fef4 Mon Sep 17 00:00:00 2001 From: egzosn Date: Mon, 22 Feb 2021 21:53:36 +0800 Subject: [PATCH 032/165] paypal v2 --- .../pay/paypal/v2/api/PayPalPayService.java | 119 ++++++++++++++++-- .../paypal/v2/api/PayPalPayServiceInf.java | 103 +++++++++++++++ .../paypal/v2/bean/PayPalTransactionType.java | 6 +- 3 files changed, 212 insertions(+), 16 deletions(-) create mode 100644 pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/api/PayPalPayServiceInf.java 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 index a35aa73..1521049 100644 --- 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 @@ -55,7 +55,7 @@ import com.egzosn.pay.paypal.v2.bean.order.ShippingDetail; * email egzosn@gmail.com * date 2021-1-16 ‏‎22:15:09 */ -public class PayPalPayService extends BasePayService { +public class PayPalPayService extends BasePayService implements PayPalPayServiceInf { /** * 沙箱环境 @@ -242,6 +242,7 @@ public class PayPalPayService extends BasePayService { /** * 返回创建的订单信息 * 订单信息与接口地址 https://developer.paypal.com/docs/api/orders/v2 + * * @param order 支付订单 * @return 订单信息 * @see PayOrder 支付订单信息 @@ -338,8 +339,7 @@ public class PayPalPayService extends BasePayService { */ @Override public Map query(String tradeNo, String outTradeNo) { - JSONObject resp = getHttpRequestTemplate().getForObject(getReqUrl(PayPalTransactionType.ORDERS_GET), authHeader(), JSONObject.class, tradeNo); - return resp; + return getHttpRequestTemplate().getForObject(getReqUrl(PayPalTransactionType.ORDERS_GET), authHeader(), JSONObject.class, tradeNo); } @Override @@ -347,24 +347,115 @@ public class PayPalPayService extends BasePayService { return null; } + /** + * 注意:最好在付款成功之后回调时进行调用 + * 确认订单并返回确认后订单信息 + * 注意:此方法一个订单只能调用一次, 建议在支付回调时进行调用 + * 这里主要用来获取captureId使用,后续退款,查订单等等使用,用来替换下单返回的id + * 详情: https://developer.paypal.com/docs/api/orders/v2/#orders_capture + * + * @param tradeNo paypal下单成功之后返回的订单号 + * @return 确认后订单信息 + * 获取captureId + */ + @Override + public Map ordersCapture(String tradeNo) { + JSONObject ordersCaptureInfo = getHttpRequestTemplate().getForObject(getReqUrl(PayPalTransactionType.ORDERS_CAPTURE), authHeader(), 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)} * - * 1.需要通过支付单号获取captureId 详情: https://developer.paypal.com/docs/api/payments/v2/#captures - * 2.通过captureId发起退款 详情: https://developer.paypal.com/docs/api/payments/v2/#captures_refund * @param refundOrder 退款订单信息 * @return 返回支付方申请退款后的结果 */ @Override public RefundResult refund(RefundOrder refundOrder) { - - JSONObject ordersCaptureInfo = getHttpRequestTemplate().getForObject(getReqUrl(PayPalTransactionType.ORDERS_CAPTURE), authHeader(), JSONObject.class, refundOrder.getTradeNo()); - if (!"COMPLETED".equals(ordersCaptureInfo.getString("status"))) { - return new PayPalRefundResult(ordersCaptureInfo, refundOrder.getTradeNo()); - } - - String captureId = ordersCaptureInfo.getJSONArray("purchaseUnits").getJSONObject(0).getJSONObject("payments").getJSONArray("captures").getJSONObject(0).getString("id"); JSONObject request = new JSONObject(); Money amount = new Money(); if (null == refundOrder.getCurType()) { @@ -376,9 +467,10 @@ public class PayPalPayService extends BasePayService { 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()); @@ -388,6 +480,7 @@ public class PayPalPayService extends BasePayService { /** * 查询退款 * 通过退款id获取退款信息 详情:https://developer.paypal.com/docs/api/payments/v2/#refunds + * * @param refundOrder 退款订单单号信息 * @return 返回支付方查询退款后的结果 */ 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 0000000..01435ab --- /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/PayPalTransactionType.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/PayPalTransactionType.java index 30e8aa6..2cde37d 100644 --- 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 @@ -30,13 +30,13 @@ public enum PayPalTransactionType implements TransactionType { */ ORDERS_GET("/v2/checkout/orders/{order_id}"), /** - * 获取订单信息 + * 确认订单并返回确认后订单信息 */ ORDERS_CAPTURE("/v2/checkout/orders/{order_id}/capture"), /** - * 获取订单信息 + * 获取确认后订单信息 */ - CAPTURE("/v2/payments/captures/{capture_id}"), + GET_CAPTURE("/v2/payments/captures/{capture_id}"), /** * 退款 */ -- Gitee From db4c051007e85c717a56121ee7d36f6c95726723 Mon Sep 17 00:00:00 2001 From: egzosn Date: Tue, 23 Feb 2021 01:39:09 +0800 Subject: [PATCH 033/165] =?UTF-8?q?=E8=B4=A6=E5=8D=95=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/common/api/PayService.java | 13 +++++++- .../com/egzosn/pay/common/bean/BillType.java | 33 +++++++++++++++++++ .../com/egzosn/pay/common/util/DateUtils.java | 1 + 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 pay-java-common/src/main/java/com/egzosn/pay/common/bean/BillType.java 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 5fce5a9..fa8df25 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 @@ -5,6 +5,7 @@ import java.io.InputStream; import java.util.Date; import java.util.Map; +import com.egzosn.pay.common.bean.BillType; import com.egzosn.pay.common.bean.MethodType; import com.egzosn.pay.common.bean.PayMessage; import com.egzosn.pay.common.bean.PayOrder; @@ -275,11 +276,21 @@ public interface PayService { * 下载对账单 * * @param billDate 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 - * @param billType 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; + * @param billType 账单类型 * @return 返回支付方下载对账单的结果 */ + @Deprecated Map downloadbill(Date billDate, String billType); + /** + * 下载对账单 + * + * @param billDate 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 + * @param billType 账单类型 + * @return 返回支付方下载对账单的结果 + */ + Map downloadBill(Date billDate, BillType billType); + /** * 下载对账单 * 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 0000000..793463a --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/BillType.java @@ -0,0 +1,33 @@ +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 getTarType(); + + /** + * 自定义属性 + * @return 自定义属性 + */ + String getCustom(); +} 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 63c2d64..fa6b4e6 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 @@ -60,6 +60,7 @@ public final class DateUtils { 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) { -- Gitee From b3540dc41fcbf4c9acfd5e5615aa47deca2590d6 Mon Sep 17 00:00:00 2001 From: egzosn Date: Tue, 23 Feb 2021 01:39:35 +0800 Subject: [PATCH 034/165] =?UTF-8?q?=E8=B4=A6=E5=8D=95=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/ali/api/AliPayService.java | 33 +++-- .../egzosn/pay/ali/bean/AliPayBillType.java | 89 +++++++++++++ .../egzosn/pay/baidu/api/BaiduPayService.java | 38 ++++-- .../egzosn/pay/baidu/bean/BaiduBillType.java | 101 +++++++++++++++ .../egzosn/pay/fuiou/api/FuiouPayService.java | 14 +++ .../pay/payoneer/api/PayoneerPayService.java | 14 ++- .../pay/paypal/api/PayPalPayService.java | 8 ++ .../pay/paypal/v2/api/PayPalPayService.java | 7 ++ .../egzosn/pay/union/api/UnionPayService.java | 33 +++++ .../pay/union/bean/UnionPayBillType.java | 12 ++ .../wx/youdian/api/WxYouDianPayService.java | 9 ++ .../com/egzosn/pay/wx/api/WxBillService.java | 5 + .../java/com/egzosn/pay/wx/api/WxConst.java | 1 + .../com/egzosn/pay/wx/api/WxPayService.java | 71 +++++++++-- .../com/egzosn/pay/wx/bean/WxPayBillType.java | 119 ++++++++++++++++++ .../egzosn/pay/yiji/api/YiJiPayService.java | 15 +++ 16 files changed, 538 insertions(+), 31 deletions(-) create mode 100644 pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliPayBillType.java create mode 100644 pay-java-baidu/src/main/java/com/egzosn/pay/baidu/bean/BaiduBillType.java create mode 100644 pay-java-union/src/main/java/com/egzosn/pay/union/bean/UnionPayBillType.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxPayBillType.java 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 4e15880..1fb432b 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,6 +1,7 @@ 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; @@ -19,6 +20,7 @@ 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; @@ -27,6 +29,7 @@ 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.BillType; import com.egzosn.pay.common.bean.MethodType; import com.egzosn.pay.common.bean.Order; import com.egzosn.pay.common.bean.PayMessage; @@ -560,26 +563,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 返回支付方下载对账单的结果 */ + @Deprecated @Override 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 返回支付方下载对账单的结果 + */ + 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 支付平台订单号或者账单类型, 具体请 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 0000000..a91cea6 --- /dev/null +++ b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliPayBillType.java @@ -0,0 +1,89 @@ +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 getTarType() { + return null; + } + + /** + * 自定义属性 + * + * @return 自定义属性 + */ + @Override + public String getCustom() { + return null; + } + + +} 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 8c2095b..b94f00f 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 @@ -7,12 +7,14 @@ import java.util.Map; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; +import com.egzosn.pay.baidu.bean.BaiduBillType; import com.egzosn.pay.baidu.bean.BaiduPayOrder; import com.egzosn.pay.baidu.bean.BaiduTransactionType; import com.egzosn.pay.baidu.bean.type.AuditStatus; import com.egzosn.pay.baidu.util.Asserts; import com.egzosn.pay.common.api.BasePayService; import com.egzosn.pay.common.bean.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.PayMessage; @@ -402,34 +404,48 @@ public class BaiduPayService extends BasePayService { } /** - * 下载资金账单 + * 下载订单对账单 * * @param billDate 账单时间:日账单格式为yyyy-MM-dd * @param accessToken 用户token * @return 对账单 */ + @Deprecated @Override public Map downloadbill(Date billDate, String accessToken) { + return downloadBill(billDate, new BaiduBillType(accessToken, BaiduTransactionType.DOWNLOAD_ORDER_BILL.name())); + } + + /** + * 下载对账单 + * + * @param billDate 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 + * @param billType 账单类型 {@link BaiduBillType} + * @return 返回支付方下载对账单的结果 + */ + 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())); } /** 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 0000000..f3c5387 --- /dev/null +++ b/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/bean/BaiduBillType.java @@ -0,0 +1,101 @@ +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; + } + + public void setDatePattern(String datePattern) { + this.datePattern = datePattern; + } + + /** + * 获取压缩类型 + * + * @return 压缩类型 + */ + @Override + public String getTarType() { + return null; + } + + + /** + * 自定义属性 + * + * @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-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 418af4b..c56638c 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 @@ -9,6 +9,7 @@ import java.util.Map; import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.common.api.BasePayService; +import com.egzosn.pay.common.bean.BillType; import com.egzosn.pay.common.bean.MethodType; import com.egzosn.pay.common.bean.PayMessage; import com.egzosn.pay.common.bean.PayOrder; @@ -444,10 +445,23 @@ public class FuiouPayService extends BasePayService { * @param billType 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; * @return 空 */ + @Deprecated @Override public Map downloadbill(Date billDate, String billType) { return Collections.emptyMap(); } + /** + * 下载对账单 + * + * @param billDate 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 + * @param billType 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; + * @return 空 + */ + @Override + public Map downloadBill(Date billDate, BillType billType) { + return Collections.emptyMap(); + } + } 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 2b4645c..e706314 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 @@ -16,6 +16,7 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.common.api.BasePayService; 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; @@ -380,9 +381,20 @@ public class PayoneerPayService extends BasePayService im * @param billType 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; * @return 返回支付方下载对账单的结果 */ + @Deprecated @Override public Map downloadbill(Date billDate, String billType) { - + return Collections.emptyMap(); + } + /** + * 下载对账单 + * + * @param billDate 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 + * @param billType 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; + * @return 空 + */ + @Override + public Map downloadBill(Date billDate, BillType billType) { return Collections.emptyMap(); } 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 e80c8a0..4a8d212 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 @@ -20,6 +20,7 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.common.api.BasePayService; 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; @@ -347,10 +348,17 @@ public class PayPalPayService extends BasePayService { return resp; } + @Deprecated @Override public Map downloadbill(Date billDate, String billType) { return Collections.emptyMap(); } + @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/PayPalPayService.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/api/PayPalPayService.java index 1521049..acf3d07 100644 --- 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 @@ -21,6 +21,7 @@ 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.BillType; import com.egzosn.pay.common.bean.CurType; import com.egzosn.pay.common.bean.DefaultCurType; import com.egzosn.pay.common.bean.MethodType; @@ -490,12 +491,18 @@ public class PayPalPayService extends BasePayService implem return resp; } + @Deprecated @Override public Map downloadbill(Date billDate, String billType) { return Collections.emptyMap(); } + public Map downloadBill(Date billDate, BillType billType) { + return Collections.emptyMap(); + } + + /** * 将请求参数或者请求流转化为 Map * 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 5a68c8e..f6a146e 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 @@ -26,6 +26,7 @@ import java.util.TreeMap; import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.common.api.BasePayService; +import com.egzosn.pay.common.bean.BillType; import com.egzosn.pay.common.bean.MethodType; import com.egzosn.pay.common.bean.PayMessage; import com.egzosn.pay.common.bean.PayOrder; @@ -661,6 +662,7 @@ public class UnionPayService extends BasePayService { * @param billType 账单类型 * @return 返回fileContent 请自行将数据落地 */ + @Deprecated @Override public Map downloadbill(Date billDate, String billType) { Map params = this.getCommonParam(); @@ -684,6 +686,37 @@ public class UnionPayService extends BasePayService { throw new PayErrorException(new PayException("failure", "验证签名失败", response.toString())); } + /** + * 下载对账单 + * + * @param billDate 账单时间 + * @param billType 账单类型 + * @return 返回fileContent 请自行将数据落地 + */ + @Override + 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.getTarType()); + params.remove(SDKConstants.param_backUrl); + params.remove(SDKConstants.param_currencyCode); + this.setSign(params); + String responseStr = getHttpRequestTemplate().postForObject(this.getFileTransUrl(), params, String.class); + JSONObject response = UriVariables.getParametersToMap(responseStr); + if (this.verify(response)) { + if (SDKConstants.OK_RESP_CODE.equals(response.get(SDKConstants.param_respCode))) { + return response; + + } + throw new PayErrorException(new PayException(response.get(SDKConstants.param_respCode).toString(), response.get(SDKConstants.param_respMsg).toString(), response.toString())); + + } + throw new PayErrorException(new PayException("failure", "验证签名失败", response.toString())); + } + /** * 创建消息 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 0000000..ef03102 --- /dev/null +++ b/pay-java-union/src/main/java/com/egzosn/pay/union/bean/UnionPayBillType.java @@ -0,0 +1,12 @@ +package com.egzosn.pay.union.bean; + +/** + * 银联账单类型 + * @author Egan + *
+ * email egzosn@gmail.com
+ * date 2021/2/23
+ * 
+ */ +public enum UnionPayBillType { +} 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 2dcdfff..347e657 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 @@ -13,6 +13,7 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.common.api.BasePayService; 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.PayMessage; @@ -493,12 +494,20 @@ public class WxYouDianPayService extends BasePayService downloadbill(Date billDate, String billType) { return Collections.emptyMap(); } + + @Override + public Map downloadBill(Date billDate, BillType billType) { + return Collections.emptyMap(); + } + + public WxYouDianPayService(WxYouDianPayConfigStorage payConfigStorage) { super(payConfigStorage); } 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 eeed364..06d9f05 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; + /** * 账单接口 + * * @author: faymanwang * email: 1057438332@qq.com * time: 2020/7/31 11:21 */ public interface WxBillService { + @Deprecated Map downloadbill(Date billDate, String billType, String path); + } diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/api/WxConst.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/api/WxConst.java index ac656b4..52ea2e9 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 @@ -32,6 +32,7 @@ public interface WxConst { String MCH_ID = "mch_id"; String NONCE_STR = "nonce_str"; String OUT_TRADE_NO = "out_trade_no"; + String GZIP = "GZIP"; } 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 e407211..8274cba 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 @@ -38,6 +38,7 @@ 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.BillType; import com.egzosn.pay.common.bean.MethodType; import com.egzosn.pay.common.bean.PayMessage; import com.egzosn.pay.common.bean.PayOrder; @@ -58,6 +59,7 @@ import com.egzosn.pay.common.util.sign.SignUtils; import com.egzosn.pay.common.util.sign.encrypt.RSA2; import com.egzosn.pay.common.util.str.StringUtils; import com.egzosn.pay.wx.bean.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; @@ -560,10 +562,15 @@ 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 返回支付方下载对账单的结果 */ + @Deprecated @Override public Map downloadbill(Date billDate, String billType) { Map parameters = getDownloadBillParam(billDate, billType, false); @@ -581,6 +588,43 @@ public class WxPayService extends BasePayService implements return ret; } + /** + * 目前只支持日账单 + * + * @param billDate 下载对账单的日期,格式:20140603 + * @param billType 账单类型 + * ALL(默认值),返回当日所有订单信息(不含充值退款订单) + * SUCCESS,返回当日成功支付的订单(不含充值退款订单) + * REFUND,返回当日退款订单(不含充值退款订单) + * RECHARGE_REFUND,返回当日充值退款订单 + * @return 返回支付方下载对账单的结果, 如果【账单类型】为gzip的话则返回值中key为data值为gzip的输入流 + */ + public Map downloadBill(Date billDate, BillType billType) { + //获取公共参数 + Map parameters = getPublicParameters(); + parameters.put("bill_type", billType); + //目前只支持日账单 + parameters.put("bill_date", DateUtils.formatDate(billDate, DateUtils.YYYYMMDD)); + setParameters(parameters, "tar_type", billType.getTarType()); + //设置签名 + setSign(parameters); + Map ret = new HashMap(3); + ret.put(RETURN_CODE, SUCCESS); + ret.put(RETURN_MSG_CODE, "ok"); + if (StringUtils.isEmpty(billType.getTarType())) { + 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; + } + + /** * 目前只支持日账单,增加账单返回格式 * @@ -589,6 +633,7 @@ public class WxPayService extends BasePayService implements * @param path 账单返回格式 账单存储的基础路径,按月切割 * @return 返回支付方下载对账单的结果 */ + @Deprecated @Override public Map downloadbill(Date billDate, String billType, String path) { Map parameters = getDownloadBillParam(billDate, billType, true); @@ -622,6 +667,7 @@ public class WxPayService extends BasePayService implements * @return 解压后输入流 * @throws IOException IOException */ + @Deprecated public static InputStream uncompress(InputStream input) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); GZIPInputStream ungzip = new GZIPInputStream(input); @@ -641,6 +687,7 @@ public class WxPayService extends BasePayService implements * @param inputStream 输入流 * @throws IOException IOException */ + @Deprecated private void writeToLocal(String destination, InputStream inputStream) throws IOException { @@ -651,7 +698,7 @@ public class WxPayService extends BasePayService implements if (!file.getParentFile().exists()) { boolean result = file.getParentFile().mkdirs(); if (!result) { - System.out.println("创建失败"); + LOG.warn("创建失败"); } } OutputStream out = new FileOutputStream(file); @@ -662,7 +709,7 @@ public class WxPayService extends BasePayService implements len += size; out.write(buf, 0, size); } - System.out.println("最终写入字节数大小:" + len); + LOG.info("最终写入字节数大小:" + len); inputStream.close(); out.close(); } @@ -677,6 +724,7 @@ public class WxPayService extends BasePayService implements * @param tarType 账单返回格式 默认返回流false ,gzip 时候true * @return */ + @Deprecated private Map getDownloadBillParam(Date billDate, String billType, boolean tarType) { //获取公共参数 Map parameters = getPublicParameters(); @@ -689,7 +737,6 @@ public class WxPayService extends BasePayService implements return parameters; } - /** * @param transactionIdOrBillDate 支付平台订单号或者账单日期, 具体请 类型为{@link String }或者 {@link Date },类型须强制限制,类型不对应则抛出异常{@link PayErrorException} * @param outTradeNoBillType 商户单号或者 账单类型 @@ -704,7 +751,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())); } @@ -728,12 +775,12 @@ public class WxPayService extends BasePayService implements * @param order 转账订单 *
      *
-     *                           注意事项:
-     *                           ◆ 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。
-     *                           ◆ XML具有可扩展性,因此返回参数可能会有新增,而且顺序可能不完全遵循此文档规范,如果在解析回包的时候发生错误,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新回包字段,会更新到此API文档中。
-     *                           ◆ 因为错误代码字段err_code的值后续可能会增加,所以商户如果遇到回包返回新的错误码,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新的错误码,会更新到此API文档中。
-     *                           ◆ 错误代码描述字段err_code_des只供人工定位问题时做参考,系统实现时请不要依赖这个字段来做自动化处理。
-     *                           
+ * 注意事项: + * ◆ 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。 + * ◆ XML具有可扩展性,因此返回参数可能会有新增,而且顺序可能不完全遵循此文档规范,如果在解析回包的时候发生错误,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新回包字段,会更新到此API文档中。 + * ◆ 因为错误代码字段err_code的值后续可能会增加,所以商户如果遇到回包返回新的错误码,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新的错误码,会更新到此API文档中。 + * ◆ 错误代码描述字段err_code_des只供人工定位问题时做参考,系统实现时请不要依赖这个字段来做自动化处理。 + *
* @return 对应的转账结果 */ @Override 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 0000000..1bdd111 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/bean/WxPayBillType.java @@ -0,0 +1,119 @@ +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 getTarType() { + 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.getTarType())){ + return wxPayBillType; + } + } + return WxPayBillType.ALL; + } + +} 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 1ef9daa..5d42fbd 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 @@ -9,6 +9,7 @@ import java.util.TreeMap; import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.common.api.BasePayService; 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; @@ -372,12 +373,26 @@ public class YiJiPayService extends BasePayService { * @param billType 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 * @return 返回支付方下载对账单的结果 */ + @Deprecated @Override public Map downloadbill(Date billDate, String billType) { return Collections.emptyMap(); } + /** + * 目前只支持日账单 + * + * @param billDate 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于易极付交易收单的业务账单;signcustomer是指基于商户易极付余额收入及支出等资金变动的帐务账单; + * @param billType 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 + * @return 返回支付方下载对账单的结果 + */ + @Override + public Map downloadBill(Date billDate, BillType billType) { + + return Collections.emptyMap(); + } + /** * 转账 这里外部进行调用{@link #buildRequest(Map, MethodType)} -- Gitee From 513b182c13fc9658605b36b1581ed50afd2ade56 Mon Sep 17 00:00:00 2001 From: egzosn Date: Tue, 23 Feb 2021 01:54:55 +0800 Subject: [PATCH 035/165] =?UTF-8?q?=E8=B4=A6=E5=8D=95=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/common/bean/BillType.java | 6 +- .../egzosn/pay/union/api/UnionPayService.java | 27 ++------ .../pay/union/bean/UnionPayBillType.java | 61 ++++++++++++++++++- .../com/egzosn/pay/wx/api/WxPayService.java | 17 +++--- .../com/egzosn/pay/wx/bean/WxPayBillType.java | 12 ++-- 5 files changed, 85 insertions(+), 38 deletions(-) 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 index 793463a..bf65b8f 100644 --- 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 @@ -20,10 +20,10 @@ public interface BillType { String getDatePattern(); /** - * 获取压缩类型 - * @return 压缩类型 + * 获取文件类型 + * @return 文件类型 */ - String getTarType(); + String getFileType(); /** * 自定义属性 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 f6a146e..37c3af6 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 @@ -46,6 +46,7 @@ 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; @@ -659,31 +660,13 @@ public class UnionPayService extends BasePayService { * 下载对账单 * * @param billDate 账单时间 - * @param billType 账单类型 + * @param fileType 文件类型 文件类型,一般商户填写00即可 * @return 返回fileContent 请自行将数据落地 */ @Deprecated @Override - public Map downloadbill(Date billDate, String 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.remove(SDKConstants.param_backUrl); - params.remove(SDKConstants.param_currencyCode); - this.setSign(params); - String responseStr = getHttpRequestTemplate().postForObject(this.getFileTransUrl(), params, String.class); - JSONObject response = UriVariables.getParametersToMap(responseStr); - if (this.verify(response)) { - if (SDKConstants.OK_RESP_CODE.equals(response.get(SDKConstants.param_respCode))) { - return response; - - } - throw new PayErrorException(new PayException(response.get(SDKConstants.param_respCode).toString(), response.get(SDKConstants.param_respMsg).toString(), response.toString())); - - } - throw new PayErrorException(new PayException("failure", "验证签名失败", response.toString())); + public Map downloadbill(Date billDate, String fileType) { + return downloadBill(billDate, new UnionPayBillType(fileType)); } /** @@ -700,7 +683,7 @@ public class UnionPayService extends BasePayService { UnionTransactionType.FILE_TRANSFER.convertMap(params); params.put(SDKConstants.param_settleDate, DateUtils.formatDate(billDate, DateUtils.MMDD)); - params.put(SDKConstants.param_fileType, billType.getTarType()); + params.put(SDKConstants.param_fileType, billType.getFileType()); params.remove(SDKConstants.param_backUrl); params.remove(SDKConstants.param_currencyCode); this.setSign(params); 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 index ef03102..c833685 100644 --- 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 @@ -1,12 +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 enum UnionPayBillType { +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-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 8274cba..3e4ddc5 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 @@ -605,13 +605,14 @@ public class WxPayService extends BasePayService implements parameters.put("bill_type", billType); //目前只支持日账单 parameters.put("bill_date", DateUtils.formatDate(billDate, DateUtils.YYYYMMDD)); - setParameters(parameters, "tar_type", billType.getTarType()); + String fileType = billType.getFileType(); + setParameters(parameters, "tar_type", fileType); //设置签名 setSign(parameters); Map ret = new HashMap(3); ret.put(RETURN_CODE, SUCCESS); ret.put(RETURN_MSG_CODE, "ok"); - if (StringUtils.isEmpty(billType.getTarType())) { + if (StringUtils.isEmpty(fileType)) { String respStr = requestTemplate.postForObject(getReqUrl(WxTransactionType.DOWNLOADBILL), XML.getMap2Xml(parameters), String.class); if (respStr.indexOf("<") == 0) { return XML.toJSONObject(respStr); @@ -775,12 +776,12 @@ public class WxPayService extends BasePayService implements * @param order 转账订单 *
      *
-     *                                        注意事项:
-     *                                        ◆ 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。
-     *                                        ◆ XML具有可扩展性,因此返回参数可能会有新增,而且顺序可能不完全遵循此文档规范,如果在解析回包的时候发生错误,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新回包字段,会更新到此API文档中。
-     *                                        ◆ 因为错误代码字段err_code的值后续可能会增加,所以商户如果遇到回包返回新的错误码,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新的错误码,会更新到此API文档中。
-     *                                        ◆ 错误代码描述字段err_code_des只供人工定位问题时做参考,系统实现时请不要依赖这个字段来做自动化处理。
-     *                                        
+ * 注意事项: + * ◆ 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。 + * ◆ XML具有可扩展性,因此返回参数可能会有新增,而且顺序可能不完全遵循此文档规范,如果在解析回包的时候发生错误,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新回包字段,会更新到此API文档中。 + * ◆ 因为错误代码字段err_code的值后续可能会增加,所以商户如果遇到回包返回新的错误码,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新的错误码,会更新到此API文档中。 + * ◆ 错误代码描述字段err_code_des只供人工定位问题时做参考,系统实现时请不要依赖这个字段来做自动化处理。 + *
* @return 对应的转账结果 */ @Override 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 index 1bdd111..b4fabfb 100644 --- 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 @@ -87,16 +87,20 @@ public enum WxPayBillType implements BillType { } /** - * 获取压缩类型 + * 获取文件类型 * - * @return 压缩类型 + * @return 文件类型 */ @Override - public String getTarType() { + public String getFileType() { return tarType; } + + + + /** * 自定义属性 * @@ -109,7 +113,7 @@ public enum WxPayBillType implements BillType { public static WxPayBillType forType(String type){ for (WxPayBillType wxPayBillType : WxPayBillType.values()){ - if (wxPayBillType.getType().equals(type) && StringUtils.isEmpty(wxPayBillType.getTarType())){ + if (wxPayBillType.getType().equals(type) && StringUtils.isEmpty(wxPayBillType.getFileType())){ return wxPayBillType; } } -- Gitee From 1b1c7531e1e11802111f07ef32aa63d09658a4bc Mon Sep 17 00:00:00 2001 From: egzosn Date: Mon, 22 Feb 2021 23:04:52 +0800 Subject: [PATCH 036/165] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egzosn/pay/common/api/BasePayService.java | 2 +- .../egzosn/pay/common/http/UriVariables.java | 54 +- .../com/egzosn/pay/common/util/DateUtils.java | 20 +- .../pay/common/util/MatrixToImageWriter.java | 217 +++---- .../java/com/egzosn/pay/common/util/XML.java | 105 ++-- .../pay/common/util/sign/SecureUtil.java | 71 +-- .../pay/common/util/sign/SignUtils.java | 154 +++-- .../pay/common/util/sign/encrypt/RSA.java | 575 ++++++++++-------- .../pay/common/util/sign/encrypt/sm3/SM3.java | 6 +- .../util/sign/encrypt/sm3/SM3Digest.java | 71 +-- .../egzosn/pay/fuiou/api/FuiouPayService.java | 10 +- 11 files changed, 706 insertions(+), 579 deletions(-) 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 38bc4be..43aa78e 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 @@ -150,7 +150,7 @@ public abstract class BasePayService implements Pay */ @Override public String toPay(O order) { - Map orderInfo = orderInfo(order); + Map orderInfo = orderInfo(order); return buildRequest(orderInfo, MethodType.POST); } 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 e7b6ae4..b8bb0c0 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 @@ -5,6 +5,9 @@ import java.net.URLEncoder; import java.util.List; import java.util.Map; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.common.bean.result.PayException; import com.egzosn.pay.common.exception.PayErrorException; @@ -12,13 +15,17 @@ import com.egzosn.pay.common.exception.PayErrorException; /** * 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 Log LOG = LogFactory.getLog(UriVariables.class); + + private UriVariables() { + } /** * 依次匹配 @@ -96,26 +103,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); @@ -202,5 +204,19 @@ 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; + } + } 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 fa6b4e6..5cd5111 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.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.util.Args; /** * 日期转换运算工具 @@ -19,11 +22,11 @@ import java.util.*; *
*/ public final class DateUtils { + private static final Log LOG = LogFactory.getLog(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>>(); @@ -76,7 +79,8 @@ public final class DateUtils { SimpleDateFormat formatFor = DateFormatHolder.formatFor(pattern); try { return formatFor.parse(date); - } catch (ParseException e) { + } + catch (ParseException e) { LOG.error(e); } return null; 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 fc32049..57b8e75 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/XML.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/XML.java
index 1ee67ec..675bdfd 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,8 +119,8 @@ public class XML { * 解析xml并转化为Json值 * * @param content json字符串 - * @param clazz 需要转化的类 - * @param 返回对应类型 + * @param clazz 需要转化的类 + * @param 返回对应类型 * @return Json值 */ public static T toBean(String content, Class clazz) { @@ -124,7 +130,8 @@ public class XML { } 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(); } @@ -224,22 +233,27 @@ 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 e) { throw new PayErrorException(new PayException("XML failure", "XML解析失败\n" + e.getMessage())); - } finally { + } + catch (SAXException e) { + throw new PayErrorException(new PayException("XML failure", "XML解析失败\n" + e.getMessage())); + } + finally { in.close(); } return m; } - /** * 将Map转换为XML格式的字符串 * @@ -254,16 +268,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 +307,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 +348,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())); } 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 index 63cbc1b..22839a4 100644 --- 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 @@ -1,19 +1,21 @@ 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.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.Signature; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +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; + public class SecureUtil { - //日志 - protected static final Log log = LogFactory.getLog(SecureUtil.class); + + private static final Log LOG = LogFactory.getLog(SecureUtil.class); /** * 算法常量: SHA1 */ @@ -34,19 +36,20 @@ public class SecureUtil { /** * 获取摘要 * - * @param data 待计算的数据 + * @param data 待计算的数据 * @param algorithm 算法名 * @return 计算结果 */ - private static byte[] digestByData (byte[] data,String algorithm) { + 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(); + } + catch (NoSuchAlgorithmException e) { + LOG.error(e); return null; } } @@ -58,7 +61,7 @@ public class SecureUtil { * @param encoding 编码 * @return 计算结果 */ - public static byte[] sha1X16 (String data, String encoding) { + public static byte[] sha1X16(String data, String encoding) { try { byte[] bytes = digestByData(data.getBytes(encoding), ALGORITHM_SHA1); StringBuilder sha1StrBuff = new StringBuilder(); @@ -66,41 +69,42 @@ public class SecureUtil { if (Integer.toHexString(0xFF & bytes[i]).length() == 1) { sha1StrBuff.append("0").append( Integer.toHexString(0xFF & bytes[i])); - } else { + } + else { sha1StrBuff.append(Integer.toHexString(0xFF & bytes[i])); } } return sha1StrBuff.toString().getBytes(encoding); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); + } + catch (UnsupportedEncodingException e) { + LOG.error(e); return null; } } - /** * sha256计算后进行16进制转换 * - * @param data - * 待计算的数据 - * @param encoding - * 编码 + * @param data 待计算的数据 + * @param encoding 编码 * @return 计算结果 */ public static String sha256X16Str(String data, String encoding) { - byte[] bytes =null; + byte[] bytes = null; try { bytes = digestByData(data.getBytes(encoding), ALGORITHM_SHA1); - } catch (UnsupportedEncodingException e) { - throw new PayErrorException(new PayException("error", e.getLocalizedMessage())); + } + 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 { + } + else { sha256StrBuff.append(Integer.toHexString(0xFF & bytes[i])); } } @@ -110,8 +114,7 @@ public class SecureUtil { /** * SM3计算. * - * @param data - * 待计算的数据 + * @param data 待计算的数据 * @return 计算结果 */ private static byte[] sm3(byte[] data) { @@ -126,25 +129,25 @@ public class SecureUtil { /** * sm3计算后进行16进制转换 * - * @param data - * 待计算的数据 - * @param encoding - * 编码 + * @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(); + } + catch (UnsupportedEncodingException e) { + LOG.error(e); } 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 { + } + else { sm3StrBuff.append(Integer.toHexString(0xFF & bytes[i])); } } 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 74ade5f..07957ea 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,35 @@ package com.egzosn.pay.common.util.sign; +import java.io.UnsupportedEncodingException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +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 java.util.UUID; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.message.BasicNameValuePair; + 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 javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import java.io.UnsupportedEncodingException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.*; /** * 签名 工具 * - * @author: egan + * @author egan *
  * email egzosn@gmail.com
  * date 2016/11/9 17:45
@@ -51,7 +63,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";
@@ -79,12 +91,15 @@ public enum SignUtils implements SignType {
                     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();
+            }
+            catch (NoSuchAlgorithmException e) {
+                LOG.error(e);
+            }
+            catch (InvalidKeyException e) {
+                LOG.error(e);
+            }
+            catch (UnsupportedEncodingException e) {
+                LOG.error(e);
             }
 
             throw new PayErrorException(new PayException("fail", "HMACSHA256 签名异常"));
@@ -162,10 +177,11 @@ public enum SignUtils implements SignType {
             return com.egzosn.pay.common.util.sign.encrypt.RSA2.verify(text, sign, publicKey, characterEncoding);
         }
     };
+    private static final Log LOG = LogFactory.getLog(SignUtils.class);
 
     /**
-     *
      * 把数组所有元素排序,并按照“参数=参数值”的模式用“@param separator”字符拼接成字符串
+     *
      * @param parameters 参数
      * @return 去掉空值与签名参数后的新签名,拼接后字符串
      */
@@ -175,10 +191,10 @@ public enum SignUtils implements SignType {
     }
 
     /**
-     *
      * 把数组所有元素排序,并按照“参数=参数值”的模式用“@param separator”字符拼接成字符串
+     *
      * @param parameters 参数
-     * @param separator 分隔符
+     * @param separator  分隔符
      * @return 去掉空值与签名参数后的新签名,拼接后字符串
      */
     public static String parameterText(Map parameters, String separator) {
@@ -186,42 +202,47 @@ public enum SignUtils implements SignType {
     }
 
     /**
-     *
      * 把数组所有元素排序,并按照“参数=参数值”的模式用“@param separator”字符拼接成字符串
+     *
      * @param parameters 参数
-     * @param separator 分隔符
-     * @param ignoreKey 需要忽略添加的key
+     * @param separator  分隔符
+     * @param ignoreKey  需要忽略添加的key
      * @return 去掉空值与签名参数后的新签名,拼接后字符串
      */
-    public static String parameterText(Map parameters, String separator, String... ignoreKey) {
+    public static String parameterText(Map parameters, String separator, String... ignoreKey) {
         return parameterText(parameters, separator, true, ignoreKey);
     }
-    
+
     /**
-     *
      * 把数组所有元素排序,并按照“参数=参数值”的模式用“@param separator”字符拼接成字符串
-     * @param parameters 参数
-     * @param separator 分隔符
+     *
+     * @param parameters      参数
+     * @param separator       分隔符
      * @param ignoreNullValue 需要忽略NULL值
-     * @param ignoreKey 需要忽略添加的key
+     * @param ignoreKey       需要忽略添加的key
      * @return 去掉空值与签名参数后的新签名,拼接后字符串
      */
-    public static String parameterText(Map parameters, String separator, boolean ignoreNullValue, String... ignoreKey ) {
-        if(parameters == null){
+    public static String parameterText(Map parameters, String separator, boolean ignoreNullValue, String... ignoreKey) {
+        if (parameters == null) {
             return "";
         }
-        StringBuffer sb = new StringBuffer();
-        if (null != ignoreKey){
+
+        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 : (Set>)parameters.entrySet()) {
+            for (Map.Entry entry :  parameters.entrySet()) {
                 Object v = entry.getValue();
-                if (null == v || "".equals(v.toString().trim()) || (null != ignoreKey && Arrays.binarySearch(ignoreKey, entry.getKey() ) >= 0)) {
+                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( v.toString().trim()).append(separator);
+                sb.append(entry.getKey()).append("=").append(valStr).append(separator);
             }
             if (sb.length() > 0 && !"".equals(separator)) {
                 sb.deleteCharAt(sb.length() - 1);
@@ -230,7 +251,13 @@ public enum SignUtils implements SignType {
 
         }
 
+        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());
         //排序
@@ -246,16 +273,19 @@ public enum SignUtils implements SignType {
 
                 for (int i = 0; i < values.length; i++) {
                     String value = values[i].trim();
-                    if ("".equals(value)){ continue;}
-                    valueStr += (i == values.length - 1) ?  value :  value + ",";
+                    if ("".equals(value)) {
+                        continue;
+                    }
+                    valueStr += (i == values.length - 1) ? value : value + ",";
                 }
-            } else {
+            }
+            else {
                 valueStr = o.toString();
             }
-            if (null == valueStr || "".equals(valueStr.toString().trim()) || (null != ignoreKey && Arrays.binarySearch(ignoreKey, k ) >= 0)) {
+            if (StringUtils.isBlank(valueStr) || (null != ignoreKey && Arrays.binarySearch(ignoreKey, k) >= 0)) {
                 continue;
             }
-            sb.append(k ).append("=").append( valueStr).append(separator);
+            sb.append(k).append("=").append(valueStr).append(separator);
         }
         if (sb.length() > 0) {
             sb.deleteCharAt(sb.length() - 1);
@@ -266,35 +296,38 @@ public enum SignUtils implements SignType {
     /**
      * 将参数集合(事前做好排序)按分割符号拼凑字符串并加密为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 分隔符
+     * @param separator  分隔符
      * @return 参数排序好的值
      */
-    public static String  parameters2MD5Str(Object parameters, String separator){
+    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();
+            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);
+        }
+        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 StringUtils.isBlank(sb.toString()) ? "" : sb.deleteCharAt(sb.length() - 1).toString();
     }
 
 
     /**
      * 获取随机字符串
+     *
      * @return 随机字符串
      */
-    public static String randomStr(){
+    public static String randomStr() {
         return UUID.randomUUID().toString().replace("-", "");
     }
 
@@ -306,24 +339,26 @@ 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) {
+    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) {
+    public String sign(Map parameters, String key, String separator, String characterEncoding) {
 
         return createSign(parameterText(parameters, separator), key, characterEncoding);
 
@@ -333,17 +368,16 @@ 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){
+    public boolean verify(Map params, String sign, String key, String characterEncoding) {
         //判断是否一样
         return this.verify(parameterText(params), sign, key, characterEncoding);
     }
 
 
-
 }
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 d75d1c8..e1c0490 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,8 +1,13 @@
 
 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;
@@ -10,288 +15,324 @@ import java.security.PublicKey;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.X509EncodedKeySpec;
 
+import javax.crypto.Cipher;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
 /**
  * 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 Log LOG = LogFactory.getLog(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); + java.security.Signature signature = java.security.Signature.getInstance(signAlgorithms); + signature.initVerify(pubKey); + 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 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); + } + + /** + * 解密 + * + * @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/sm3/SM3.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/sm3/SM3.java index 09f319b..c0ded5b 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 cf62b24..6b3a792 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-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 c56638c..8bae0c2 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 @@ -141,7 +141,7 @@ public class FuiouPayService extends BasePayService { */ public boolean signVerify(Map params, String responseSign) { - String sign = createSign(SignUtils.parameters2MD5Str(params, "|"), payConfigStorage.getInputCharset()); + String sign = createSign(SignUtils.parameters2Md5Str(params, "|"), payConfigStorage.getInputCharset()); return responseSign.equals(sign); } @@ -156,7 +156,7 @@ public class FuiouPayService extends BasePayService { 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(SignUtils.parameters2Md5Str(params, "|"), payConfigStorage.getInputCharset())); JSONObject resultJson = getHttpRequestTemplate().postForObject(getReqUrl() + URL_FuiouSmpAQueryGate + "?" + UriVariables.getMapToParameters(params), null, JSONObject.class); if (null == resultJson) { return false; @@ -178,7 +178,7 @@ public class FuiouPayService extends BasePayService { } Map parameters = getOrderInfo(order); - String sign = createSign(SignUtils.parameters2MD5Str(parameters, "|"), payConfigStorage.getInputCharset()); + String sign = createSign(SignUtils.parameters2Md5Str(parameters, "|"), payConfigStorage.getInputCharset()); parameters.put("md5", sign); return parameters; } @@ -381,7 +381,7 @@ public class FuiouPayService extends BasePayService { LinkedHashMap params = new LinkedHashMap<>(); params.put("mchnt_cd", payConfigStorage.getPid()); params.put("order_id", outTradeNo); - params.put("md5", createSign(SignUtils.parameters2MD5Str(params, "|"), payConfigStorage.getInputCharset())); + params.put("md5", createSign(SignUtils.parameters2Md5Str(params, "|"), payConfigStorage.getInputCharset())); JSONObject resultJson = getHttpRequestTemplate().postForObject(getReqUrl() + URL_FuiouSmpAQueryGate + "?" + UriVariables.getMapToParameters(params), null, JSONObject.class); return resultJson; } @@ -420,7 +420,7 @@ 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(SignUtils.parameters2Md5Str(params, "|"), payConfigStorage.getInputCharset())); JSONObject resultJson = getHttpRequestTemplate().postForObject(getReqUrl() + URL_FuiouSmpRefundGate, params, JSONObject.class); return FuiouRefundResult.create(resultJson); } -- Gitee From 817471d27ef30d6add0393239212d20e43ded295 Mon Sep 17 00:00:00 2001 From: egzosn Date: Mon, 22 Feb 2021 23:05:05 +0800 Subject: [PATCH 037/165] =?UTF-8?q?=E5=B0=8F=E7=A8=8B=E5=BA=8F=E5=8F=91?= =?UTF-8?q?=E7=BA=A2=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/wx/api/WxPayService.java | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) 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 3e4ddc5..b0016a1 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 @@ -47,11 +47,13 @@ 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; @@ -776,12 +778,12 @@ public class WxPayService extends BasePayService implements * @param order 转账订单 *
      *
-     *                                                     注意事项:
-     *                                                     ◆ 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。
-     *                                                     ◆ XML具有可扩展性,因此返回参数可能会有新增,而且顺序可能不完全遵循此文档规范,如果在解析回包的时候发生错误,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新回包字段,会更新到此API文档中。
-     *                                                     ◆ 因为错误代码字段err_code的值后续可能会增加,所以商户如果遇到回包返回新的错误码,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新的错误码,会更新到此API文档中。
-     *                                                     ◆ 错误代码描述字段err_code_des只供人工定位问题时做参考,系统实现时请不要依赖这个字段来做自动化处理。
-     *                                                     
+ * 注意事项: + * ◆ 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。 + * ◆ XML具有可扩展性,因此返回参数可能会有新增,而且顺序可能不完全遵循此文档规范,如果在解析回包的时候发生错误,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新回包字段,会更新到此API文档中。 + * ◆ 因为错误代码字段err_code的值后续可能会增加,所以商户如果遇到回包返回新的错误码,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新的错误码,会更新到此API文档中。 + * ◆ 错误代码描述字段err_code_des只供人工定位问题时做参考,系统实现时请不要依赖这个字段来做自动化处理。 + *
* @return 对应的转账结果 */ @Override @@ -912,17 +914,30 @@ public class WxPayService extends BasePayService implements 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); + 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(SignUtils.parameterText(params), payConfigStorage.getInputCharset()); + params.put("signType", payConfigStorage.getSignType()); + params.put("paySign", paySign); + return params; } -- Gitee From 0f53fb4527f14e1379d5da539ac16aea84bfeeb6 Mon Sep 17 00:00:00 2001 From: egzosn Date: Mon, 22 Feb 2021 23:08:17 +0800 Subject: [PATCH 038/165] =?UTF-8?q?=E8=B4=A6=E5=8D=95=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/ali/bean/AliPayBillType.java | 7 ++++--- .../com/egzosn/pay/baidu/bean/BaiduBillType.java | 15 ++++++++------- 2 files changed, 12 insertions(+), 10 deletions(-) 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 index a91cea6..297398f 100644 --- 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 @@ -66,15 +66,16 @@ public enum AliPayBillType implements BillType { } /** - * 获取压缩类型 + * 获取文件类型 * - * @return 压缩类型 + * @return 文件类型 */ @Override - public String getTarType() { + public String getFileType() { return null; } + /** * 自定义属性 * 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 index f3c5387..ded156b 100644 --- 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 @@ -62,20 +62,21 @@ public class BaiduBillType implements BillType { return datePattern; } - public void setDatePattern(String datePattern) { - this.datePattern = datePattern; - } - /** - * 获取压缩类型 + * 获取文件类型 * - * @return 压缩类型 + * @return 文件类型 */ @Override - public String getTarType() { + public String getFileType() { return null; } + public void setDatePattern(String datePattern) { + this.datePattern = datePattern; + } + + /** * 自定义属性 -- Gitee From 2413e091b6deceb51d73cd8d76d3440318437e32 Mon Sep 17 00:00:00 2001 From: egzosn Date: Tue, 23 Feb 2021 23:07:05 +0800 Subject: [PATCH 039/165] 2.14.0 --- README.md | 2 +- pay-java-ali/pom.xml | 2 +- pay-java-baidu/pom.xml | 2 +- pay-java-common/pom.xml | 2 +- pay-java-demo/pom.xml | 2 +- pay-java-fuiou/pom.xml | 2 +- pay-java-payoneer/pom.xml | 2 +- pay-java-paypal/pom.xml | 2 +- pay-java-union/pom.xml | 2 +- pay-java-wx-youdian/pom.xml | 2 +- pay-java-wx/pom.xml | 2 +- pay-java-yiji/pom.xml | 2 +- .../main/java/com/egzosn/pay/yiji/api/YiJiPayService.java | 3 --- pom.xml | 6 +++--- 14 files changed, 15 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 8675b94..b906de9 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ com.egzosn {module-name} - 2.13.3-b1 + 2.14.0 ``` diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index f31a779..c56343e 100644 --- a/pay-java-ali/pom.xml +++ b/pay-java-ali/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.4-SNAPSHOT + 2.14.0 4.0.0 pay-java-ali diff --git a/pay-java-baidu/pom.xml b/pay-java-baidu/pom.xml index 228a3d0..db0db4f 100644 --- a/pay-java-baidu/pom.xml +++ b/pay-java-baidu/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.4-SNAPSHOT + 2.14.0 4.0.0 pay-java-baidu diff --git a/pay-java-common/pom.xml b/pay-java-common/pom.xml index e0ad8d1..f29abf3 100644 --- a/pay-java-common/pom.xml +++ b/pay-java-common/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.4-SNAPSHOT + 2.14.0 4.0.0 jar diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index 7c0de2a..53690b5 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.4-SNAPSHOT + 2.14.0 4.0.0 war diff --git a/pay-java-fuiou/pom.xml b/pay-java-fuiou/pom.xml index 6a834c7..beb641d 100644 --- a/pay-java-fuiou/pom.xml +++ b/pay-java-fuiou/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.4-SNAPSHOT + 2.14.0 4.0.0 pay-java-fuiou diff --git a/pay-java-payoneer/pom.xml b/pay-java-payoneer/pom.xml index 9df1ded..fcf63bf 100644 --- a/pay-java-payoneer/pom.xml +++ b/pay-java-payoneer/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.4-SNAPSHOT + 2.14.0 4.0.0 pay-java-payoneer diff --git a/pay-java-paypal/pom.xml b/pay-java-paypal/pom.xml index 0abbb06..5926fa1 100644 --- a/pay-java-paypal/pom.xml +++ b/pay-java-paypal/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.4-SNAPSHOT + 2.14.0 4.0.0 diff --git a/pay-java-union/pom.xml b/pay-java-union/pom.xml index 6c539c1..7c412a0 100644 --- a/pay-java-union/pom.xml +++ b/pay-java-union/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.4-SNAPSHOT + 2.14.0 4.0.0 diff --git a/pay-java-wx-youdian/pom.xml b/pay-java-wx-youdian/pom.xml index 2081227..2aee8c8 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.4-SNAPSHOT + 2.14.0 4.0.0 pay-java-wx-youdian diff --git a/pay-java-wx/pom.xml b/pay-java-wx/pom.xml index dfe0e5d..7aed12a 100644 --- a/pay-java-wx/pom.xml +++ b/pay-java-wx/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.4-SNAPSHOT + 2.14.0 4.0.0 pay-java-wx diff --git a/pay-java-yiji/pom.xml b/pay-java-yiji/pom.xml index 0a3ac4e..5d0199a 100644 --- a/pay-java-yiji/pom.xml +++ b/pay-java-yiji/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.13.4-SNAPSHOT + 2.14.0 4.0.0 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 5d42fbd..ba19707 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 @@ -53,9 +53,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"; /** * 获取对应的请求地址 diff --git a/pom.xml b/pom.xml index fc3322b..3f51c77 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.egzosn pay-java-parent pom - 2.13.4-SNAPSHOT + 2.14.0 Pay Java - Parent Pay Java Parent @@ -58,13 +58,13 @@ pay-java-paypal pay-java-yiji pay-java-baidu - pay-java-demo + - 2.13.4-SNAPSHOT + 2.14.0 4.5.4 1.2.17 1.2.73 -- Gitee From 5186ce9f3c379a752daa65be7efab621218c8df0 Mon Sep 17 00:00:00 2001 From: egzosn Date: Tue, 23 Feb 2021 23:07:21 +0800 Subject: [PATCH 040/165] 2.14.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3f51c77..02e8d5e 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ pay-java-paypal pay-java-yiji pay-java-baidu - + pay-java-demo -- Gitee From 1b1db0f32101826157d8c32b0d3f55dc769355d1 Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 24 Feb 2021 01:56:23 +0800 Subject: [PATCH 041/165] 2.14.1 --- README.md | 2 +- pay-java-ali/pom.xml | 2 +- pay-java-baidu/pom.xml | 2 +- pay-java-common/pom.xml | 2 +- pay-java-demo/pom.xml | 2 +- pay-java-fuiou/pom.xml | 2 +- pay-java-payoneer/pom.xml | 2 +- pay-java-paypal/pom.xml | 2 +- pay-java-union/pom.xml | 2 +- pay-java-wx-youdian/pom.xml | 2 +- pay-java-wx/pom.xml | 2 +- pay-java-yiji/pom.xml | 2 +- pom.xml | 4 ++-- 13 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index b906de9..a3807e7 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ com.egzosn {module-name} - 2.14.0 + 2.14.1 ``` diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index c56343e..a791d6f 100644 --- a/pay-java-ali/pom.xml +++ b/pay-java-ali/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.0 + 2.14.1 4.0.0 pay-java-ali diff --git a/pay-java-baidu/pom.xml b/pay-java-baidu/pom.xml index db0db4f..d835c66 100644 --- a/pay-java-baidu/pom.xml +++ b/pay-java-baidu/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.0 + 2.14.1 4.0.0 pay-java-baidu diff --git a/pay-java-common/pom.xml b/pay-java-common/pom.xml index f29abf3..1082296 100644 --- a/pay-java-common/pom.xml +++ b/pay-java-common/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.0 + 2.14.1 4.0.0 jar diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index 53690b5..160cca1 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.0 + 2.14.1 4.0.0 war diff --git a/pay-java-fuiou/pom.xml b/pay-java-fuiou/pom.xml index beb641d..183e19a 100644 --- a/pay-java-fuiou/pom.xml +++ b/pay-java-fuiou/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.0 + 2.14.1 4.0.0 pay-java-fuiou diff --git a/pay-java-payoneer/pom.xml b/pay-java-payoneer/pom.xml index fcf63bf..ebff04b 100644 --- a/pay-java-payoneer/pom.xml +++ b/pay-java-payoneer/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.0 + 2.14.1 4.0.0 pay-java-payoneer diff --git a/pay-java-paypal/pom.xml b/pay-java-paypal/pom.xml index 5926fa1..887ff3e 100644 --- a/pay-java-paypal/pom.xml +++ b/pay-java-paypal/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.0 + 2.14.1 4.0.0 diff --git a/pay-java-union/pom.xml b/pay-java-union/pom.xml index 7c412a0..d52eb4d 100644 --- a/pay-java-union/pom.xml +++ b/pay-java-union/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.0 + 2.14.1 4.0.0 diff --git a/pay-java-wx-youdian/pom.xml b/pay-java-wx-youdian/pom.xml index 2aee8c8..80500ae 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.14.0 + 2.14.1 4.0.0 pay-java-wx-youdian diff --git a/pay-java-wx/pom.xml b/pay-java-wx/pom.xml index 7aed12a..f68a0b6 100644 --- a/pay-java-wx/pom.xml +++ b/pay-java-wx/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.0 + 2.14.1 4.0.0 pay-java-wx diff --git a/pay-java-yiji/pom.xml b/pay-java-yiji/pom.xml index 5d0199a..0411dfd 100644 --- a/pay-java-yiji/pom.xml +++ b/pay-java-yiji/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.0 + 2.14.1 4.0.0 diff --git a/pom.xml b/pom.xml index 02e8d5e..3874de6 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.egzosn pay-java-parent pom - 2.14.0 + 2.14.1 Pay Java - Parent Pay Java Parent @@ -64,7 +64,7 @@ - 2.14.0 + 2.14.1 4.5.4 1.2.17 1.2.73 -- Gitee From 3e98301f6d171349b909135a22a9810f995936b6 Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 24 Feb 2021 22:49:09 +0800 Subject: [PATCH 042/165] 2.14.1 --- .../com/egzosn/pay/ali/api/AliPayService.java | 2 +- .../pay/ali/utils/AntCertificationUtil.java | 1 + pay-java-ali/src/test/java/PayTest.java | 2 +- .../egzosn/pay/baidu/api/BaiduPayService.java | 2 +- .../pay/baidu/bean/BaiduRefundOrder.java | 1 + .../pay/baidu/api/BaiduPayServiceTest.java | 2 +- .../pay/common/api/BasePayConfigStorage.java | 2 +- .../egzosn/pay/common/api/BasePayService.java | 2 +- .../com/egzosn/pay/common/api/Callback.java | 2 +- .../com/egzosn/pay/common/bean/BillType.java | 6 ++++-- .../egzosn/pay/common/bean/MethodType.java | 2 +- .../com/egzosn/pay/common/bean/SignType.java | 2 +- .../common/bean/outbuilder/JsonBuilder.java | 2 +- .../pay/common/bean/result/PayException.java | 2 +- .../pay/common/http/ClientHttpRequest.java | 2 +- .../pay/common/http/HttpConfigStorage.java | 2 +- .../pay/common/http/HttpRequestTemplate.java | 2 +- .../java/com/egzosn/pay/common/util/XML.java | 11 +++++----- .../pay/demo/controller/AliPayController.java | 5 +++-- .../demo/controller/FuiouPayController.java | 2 +- .../pay/demo/controller/PayController.java | 2 +- .../demo/controller/PayPalPayController.java | 2 +- .../controller/PayPalV2PayController.java | 2 +- .../demo/controller/UnionPayController.java | 2 +- .../pay/demo/controller/WxPayController.java | 4 ++-- .../pay/demo/dao/ApyAccountRepository.java | 10 ++++----- .../egzosn/pay/demo/entity/ApyAccount.java | 2 +- .../com/egzosn/pay/demo/entity/PayType.java | 6 +++--- .../egzosn/pay/demo/request/QueryOrder.java | 2 +- .../pay/demo/service/ApyAccountService.java | 2 +- .../egzosn/pay/demo/service/PayResponse.java | 2 +- .../interceptor/AliPayMessageInterceptor.java | 2 +- .../YoudianPayMessageInterceptor.java | 2 +- .../src/main/resources/apy_account.sql | 2 +- .../payoneer/api/PayoneerConfigStorage.java | 1 + .../pay/paypal/api/PayPalPayService.java | 2 +- .../egzosn/pay/paypal/bean/order/Error.java | 4 +--- .../egzosn/pay/paypal/bean/order/Links.java | 4 +--- .../pay/paypal/v2/api/PayPalPayService.java | 4 ++-- .../pay/union/api/UnionPayConfigStorage.java | 1 - .../pay/wx/youdian/bean/YdPayError.java | 2 +- .../com/egzosn/pay/wx/api/WxBillService.java | 2 +- .../com/egzosn/pay/wx/api/WxPayService.java | 12 +++++------ .../egzosn/pay/wx/bean/WxTransactionType.java | 21 ++++++++++++------- pay-java-wx/src/test/java/PayTest.java | 2 +- pom.xml | 2 ++ 46 files changed, 80 insertions(+), 73 deletions(-) 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 1fb432b..6e6792f 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 @@ -301,7 +301,7 @@ public class AliPayService extends BasePayService { */ private Map getPublicParameters(TransactionType transactionType) { Map orderInfo = new TreeMap<>(); - orderInfo.put("app_id", payConfigStorage.getAppid()); + orderInfo.put("app_id", payConfigStorage.getAppId()); orderInfo.put("method", transactionType.getMethod()); orderInfo.put("charset", payConfigStorage.getInputCharset()); orderInfo.put("timestamp", DateUtils.format(new Date())); 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 index 7dae2ac..e225431 100644 --- 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 @@ -54,6 +54,7 @@ public class AntCertificationUtil { * * @param certContent 需要验证的目标证书或者证书链 * @param rootCertContent 可信根证书列表 + * @return 是否校验成功 */ public static boolean isTrusted(String certContent, String rootCertContent) { X509Certificate[] certificates; diff --git a/pay-java-ali/src/test/java/PayTest.java b/pay-java-ali/src/test/java/PayTest.java index c098259..b22ba22 100644 --- a/pay-java-ali/src/test/java/PayTest.java +++ b/pay-java-ali/src/test/java/PayTest.java @@ -50,7 +50,7 @@ public class PayTest { AliPayConfigStorage aliPayConfigStorage = new AliPayConfigStorage(); aliPayConfigStorage.setPid("合作者id"); - aliPayConfigStorage.setAppid("应用id"); + aliPayConfigStorage.setAppId("应用id"); //普通公钥方式与证书公钥方式为两者取其一的方式 keyPublic(aliPayConfigStorage); // certKeyPublic(aliPayConfigStorage); 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 b94f00f..a1e3267 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 @@ -112,7 +112,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; } 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 91d6510..50974eb 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/test/java/com/egzosn/pay/baidu/api/BaiduPayServiceTest.java b/pay-java-baidu/src/test/java/com/egzosn/pay/baidu/api/BaiduPayServiceTest.java index 6f9b5c2..9cb7434 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 @@ -13,7 +13,7 @@ public class BaiduPayServiceTest { @Test public void orderInfo() { BaiduPayConfigStorage configStorage = new BaiduPayConfigStorage(); - configStorage.setAppid("APP ID"); + configStorage.setAppId("APP ID"); configStorage.setAppKey("APP KEY"); configStorage.setDealId("DEAL ID"); configStorage.setKeyPublic("KEY PUBLIC"); 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 b8b2b17..f813936 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 @@ -7,7 +7,7 @@ import java.util.concurrent.locks.Lock; /** * 支付基础配置存储 * - * @author: egan + * @author egan *
  *     email egzosn@gmail.com
  *     date 2017/3/5 20:33
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 43aa78e..38e00a9 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
@@ -31,7 +31,7 @@ import com.egzosn.pay.common.util.str.StringUtils;
 /**
  * 支付基础服务
  *
- * @author: egan
+ * @author egan
  * 
  *      email egzosn@gmail.com
  *      date 2017/3/5 20:36
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 dbb537b..4328942 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/bean/BillType.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/BillType.java
index bf65b8f..a85bb11 100644
--- 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
@@ -3,8 +3,10 @@ package com.egzosn.pay.common.bean;
 /**
  * 账单类型
  * @author Egan
- * @email egzosn@gmail.com
- * @date 2021/2/22
+ * 
+ * email egzosn@gmail.com
+ * date 2021/2/22
+ * 
*/ public interface BillType { /** 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 dd9f31b..72d9c9b 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 @@ -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/SignType.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/SignType.java
index 11230e8..4f9c159 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 726a2ed..179865e 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/result/PayException.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/result/PayException.java
index e54abc6..2e738ac 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/http/ClientHttpRequest.java b/pay-java-common/src/main/java/com/egzosn/pay/common/http/ClientHttpRequest.java
index f58b25f..8b6fcce 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
@@ -39,7 +39,7 @@ import com.egzosn.pay.common.util.str.StringUtils;
 /**
  * 一个HTTP请求的客户端
  *
- * @author: egan
+ * @author egan
  * 
  * email egzosn@gmail.com
  * date 2017/3/4 17:56
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 274b9d9..f5cfc70 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 ceaf45b..ae4ff73 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
@@ -36,7 +36,7 @@ import java.util.Map;
 
 /**
  * http请求工具
- * @author: egan
+ * @author egan
  *  
  * email egzosn@gmail.com 
* date 2017/3/3 21:33 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 675bdfd..5ebf06c 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 @@ -125,7 +125,7 @@ public class XML { */ 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"))) { @@ -207,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); } @@ -220,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(); } @@ -351,10 +350,10 @@ public class XML { private static void object2Xml(Object value, Document document, org.w3c.dom.Element element) { if (value instanceof Map) { - map2Xml((Map) value, document, element); + map2Xml((Map) value, document, element); } else if (value instanceof List) { - List vs = (List) value; + List vs = (List) value; for (Object v : vs) { object2Xml(v, document, element); } 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 77a25b9..acd961b 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 @@ -39,7 +39,7 @@ import com.egzosn.pay.demo.service.interceptor.AliPayMessageInterceptor; /** * 发起支付入口 * - * @author: egan + * @author egan * email egzosn@gmail.com * date 2016/11/18 0:25 */ @@ -81,7 +81,8 @@ public class AliPayController { public void init() { AliPayConfigStorage aliPayConfigStorage = new AliPayConfigStorage(); aliPayConfigStorage.setPid("2088102169916436"); - aliPayConfigStorage.setAppid("2016080400165436"); + 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=="); 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 e3e79a0..046001e 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 @@ -20,7 +20,7 @@ import java.util.UUID; /** * 发起支付入口 * - * @author: egan + * @author egan * email egzosn@gmail.com * date 2016/11/18 0:25 */ 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 7f1b916..0789efb 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 @@ -46,7 +46,7 @@ import com.egzosn.pay.wx.bean.WxTransactionType; /** * 发起支付入口 * - * @author: egan + * @author egan * email egzosn@gmail.com * date 2016/11/18 0:25 */ 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 a9b6a08..934b8b1 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 @@ -26,7 +26,7 @@ import com.egzosn.pay.paypal.bean.PayPalTransactionType; /** * 发起支付入口 * - * @author: egan + * @author egan * email egzosn@gmail.com * date 2018/05/06 10:30 */ 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 index 95abfd1..94917e7 100644 --- 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 @@ -27,7 +27,7 @@ import com.egzosn.pay.paypal.v2.bean.order.ShippingDetail; /** * 发起支付入口 * - * @author: egan + * @author egan * email egzosn@gmail.com * date 2018/05/06 10:30 */ 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 15a6b40..0b9493f 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 @@ -32,7 +32,7 @@ import static com.egzosn.pay.union.bean.UnionTransactionType.WEB; /** * 银联相关 * - * @author: egan + * @author egan * email egzosn@gmail.com * date 2016/11/18 0:25 */ 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 1269ebc..4b0b241 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 @@ -24,7 +24,7 @@ import java.util.UUID; /** * 发起支付入口 * - * @author: egan + * @author egan * email egzosn@gmail.com * date 2016/11/18 0:25 */ @@ -42,7 +42,7 @@ public class WxPayController { @PostConstruct public void init() { WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage(); - wxPayConfigStorage.setAppid("公众账号ID"); + wxPayConfigStorage.setAppId("公众账号ID"); wxPayConfigStorage.setMchId("合作者id(商户号)"); //以下两个参数在 服务商版模式中必填-------- 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 58cf5e2..b659dd7 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 @@ -13,7 +13,7 @@ import com.egzosn.pay.demo.entity.PayType; /** * 账户 * - * @author: egan + * @author egan * email egzosn@gmail.com * date 2016/11/18 1:21 */ @@ -29,7 +29,7 @@ public class ApyAccountRepository { 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=="); @@ -47,7 +47,7 @@ public class ApyAccountRepository { ApyAccount apyAccount2 = new ApyAccount(); apyAccount2.setPayId(2); apyAccount2.setPartner("1469188802"); - apyAccount2.setAppid("wx3344f4aed352deae"); + apyAccount2.setAppId("wx3344f4aed352deae"); // 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"); @@ -65,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"); @@ -106,7 +106,7 @@ public class ApyAccountRepository { 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); 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 ccc202b..236bdfa 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 @@ -7,7 +7,7 @@ package com.egzosn.pay.demo.entity; /** * 支付账户 * - * @author: egan + * @author egan * email egzosn@gmail.com * date 2016/11/18 0:36 */ 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 7437aa4..27fd489 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 @@ -51,7 +51,7 @@ public enum PayType implements BasePayType { //配置的附加参数的使用 configStorage.setAttach(apyAccount.getPayId()); configStorage.setPid(apyAccount.getPartner()); - configStorage.setAppid(apyAccount.getAppid()); + configStorage.setAppId(apyAccount.getAppId()); configStorage.setKeyPublic(apyAccount.getPublicKey()); configStorage.setKeyPrivate(apyAccount.getPrivateKey()); configStorage.setNotifyUrl(apyAccount.getNotifyUrl()); @@ -84,7 +84,7 @@ public enum PayType implements BasePayType { 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()); @@ -239,7 +239,7 @@ public enum PayType implements BasePayType { PayPalConfigStorage storage = new PayPalConfigStorage(); //配置的附加参数的使用 storage.setAttach(apyAccount.getPayId()); - storage.setClientID(apyAccount.getAppid()); + storage.setClientID(apyAccount.getAppId()); storage.setClientSecret(apyAccount.getPrivateKey()); storage.setTest(true); //发起付款后的页面转跳地址 diff --git a/pay-java-demo/src/main/java/com/egzosn/pay/demo/request/QueryOrder.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/request/QueryOrder.java index 38d814d..5e8579d 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 a55bde1..ea3b407 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 1be58b9..c085fe5 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 @@ -23,7 +23,7 @@ import com.egzosn.pay.demo.service.interceptor.YoudianPayMessageInterceptor; /** * 支付响应对象 * - * @author: egan + * @author egan * email egzosn@gmail.com * date 2016/11/18 0:34 */ 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 3d35c88..849dec1 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 efb8a07..02b4e05 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/apy_account.sql b/pay-java-demo/src/main/resources/apy_account.sql index 2180cf1..c248a8e 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-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 c93ec95..979eea2 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 @@ -100,6 +100,7 @@ public class PayoneerConfigStorage extends BasePayConfigStorage { /** * 获取 PayoneerPay API password + * @return PayoneerPay API password */ public String getApiPassword() { return getKeyPrivate(); 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 4a8d212..ae0e236 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 @@ -111,7 +111,7 @@ public class PayPalPayService extends BasePayService { if (payConfigStorage.isAccessTokenExpired()) { Map header = new HashMap<>(); - header.put("Authorization", "Basic " + authorizationString(getPayConfigStorage().getAppid(), getPayConfigStorage().getKeyPrivate())); + header.put("Authorization", "Basic " + authorizationString(getPayConfigStorage().getAppId(), getPayConfigStorage().getKeyPrivate())); header.put("Accept", "application/json"); header.put("Content-Type", "application/x-www-form-urlencoded"); try { 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 8543046..1207b88 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 @@ -40,9 +40,7 @@ public class Error { public Error() { } - /** - * Parameterized Constructor - */ + public Error(String name, String message, String informationLink, String debugId) { this.name = name; this.message = message; 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 55d6665..ebb7376 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/v2/api/PayPalPayService.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/api/PayPalPayService.java index acf3d07..10289dd 100644 --- 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 @@ -122,7 +122,7 @@ public class PayPalPayService extends BasePayService implem } if (payConfigStorage.isAccessTokenExpired()) { Map header = new HashMap<>(); - header.put("Authorization", "Basic " + authorizationString(getPayConfigStorage().getAppid(), getPayConfigStorage().getKeyPrivate())); + header.put("Authorization", "Basic " + authorizationString(getPayConfigStorage().getAppId(), getPayConfigStorage().getKeyPrivate())); header.put("Accept", "application/json"); header.put("Content-Type", "application/x-www-form-urlencoded"); try { @@ -153,7 +153,7 @@ public class PayPalPayService extends BasePayService implem * 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 + * @return 是否成功 true成功 */ @Override public boolean verify(Map params) { 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 5c2f2e6..010ed7d 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 @@ -9,7 +9,6 @@ import com.egzosn.pay.common.bean.CertStoreType; /** * @author Actinia - *

*

  *         email hayesfu@qq.com
  *           create 2017 2017/11/4 0004
diff --git a/pay-java-wx-youdian/src/main/java/com/egzosn/pay/wx/youdian/bean/YdPayError.java b/pay-java-wx-youdian/src/main/java/com/egzosn/pay/wx/youdian/bean/YdPayError.java
index b782a50..9aed247 100644
--- a/pay-java-wx-youdian/src/main/java/com/egzosn/pay/wx/youdian/bean/YdPayError.java
+++ b/pay-java-wx-youdian/src/main/java/com/egzosn/pay/wx/youdian/bean/YdPayError.java
@@ -20,7 +20,7 @@ package com.egzosn.pay.wx.youdian.bean;
 import com.egzosn.pay.common.bean.result.PayError;
 
 /**
- * @author: egan
+ * @author egan
  *
  * email egzosn@gmail.com
  * date 2017/3/6 19:41
diff --git a/pay-java-wx/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 06d9f05..09232df 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
@@ -8,7 +8,7 @@ import com.egzosn.pay.common.bean.BillType;
 /**
  * 账单接口
  *
- * @author: faymanwang
+ * @author faymanwang
  * email: 1057438332@qq.com
  * time: 2020/7/31 11:21
  */
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 b0016a1..f955647 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
@@ -277,14 +277,14 @@ public class WxPayService extends BasePayService implements
 
             if (WxTransactionType.JSAPI == order.getTransactionType()) {
                 params.put("signType", payConfigStorage.getSignType());
-                params.put("appId", payConfigStorage.getAppid());
+                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()) {
                 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));
@@ -824,7 +824,7 @@ public class WxPayService extends BasePayService implements
      */
     public 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());
         //默认不校验真实姓名
@@ -908,7 +908,7 @@ 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) {
@@ -948,7 +948,7 @@ 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) {
@@ -968,7 +968,7 @@ public class WxPayService extends BasePayService implements
     private void redPackParam(RedpackOrder redpackOrder, Map parameters) {
         parameters.put(NONCE_STR, SignUtils.randomStr());
         parameters.put(MCH_ID, payConfigStorage.getPid());
-        parameters.put("wxappid", payConfigStorage.getAppid());
+        parameters.put("wxappid", payConfigStorage.getAppId());
         parameters.put("send_name", redpackOrder.getSendName());
         parameters.put("re_openid", redpackOrder.getReOpenid());
         parameters.put("mch_billno", redpackOrder.getMchBillno());
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 740f9b5..a049754 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/test/java/PayTest.java b/pay-java-wx/src/test/java/PayTest.java index 4588157..8137df0 100644 --- a/pay-java-wx/src/test/java/PayTest.java +++ b/pay-java-wx/src/test/java/PayTest.java @@ -26,7 +26,7 @@ public class PayTest { public static void main(String[] args) { WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage(); - wxPayConfigStorage.setAppid("公众账号ID"); + wxPayConfigStorage.setAppId("公众账号ID"); wxPayConfigStorage.setMchId("合作者id(商户号)"); //以下两个参数在 服务商版模式中必填-------- diff --git a/pom.xml b/pom.xml index 3874de6..8c7479b 100644 --- a/pom.xml +++ b/pom.xml @@ -185,6 +185,7 @@ attach-javadocs + install jar @@ -198,6 +199,7 @@ attach-sources + install jar-no-fork -- Gitee From 66cceb06f593067c8fa01891510ba4e77da450c4 Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 24 Feb 2021 22:55:20 +0800 Subject: [PATCH 043/165] 2.14.1 --- pay-java-ali/README.md | 6 ++++-- pay-java-demo/README.md | 4 ++-- pay-java-wx/README.md | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pay-java-ali/README.md b/pay-java-ali/README.md index 0f27875..166131d 100644 --- a/pay-java-ali/README.md +++ b/pay-java-ali/README.md @@ -9,7 +9,8 @@ AliPayConfigStorage aliPayConfigStorage = new AliPayConfigStorage(); aliPayConfigStorage.setPid("合作者id"); - aliPayConfigStorage.setAppid("应用id"); + aliPayConfigStorage.setAppId("应用id"); +// aliPayConfigStorage.setAppAuthToken("ISV代商户代用,指定appAuthToken"); aliPayConfigStorage.setKeyPublic("支付宝公钥"); aliPayConfigStorage.setKeyPrivate("应用私钥"); aliPayConfigStorage.setNotifyUrl("异步回调地址"); @@ -27,7 +28,8 @@ AliPayConfigStorage aliPayConfigStorage = new AliPayConfigStorage(); aliPayConfigStorage.setPid("合作者id"); - aliPayConfigStorage.setAppid("应用id"); + aliPayConfigStorage.setAppId("应用id"); +// aliPayConfigStorage.setAppAuthToken("ISV代商户代用,指定appAuthToken"); aliPayConfigStorage.setKeyPrivate("应用私钥"); //设置为证书方式 aliPayConfigStorage.setCertSign(true); diff --git a/pay-java-demo/README.md b/pay-java-demo/README.md index d6b9a7e..869d81c 100644 --- a/pay-java-demo/README.md +++ b/pay-java-demo/README.md @@ -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()); diff --git a/pay-java-wx/README.md b/pay-java-wx/README.md index da29557..6012fe6 100644 --- a/pay-java-wx/README.md +++ b/pay-java-wx/README.md @@ -8,7 +8,7 @@ WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage(); wxPayConfigStorage.setMchId("合作者id(商户号)"); - wxPayConfigStorage.setAppid("应用id"); + wxPayConfigStorage.setAppId("应用id"); wxPayConfigStorage.setKeyPublic("转账公钥,转账时必填"); wxPayConfigStorage.setSecretKey("密钥"); wxPayConfigStorage.setNotifyUrl("异步回调地址"); -- Gitee From f517b8488ed67380455da8a86191d6ba2e2eb5c5 Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 24 Feb 2021 22:59:42 +0800 Subject: [PATCH 044/165] 2.14.1 --- .../java/com/egzosn/pay/demo/entity/ApyAccount.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 236bdfa..8aec899 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 @@ -24,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; // 应用私钥(生成签名) @@ -77,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() { @@ -178,7 +178,7 @@ public class ApyAccount { return "ApyAccount{" + "payId=" + payId + ", partner='" + partner + '\'' + - ", appid='" + appid + '\'' + + ", appId='" + appId + '\'' + ", publicKey='" + publicKey + '\'' + ", privateKey='" + privateKey + '\'' + ", notifyUrl='" + notifyUrl + '\'' + -- Gitee From 860a805c555cac1184154c827f30ab0180fb172d Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 24 Feb 2021 22:00:39 +0800 Subject: [PATCH 045/165] =?UTF-8?q?2.14.2=20=E5=BC=80=E5=A7=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pay-java-ali/pom.xml | 2 +- pay-java-baidu/pom.xml | 2 +- pay-java-common/pom.xml | 2 +- pay-java-demo/pom.xml | 2 +- pay-java-fuiou/pom.xml | 2 +- pay-java-payoneer/pom.xml | 2 +- pay-java-paypal/pom.xml | 2 +- pay-java-union/pom.xml | 2 +- pay-java-wx-youdian/pom.xml | 2 +- pay-java-wx/pom.xml | 2 +- pay-java-yiji/pom.xml | 2 +- pom.xml | 4 ++-- 12 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index a791d6f..7e4683a 100644 --- a/pay-java-ali/pom.xml +++ b/pay-java-ali/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.1 + 2.14.2-SNAPSHOT 4.0.0 pay-java-ali diff --git a/pay-java-baidu/pom.xml b/pay-java-baidu/pom.xml index d835c66..8861985 100644 --- a/pay-java-baidu/pom.xml +++ b/pay-java-baidu/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.1 + 2.14.2-SNAPSHOT 4.0.0 pay-java-baidu diff --git a/pay-java-common/pom.xml b/pay-java-common/pom.xml index 1082296..143d887 100644 --- a/pay-java-common/pom.xml +++ b/pay-java-common/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.1 + 2.14.2-SNAPSHOT 4.0.0 jar diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index 160cca1..78628fe 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.1 + 2.14.2-SNAPSHOT 4.0.0 war diff --git a/pay-java-fuiou/pom.xml b/pay-java-fuiou/pom.xml index 183e19a..4ba3acd 100644 --- a/pay-java-fuiou/pom.xml +++ b/pay-java-fuiou/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.1 + 2.14.2-SNAPSHOT 4.0.0 pay-java-fuiou diff --git a/pay-java-payoneer/pom.xml b/pay-java-payoneer/pom.xml index ebff04b..a8e8334 100644 --- a/pay-java-payoneer/pom.xml +++ b/pay-java-payoneer/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.1 + 2.14.2-SNAPSHOT 4.0.0 pay-java-payoneer diff --git a/pay-java-paypal/pom.xml b/pay-java-paypal/pom.xml index 887ff3e..ce4d4a7 100644 --- a/pay-java-paypal/pom.xml +++ b/pay-java-paypal/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.1 + 2.14.2-SNAPSHOT 4.0.0 diff --git a/pay-java-union/pom.xml b/pay-java-union/pom.xml index d52eb4d..c1ca7f9 100644 --- a/pay-java-union/pom.xml +++ b/pay-java-union/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.1 + 2.14.2-SNAPSHOT 4.0.0 diff --git a/pay-java-wx-youdian/pom.xml b/pay-java-wx-youdian/pom.xml index 80500ae..e88c55c 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.14.1 + 2.14.2-SNAPSHOT 4.0.0 pay-java-wx-youdian diff --git a/pay-java-wx/pom.xml b/pay-java-wx/pom.xml index f68a0b6..b0d3669 100644 --- a/pay-java-wx/pom.xml +++ b/pay-java-wx/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.1 + 2.14.2-SNAPSHOT 4.0.0 pay-java-wx diff --git a/pay-java-yiji/pom.xml b/pay-java-yiji/pom.xml index 0411dfd..2458fcf 100644 --- a/pay-java-yiji/pom.xml +++ b/pay-java-yiji/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.1 + 2.14.2-SNAPSHOT 4.0.0 diff --git a/pom.xml b/pom.xml index 8c7479b..018c8bf 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.egzosn pay-java-parent pom - 2.14.1 + 2.14.2-SNAPSHOT Pay Java - Parent Pay Java Parent @@ -64,7 +64,7 @@ - 2.14.1 + 2.14.2-SNAPSHOT 4.5.4 1.2.17 1.2.73 -- Gitee From e87e62c241b36ac33725097415f2e8f2e114a4c0 Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 24 Feb 2021 22:02:09 +0800 Subject: [PATCH 046/165] =?UTF-8?q?https://github.com/egzosn/pay-java-pare?= =?UTF-8?q?nt/issues/57=20=E6=97=A9=E6=9C=9FQUICK=5FWAP=5FWAY=E8=99=BD?= =?UTF-8?q?=E7=84=B6=E4=B9=9F=E5=8F=AF=E4=BB=A5=E8=BF=9B=E8=A1=8C=E6=94=AF?= =?UTF-8?q?=E4=BB=98=EF=BC=8C=E6=A0=B9=E6=8D=AE=E6=9C=80=E6=96=B0=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E8=B0=83=E6=95=B4=E4=B8=BAQUICK=5FWAP=5FPAY?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/egzosn/pay/ali/api/AliPayService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6e6792f..02d3608 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 @@ -246,7 +246,7 @@ public class AliPayService extends BasePayService { break; case WAP: bizContent.put(PASSBACK_PARAMS, order.getAddition()); - bizContent.put(PRODUCT_CODE, "QUICK_WAP_WAY"); + bizContent.put(PRODUCT_CODE, "QUICK_WAP_PAY"); setReturnUrl(orderInfo, order); break; case APP: -- Gitee From ec11b7af56c8652988eddb471091fd2f3acc6c83 Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 24 Feb 2021 22:02:31 +0800 Subject: [PATCH 047/165] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E9=80=80=E6=AC=BE?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E5=9B=9E=E8=B0=83=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/wx/api/WxPayService.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) 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 f955647..c1baeca 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 @@ -40,6 +40,7 @@ import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.common.api.BasePayService; import com.egzosn.pay.common.bean.BillType; import com.egzosn.pay.common.bean.MethodType; +import com.egzosn.pay.common.bean.Order; import com.egzosn.pay.common.bean.PayMessage; import com.egzosn.pay.common.bean.PayOrder; import com.egzosn.pay.common.bean.PayOutMessage; @@ -214,8 +215,7 @@ public class WxPayService extends BasePayService implements // 总金额单位为分 parameters.put("total_fee", Util.conversionCentAmount(order.getPrice())); setParameters(parameters, "attach", order.getAddition()); - parameters.put("notify_url", payConfigStorage.getNotifyUrl()); - setParameters(parameters, "notify_url", order); + initNotifyUrl(parameters, order); parameters.put("trade_type", order.getTransactionType().getType()); if (null != order.getExpirationTime()) { parameters.put("time_start", DateUtils.formatDate(new Date(), DateUtils.YYYYMMDDHHMMSS)); @@ -512,6 +512,12 @@ public class WxPayService extends BasePayService implements } + private Map initNotifyUrl(Map parameters, Order order) { + setParameters(parameters, "notify_url", payConfigStorage.getNotifyUrl()); + setParameters(parameters, "notify_url", order); + return parameters; + } + /** * 申请退款接口 * @@ -528,7 +534,7 @@ public class WxPayService extends BasePayService implements setParameters(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()); } @@ -778,12 +784,12 @@ public class WxPayService extends BasePayService implements * @param order 转账订单 *

      *
-     *                           注意事项:
-     *                           ◆ 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。
-     *                           ◆ XML具有可扩展性,因此返回参数可能会有新增,而且顺序可能不完全遵循此文档规范,如果在解析回包的时候发生错误,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新回包字段,会更新到此API文档中。
-     *                           ◆ 因为错误代码字段err_code的值后续可能会增加,所以商户如果遇到回包返回新的错误码,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新的错误码,会更新到此API文档中。
-     *                           ◆ 错误代码描述字段err_code_des只供人工定位问题时做参考,系统实现时请不要依赖这个字段来做自动化处理。
-     *                           
+ * 注意事项: + * ◆ 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。 + * ◆ XML具有可扩展性,因此返回参数可能会有新增,而且顺序可能不完全遵循此文档规范,如果在解析回包的时候发生错误,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新回包字段,会更新到此API文档中。 + * ◆ 因为错误代码字段err_code的值后续可能会增加,所以商户如果遇到回包返回新的错误码,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新的错误码,会更新到此API文档中。 + * ◆ 错误代码描述字段err_code_des只供人工定位问题时做参考,系统实现时请不要依赖这个字段来做自动化处理。 + *
* @return 对应的转账结果 */ @Override -- Gitee From 88ef6146f6ddac868baf336b91fc38bfaac15214 Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 24 Feb 2021 22:15:07 +0800 Subject: [PATCH 048/165] =?UTF-8?q?https://github.com/egzosn/pay-java-pare?= =?UTF-8?q?nt/issues/66=20=E6=94=AF=E4=BB=98=E5=AE=9D=E5=85=AC=E9=92=A5?= =?UTF-8?q?=E8=AF=81=E4=B9=A6=20verify=20=E6=8A=A5=E9=94=99=E9=9C=80?= =?UTF-8?q?=E8=A6=81=E5=BF=BD=E7=95=A5alipay=5Fcert=5Fsn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/egzosn/pay/ali/api/AliPayService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 02d3608..da0de8f 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 @@ -6,6 +6,7 @@ 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; @@ -130,7 +131,7 @@ public class AliPayService extends BasePayService { 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()); @@ -164,7 +165,7 @@ public class AliPayService extends BasePayService { * @return 支付宝公钥证书序列号 */ public String getAliPayCertSN(java.util.Map respMap) { - return (String) respMap.get(AliPayConst.ALIPAY_CERT_SN_FIELD); + return (String) respMap.get(ALIPAY_CERT_SN_FIELD); } /** -- Gitee From f4067a546ec2ba96075ea54a8973be619d970db7 Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 24 Feb 2021 22:20:17 +0800 Subject: [PATCH 049/165] =?UTF-8?q?https://github.com/egzosn/pay-java-pare?= =?UTF-8?q?nt/issues/66=20=E6=94=AF=E4=BB=98=E5=AE=9D=E5=85=AC=E9=92=A5?= =?UTF-8?q?=E8=AF=81=E4=B9=A6=20verify=20=E6=8A=A5=E9=94=99=E9=9C=80?= =?UTF-8?q?=E8=A6=81=E5=BF=BD=E7=95=A5alipay=5Fcert=5Fsn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/ali/api/AliPayService.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 da0de8f..dd6df3d 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 @@ -151,7 +151,7 @@ public class AliPayService extends BasePayService { * @param params 响应参数 * @return 公钥信息 */ - private String getKeyPublic(Map params) { + protected String getKeyPublic(Map params) { if (!payConfigStorage.isCertSign()) { return payConfigStorage.getKeyPublic(); } @@ -174,7 +174,7 @@ public class AliPayService extends BasePayService { * @param id 业务id, 数据的真实性. * @return true通过 */ - public boolean verifySource(String id) { + protected boolean verifySource(String id) { return true; } @@ -185,7 +185,7 @@ 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()); @@ -225,7 +225,7 @@ public class AliPayService extends BasePayService { * @return 返回支付宝预下单信息 * @see PayOrder 支付订单信息 */ - private Map getOrder(PayOrder order) { + protected Map getOrder(PayOrder order) { Map orderInfo = getPublicParameters(order.getTransactionType()); @@ -300,7 +300,7 @@ public class AliPayService extends BasePayService { * @param transactionType 交易类型 * @return 放回公共请求参数 */ - private Map getPublicParameters(TransactionType transactionType) { + protected Map getPublicParameters(TransactionType transactionType) { Map orderInfo = new TreeMap<>(); orderInfo.put("app_id", payConfigStorage.getAppId()); orderInfo.put("method", transactionType.getMethod()); @@ -316,7 +316,7 @@ public class AliPayService extends BasePayService { * * @param orderInfo 订单信息 */ - private void loadCertSn(Map orderInfo) { + protected void loadCertSn(Map orderInfo) { if (payConfigStorage.isCertSign()) { final CertEnvironment certEnvironment = payConfigStorage.getCertEnvironment(); setParameters(orderInfo, "app_cert_sn", certEnvironment.getMerchantCertSN()); @@ -492,7 +492,7 @@ public class AliPayService extends BasePayService { * @param attrs 订单属性 * @return 参数 */ - private void setAppAuthToken(Map parameters, Map attrs) { + protected void setAppAuthToken(Map parameters, Map attrs) { setAppAuthToken(parameters); setParameters(parameters, APP_AUTH_TOKEN, (String) attrs.remove(APP_AUTH_TOKEN)); } @@ -503,7 +503,7 @@ public class AliPayService extends BasePayService { * @param parameters 参数 * @return 参数 */ - private void setAppAuthToken(Map parameters) { + protected void setAppAuthToken(Map parameters) { setParameters(parameters, APP_AUTH_TOKEN, payConfigStorage.getAppAuthToken()); } -- Gitee From 05cd058be7c01a484b5e9de0d261b1acb6dd1c3e Mon Sep 17 00:00:00 2001 From: egzosn Date: Sat, 13 Mar 2021 15:45:39 +0800 Subject: [PATCH 050/165] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E8=BD=AC=E8=B4=A6?= =?UTF-8?q?=E5=88=B0=E9=9B=B6=E9=92=B1=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/egzosn/pay/wx/api/WxPayService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 c1baeca..f854859 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 @@ -876,14 +876,16 @@ public class WxPayService extends BasePayService implements 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")); } //如果类型为余额方式 if (TRANSFERS.getType().equals(wxTransferType) || GETTRANSFERINFO.getType().equals(wxTransferType)) { + parameters.put(APPID, payConfigStorage.getAppId()); + parameters.put(SIGN, createSign(SignUtils.parameterText(parameters, "&", SIGN), payConfigStorage.getInputCharset())); return getHttpRequestTemplate().postForObject(getReqUrl(GETTRANSFERINFO), XML.getMap2Xml(parameters), JSONObject.class); } + parameters.put(SIGN, createSign(SignUtils.parameterText(parameters, "&", SIGN), payConfigStorage.getInputCharset())); //默认查询银行卡的记录 return getHttpRequestTemplate().postForObject(getReqUrl(QUERY_BANK), XML.getMap2Xml(parameters), JSONObject.class); } -- Gitee From 91c586a4abc29ce8db46122a0740cdf1c2928679 Mon Sep 17 00:00:00 2001 From: egzosn Date: Thu, 18 Mar 2021 22:14:39 +0800 Subject: [PATCH 051/165] =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=AE=9D=E9=80=80?= =?UTF-8?q?=E6=AC=BE=E5=BA=8F=E5=88=97=E5=8C=96=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/egzosn/pay/ali/bean/AliRefundResult.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 index 6fbf661..65c6cbc 100644 --- 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 @@ -2,6 +2,7 @@ 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; @@ -92,7 +93,7 @@ public class AliRefundResult extends BaseRefundResult { * 只有在签约中指定需要返回资金明细,或者入参的query_options中指定时才返回该字段信息。 */ @JSONField(name = "refund_detail_item_list") - private TradeFundBill refundDetailItemList; + private List refundDetailItemList; /** * 交易在支付时候的门店名称 */ @@ -306,11 +307,11 @@ public class AliRefundResult extends BaseRefundResult { this.gmtRefundPay = gmtRefundPay; } - public TradeFundBill getRefundDetailItemList() { + public List getRefundDetailItemList() { return refundDetailItemList; } - public void setRefundDetailItemList(TradeFundBill refundDetailItemList) { + public void setRefundDetailItemList(List refundDetailItemList) { this.refundDetailItemList = refundDetailItemList; } -- Gitee From dfe5dc9f8e45c79091a22f6028bbc297f8c2879f Mon Sep 17 00:00:00 2001 From: egzosn Date: Thu, 18 Mar 2021 22:16:09 +0800 Subject: [PATCH 052/165] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E4=B8=80=E4=B8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 770ee41..ac55e55 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ # Compiled class file *.class - # Log file *.log -- Gitee From 1d6189d63abb80342c5f131fe540a0761ec77c09 Mon Sep 17 00:00:00 2001 From: Actinia-517 <412605202@qq.com> Date: Thu, 18 Mar 2021 22:22:26 +0800 Subject: [PATCH 053/165] =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=AE=9D=E9=80=80?= =?UTF-8?q?=E6=AC=BE=E5=BA=8F=E5=88=97=E5=8C=96=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ac55e55..770ee41 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Compiled class file *.class + # Log file *.log -- Gitee From a0df7d71f2407ef9b66d1785c7633df592e14d18 Mon Sep 17 00:00:00 2001 From: egzosn Date: Tue, 23 Mar 2021 12:20:17 +0800 Subject: [PATCH 054/165] =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/egzosn/pay/ali/api/AliPayService.java | 2 -- 1 file changed, 2 deletions(-) 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 dd6df3d..cd81c74 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 @@ -490,7 +490,6 @@ public class AliPayService extends BasePayService { * * @param parameters 参数 * @param attrs 订单属性 - * @return 参数 */ protected void setAppAuthToken(Map parameters, Map attrs) { setAppAuthToken(parameters); @@ -501,7 +500,6 @@ public class AliPayService extends BasePayService { * 设置支付宝授权Token * * @param parameters 参数 - * @return 参数 */ protected void setAppAuthToken(Map parameters) { setParameters(parameters, APP_AUTH_TOKEN, payConfigStorage.getAppAuthToken()); -- Gitee From cc9dcd7fc2219191dbb7d35b2ac897defdc081dd Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 24 Mar 2021 12:31:38 +0800 Subject: [PATCH 055/165] =?UTF-8?q?=E6=9B=BF=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pay-java-demo/src/main/webapp/gzh.png | Bin 131545 -> 37332 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/pay-java-demo/src/main/webapp/gzh.png b/pay-java-demo/src/main/webapp/gzh.png index bb9a069e24afa708eabced9fcc2b43bd3481c989..e607cb9ba904d1ac409680f6d8e7c282a0607ff8 100644 GIT binary patch literal 37332 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 literal 131545 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 -- Gitee From 74155a47138b9f2439bd6993f49496236378b568 Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 24 Mar 2021 12:40:35 +0800 Subject: [PATCH 056/165] =?UTF-8?q?=E6=9B=BF=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a3807e7..d1f0393 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ com.egzosn {module-name} - 2.14.1 + 2.14.1-b ``` -- Gitee From 66a9966e6ca1a13ad51e5b24ffd3f09d580fc393 Mon Sep 17 00:00:00 2001 From: Menjoe <1030009014@qq.com> Date: Mon, 12 Apr 2021 22:04:20 +0800 Subject: [PATCH 057/165] =?UTF-8?q?=E6=8E=A5=E9=80=9A=E7=99=BE=E5=BA=A6?= =?UTF-8?q?=E6=94=AF=E4=BB=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pay/baidu/api/BaiduPayConfigStorage.java | 39 ++--- .../egzosn/pay/baidu/api/BaiduPayService.java | 138 ++++++++++++++++-- .../pay/baidu/api/BaiduPayServiceTest.java | 2 +- 3 files changed, 149 insertions(+), 30 deletions(-) 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 7786b80..61ee1cd 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,13 +3,23 @@ package com.egzosn.pay.baidu.api; import com.egzosn.pay.common.api.BasePayConfigStorage; public class BaiduPayConfigStorage extends BasePayConfigStorage { - private String appId; + + private String appid; + private String dealId; + /** + * 支付平台公钥(签名校验使用) + */ + private String keyPublic; @Override - @Deprecated public String getAppid() { - return this.appId; + return this.appid; + } + + @Override + public String getAppId() { + return this.appid; } @Override @@ -17,9 +27,10 @@ public class BaiduPayConfigStorage extends BasePayConfigStorage { return getDealId(); } + //使用json序列化的时候会报错,所以不要直接抛出异常 @Override public String getSeller() { - throw new UnsupportedOperationException("不支持"); + return getDealId(); } public String getDealId() { @@ -31,33 +42,25 @@ public class BaiduPayConfigStorage extends BasePayConfigStorage { } 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); - } - - public void setAppid(String appId) { - this.appId = appId; + this.keyPublic = keyPublic; } - @Override - public String getAppId() { - return appId; + public void setAppid(String appid) { + this.appid = appid; } - 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 a1e3267..e234257 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,9 +1,13 @@ package com.egzosn.pay.baidu.api; +import java.io.UnsupportedEncodingException; import java.math.BigDecimal; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; +import java.net.URLEncoder; +import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.*; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; @@ -51,6 +55,13 @@ 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 static final String PAY_MONEY = "payMoney"; + + public BaiduPayService(BaiduPayConfigStorage payConfigStorage) { super(payConfigStorage); } @@ -68,12 +79,103 @@ public class BaiduPayService extends BasePayService { */ @Override public boolean verify(Map params) { - if (!RESPONSE_SUCCESS.equals(params.get(RESPONSE_STATUS))) { + 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))); + LOG.info("开始验证回调签名参数:" + params); + try { + boolean checkSign = this.checkReturnSign(params, payConfigStorage.getKeyPublic(), (String) params.get(RSA_SIGN)); + return checkSign; + } 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.getDecoder().decode(rsaSign.getBytes(CHARSET))); + LOG.info("使用公钥进行验签: " + verify); + return verify; + } catch (Exception e) { + LOG.info("使用公钥进行验签出错, 返回false", e); + } + return false; + } + + + /** + * 将公钥字符串进行Base64 decode之后,生成X509标准公钥 + * + * @param publicKey 公钥原始字符串 + * + * @return X509标准公钥 + * + * @throws InvalidKeySpecException + * @throws NoSuchAlgorithmException + */ + private static PublicKey getPublicKeyX509(String publicKey) throws InvalidKeySpecException, + NoSuchAlgorithmException, UnsupportedEncodingException { + if (StringUtils.isEmpty(publicKey)) { + return null; + } + KeyFactory keyFactory = KeyFactory.getInstance(SIGN_TYPE_RSA); + byte[] decodedKey = Base64.getDecoder().decode(publicKey.getBytes(CHARSET)); + return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey)); } + /** + * 对输入参数进行key过滤排序和字符串拼接 + * + * @param params 待签名参数集合 + * + * @return 待签名内容 + * + * @throws UnsupportedEncodingException + */ + private String signContent(Map params) throws UnsupportedEncodingException { + Map sortedParams = new TreeMap<>(Comparator.naturalOrder()); + 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); + } + + /** * 验证签名 * @@ -97,8 +199,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; } @@ -128,12 +231,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; } @@ -489,7 +595,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 = SignUtils.parameterText(result, "&", false, ignoreKeys); + return SignUtils.RSA.createSign(waitSignVal, payConfigStorage.getKeyPrivate(), payConfigStorage.getInputCharset()); } } 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 9cb7434..6f9b5c2 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 @@ -13,7 +13,7 @@ public class BaiduPayServiceTest { @Test public void orderInfo() { BaiduPayConfigStorage configStorage = new BaiduPayConfigStorage(); - configStorage.setAppId("APP ID"); + configStorage.setAppid("APP ID"); configStorage.setAppKey("APP KEY"); configStorage.setDealId("DEAL ID"); configStorage.setKeyPublic("KEY PUBLIC"); -- Gitee From 0f24b22a325ffa2bb51f74f967f6625db9f532a7 Mon Sep 17 00:00:00 2001 From: "menjoe.zhou@islide.cc" Date: Wed, 14 Apr 2021 18:34:58 +0800 Subject: [PATCH 058/165] =?UTF-8?q?1.=20=E6=8E=92=E9=99=A41.8=E7=89=B9?= =?UTF-8?q?=E6=80=A7=202.=20=E6=9B=BF=E6=8D=A2base64=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/baidu/api/BaiduPayService.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) 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 e234257..8275398 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 @@ -5,9 +5,9 @@ import java.math.BigDecimal; import java.net.URLEncoder; import java.security.*; import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.*; +import com.egzosn.pay.common.util.sign.encrypt.Base64; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; @@ -98,7 +98,7 @@ public class BaiduPayService extends BasePayService { Signature signature = Signature.getInstance(SIGN_ALGORITHMS); signature.initVerify(this.getPublicKeyX509(publicKey)); signature.update(content.getBytes(CHARSET)); - boolean verify = signature.verify(Base64.getDecoder().decode(rsaSign.getBytes(CHARSET))); + boolean verify = signature.verify(Base64.decode(rsaSign)); LOG.info("使用公钥进行验签: " + verify); return verify; } catch (Exception e) { @@ -119,12 +119,12 @@ public class BaiduPayService extends BasePayService { * @throws NoSuchAlgorithmException */ private static PublicKey getPublicKeyX509(String publicKey) throws InvalidKeySpecException, - NoSuchAlgorithmException, UnsupportedEncodingException { + NoSuchAlgorithmException { if (StringUtils.isEmpty(publicKey)) { return null; } KeyFactory keyFactory = KeyFactory.getInstance(SIGN_TYPE_RSA); - byte[] decodedKey = Base64.getDecoder().decode(publicKey.getBytes(CHARSET)); + byte[] decodedKey = Base64.decode(publicKey); return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey)); } @@ -138,7 +138,12 @@ public class BaiduPayService extends BasePayService { * @throws UnsupportedEncodingException */ private String signContent(Map params) throws UnsupportedEncodingException { - Map sortedParams = new TreeMap<>(Comparator.naturalOrder()); + 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)) { -- Gitee From dfc55e271aafe3dd6827c97f6bc786c639143712 Mon Sep 17 00:00:00 2001 From: "menjoe.zhou@islide.cc" Date: Thu, 15 Apr 2021 14:17:12 +0800 Subject: [PATCH 059/165] =?UTF-8?q?=E5=8E=BB=E9=99=A4=E4=B8=8D=E5=BF=85?= =?UTF-8?q?=E8=A6=81=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/egzosn/pay/baidu/api/BaiduPayService.java | 1 - 1 file changed, 1 deletion(-) 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 8275398..3845a11 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 @@ -59,7 +59,6 @@ public class BaiduPayService extends BasePayService { private static final String SIGN_ALGORITHMS = "SHA1WithRSA"; private static final String SIGN_TYPE_RSA = "RSA"; private static final String SIGN_KEY = "rsaSign"; - public static final String PAY_MONEY = "payMoney"; public BaiduPayService(BaiduPayConfigStorage payConfigStorage) { -- Gitee From 3c56125a3575b21af47b8193f2dc33c8c84a8cdd Mon Sep 17 00:00:00 2001 From: egzosn Date: Tue, 27 Apr 2021 22:35:05 +0800 Subject: [PATCH 060/165] =?UTF-8?q?=E5=B0=86=E6=94=AF=E4=BB=98=E5=AE=9Ddem?= =?UTF-8?q?o=E5=85=AC=E9=92=A5=E8=AF=81=E4=B9=A6=E6=A1=88=E4=BE=8B?= =?UTF-8?q?=EF=BC=8C=E7=94=B1=E7=BB=9D=E5=AF=B9=E8=B7=AF=E5=BE=84=E8=AF=81?= =?UTF-8?q?=E4=B9=A6=E5=AD=98=E5=82=A8=E6=96=B9=E5=BC=8F=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E4=B8=BAclassPath=E6=96=B9=E5=BC=8F=EF=BC=8C=E7=94=A8=E4=BA=8E?= =?UTF-8?q?=E7=9B=B4=E6=8E=A5=E8=BF=9B=E8=A1=8C=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++- pay-java-demo/pom.xml | 2 +- .../pay/demo/controller/AliPayController.java | 16 ++++++++-------- pom.xml | 5 +++++ 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d1f0393..70468d6 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,8 @@ 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) 也感谢各大友友同学帮忙进行接口测试 diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index 78628fe..c83932b 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -86,7 +86,7 @@ com.fasterxml.jackson.core jackson-databind - 2.11.2 + 2.9.10.6 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 acd961b..0933696 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 @@ -71,10 +71,10 @@ public class AliPayController { //设置为证书方式 aliPayConfigStorage.setCertSign(true); //设置证书存储方式,这里为路径 - aliPayConfigStorage.setCertStoreType(CertStoreType.PATH); - aliPayConfigStorage.setMerchantCert("E:\\pay-java-parent\\pay-java-demo\\src\\main\\resources\\ali\\alipayCertPublicKey_RSA2.crt"); - aliPayConfigStorage.setAliPayCert("E:\\pay-java-parent\\pay-java-demo\\src\\main\\resources\\ali\\alipayCertPublicKey_RSA2.crt"); - aliPayConfigStorage.setAliPayRootCert("E:\\pay-java-parent\\pay-java-demo\\src\\main\\resources\\ali\\alipayRootCert.crt"); + aliPayConfigStorage.setCertStoreType(CertStoreType.CLASS_PATH); + aliPayConfigStorage.setMerchantCert("ali/appCertPublicKey_2016080400165436.crt"); + aliPayConfigStorage.setAliPayCert("ali/alipayCertPublicKey_RSA2.crt"); + aliPayConfigStorage.setAliPayRootCert("ali/alipayRootCert.crt"); } @PostConstruct @@ -84,10 +84,10 @@ public class AliPayController { 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=="); +// 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.RSA2.name()); diff --git a/pom.xml b/pom.xml index 018c8bf..193a8d6 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,11 @@ hocgin@gmail.com https://github.com/hocgin + + Menjoe + 1030009014@qq.com + https://gitee.com/menjoe-z + scm:git:https://github.com/egzosn/pay-java-parent.git -- Gitee From 0fb2b8dba93807977db74ef7db7878d33bd67525 Mon Sep 17 00:00:00 2001 From: egzosn Date: Sun, 30 May 2021 20:37:04 +0800 Subject: [PATCH 061/165] =?UTF-8?q?=E9=93=B6=E8=81=94app=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E7=9A=84=E8=AF=B7=E6=B1=82=E8=BF=9E=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/demo/entity/PayType.java | 41 +++++++++---------- .../egzosn/pay/union/api/UnionPayService.java | 13 +++--- 2 files changed, 27 insertions(+), 27 deletions(-) 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 27fd489..dbfa77f 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 @@ -7,7 +7,7 @@ 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.TransactionType; -import com.egzosn.pay.common.http.HttpConfigStorage; +import com.egzosn.pay.common.util.sign.SignUtils; import com.egzosn.pay.demo.service.handler.WxPayMessageHandler; import com.egzosn.pay.fuiou.api.FuiouPayConfigStorage; import com.egzosn.pay.fuiou.api.FuiouPayService; @@ -47,27 +47,24 @@ 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.setInputCharset(apyAccount.getInputCharset()); - configStorage.setTest(apyAccount.isTest()); - //请求连接池配置 - HttpConfigStorage httpConfigStorage = new HttpConfigStorage(); - //最大连接数 - httpConfigStorage.setMaxTotal(20); - //默认的每个路由的最大连接数 - httpConfigStorage.setDefaultMaxPerRoute(10); - return new AliPayService(configStorage, httpConfigStorage); + AliPayConfigStorage aliPayConfigStorage = new AliPayConfigStorage(); + aliPayConfigStorage.setPid("2088102169916436"); + aliPayConfigStorage.setAppId("2016080400165436"); + aliPayConfigStorage.setCertSign(true); + //设置证书存储方式,这里为路径 + aliPayConfigStorage.setCertStoreType(CertStoreType.CLASS_PATH); + aliPayConfigStorage.setMerchantCert("ali/appCertPublicKey_2016080400165436.crt"); + aliPayConfigStorage.setAliPayCert("ali/alipayCertPublicKey_RSA2.crt"); + aliPayConfigStorage.setAliPayRootCert("ali/alipayRootCert.crt"); + aliPayConfigStorage.setKeyPrivate("MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCw7MD2Cwv/jnXssFjXnGx3JlGF57gJa2aYbJRV8MnNiPVpX4Ha+8ZjnQDhvkrWH4hHmzcujOr213HqloMpUSYBzCPiXGVRUUvdimejcHHTod7nI4g6nztzzfey/TXNDHmp7vY3pOIcjB0Zn0pkNAz2tKAFkqb4raHOqTB0QA0zD24Cn+26J2UJyYRcgeH0GtSQuUrm7yaGsuKakh+qtgWF6R71n5PMGOTQ5LH3i0WVHfCBkNGgJC6yC96HR4D7cosoyKD0+lp8UB/NVUWl7Tt/KLOgFUwh0GKSYFfv56O/VBV2+xqCGE4PlZESfVuOqz5vjjxzw3xDAUJrV8hSX/AJAgMBAAECggEBAKE0d3U4B4yo/2XUIH8EdgfykCFUSum6RFbpyBauORHfksyaSzV+ZvtomN8XhhSn0oJ8OMFfgM+86nz2+zdwSxMkMCYWTfLUAi4v59KRqAVO3kz4oS3Y3FDeAK3D7XuRvGFL7GgzAhtEx1cLPrsiehVn6s5pG15GxsIIgq/JlL1J88wn1zENLrVHmD6z/JpXvfb/RS1yR+5lyoohp4g0Ph9jJ3bCyUbRpK0QkPEzgAuWL0K2ITCL7PYHNAplI8d2xHHOLF9Qdjyx+ZrQ/RxtqzfyWzhqjsmp2qlgNCxWlt3woS9UhDB+nRvjEoWTJmIOszAMYuj8wGlX+3Ui3ALOdQECgYEA25EqnFPFinUnzgNvB6NYmh5STmZun6s4bUOLqwefKtEvrOtRwTu7sB7NIf37fizG3/MJUWHxiLy2/3ub4d2JxdDNBtJoEqnp6QB12qglCNa4CajdjtJa1dR81F9QvytsqEkmPYXFPPyviB0FcSIDAGMb3IbwvIfzBPY9WY8dJnECgYEAzkg3yKEFBZ8BU0WQ+3hyfKUoAhBEnxouxRSTBcXxwstJRiqaGTVe5aoJGQI+0xS7Z6q07XDtN2t97s6DnRLWbljsX6B64itzNhXRyzjdD3iZDU/KSw7khjhXf8XOZaj9eXmACDiUnkEn1xsM8bLiRGqB8y5f3aMY/RpuACGXnxkCgYEAx/zwT9Vpr1RIfjfYcJ+Su0X0994K0roUukj0tUJK8qf4gcsQ+y1aJe/YLib1ZBaKyj7G9O5+HmqtUAUZld/AdoJZzOXmz2EeYhD+R7wxh1xz4rCBpW3qOKvDS3jJxmZaIOoHv6/RWFxb0WGFrGcrTrX3EaWDLmWxr4pNlP5qsbECgYATllntrBR8/ycyEAX/SuWcHlaZM5BAh0zvm8+GGdCmDYWMqxjs0duL9URd4o+ynWJaKqR5c2KjA4r2tRdcP+Cqo7j2L5fbiAKtnQ7JvEGJaYsm72+nBuf+MrVkRZUepBhFg5r7rNu31zoAO+pTvQetNWvXeozRz93ckrjlPEtYaQKBgQDFwbV92rlRMLjZzlY+o0knoeJBjPQmPdiBTpGNimdy9L4c2Ure7affjcUiYhkKqrK5k5SScJTATgyQ7JF346FdtUtZ/6Kkj1RwJmmprPrDa9CATLoTle7g9OVd4sHT2ITHZMzPaF3ILvzcwJ70AD1xcxCQb+/7sDPmw7Mc8gOA7Q=="); + aliPayConfigStorage.setNotifyUrl("http://pay.egzosn.com/payBack.json"); + aliPayConfigStorage.setReturnUrl("http://pay.egzosn.com/payBack.html"); + aliPayConfigStorage.setSignType(SignUtils.RSA2.name()); + aliPayConfigStorage.setSeller("2088102169916436"); + aliPayConfigStorage.setInputCharset("utf-8"); + //是否为测试账号,沙箱环境 + aliPayConfigStorage.setTest(true); + return new AliPayService(aliPayConfigStorage); } @Override 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 37c3af6..d82fd78 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 @@ -149,6 +149,9 @@ public class UnionPayService extends BasePayService { public String getBackTransUrl() { 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()); @@ -398,9 +401,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)); @@ -430,7 +433,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))) { //成功 @@ -450,7 +453,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; } @@ -539,7 +542,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号 -- Gitee From d40efb21c52da23ff240bbaaf59dd6b4ee5ab36e Mon Sep 17 00:00:00 2001 From: egzosn Date: Sun, 30 May 2021 20:51:16 +0800 Subject: [PATCH 062/165] =?UTF-8?q?=E5=88=87=E6=8D=A2=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pay/ali/utils/AntCertificationUtil.java | 6 ++--- pay-java-common/pom.xml | 5 +++-- .../egzosn/pay/common/api/BasePayService.java | 11 +++++----- .../common/api/DefaultPayMessageHandler.java | 11 +++++----- .../pay/common/api/PayMessageRouter.java | 6 ++--- .../pay/common/http/ClientHttpRequest.java | 6 ++--- .../pay/common/http/HttpRequestTemplate.java | 12 +++++----- .../egzosn/pay/common/http/UriVariables.java | 8 +++---- .../com/egzosn/pay/common/util/DateUtils.java | 8 +++---- .../pay/common/util/LogExceptionHandler.java | 8 +++---- .../pay/common/util/sign/CertDescriptor.java | 6 ++--- .../pay/common/util/sign/SecureUtil.java | 12 +++++----- .../pay/common/util/sign/SignUtils.java | 12 +++++----- .../pay/common/util/sign/encrypt/RSA.java | 22 +++++++++---------- .../pay/paypal/v2/api/PayPalPayService.java | 2 +- .../egzosn/pay/union/api/UnionPayService.java | 6 ++--- .../com/egzosn/pay/wx/api/WxPayService.java | 2 +- 17 files changed, 73 insertions(+), 70 deletions(-) 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 index e225431..b3057cd 100644 --- 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 @@ -24,8 +24,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.bouncycastle.jce.provider.BouncyCastleProvider; import com.egzosn.pay.common.bean.result.PayException; @@ -44,7 +44,7 @@ import com.egzosn.pay.common.util.str.StringUtils; * */ public class AntCertificationUtil { - private static final Log LOGGER = LogFactory.getLog(AntCertificationUtil.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AntCertificationUtil.class); static { Security.removeProvider("SunEC"); Security.addProvider(new BouncyCastleProvider()); diff --git a/pay-java-common/pom.xml b/pay-java-common/pom.xml index 143d887..fb24df8 100644 --- a/pay-java-common/pom.xml +++ b/pay-java-common/pom.xml @@ -28,8 +28,9 @@ - log4j - log4j + org.slf4j + slf4j-api + 1.7.30 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 38e00a9..4e0a2aa 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 @@ -11,8 +11,9 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.alibaba.fastjson.JSON; import com.egzosn.pay.common.bean.MethodType; @@ -38,7 +39,7 @@ import com.egzosn.pay.common.util.str.StringUtils; * */ 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; @@ -112,7 +113,7 @@ public abstract class BasePayService implements Pay 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); + LOG.error("", e); } return base64ClientID; @@ -202,7 +203,7 @@ public abstract class BasePayService implements Pay } } catch (UnsupportedEncodingException e) { - LOG.error(e); + LOG.error("", e); } } params.put(name, valueStr); 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 496b7a3..0f136f5 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/PayMessageRouter.java b/pay-java-common/src/main/java/com/egzosn/pay/common/api/PayMessageRouter.java index 2fe8825..22817b7 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 @@ -8,8 +8,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.egzosn.pay.common.bean.PayMessage; import com.egzosn.pay.common.bean.PayOutMessage; @@ -44,8 +44,8 @@ import com.egzosn.pay.common.util.LogExceptionHandler; * @author egan */ public class PayMessageRouter { + protected final Logger LOG = LoggerFactory.getLogger(PayMessageRouter.class); - protected final Log LOG = LogFactory.getLog(PayMessageRouter.class); /** * 异步线程大小 */ 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 8b6fcce..49681ed 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 @@ -9,8 +9,8 @@ import java.net.URI; import java.nio.charset.Charset; import java.util.Map; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.apache.http.Consts; import org.apache.http.Header; import org.apache.http.HttpEntity; @@ -46,7 +46,7 @@ import com.egzosn.pay.common.util.str.StringUtils; * */ 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); 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 ae4ff73..b7b1ed3 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 @@ -4,8 +4,8 @@ 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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; @@ -44,7 +44,7 @@ import java.util.Map; */ public class HttpRequestTemplate { - protected static final Log LOG = LogFactory.getLog(HttpRequestTemplate.class); + protected static final Logger LOG = LoggerFactory.getLogger(HttpRequestTemplate.class); protected CloseableHttpClient httpClient; @@ -123,7 +123,7 @@ public class HttpRequestTemplate { try { return sslsf = new SSLConnectionSocketFactory(SSLContext.getDefault()); } catch (NoSuchAlgorithmException e) { - LOG.error(e); + LOG.error("", e); } } @@ -149,9 +149,9 @@ public class HttpRequestTemplate { return sslsf; } catch (IOException e) { - LOG.error(e); + LOG.error("", e); } catch (GeneralSecurityException e) { - LOG.error(e); + LOG.error("", e); } return null; 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 b8bb0c0..8f59338 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 @@ -5,8 +5,8 @@ import java.net.URLEncoder; import java.util.List; import java.util.Map; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.common.bean.result.PayException; @@ -22,7 +22,7 @@ import com.egzosn.pay.common.exception.PayErrorException; * */ public final class UriVariables { - private static final Log LOG = LogFactory.getLog(UriVariables.class); + private static final Logger LOG = LoggerFactory.getLogger(UriVariables.class); private UriVariables() { } @@ -213,7 +213,7 @@ public final class UriVariables { return URLEncoder.encode(str, enc); } catch (UnsupportedEncodingException e) { - LOG.error(e); + LOG.error("", e); } return str; } 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 5cd5111..daa6747 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 @@ -8,8 +8,8 @@ import java.util.HashMap; import java.util.Map; import java.util.TimeZone; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.apache.http.util.Args; /** @@ -22,7 +22,7 @@ import org.apache.http.util.Args; * */ public final class DateUtils { - private static final Log LOG = LogFactory.getLog(DateUtils.class); + private static final Logger LOG = LoggerFactory.getLogger(DateUtils.class); private DateUtils() { } @@ -81,7 +81,7 @@ public final class DateUtils { return formatFor.parse(date); } catch (ParseException e) { - LOG.error(e); + LOG.error("", e); } return null; } 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 35550c5..01a1c46 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/sign/CertDescriptor.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/CertDescriptor.java index 3577a11..2419571 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 @@ -15,8 +15,8 @@ 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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -35,7 +35,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); /** * 证书容器,存储对商户请求报文签名私钥证书. */ 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 index 22839a4..24093c3 100644 --- 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 @@ -6,8 +6,8 @@ import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.Signature; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.egzosn.pay.common.bean.result.PayException; import com.egzosn.pay.common.exception.PayErrorException; @@ -15,7 +15,7 @@ import com.egzosn.pay.common.util.sign.encrypt.sm3.SM3Digest; public class SecureUtil { - private static final Log LOG = LogFactory.getLog(SecureUtil.class); + private static final Logger LOG = LoggerFactory.getLogger(SecureUtil.class); /** * 算法常量: SHA1 */ @@ -49,7 +49,7 @@ public class SecureUtil { return md.digest(); } catch (NoSuchAlgorithmException e) { - LOG.error(e); + LOG.error("", e); return null; } } @@ -77,7 +77,7 @@ public class SecureUtil { return sha1StrBuff.toString().getBytes(encoding); } catch (UnsupportedEncodingException e) { - LOG.error(e); + LOG.error("", e); return null; } } @@ -139,7 +139,7 @@ public class SecureUtil { bytes = SecureUtil.sm3(data.getBytes(encoding)); } catch (UnsupportedEncodingException e) { - LOG.error(e); + LOG.error("", e); } StringBuilder sm3StrBuff = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { 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 07957ea..4c3f106 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 @@ -17,8 +17,8 @@ import java.util.UUID; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.apache.http.message.BasicNameValuePair; import com.egzosn.pay.common.bean.SignType; @@ -93,13 +93,13 @@ public enum SignUtils implements SignType { return sb.toString().toUpperCase(); } catch (NoSuchAlgorithmException e) { - LOG.error(e); + LOG.error("", e); } catch (InvalidKeyException e) { - LOG.error(e); + LOG.error("", e); } catch (UnsupportedEncodingException e) { - LOG.error(e); + LOG.error("", e); } throw new PayErrorException(new PayException("fail", "HMACSHA256 签名异常")); @@ -177,7 +177,7 @@ public enum SignUtils implements SignType { return com.egzosn.pay.common.util.sign.encrypt.RSA2.verify(text, sign, publicKey, characterEncoding); } }; - private static final Log LOG = LogFactory.getLog(SignUtils.class); + private static final Logger LOG = LoggerFactory.getLogger(SignUtils.class); /** * 把数组所有元素排序,并按照“参数=参数值”的模式用“@param separator”字符拼接成字符串 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 e1c0490..ec060ac 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 @@ -17,8 +17,8 @@ import java.security.spec.X509EncodedKeySpec; import javax.crypto.Cipher; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * RSA @@ -29,7 +29,7 @@ import org.apache.commons.logging.LogFactory; * */ public class RSA { - private static final Log LOG = LogFactory.getLog(RSA.class); + private static final Logger LOG = LoggerFactory.getLogger(RSA.class); private static final String ALGORITHM = "RSA"; @@ -61,10 +61,10 @@ public class RSA { return Base64.encode(signed); } catch (GeneralSecurityException e) { - LOG.error(e); + LOG.error("", e); } catch (UnsupportedEncodingException e) { - LOG.error(e); + LOG.error("", e); } return null; @@ -89,10 +89,10 @@ public class RSA { return Base64.encode(signed); } catch (GeneralSecurityException e) { - LOG.error(e); + LOG.error("", e); } catch (UnsupportedEncodingException e) { - LOG.error(e); + LOG.error("", e); } return null; @@ -142,10 +142,10 @@ public class RSA { return signature.verify(Base64.decode(sign)); } catch (GeneralSecurityException e) { - LOG.error(e); + LOG.error("", e); } catch (IOException e) { - LOG.error(e); + LOG.error("", e); } return false; } @@ -168,10 +168,10 @@ public class RSA { return signature.verify(Base64.decode(sign)); } catch (GeneralSecurityException e) { - LOG.error(e); + LOG.error("", e); } catch (IOException e) { - LOG.error(e); + LOG.error("", e); } return false; } 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 index 10289dd..3fd521f 100644 --- 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 @@ -528,7 +528,7 @@ public class PayPalPayService extends BasePayService implem } } catch (UnsupportedEncodingException e) { - LOG.error(e); + LOG.error("", e); } } params.put(name, valueStr); 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 d82fd78..6fde8a7 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 @@ -115,7 +115,7 @@ public class UnionPayService extends BasePayService { certDescriptor.initRootCert(payConfigStorage.getAcpRootCertInputStream()); } catch (IOException e) { - LOG.error(e); + LOG.error("", e); } @@ -386,10 +386,10 @@ public class UnionPayService extends BasePayService { LOG.error("verify certificate chain fail.", e); } catch (CertificateExpiredException e) { - LOG.error(e); + LOG.error("", e); } catch (GeneralSecurityException e) { - LOG.error(e); + LOG.error("", e); } return null; } 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 f854859..fb4c039 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 @@ -148,7 +148,7 @@ public class WxPayService extends BasePayService implements return signVerify(params, (String) params.get(SIGN)); } catch (PayErrorException e) { - LOG.error(e); + LOG.error("", e); } return false; } -- Gitee From 4d1ad05fa8d6dd2d24c271aafda86c20628da4b1 Mon Sep 17 00:00:00 2001 From: egan Date: Sun, 1 Aug 2021 23:33:02 +0800 Subject: [PATCH 063/165] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E8=BF=87=E6=97=B6?= =?UTF-8?q?=E6=96=B9=E6=B3=95=EF=BC=8C=E5=B7=A5=E5=85=B7=E7=B1=BB=E8=BF=81?= =?UTF-8?q?=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/ali/api/AliPayService.java | 12 ++-- .../egzosn/pay/baidu/api/BaiduPayService.java | 31 ++++----- .../com/egzosn/pay/baidu/package-info.java | 1 - .../egzosn/pay/common/api/BasePayService.java | 19 +++--- .../com/egzosn/pay/common/api/PayService.java | 15 +---- .../common/exception/PayErrorException.java | 5 ++ .../pay/demo/controller/PayController.java | 6 +- .../demo/controller/UnionPayController.java | 11 ++-- .../pay/demo/dao/ApyAccountRepository.java | 2 +- .../egzosn/pay/fuiou/api/FuiouPayService.java | 24 ++----- .../pay/payoneer/api/PayoneerPayService.java | 13 +--- .../pay/paypal/api/PayPalPayService.java | 5 -- .../pay/paypal/v2/api/PayPalPayService.java | 8 --- .../egzosn/pay/union/api/UnionPayService.java | 29 ++++----- pay-java-union/src/test/java/PayTest.java | 2 +- .../wx/youdian/api/WxYouDianPayService.java | 36 +++++------ pay-java-wx/README.md | 2 +- .../com/egzosn/pay/wx/api/WxPayService.java | 63 ++++++++----------- .../egzosn/pay/yiji/api/YiJiPayService.java | 17 +---- 19 files changed, 113 insertions(+), 188 deletions(-) delete mode 100644 pay-java-baidu/src/main/java/com/egzosn/pay/baidu/package-info.java 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 cd81c74..1e94971 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 @@ -46,6 +46,7 @@ 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; @@ -94,7 +95,6 @@ public class AliPayService extends BasePayService { public AliPayService(AliPayConfigStorage payConfigStorage, HttpConfigStorage configStorage) { super(payConfigStorage, configStorage); - payConfigStorage.loadCertEnvironment(); } public AliPayService(AliPayConfigStorage payConfigStorage) { @@ -187,7 +187,7 @@ public class AliPayService extends BasePayService { */ 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; @@ -566,9 +566,7 @@ public class AliPayService extends BasePayService { * @param billType 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; * @return 返回支付方下载对账单的结果 */ - @Deprecated - @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); } @@ -614,7 +612,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())); } @@ -627,7 +625,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); } /** 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 3845a11..7d0fcde 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 @@ -3,11 +3,17 @@ package com.egzosn.pay.baidu.api; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.net.URLEncoder; -import java.security.*; +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.*; -import com.egzosn.pay.common.util.sign.encrypt.Base64; +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; @@ -30,7 +36,9 @@ 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; @@ -85,7 +93,8 @@ public class BaiduPayService extends BasePayService { try { boolean checkSign = this.checkReturnSign(params, payConfigStorage.getKeyPublic(), (String) params.get(RSA_SIGN)); return checkSign; - } catch (Exception e) { + } + catch (Exception e) { LOG.info("验签失败", e); } return false; @@ -100,7 +109,8 @@ public class BaiduPayService extends BasePayService { boolean verify = signature.verify(Base64.decode(rsaSign)); LOG.info("使用公钥进行验签: " + verify); return verify; - } catch (Exception e) { + } + catch (Exception e) { LOG.info("使用公钥进行验签出错, 返回false", e); } return false; @@ -111,9 +121,7 @@ public class BaiduPayService extends BasePayService { * 将公钥字符串进行Base64 decode之后,生成X509标准公钥 * * @param publicKey 公钥原始字符串 - * * @return X509标准公钥 - * * @throws InvalidKeySpecException * @throws NoSuchAlgorithmException */ @@ -131,9 +139,7 @@ public class BaiduPayService extends BasePayService { * 对输入参数进行key过滤排序和字符串拼接 * * @param params 待签名参数集合 - * * @return 待签名内容 - * * @throws UnsupportedEncodingException */ private String signContent(Map params) throws UnsupportedEncodingException { @@ -172,7 +178,6 @@ public class BaiduPayService extends BasePayService { * 非空、且非签名字段 * * @param key 待签名参数key值 - * * @return true | false */ private static boolean legalKey(String key) { @@ -520,9 +525,7 @@ public class BaiduPayService extends BasePayService { * @param accessToken 用户token * @return 对账单 */ - @Deprecated - @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())); } @@ -609,7 +612,7 @@ public class BaiduPayService extends BasePayService { LOG.info("百度支付签名参数:" + JSON.toJSONString(result)); - String waitSignVal = SignUtils.parameterText(result, "&", false, ignoreKeys); + 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/package-info.java b/pay-java-baidu/src/main/java/com/egzosn/pay/baidu/package-info.java deleted file mode 100644 index e9370e2..0000000 --- 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-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 4e0a2aa..187cbed 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 @@ -11,11 +11,11 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.fastjson.JSON; +import com.egzosn.pay.common.bean.BillType; import com.egzosn.pay.common.bean.MethodType; import com.egzosn.pay.common.bean.Order; import com.egzosn.pay.common.bean.PayMessage; @@ -252,7 +252,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)); } /** @@ -264,7 +264,7 @@ public abstract class BasePayService implements Pay */ @Override public Map cancel(String tradeNo, String outTradeNo) { - return Collections.EMPTY_MAP; + return Collections.emptyMap(); } @@ -297,19 +297,16 @@ 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)); + public Map downloadBill(Date billDate, String billType){ + return Collections.emptyMap(); } - /** * 转账 * 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 fa8df25..bf5256f 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 @@ -276,11 +276,10 @@ public interface PayService { * 下载对账单 * * @param billDate 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 - * @param billType 账单类型 + * @param billType 账单类型 内部自动转化 {@link BillType} * @return 返回支付方下载对账单的结果 */ - @Deprecated - Map downloadbill(Date billDate, String billType); + Map downloadBill(Date billDate, String billType); /** * 下载对账单 @@ -291,16 +290,6 @@ public interface PayService { */ Map downloadBill(Date billDate, BillType billType); - /** - * 下载对账单 - * - * @param billDate 账单时间:具体请查看对应支付平台 - * @param billType 账单类型,具体请查看对应支付平台 - * @param callback 处理器 - * @param 返回类型 - * @return 返回支付方下载对账单的结果 - */ - T downloadbill(Date billDate, String billType, Callback callback); /** 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 3b077b4..6362978 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-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 0789efb..d6aa4d0 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 @@ -460,11 +460,11 @@ 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()); + return payResponse.getService().downloadBill(order.getBillDate(), order.getBillType()); } 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 0b9493f..5cb3135 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 @@ -8,6 +8,7 @@ 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; @@ -130,7 +131,7 @@ public class UnionPayController { public Map app() { Map data = new HashMap<>(); data.put("code", 0); - PayOrder order = new PayOrder("订单title", "摘要", BigDecimal.valueOf(0.01), SignUtils.randomStr()); + PayOrder order = new PayOrder("订单title", "摘要", BigDecimal.valueOf(0.01), SignTextUtils.randomStr()); //App支付 order.setTransactionType(UnionTransactionType.APP); @@ -178,7 +179,7 @@ public class UnionPayController { public Map microPay(BigDecimal price, String authCode) { //获取对应的支付账户操作工具(可根据账户id) //条码付 - PayOrder order = new PayOrder("egan order", "egan order", null == price ? BigDecimal.valueOf(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); //支付结果 @@ -275,9 +276,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/dao/ApyAccountRepository.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/dao/ApyAccountRepository.java index b659dd7..067a180 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 @@ -47,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"); 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 8bae0c2..bb16674 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 @@ -22,6 +22,7 @@ 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; @@ -141,7 +142,7 @@ public class FuiouPayService extends BasePayService { */ 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); } @@ -156,7 +157,7 @@ public class FuiouPayService extends BasePayService { 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) { return false; @@ -178,7 +179,7 @@ public class FuiouPayService extends BasePayService { } 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; } @@ -381,7 +382,7 @@ public class FuiouPayService extends BasePayService { LinkedHashMap params = new LinkedHashMap<>(); params.put("mchnt_cd", payConfigStorage.getPid()); params.put("order_id", outTradeNo); - 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); return resultJson; } @@ -420,7 +421,7 @@ 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 FuiouRefundResult.create(resultJson); } @@ -438,19 +439,6 @@ public class FuiouPayService extends BasePayService { } - /** - * 下载对账单 - * - * @param billDate 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 - * @param billType 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; - * @return 空 - */ - @Deprecated - @Override - public Map downloadbill(Date billDate, String billType) { - return Collections.emptyMap(); - } - /** * 下载对账单 * 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 e706314..5561b40 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 @@ -374,18 +374,7 @@ public class PayoneerPayService extends BasePayService im return Collections.emptyMap(); } - /** - * 下载对账单 - * - * @param billDate 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 - * @param billType 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; - * @return 返回支付方下载对账单的结果 - */ - @Deprecated - @Override - public Map downloadbill(Date billDate, String billType) { - return Collections.emptyMap(); - } + /** * 下载对账单 * 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 ae0e236..9da31e2 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 @@ -348,11 +348,6 @@ public class PayPalPayService extends BasePayService { return resp; } - @Deprecated - @Override - public Map downloadbill(Date billDate, String billType) { - return Collections.emptyMap(); - } @Override 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 index 3fd521f..898b35c 100644 --- 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 @@ -439,7 +439,6 @@ public class PayPalPayService extends BasePayService implem * } * * - * */ @Override public Map getCapture(String captureId) { @@ -491,13 +490,6 @@ public class PayPalPayService extends BasePayService implem return resp; } - @Deprecated - @Override - public Map downloadbill(Date billDate, String billType) { - return Collections.emptyMap(); - } - - public Map downloadBill(Date billDate, BillType billType) { return Collections.emptyMap(); } 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 6fde8a7..1c10645 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 @@ -41,6 +41,7 @@ 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; @@ -80,7 +81,7 @@ public class UnionPayService extends BasePayService { /** * 证书解释器 */ - private CertDescriptor certDescriptor; + private volatile CertDescriptor certDescriptor; /** * 构造函数 @@ -103,12 +104,13 @@ public class UnionPayService extends BasePayService { */ @Override public UnionPayService setPayConfigStorage(UnionPayConfigStorage payConfigStorage) { - super.setPayConfigStorage(payConfigStorage); - if (!payConfigStorage.isCertSign() || null != certDescriptor) { + this.payConfigStorage = payConfigStorage; + if (!payConfigStorage.isCertSign()) { return this; } - - certDescriptor = new CertDescriptor(); + if (null == certDescriptor) { + certDescriptor = new CertDescriptor(); + } try { certDescriptor.initPrivateSignCert(payConfigStorage.getKeyPrivateCertInputStream(), payConfigStorage.getKeyPrivateCertPwd(), "PKCS12"); certDescriptor.initPublicCert(payConfigStorage.getAcpMiddleCertInputStream()); @@ -149,6 +151,7 @@ public class UnionPayService extends BasePayService { public String getBackTransUrl() { return String.format(BACK_TRANS_URL, getReqUrl()); } + public String getAppTransUrl() { return String.format(APP_TRANS_URL, getReqUrl()); } @@ -217,7 +220,7 @@ public class UnionPayService extends BasePayService { 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()); @@ -322,20 +325,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; @@ -401,7 +404,7 @@ public class UnionPayService extends BasePayService { * @return 返回支付结果 */ - public JSONObject postOrder(PayOrder order, String url) { + public JSONObject postOrder(PayOrder order, String url) { Map params = orderInfo(order); String responseStr = getHttpRequestTemplate().postForObject(url, params, String.class); JSONObject response = UriVariables.getParametersToMap(responseStr); @@ -666,10 +669,8 @@ public class UnionPayService extends BasePayService { * @param fileType 文件类型 文件类型,一般商户填写00即可 * @return 返回fileContent 请自行将数据落地 */ - @Deprecated - @Override - public Map downloadbill(Date billDate, String fileType) { - return downloadBill(billDate, new UnionPayBillType(fileType)); + public Map downloadBill(Date billDate, String fileType) { + return downloadBill(billDate, new UnionPayBillType(fileType)); } /** diff --git a/pay-java-union/src/test/java/PayTest.java b/pay-java-union/src/test/java/PayTest.java index 659ea4c..93bda12 100644 --- a/pay-java-union/src/test/java/PayTest.java +++ b/pay-java-union/src/test/java/PayTest.java @@ -98,7 +98,7 @@ public class PayTest { /*-----------文件传输类接口:后台获取对账文件交易,只有同步应答 ------------------------------*/ - Map fileConten = service.downloadbill(new Date(), "文件类型,一般商户填写00即可"); /*-----------退货交易:后台资金类交易,有同步应答和后台通知应答------------------------------*/ + Map fileConten = service.downloadBill(new Date(), "文件类型,一般商户填写00即可"); /*-----------退货交易:后台资金类交易,有同步应答和后台通知应答------------------------------*/ /*-----------回调处理-------------------*/ 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 347e657..5139f48 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 @@ -26,6 +26,7 @@ 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; @@ -80,7 +81,7 @@ public class WxYouDianPayService extends BasePayService builder = new TreeMap<>(); 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(); } @@ -386,7 +387,7 @@ public class WxYouDianPayService extends BasePayService query(String tradeNo, String outTradeNo) { - String apbNonce = SignUtils.randomStr(); + String apbNonce = SignTextUtils.randomStr(); TreeMap data = new TreeMap<>(); data.put("access_token", payConfigStorage.getAccessToken()); @@ -396,8 +397,8 @@ public class WxYouDianPayService extends BasePayService data = new TreeMap<>(); data.put("access_token", payConfigStorage.getAccessToken()); @@ -430,8 +431,8 @@ public class WxYouDianPayService extends BasePayService downloadbill(Date billDate, String billType) { - return Collections.emptyMap(); - } - - @Override public Map downloadBill(Date billDate, BillType billType) { diff --git a/pay-java-wx/README.md b/pay-java-wx/README.md index 6012fe6..2689e11 100644 --- a/pay-java-wx/README.md +++ b/pay-java-wx/README.md @@ -217,7 +217,7 @@ #### 交易关闭接口 ```java - Map result = service..close("微信单号", "我方系统单号"); + Map result = service.close("微信单号", "我方系统单号"); ``` 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 fb4c039..2042622 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 @@ -58,6 +58,7 @@ 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.RSA2; import com.egzosn.pay.common.util.str.StringUtils; @@ -171,7 +172,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()); } @@ -188,7 +189,7 @@ public class WxPayService extends BasePayService implements //判断如果是服务商模式信息则加入 setParameters(parameters, "sub_mch_id", payConfigStorage.getSubMchId()); setParameters(parameters, "sub_appid", payConfigStorage.getSubAppId()); - parameters.put(NONCE_STR, SignUtils.randomStr()); + parameters.put(NONCE_STR, SignTextUtils.randomStr()); return parameters; @@ -290,7 +291,7 @@ public class WxPayService extends BasePayService implements params.put("noncestr", result.get(NONCE_STR)); params.put("package", "Sign=WXPay"); } - String paySign = createSign(SignUtils.parameterText(params), payConfigStorage.getInputCharset()); + String paySign = createSign(SignTextUtils.parameterText(params), payConfigStorage.getInputCharset()); params.put(WxTransactionType.JSAPI.equals(order.getTransactionType()) ? "paySign" : SIGN, paySign); return params; } @@ -311,7 +312,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; } @@ -328,9 +329,9 @@ 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); HttpStringEntity entity = new HttpStringEntity(XML.getMap2Xml(parameters), ClientHttpRequest.APPLICATION_XML_UTF_8); @@ -578,22 +579,8 @@ public class WxPayService extends BasePayService implements * RECHARGE_REFUND,返回当日充值退款订单 * @return 返回支付方下载对账单的结果 */ - @Deprecated - @Override - public Map downloadbill(Date billDate, String billType) { - Map parameters = getDownloadBillParam(billDate, billType, false); - //设置签名 - 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); - return ret; + public Map downloadBill(Date billDate, String billType) { + return downloadBill(billDate, WxPayBillType.valueOf(billType)); } /** @@ -784,12 +771,12 @@ public class WxPayService extends BasePayService implements * @param order 转账订单 *

      *
-     *                                        注意事项:
-     *                                        ◆ 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。
-     *                                        ◆ XML具有可扩展性,因此返回参数可能会有新增,而且顺序可能不完全遵循此文档规范,如果在解析回包的时候发生错误,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新回包字段,会更新到此API文档中。
-     *                                        ◆ 因为错误代码字段err_code的值后续可能会增加,所以商户如果遇到回包返回新的错误码,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新的错误码,会更新到此API文档中。
-     *                                        ◆ 错误代码描述字段err_code_des只供人工定位问题时做参考,系统实现时请不要依赖这个字段来做自动化处理。
-     *                                        
+ * 注意事项: + * ◆ 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。 + * ◆ XML具有可扩展性,因此返回参数可能会有新增,而且顺序可能不完全遵循此文档规范,如果在解析回包的时候发生错误,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新回包字段,会更新到此API文档中。 + * ◆ 因为错误代码字段err_code的值后续可能会增加,所以商户如果遇到回包返回新的错误码,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新的错误码,会更新到此API文档中。 + * ◆ 错误代码描述字段err_code_des只供人工定位问题时做参考,系统实现时请不要依赖这个字段来做自动化处理。 + * * @return 对应的转账结果 */ @Override @@ -802,7 +789,7 @@ 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()); @@ -812,7 +799,7 @@ public class WxPayService extends BasePayService implements 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); } @@ -875,17 +862,17 @@ public class WxPayService extends BasePayService implements Map parameters = new TreeMap(); parameters.put(MCH_ID, payConfigStorage.getPid()); parameters.put("partner_trade_no", outNo); - parameters.put(NONCE_STR, SignUtils.randomStr()); + parameters.put(NONCE_STR, SignTextUtils.randomStr()); if (StringUtils.isEmpty(wxTransferType)) { throw new PayErrorException(new WxPayError(FAILURE, "微信转账类型 #transferQuery(String outNo, String wxTransferType) 必填,详情com.egzosn.pay.wx.bean.WxTransferType")); } //如果类型为余额方式 if (TRANSFERS.getType().equals(wxTransferType) || GETTRANSFERINFO.getType().equals(wxTransferType)) { - parameters.put(APPID, payConfigStorage.getAppId()); - parameters.put(SIGN, createSign(SignUtils.parameterText(parameters, "&", SIGN), payConfigStorage.getInputCharset())); + 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(SignUtils.parameterText(parameters, "&", SIGN), payConfigStorage.getInputCharset())); + parameters.put(SIGN, createSign(SignTextUtils.parameterText(parameters, "&", SIGN), payConfigStorage.getInputCharset())); //默认查询银行卡的记录 return getHttpRequestTemplate().postForObject(getReqUrl(QUERY_BANK), XML.getMap2Xml(parameters), JSONObject.class); } @@ -932,7 +919,7 @@ public class WxPayService extends BasePayService implements parameters.put("notify_way", "MINI_PROGRAM_JSAPI"); } - parameters.put(SIGN, createSign(SignUtils.parameterText(parameters, "&", SIGN), payConfigStorage.getInputCharset())); + 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; @@ -942,7 +929,7 @@ public class WxPayService extends BasePayService implements params.put("timeStamp", System.currentTimeMillis() / 1000 + ""); params.put("nonceStr", parameters.get(NONCE_STR)); params.put("package", UriVariables.urlEncoder(resp.getString("package"))); - String paySign = createSign(SignUtils.parameterText(params), payConfigStorage.getInputCharset()); + String paySign = createSign(SignTextUtils.parameterText(params), payConfigStorage.getInputCharset()); params.put("signType", payConfigStorage.getSignType()); params.put("paySign", paySign); return params; @@ -963,7 +950,7 @@ public class WxPayService extends BasePayService implements Map parameters = this.getPublicParameters(); 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); } @@ -974,7 +961,7 @@ 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()); parameters.put("send_name", redpackOrder.getSendName()); 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 ba19707..d530673 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 @@ -23,6 +23,7 @@ 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; @@ -120,7 +121,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; @@ -363,22 +364,8 @@ public class YiJiPayService extends BasePayService { } - /** - * 目前只支持日账单 - * - * @param billDate 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于易极付交易收单的业务账单;signcustomer是指基于商户易极付余额收入及支出等资金变动的帐务账单; - * @param billType 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 - * @return 返回支付方下载对账单的结果 - */ - @Deprecated - @Override - public Map downloadbill(Date billDate, String billType) { - - return Collections.emptyMap(); - } /** - * 目前只支持日账单 * * @param billDate 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于易极付交易收单的业务账单;signcustomer是指基于商户易极付余额收入及支出等资金变动的帐务账单; * @param billType 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 -- Gitee From 4bc739d88453659ba2c5e119791c1e544cb623f6 Mon Sep 17 00:00:00 2001 From: egan Date: Sun, 1 Aug 2021 23:33:51 +0800 Subject: [PATCH 064/165] =?UTF-8?q?=E8=AF=B7=E6=B1=82=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=93=8D=E5=BA=94=E5=AE=9E=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pay/common/http/HttpRequestTemplate.java | 243 ++++++++++++------ .../pay/common/http/HttpStringEntity.java | 36 +-- .../pay/common/http/ResponseEntity.java | 33 +++ .../egzosn/pay/common/http/UriVariables.java | 27 ++ 4 files changed, 244 insertions(+), 95 deletions(-) create mode 100644 pay-java-common/src/main/java/com/egzosn/pay/common/http/ResponseEntity.java 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 b7b1ed3..218a26e 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.slf4j.Logger; -import org.slf4j.LoggerFactory; +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,24 +29,22 @@ 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 - * + * * email egzosn@gmail.com
* date 2017/3/3 21:33 - *
+ *
*/ public class HttpRequestTemplate { @@ -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) { + } + catch (NoSuchAlgorithmException e) { LOG.error("", e); } } //读取本机存放的PKCS12证书文件 - try(InputStream instream = configStorage.getKeystoreInputStream()){ + try (InputStream instream = configStorage.getKeystoreInputStream()) { //指定读取证书格式为PKCS12 KeyStore keyStore = KeyStore.getInstance("PKCS12"); @@ -148,9 +157,11 @@ public class HttpRequestTemplate { new DefaultHostnameVerifier()); return sslsf; - } catch (IOException e) { + } + catch (IOException e) { LOG.error("", e); - } catch (GeneralSecurityException 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); } @@ -277,101 +288,179 @@ public class HttpRequestTemplate { * @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 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 类型对象 + * @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 T getForObject(String uri, HttpHeader header, Class responseType, Object... uriVariables) { - return doExecute(URI.create(UriVariables.getUri(uri, uriVariables)), header, responseType, MethodType.GET); + return getForObjectEntity(uri, header, responseType, uriVariables).getBody(); } /** * get 请求 * * @param uri 请求地址 - * @param header 请求头 + * @param header 请求头 * @param responseType 响应类型 * @param uriVariables 用于匹配表达式 - * @param 响应类型 + * @param 响应类型 * @return 类型对象 + * * - * Map<String, String> uriVariables = new HashMap<String, String>();
+ * 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 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 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 T getForObject(String uri, HttpHeader header, Class responseType, Map uriVariables){ - return doExecute(URI.create(UriVariables.getUri(uri, uriVariables)), header, responseType, MethodType.GET); + 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 uri 地址 + * @param request 请求数据 * @param responseType 响应类型 - * @param method 请求方法 - * @param 响应类型 + * @param method 请求方法 + * @param 响应类型 * @return 类型对象 */ - public T doExecute(URI uri, Object request, Class responseType, MethodType method){ + public T doExecute(URI uri, Object request, Class responseType, MethodType method) { + return doExecuteEntity(uri, request, responseType, method).getBody(); + + } + + /** + * http 请求执行 + * + * @param uri 地址 + * @param request 请求数据 + * @param responseType 响应类型 + * @param method 请求方法 + * @param 响应类型 + * @return 类型对象 + */ + 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){ + 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())); - }finally { + } + 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 828c9be..53e5062 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 0000000..995bc67 --- /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 8f59338..cff3608 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,6 +1,8 @@ package com.egzosn.pay.common.http; import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URLEncoder; import java.util.List; import java.util.Map; @@ -11,6 +13,7 @@ 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表达式处理器 @@ -23,6 +26,7 @@ import com.egzosn.pay.common.exception.PayErrorException; */ public final class UriVariables { private static final Logger LOG = LoggerFactory.getLogger(UriVariables.class); + public static final String QUESTION = "?"; private UriVariables() { } @@ -218,5 +222,28 @@ public final class UriVariables { 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); + } + } } -- Gitee From bb91d24d7405bd0e88d2fd224e003b4aba345ec3 Mon Sep 17 00:00:00 2001 From: egan Date: Sun, 1 Aug 2021 23:34:15 +0800 Subject: [PATCH 065/165] =?UTF-8?q?=E5=B7=A5=E5=85=B7=E7=B1=BB=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/common/util/DateUtils.java | 26 ++- .../com/egzosn/pay/common/util/MapGen.java | 36 ++++ .../java/com/egzosn/pay/common/util/Util.java | 11 +- .../pay/common/util/sign/SecureUtil.java | 172 ----------------- .../pay/common/util/sign/SignTextUtils.java | 182 ++++++++++++++++++ .../pay/common/util/sign/SignUtils.java | 178 +---------------- .../common/util/sign/encrypt/HmacSha256.java | 56 ++++++ .../pay/common/util/str/StringUtils.java | 53 ++++- 8 files changed, 350 insertions(+), 364 deletions(-) create mode 100644 pay-java-common/src/main/java/com/egzosn/pay/common/util/MapGen.java delete mode 100644 pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/SecureUtil.java create mode 100644 pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/SignTextUtils.java create mode 100644 pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/HmacSha256.java 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 daa6747..f5397da 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 @@ -8,9 +8,9 @@ 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; -import org.apache.http.util.Args; /** * 日期转换运算工具 @@ -90,15 +90,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); } @@ -108,8 +108,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); } /** @@ -118,7 +118,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; } @@ -128,8 +128,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/MapGen.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/MapGen.java new file mode 100644 index 0000000..45b8a89 --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/util/MapGen.java @@ -0,0 +1,36 @@ +package com.egzosn.pay.common.util; + +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) { + 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/Util.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/Util.java index e8c2b14..7991c3c 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 @@ -571,14 +571,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,7 +592,7 @@ 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); } 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 24093c3..0000000 --- a/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/SecureUtil.java +++ /dev/null @@ -1,172 +0,0 @@ -package com.egzosn.pay.common.util.sign; - -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.Signature; - -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.sign.encrypt.sm3.SM3Digest; - -public class SecureUtil { - - private static final Logger LOG = LoggerFactory.getLogger(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 (NoSuchAlgorithmException e) { - LOG.error("", e); - 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) { - LOG.error("", e); - 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) { - LOG.error("", e); - } - 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 0000000..dfe94dd --- /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 4c3f106..0085513 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 @@ -21,9 +21,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.http.message.BasicNameValuePair; +import static com.egzosn.pay.common.util.sign.SignTextUtils.parameterText; + 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.sign.encrypt.HmacSha256; import com.egzosn.pay.common.util.str.StringUtils; /** @@ -80,29 +83,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) { - LOG.error("", e); - } - catch (InvalidKeyException e) { - LOG.error("", e); - } - catch (UnsupportedEncodingException e) { - LOG.error("", e); - } - - throw new PayErrorException(new PayException("fail", "HMACSHA256 签名异常")); + return HmacSha256.createSign(content, key, characterEncoding); } /** @@ -177,160 +158,9 @@ public enum SignUtils implements SignType { return com.egzosn.pay.common.util.sign.encrypt.RSA2.verify(text, sign, publicKey, characterEncoding); } }; - private static final Logger LOG = LoggerFactory.getLogger(SignUtils.class); - - /** - * 把数组所有元素排序,并按照“参数=参数值”的模式用“@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 UUID.randomUUID().toString().replace("-", ""); - } - @Override public String getName() { return this.name(); 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 0000000..2640915 --- /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/str/StringUtils.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/str/StringUtils.java index 79a0636..42ae96f 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,36 @@ 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 (StringUtils.isEmpty(s)) { + continue; + } + if (builder.length() > 0) { + builder.append(separator); + } + builder.append(s); + } + return builder.toString(); + } + + /** + * 获取随机字符串 + * + * @return 随机字符串 + */ + public static String randomStr() { + return UUID.randomUUID().toString().replace("-", ""); + } + + } -- Gitee From 3062726c9da64983cf5724056857b61076a533fb Mon Sep 17 00:00:00 2001 From: egan Date: Sun, 1 Aug 2021 23:34:44 +0800 Subject: [PATCH 066/165] =?UTF-8?q?=E5=BE=AE=E4=BF=A1V3=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pay/wx/v3/api/WxPayConfigStorage.java | 225 ++++++ .../egzosn/pay/wx/v3/api/WxPayService.java | 709 ++++++++++++++++++ .../pay/wx/v3/bean/CertEnvironment.java | 62 ++ .../egzosn/pay/wx/v3/bean/WxAccountType.java | 116 +++ .../com/egzosn/pay/wx/v3/bean/WxBillType.java | 113 +++ .../pay/wx/v3/bean/WxTransactionType.java | 156 ++++ .../egzosn/pay/wx/v3/bean/order/Amount.java | 75 ++ .../egzosn/pay/wx/v3/bean/order/Detail.java | 51 ++ .../com/egzosn/pay/wx/v3/bean/order/From.java | 39 + .../pay/wx/v3/bean/order/GoodsDetail.java | 84 +++ .../egzosn/pay/wx/v3/bean/order/H5Info.java | 94 +++ .../pay/wx/v3/bean/order/RefundAmount.java | 113 +++ .../pay/wx/v3/bean/order/SceneInfo.java | 67 ++ .../pay/wx/v3/bean/order/StoreInfo.java | 62 ++ .../wx/v3/bean/response/WxRefundResult.java | 324 ++++++++ .../pay/wx/v3/utils/AntCertificationUtil.java | 66 ++ .../com/egzosn/pay/wx/v3/utils/WxConst.java | 41 + 17 files changed, 2397 insertions(+) create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayConfigStorage.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayService.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/CertEnvironment.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxAccountType.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxBillType.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxTransactionType.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/Amount.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/Detail.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/From.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/GoodsDetail.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/H5Info.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/RefundAmount.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/SceneInfo.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/StoreInfo.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/WxRefundResult.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/utils/AntCertificationUtil.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/utils/WxConst.java 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 0000000..e61f963 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayConfigStorage.java @@ -0,0 +1,225 @@ +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; + + /** + * 商户API证书 + * 包含商户的商户号、公司名称、公钥信息 + * 详情 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_1.shtml + */ + private Object apiClientKeyP12; + + /** + * 证书存储类型 + */ + private CertStoreType certStoreType; + + + /** + * 证书信息 + */ + private CertEnvironment certEnvironment; + + @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; + } + + /** + * 为商户平台设置的密钥key + * + * @return 微信密钥 + */ + public String getSecretKey() { + return getKeyPrivate(); + } + + public void setSecretKey(String 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; + } + + 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; + } + + /** + * 应用id + * 纠正名称 + * + * @return 应用id + * @see #getSubAppId() + */ + @Deprecated + public String getSubAppid() { + return subAppId; + } + + @Deprecated + public void setSubAppid(String subAppid) { + this.subAppId = subAppid; + } + + + public String getSubMchId() { + return subMchId; + } + + public void setSubMchId(String subMchId) { + this.subMchId = subMchId; + } + + public Object getApiClientKeyP12() { + return apiClientKeyP12; + } + + public void setApiClientKeyP12(Object apiClientKeyP12) { + this.apiClientKeyP12 = apiClientKeyP12; + } + + public CertStoreType getCertStoreType() { + return certStoreType; + } + + public void setCertStoreType(CertStoreType 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 apiKeyCert = certStoreType.getInputStream(getApiClientKeyP12())) { + this.certEnvironment = AntCertificationUtil.initCertification(apiKeyCert, WxConst.CERT_ALIAS, getMchId()); + } + catch (IOException e) { + throw new PayErrorException(new PayException("读取证书异常", e.getMessage())); + } + } +} 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 0000000..ba4255a --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayService.java @@ -0,0 +1,709 @@ +package com.egzosn.pay.wx.v3.api; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URLEncoder; +import java.security.PrivateKey; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; + +import org.apache.http.entity.ContentType; +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.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.SUCCESS; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.egzosn.pay.common.api.BasePayService; +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.Order; +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.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.XML; +import com.egzosn.pay.common.util.sign.SignTextUtils; +import com.egzosn.pay.common.util.sign.encrypt.RSA2; +import com.egzosn.pay.common.util.str.StringUtils; +import com.egzosn.pay.wx.api.WxRedPackService; +import com.egzosn.pay.wx.bean.RedpackOrder; +import com.egzosn.pay.wx.bean.WxPayError; +import com.egzosn.pay.wx.bean.WxPayMessage; +import com.egzosn.pay.wx.bean.WxTransferType; +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.order.Amount; +import com.egzosn.pay.wx.v3.bean.order.RefundAmount; +import com.egzosn.pay.wx.v3.bean.response.WxRefundResult; +import com.egzosn.pay.wx.v3.utils.WxConst; + +/** + * 微信支付服务 + * + * @author egan + *
+ * email egzosn@gmail.com
+ * date 2016-5-18 14:09:01
+ * 
+ */ +public class WxPayService extends BasePayService implements WxRedPackService { + + + /** + * api服务地址,默认为国内 + */ + private String apiServerUrl = WxConst.URI; + /** + * 是否为服务商模式, 默认为false + */ + private boolean partner = false; + + /** + * 创建支付服务 + * + * @param payConfigStorage 微信对应的支付配置 + */ + public WxPayService(WxPayConfigStorage payConfigStorage) { + super(payConfigStorage); + } + + /** + * 创建支付服务 + * + * @param payConfigStorage 微信对应的支付配置 + * @param configStorage 微信对应的网络配置,包含代理配置、ssl证书配置 + */ + public WxPayService(WxPayConfigStorage payConfigStorage, HttpConfigStorage configStorage) { + super(payConfigStorage, configStorage); + } + + /** + * 设置支付配置 + * + * @param payConfigStorage 支付配置 + */ + @Override + public BasePayService setPayConfigStorage(WxPayConfigStorage payConfigStorage) { + payConfigStorage.loadCertEnvironment(); + this.payConfigStorage = payConfigStorage; + return this; + } + + /** + * 设置api服务器地址 + * + * @param apiServerUrl api服务器地址 + * @return 自身 + */ + public WxPayService setApiServerUrl(String apiServerUrl) { + this.apiServerUrl = apiServerUrl; + return this; + } + + /** + * 根据交易类型获取url + * + * @param transactionType 交易类型 + * @return 请求url + */ + @Override + public String getReqUrl(TransactionType transactionType) { + String type = transactionType.getType(); + String partnerStr = ""; + if (partner) { + 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("", "等待作者实现")); + + } + + + /** + * 获取公共参数 + * + * @return 公共参数 + */ + private Map getPublicParameters() { + + Map parameters = new TreeMap(); + parameters.put(WxConst.APPID, payConfigStorage.getAppId()); + parameters.put(WxConst.MCH_ID, payConfigStorage.getMchId()); + + return parameters; + + + } + + + /** + * 初始化商户相关信息 + * + * @param parameters 参数信息 + * @return 参数信息 + */ + private Map initPartner(Map parameters) { + if (null == parameters) { + parameters = new HashMap<>(); + } + if (StringUtils.isNotEmpty(payConfigStorage.getSpAppId()) && StringUtils.isNotEmpty(payConfigStorage.getSpMchId())) { + this.partner = true; + parameters.put("sp_appid", payConfigStorage.getSpAppId()); + parameters.put(WxConst.SP_MCH_ID, payConfigStorage.getSpMchId()); + } + setParameters(parameters, "sub_appid", payConfigStorage.getSubAppId()); + initSubMchId(parameters); + + return parameters; + + } + + /** + * 初始化商户相关信息 + * + * @param parameters 参数信息 + * @return 参数信息 + */ + private Map initSubMchId(Map parameters) { + if (null == parameters) { + parameters = new HashMap<>(); + } + if (StringUtils.isNotEmpty(payConfigStorage.getSubMchId())) { + this.partner = true; + parameters.put(WxConst.SUB_MCH_ID, payConfigStorage.getSubMchId()); + } + return parameters; + + } + + + /** + * 加载结算信息 + * + * @param parameters 订单参数 + * @param order 支付订单 + * @return 订单参数 + */ + private Map 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); + } + else { + //结算信息 + setParameters(parameters, "settle_info", order); + } + + return parameters; + + } + + /** + * 发起请求 + * + * @param parameters 支付参数 + * @param transactionType 交易类型 + * @return 响应内容体 + */ + protected JSONObject doExecute(Map parameters, TransactionType transactionType) { + String requestBody = JSON.toJSONString(parameters, SerializerFeature.WriteMapNullValue); + return doExecute(requestBody, transactionType); + } + + + /** + * 发起请求 + * + * @param body 请求内容 + * @param transactionType 交易类型 + * @param uriVariables 用于匹配表达式 + * @return 响应内容体 + */ + protected JSONObject doExecute(String body, TransactionType transactionType, Object... uriVariables) { + String reqUrl = UriVariables.getUri(getReqUrl(transactionType), uriVariables); + MethodType method = MethodType.valueOf(transactionType.getMethod()); + if (MethodType.GET == method && StringUtils.isNotEmpty(body)) { + reqUrl += UriVariables.QUESTION.concat(body); + body = ""; + } + HttpStringEntity entity = initHttpStringEntity(reqUrl, body, transactionType.getMethod()); + ResponseEntity responseEntity = requestTemplate.doExecuteEntity(reqUrl, entity, JSONObject.class, method); + 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())); + } + return responseBody; + } + + /** + * 发起请求 + * + * @param parameters 支付参数 + * @param order 订单 + * @return 请求响应 + */ + protected JSONObject doExecute(Map parameters, PayOrder order) { + TransactionType transactionType = order.getTransactionType(); + return doExecute(parameters, transactionType); + } + + + /** + * 微信统一下单接口 + * + * @param order 支付订单集 + * @return 下单结果 + */ + public JSONObject unifiedOrder(PayOrder order) { + + //统一下单 + Map parameters = getPublicParameters(); + initPartner(parameters); + // 商品描述 + setParameters(parameters, "description", order.getSubject()); + setParameters(parameters, "description", order.getBody()); + // 订单号 + parameters.put(WxConst.OUT_TRADE_NO, order.getOutTradeNo()); + //交易结束时间 + if (null != order.getExpirationTime()) { + parameters.put("time_expire", DateUtils.formatDate(order.getExpirationTime(), DateUtils.YYYYMMDDHHMMSS)); + } + setParameters(parameters, "attach", order.getAddition()); + initNotifyUrl(parameters, order); + //订单优惠标记 + setParameters(parameters, "goods_tag", order); + parameters.put("amount", Amount.getAmount(order.getPrice(), order.getCurType())); + + //优惠功能 + setParameters(parameters, "detail", order); + //支付场景描述 + setParameters(parameters, WxConst.SCENE_INFO, order); + loadSettleInfo(parameters, order); + + TransactionType transactionType = order.getTransactionType(); + ((WxTransactionType) transactionType).setAttribute(parameters, order); + + return doExecute(parameters, order); + } + + + /** + * 返回创建的订单信息 + * + * @param order 支付订单 + * @return 订单信息 + * @see PayOrder 支付订单信息 + */ + @Override + public Map orderInfo(PayOrder order) { + + ////统一下单 + JSONObject result = unifiedOrder(order); + throw new PayErrorException(new WxPayError("", "等待作者实现")); + + } + + + /** + * 初始化请求实体 + * 这里也做签名处理 + * + * @param body 请求内容体 + * @param method 请求方法 + * @return 请求实体 + */ + private HttpStringEntity initHttpStringEntity(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 = 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-Service")); + return entity; + } + + + /** + * 签名 + * + * @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); + } + + + /** + * 初始化通知URL必须为直接可访问的URL,不允许携带查询串,要求必须为https地址。 + * + * @param parameters 订单参数 + * @param order 订单信息 + * @return 订单参数 + */ + private void initNotifyUrl(Map parameters, Order order) { + setParameters(parameters, WxConst.NOTIFY_URL, payConfigStorage.getNotifyUrl()); + setParameters(parameters, WxConst.NOTIFY_URL, order); + } + + /** + * 获取服务商相关信息 + * + * @return 服务商相关信息 + */ + private String getSpParameters() { + Map attr = initSubMchId(null); + setParameters(attr, WxConst.SP_MCH_ID, payConfigStorage.getSpMchId()); + String parameters = UriVariables.getMapToParameters(attr); + return parameters; + } + + + /** + * 将请求参数或者请求流转化为 Map + * + * @param parameterMap 请求参数 + * @param is 请求流 + * @return 获得回调的请求参数 + */ + @Override + public Map getParameter2Map(Map parameterMap, InputStream is) { + TreeMap map = new TreeMap(); + try { + return XML.inputStream2Map(is, map); + } + catch (IOException e) { + throw new PayErrorException(new PayException("IOException", e.getMessage())); + } + + } + + /** + * 获取输出消息,用户返回给支付端 + * + * @param code 状态 + * @param message 消息 + * @return 返回输出消息 + */ + @Override + public PayOutMessage getPayOutMessage(String code, String message) { + return PayOutMessage.XML().code(code.toUpperCase()).content(message).build(); + } + + + /** + * 获取成功输出消息,用户返回给支付端 + * 主要用于拦截器中返回 + * + * @param payMessage 支付回调消息 + * @return 返回输出消息 + */ + @Override + public PayOutMessage successPayOutMessage(PayMessage payMessage) { + return PayOutMessage.XML().code("SUCCESS").content("成功").build(); + } + + + /** + * 获取输出消息,用户返回给支付端, 针对于web端 + * + * @param orderInfo 发起支付的订单信息 + * @param method 请求方式 "post" "get", + * @return 获取输出消息,用户返回给支付端, 针对于web端 + * @see MethodType 请求类型 + */ + @Override + public String buildRequest(Map orderInfo, MethodType method) { + if (!SUCCESS.equals(orderInfo.get(RETURN_CODE))) { + throw new PayErrorException(new WxPayError((String) orderInfo.get(RETURN_CODE), (String) orderInfo.get(RETURN_MSG_CODE))); + } + if (WxTransactionType.H5.name().equals(orderInfo.get("trade_type"))) { + return String.format("", orderInfo.get("mweb_url"), StringUtils.isEmpty(payConfigStorage.getReturnUrl()) ? "" : "&redirect_url=" + URLEncoder.encode(payConfigStorage.getReturnUrl())); + } + throw new UnsupportedOperationException(); + + } + + /** + * 获取输出二维码信息, + * + * @param order 发起支付的订单信息 + * @return 返回二维码信息,,支付时需要的 + */ + @Override + public String getQrPay(PayOrder order) { + order.setTransactionType(WxTransactionType.NATIVE); + Map orderInfo = orderInfo(order); + + return (String) orderInfo.get("code_url"); + } + + /** + * 刷卡付,pos主动扫码付款 + * + * @param order 发起支付的订单信息 + * @return 返回支付结果 + */ + @Override + public Map microPay(PayOrder order) { + throw new PayErrorException(new PayException("failure", "V3暂时没有提供此功能,请查看V2版本功能")); + } + + /** + * 交易查询接口 + * + * @param transactionId 微信支付平台订单号 + * @param outTradeNo 商户单号 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(String transactionId, String outTradeNo) { + + String parameters = getSpParameters(); + WxTransactionType transactionType = WxTransactionType.QUERY_TRANSACTION_ID; + String uriVariable = transactionId; + if (StringUtils.isNotEmpty(outTradeNo)) { + transactionType = WxTransactionType.QUERY_OUT_TRADE_NO; + uriVariable = outTradeNo; + } + return doExecute(parameters, transactionType, uriVariable); + } + + + /** + * 交易关闭接口 + * + * @param transactionId 支付平台订单号 + * @param outTradeNo 商户单号 + * @return 返回支付方交易关闭后的结果 + */ + @Override + public Map close(String transactionId, String outTradeNo) { + String parameters = getSpParameters(); + return doExecute(parameters, WxTransactionType.CLOSE, outTradeNo); + } + + /** + * 申请退款接口 + * + * @param refundOrder 退款订单信息 + * @return 返回支付方申请退款后的结果 + */ + @Override + public WxRefundResult refund(RefundOrder refundOrder) { + //获取公共参数 + Map parameters = initSubMchId(null); + + setParameters(parameters, "transaction_id", refundOrder.getTradeNo()); + setParameters(parameters, OUT_TRADE_NO, refundOrder.getOutTradeNo()); + setParameters(parameters, "out_refund_no", refundOrder.getRefundNo()); + setParameters(parameters, "reason", refundOrder.getDescription()); + setParameters(parameters, "funds_account", refundOrder); + 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); + setParameters(parameters, "amount", refundOrder); + return WxRefundResult.create(doExecute(parameters, WxTransactionType.REFUND)); + } + + + /** + * 查询退款 + * + * @param refundOrder 退款订单单号信息 + * @return 返回支付方查询退款后的结果 + */ + @Override + public Map refundquery(RefundOrder refundOrder) { + String parameters = UriVariables.getMapToParameters(initSubMchId(null)); + return 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的输入流 + */ + public Map downloadBill(Date billDate, BillType billType) { + //获取公共参数 + Map parameters = new HashMap<>(5); + + //目前只支持日账单 + parameters.put("bill_date", DateUtils.formatDate(billDate, DateUtils.YYYYMMDD)); + String fileType = billType.getFileType(); + setParameters(parameters, "tar_type", fileType); + if (billType instanceof WxAccountType) { + setParameters(parameters, "account_type", billType.getType()); + } + else { + initSubMchId(parameters).put("bill_type", billType.getType()); + } + String body = UriVariables.getMapToParameters(parameters); + JSONObject result = doExecute(body, WxTransactionType.valueOf(billType.getCustom())); + String downloadUrl = result.getString("download_url"); + MethodType methodType = MethodType.GET; + HttpStringEntity entity = initHttpStringEntity(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 result; + } + + + /** + * 转账 + * + * @param order 转账订单 + *
+     *
+     *                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               注意事项:
+     *                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               ◆ 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。
+     *                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               ◆ XML具有可扩展性,因此返回参数可能会有新增,而且顺序可能不完全遵循此文档规范,如果在解析回包的时候发生错误,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新回包字段,会更新到此API文档中。
+     *                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               ◆ 因为错误代码字段err_code的值后续可能会增加,所以商户如果遇到回包返回新的错误码,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新的错误码,会更新到此API文档中。
+     *                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               ◆ 错误代码描述字段err_code_des只供人工定位问题时做参考,系统实现时请不要依赖这个字段来做自动化处理。
+     *                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
+ * @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 WxPayMessage.create(message); + } + + /** + * 微信发红包 + * + * @param redpackOrder 红包实体 + * @return 返回发红包实体后的结果 + */ + @Override + public Map sendredpack(RedpackOrder redpackOrder) { + throw new PayErrorException(new WxPayError("", "等待作者实现")); + } + + + /** + * 查询红包记录 + * 用于商户对已发放的红包进行查询红包的具体信息,可支持普通红包和裂变包 + * 查询红包记录API只支持查询30天内的红包订单,30天之前的红包订单请登录商户平台查询。 + * + * @param mchBillno 商户发放红包的商户订单号 + * @return 返回查询结果 + */ + @Override + public Map gethbinfo(String mchBillno) { + throw new PayErrorException(new WxPayError("", "等待作者实现")); + } + +} 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 0000000..7bc13f2 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/CertEnvironment.java @@ -0,0 +1,62 @@ +package com.egzosn.pay.wx.v3.bean; + +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * 证书模式运行时环境 + * + * @author egan + * email egzosn@gmail.com + * date 2021/07/18.20:29 + */ +public class CertEnvironment { + /** + * 存放私钥 + */ + private PrivateKey privateKey; + + /** + * 存放公钥 + */ + private PublicKey publicKey; + + /** + * 公钥序列 + */ + private String serialNumber; + + + 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; + } +} \ No newline at end of file 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 0000000..fc64193 --- /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 0000000..6b0cd7b --- /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/WxTransactionType.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxTransactionType.java new file mode 100644 index 0000000..46ed6a4 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxTransactionType.java @@ -0,0 +1,156 @@ +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; + +/** + * 微信交易类型 + * + * @author egan + *

+ * email egzosn@gmail.com + * date 2016/10/19 22:58 + */ +public enum WxTransactionType implements TransactionType { + /** + * 获取证书. + */ + CERT("certificates", MethodType.GET), + /** + * 公众号支付 + */ + JSAPI("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("pay{partner}/transactions/native", MethodType.POST, true), + /** + * 移动支付 + */ + APP("pay{partner}/transactions/app", MethodType.POST), + /** + * H5支付 + */ + H5("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())); + } + } + + }, + + /** + * 查询订单 + * 兼容V2的方式,通过入参来决定 + */ + QUERY("pay{partner}/transactions/", MethodType.GET), + /** + * 微信支付订单号查询 + */ + QUERY_TRANSACTION_ID("pay{partner}/transactions/id/{transaction_id}", MethodType.GET), + /** + * 商户订单号查询 + */ + QUERY_OUT_TRADE_NO("pay{partner}/transactions/out-trade-no/{out_trade_no}", MethodType.GET), + /** + * 关闭订单 + */ + CLOSE("pay{partner}/transactions/out-trade-no/{out_trade_no}/close", MethodType.POST), + /** + * 申请退款 + */ + REFUND("refund/domestic/refunds", MethodType.POST), + /** + * 查询退款 + */ + REFUND_QUERY("refund/domestic/refunds/{out_refund_no}", MethodType.GET), + /** + * 申请交易账单 + */ + TRADE_BILL("bill/tradebill", MethodType.GET), + /** + * 申请资金账单 + */ + FUND_FLOW_BILL("bill/fundflowbill", MethodType.GET), + + + ; + + 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/order/Amount.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/Amount.java new file mode 100644 index 0000000..7c04136 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/Amount.java @@ -0,0 +1,75 @@ +package com.egzosn.pay.wx.v3.bean.order; + + +import java.math.BigDecimal; + +import com.egzosn.pay.common.bean.CurType; +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; + + 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) { + 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 0000000..ede9be6 --- /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 int costPrice; + /** + * 商家小票 + */ + @JSONField(name = "invoice_id") + private String invoiceId; + + @JSONField(name = "goods_detail") + private List goodsDetail; + + public int getCostPrice() { + return costPrice; + } + + public void setCostPrice(int 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 0000000..84812ae --- /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 int amount; + + public String getAccount() { + return account; + } + + public void setAccount(String account) { + this.account = account; + } + + public int getAmount() { + return amount; + } + + public void setAmount(int 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 0000000..e1155d0 --- /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 int quantity; + /** + * 商品单价 + * 商品单价,单位为分 + */ + @JSONField(name = "unit_price") + private int 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 int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + public int getUnitPrice() { + return unitPrice; + } + + public void setUnitPrice(int 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 0000000..03e5f71 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/H5Info.java @@ -0,0 +1,94 @@ +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 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 0000000..fbac901 --- /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 int refund; + + /** + * 退款出资的账户类型及金额信息 + */ + private List from; + + /** + * 用户支付金额,单位分 + */ + @JSONField(name = "payer_total") + private int payerTotal; + /** + * 用户退款金额 + * 退款给用户的金额,不包含所有优惠券金额 + */ + @JSONField(name = "payer_refund") + private int payerRefund; + /** + * 应结退款金额 + * 去掉非充值代金券退款金额后的退款金额,单位为分,退款金额=申请退款金额-非充值代金券退款金额,退款金额<=申请退款金额 + */ + @JSONField(name = "settlement_refund") + private int settlementRefund; + /** + * 应结订单金额 + * 应结订单金额=订单金额-免充值代金券金额,应结订单金额<=订单金额,单位为分 + */ + @JSONField(name = "settlement_total") + private int settlementTotal; + /** + * 优惠退款金额 + * 优惠退款金额<=退款金额,退款金额-代金券或立减优惠退款金额为现金,说明详见代金券或立减优惠,单位为分 + */ + @JSONField(name = "discount_refund") + private int discountRefund; + + + public int getRefund() { + return refund; + } + + public void setRefund(int refund) { + this.refund = refund; + } + + public List getFrom() { + return from; + } + + public void setFrom(List from) { + this.from = from; + } + + public int getPayerTotal() { + return payerTotal; + } + + public void setPayerTotal(int payerTotal) { + this.payerTotal = payerTotal; + } + + public int getPayerRefund() { + return payerRefund; + } + + public void setPayerRefund(int payerRefund) { + this.payerRefund = payerRefund; + } + + public int getSettlementRefund() { + return settlementRefund; + } + + public void setSettlementRefund(int settlementRefund) { + this.settlementRefund = settlementRefund; + } + + public int getSettlementTotal() { + return settlementTotal; + } + + public void setSettlementTotal(int settlementTotal) { + this.settlementTotal = settlementTotal; + } + + public int getDiscountRefund() { + return discountRefund; + } + + public void setDiscountRefund(int 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 0000000..055cf35 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/SceneInfo.java @@ -0,0 +1,67 @@ +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; + /** + * 商户端设备号 + */ + @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/StoreInfo.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/StoreInfo.java new file mode 100644 index 0000000..271d5e6 --- /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/response/WxRefundResult.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/WxRefundResult.java new file mode 100644 index 0000000..fc9fb19 --- /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/utils/AntCertificationUtil.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/utils/AntCertificationUtil.java new file mode 100644 index 0000000..1e06084 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/utils/AntCertificationUtil.java @@ -0,0 +1,66 @@ +package com.egzosn.pay.wx.v3.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.X509Certificate; + +import com.egzosn.pay.common.exception.PayErrorException; +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 { + private AntCertificationUtil() { + } + + private static final KeyStore PKCS12_KEY_STORE; + + static { + try { + PKCS12_KEY_STORE = KeyStore.getInstance("PKCS12"); + } + catch (KeyStoreException e) { + throw new PayErrorException(new WxPayError("500", " keystore 初始化失败")); + } + } + + /** + * 获取公私钥. + * + * @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 (GeneralSecurityException e) { + throw new PayErrorException(new WxPayError("500", "获取公私钥失败"), e); + } + catch (IOException e) { + throw new PayErrorException(new WxPayError("500", "私钥证书流加载失败"), 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 0000000..fe0d1e0 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/utils/WxConst.java @@ -0,0 +1,41 @@ +package com.egzosn.pay.wx.v3.utils; + +/** + * 微信所需常量 + * + * @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/v3/"; + /** + * 证书别名 + */ + public static final String CERT_ALIAS = "Tenpay Certificate"; + /** + * 沙箱 + */ + public static final String SANDBOXNEW = "sandboxnew/"; + public static final String APPID = "appid"; + public static final String MCH_ID = "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 NOTIFY_URL = "notify_url"; + + 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"; +} -- Gitee From 5ebd70472382995c89d622fc8b98afd27b330360 Mon Sep 17 00:00:00 2001 From: egan Date: Fri, 6 Aug 2021 04:40:29 +0000 Subject: [PATCH 067/165] update README.md. --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 70468d6..c7a2071 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,12 @@ android 例子 [pay-java-android](https://gitee.com/egzosn/pay-java-android) E-Mail:egzosn@gmail.com -QQ群:542193977 + **QQ群:** + + +1. pay-java(1群): 542193977(已满) +2. pay-java(2群):766275051 + 微信群: ![微信群](https://egzosn.gitee.io/pay-java-parent/wx.jpg "wx.jpg") -- Gitee From 44c72fa710a6520290e0474b3b685044bac3354d Mon Sep 17 00:00:00 2001 From: egan Date: Sun, 8 Aug 2021 23:04:12 +0800 Subject: [PATCH 068/165] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egzosn/pay/ali/bean/CertEnvironment.java | 10 +-- pay-java-ali/src/test/java/PayTest.java | 4 +- .../pay/common/util/sign/CertDescriptor.java | 73 ++++++++++++------- pay-java-demo/README.md | 12 +-- .../pay/demo/controller/AliPayController.java | 4 +- .../pay/demo/controller/WxPayController.java | 4 +- pay-java-fuiou/src/test/java/PayTest.java | 4 +- pay-java-payoneer/test/java/PayTest.java | 4 +- .../src/test/java/PayTest.java | 4 +- 9 files changed, 71 insertions(+), 48 deletions(-) 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 index f8b262c..79dcb82 100644 --- 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 @@ -44,7 +44,7 @@ public class CertEnvironment { /** * 缓存的不同支付宝公钥证书序列号对应的支付宝公钥 */ - private Map cachedAliPayPublicKey = new ConcurrentHashMap(); + private static final Map CACHED_ALI_PAY_PUBLIC_KEY = new ConcurrentHashMap(); /** * 构造证书运行环境 @@ -64,7 +64,7 @@ public class CertEnvironment { String aliPayPublicCertContent = AntCertificationUtil.readFromInputStream(aliPayCert); aliPayPublicKeySN = AntCertificationUtil.getCertSN(aliPayPublicCertContent); - cachedAliPayPublicKey.put(aliPayPublicKeySN, + CACHED_ALI_PAY_PUBLIC_KEY.put(aliPayPublicKeySN, AntCertificationUtil.getCertPublicKey(aliPayPublicCertContent)); } @@ -79,11 +79,11 @@ public class CertEnvironment { public String getAliPayPublicKey(String sn) { //如果没有指定sn,则默认取缓存中的第一个值 if (StringUtils.isEmpty(sn)) { - return cachedAliPayPublicKey.values().iterator().next(); + return CACHED_ALI_PAY_PUBLIC_KEY.values().iterator().next(); } - if (cachedAliPayPublicKey.containsKey(sn)) { - return cachedAliPayPublicKey.get(sn); + if (CACHED_ALI_PAY_PUBLIC_KEY.containsKey(sn)) { + return CACHED_ALI_PAY_PUBLIC_KEY.get(sn); } else { //网关在支付宝公钥证书变更前,一定会确认通知到商户并在商户做出反馈后,才会更新该商户的支付宝公钥证书 //TODO: 后续可以考虑加入自动升级支付宝公钥证书逻辑,注意并发更新冲突问题 diff --git a/pay-java-ali/src/test/java/PayTest.java b/pay-java-ali/src/test/java/PayTest.java index b22ba22..416ec15 100644 --- a/pay-java-ali/src/test/java/PayTest.java +++ b/pay-java-ali/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 { 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 2419571..947c7e1 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.slf4j.Logger; -import org.slf4j.LoggerFactory; - 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证书工具类,主要用于对证书的加载和使用 @@ -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-demo/README.md b/pay-java-demo/README.md index 869d81c..d4f5301 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 { @@ -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 { /** 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 0933696..867d7c5 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 @@ -335,8 +335,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()); } 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 4b0b241..fc0bef7 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 @@ -333,8 +333,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-fuiou/src/test/java/PayTest.java b/pay-java-fuiou/src/test/java/PayTest.java index 53dfd25..f838a26 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 { diff --git a/pay-java-payoneer/test/java/PayTest.java b/pay-java-payoneer/test/java/PayTest.java index b591876..ddf2691 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-wx-youdian/src/test/java/PayTest.java b/pay-java-wx-youdian/src/test/java/PayTest.java index e4d3089..26d342f 100644 --- a/pay-java-wx-youdian/src/test/java/PayTest.java +++ b/pay-java-wx-youdian/src/test/java/PayTest.java @@ -16,8 +16,8 @@ import java.util.UUID; * * 友店微信 * @author egan - * @email egzosn@gmail.com - * @date 2017/8/18 + * email egzosn@gmail.com + * date 2017/8/18 */ public class PayTest { -- Gitee From 18654a399df95818c2789456a0929e9f1bf43230 Mon Sep 17 00:00:00 2001 From: egan Date: Sun, 8 Aug 2021 23:04:34 +0800 Subject: [PATCH 069/165] =?UTF-8?q?=E6=94=AF=E4=BB=98=E9=80=9A=E7=9F=A5?= =?UTF-8?q?=E8=AF=B7=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egzosn/pay/common/bean/NoticeParams.java | 85 +++++++++++++++++++ .../egzosn/pay/common/bean/NoticeRequest.java | 47 ++++++++++ 2 files changed, 132 insertions(+) create mode 100644 pay-java-common/src/main/java/com/egzosn/pay/common/bean/NoticeParams.java create mode 100644 pay-java-common/src/main/java/com/egzosn/pay/common/bean/NoticeRequest.java 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 0000000..71880b0 --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/NoticeParams.java @@ -0,0 +1,85 @@ +/* + * Copyright 2017-2021 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 { + + + /** + * 为了获取request里面传过来的动态参数 + */ + private Map body; + + /** + * 存放请求头信息 + */ + private Map> headers; + + + public NoticeParams(Map body) { + this.body = body; + } + + public NoticeParams(Map body, Map> headers) { + this.body = body; + this.headers = headers; + } + + public String getHeader(String name) { + List value = this.headers.get(name); + return (null == value || value.isEmpty()) ? null : value.get(0); + } + + public Enumeration getHeaders(String name) { + List value = this.headers.get(name); + return (Collections.enumeration(value != null ? value : Collections.emptySet())); + } + + public Enumeration getHeaderNames() { + 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; + } +} 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 0000000..3ec9af7 --- /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(); +} -- Gitee From a5a278611d8840172dc404c643398a882973325a Mon Sep 17 00:00:00 2001 From: egan Date: Sun, 8 Aug 2021 23:05:30 +0800 Subject: [PATCH 070/165] =?UTF-8?q?=E5=BE=AE=E4=BF=A1V3=20=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E8=AF=81=E4=B9=A6=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wx/v3/api/DefaultWxPayAssistService.java | 204 ++++++++++++++++++ .../pay/wx/v3/api/WxPayAssistService.java | 76 +++++++ .../egzosn/pay/wx/v3/api/WxPayService.java | 201 ++++++----------- .../pay/wx/v3/bean/CertEnvironment.java | 4 + .../pay/wx/v3/bean/WxTransactionType.java | 6 +- .../pay/wx/v3/utils/AntCertificationUtil.java | 113 +++++++++- .../com/egzosn/pay/wx/v3/utils/WxConst.java | 6 + pay-java-wx/src/test/java/PayTest.java | 4 +- 8 files changed, 473 insertions(+), 141 deletions(-) create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/DefaultWxPayAssistService.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayAssistService.java 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 0000000..994335e --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/DefaultWxPayAssistService.java @@ -0,0 +1,204 @@ +package com.egzosn.pay.wx.v3.api; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.security.Signature; +import java.security.cert.Certificate; +import java.util.Map; + +import org.apache.http.HttpEntity; +import org.apache.http.entity.ContentType; +import org.apache.http.message.BasicHeader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +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.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 { + protected final Logger LOG = LoggerFactory.getLogger(getClass()); + private WxPayConfigStorage payConfigStorage; + + private HttpRequestTemplate requestTemplate; + + private WxPayService wxPayService; + + + public DefaultWxPayAssistService(WxPayService wxPayService) { + this.wxPayService = wxPayService; + payConfigStorage = wxPayService.getPayConfigStorage(); + requestTemplate = wxPayService.getHttpRequestTemplate(); + + } + + + /** + * 发起请求 + * + * @param parameters 支付参数 + * @param transactionType 交易类型 + * @return 响应内容体 + */ + public JSONObject doExecute(Map parameters, TransactionType transactionType) { + String requestBody = JSON.toJSONString(parameters, SerializerFeature.WriteMapNullValue); + return doExecute(requestBody, transactionType); + } + + + /** + * 发起请求 + * + * @param body 请求内容 + * @param transactionType 交易类型 + * @param uriVariables 用于匹配表达式 + * @return 响应内容体 + */ + public JSONObject doExecute(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); + 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())); + } + return responseBody; + } + + /** + * 发起请求 + * + * @param parameters 支付参数 + * @param order 订单 + * @return 请求响应 + */ + public JSONObject doExecute(Map parameters, PayOrder order) { + TransactionType transactionType = order.getTransactionType(); + return doExecute(parameters, transactionType); + } + + + /** + * 构建请求实体 + * 这里也做签名处理 + * + * @param body 请求内容体 + * @param method 请求方法 + * @return 请求实体 + */ + 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-Service")); + return 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.getSecretKey(), 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) { + final Certificate certificate = AntCertificationUtil.getCertificate(serialNo); + if (null == certificate){ + refreshCertificate(); + } + + + + return null; + } + + +/* *//** + * 我方对响应验签,和应答签名做比较,使用微信平台证书. + * + * @param params the params + * @return the boolean + *//* + public boolean responseSignVerify(ResponseSignVerifyParams params) { + + String wechatpaySerial = params.getWechatpaySerial(); + if (CERTIFICATE_MAP.isEmpty() || !CERTIFICATE_MAP.containsKey(wechatpaySerial)) { + wechatMetaContainer.getTenantIds().forEach(this::refreshCertificate); + } + Certificate certificate = CERTIFICATE_MAP.get(wechatpaySerial); + + final String signatureStr = createSign(true, params.getWechatpayTimestamp(), params.getWechatpayNonce(), params.getBody()); + Signature signer = Signature.getInstance("SHA256withRSA"); + signer.initVerify(certificate); + signer.update(signatureStr.getBytes(StandardCharsets.UTF_8)); + + return signer.verify(Base64Utils.decodeFromString(params.getWechatpaySignature())); + }*/ +} 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 0000000..8bf20e9 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayAssistService.java @@ -0,0 +1,76 @@ +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.HttpStringEntity; + +/** + * 微信支付辅助服务 + * + * @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 parameters 支付参数 + * @param order 订单 + * @return 请求响应 + */ + JSONObject doExecute(Map parameters, PayOrder order); + + /** + * 构建请求实体 + * 这里也做签名处理 + * + * @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/WxPayService.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayService.java index ba4255a..7615270 100644 --- 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 @@ -6,11 +6,11 @@ import java.net.URLEncoder; import java.security.PrivateKey; import java.util.Date; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import java.util.TreeMap; -import org.apache.http.entity.ContentType; -import org.apache.http.message.BasicHeader; +import org.apache.http.HttpEntity; import static com.egzosn.pay.wx.api.WxConst.OUT_TRADE_NO; import static com.egzosn.pay.wx.api.WxConst.RETURN_CODE; @@ -20,7 +20,6 @@ import static com.egzosn.pay.wx.api.WxConst.SUCCESS; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; -import com.alibaba.fastjson.serializer.SerializerFeature; import com.egzosn.pay.common.api.BasePayService; import com.egzosn.pay.common.bean.BillType; import com.egzosn.pay.common.bean.CurType; @@ -44,10 +43,9 @@ import com.egzosn.pay.common.util.MapGen; 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.RSA2; import com.egzosn.pay.common.util.str.StringUtils; -import com.egzosn.pay.wx.api.WxRedPackService; -import com.egzosn.pay.wx.bean.RedpackOrder; import com.egzosn.pay.wx.bean.WxPayError; import com.egzosn.pay.wx.bean.WxPayMessage; import com.egzosn.pay.wx.bean.WxTransferType; @@ -68,7 +66,7 @@ import com.egzosn.pay.wx.v3.utils.WxConst; * date 2016-5-18 14:09:01 * */ -public class WxPayService extends BasePayService implements WxRedPackService { +public class WxPayService extends BasePayService { /** @@ -80,6 +78,22 @@ public class WxPayService extends BasePayService implements */ private boolean partner = false; + /** + * 辅助api + */ + private volatile WxPayAssistService wxPayAssistService; + + public WxPayAssistService getAssistService() { + if (null == wxPayAssistService) { + wxPayAssistService = new DefaultWxPayAssistService(this); + } + return wxPayAssistService; + } + + public void setAssistService(WxPayAssistService wxPayAssistService) { + this.wxPayAssistService = wxPayAssistService; + } + /** * 创建支付服务 * @@ -108,6 +122,7 @@ public class WxPayService extends BasePayService implements public BasePayService setPayConfigStorage(WxPayConfigStorage payConfigStorage) { payConfigStorage.loadCertEnvironment(); this.payConfigStorage = payConfigStorage; + return this; } @@ -175,7 +190,7 @@ public class WxPayService extends BasePayService implements * @param parameters 参数信息 * @return 参数信息 */ - private Map initPartner(Map parameters) { + private void initPartner(Map parameters) { if (null == parameters) { parameters = new HashMap<>(); } @@ -186,9 +201,6 @@ public class WxPayService extends BasePayService implements } setParameters(parameters, "sub_appid", payConfigStorage.getSubAppId()); initSubMchId(parameters); - - return parameters; - } /** @@ -217,69 +229,17 @@ public class WxPayService extends BasePayService implements * @param order 支付订单 * @return 订单参数 */ - private Map loadSettleInfo(Map parameters, PayOrder order) { + private 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; } - else { - //结算信息 - setParameters(parameters, "settle_info", order); - } - - return parameters; + //结算信息 + setParameters(parameters, "settle_info", order); - } - /** - * 发起请求 - * - * @param parameters 支付参数 - * @param transactionType 交易类型 - * @return 响应内容体 - */ - protected JSONObject doExecute(Map parameters, TransactionType transactionType) { - String requestBody = JSON.toJSONString(parameters, SerializerFeature.WriteMapNullValue); - return doExecute(requestBody, transactionType); - } - - - /** - * 发起请求 - * - * @param body 请求内容 - * @param transactionType 交易类型 - * @param uriVariables 用于匹配表达式 - * @return 响应内容体 - */ - protected JSONObject doExecute(String body, TransactionType transactionType, Object... uriVariables) { - String reqUrl = UriVariables.getUri(getReqUrl(transactionType), uriVariables); - MethodType method = MethodType.valueOf(transactionType.getMethod()); - if (MethodType.GET == method && StringUtils.isNotEmpty(body)) { - reqUrl += UriVariables.QUESTION.concat(body); - body = ""; - } - HttpStringEntity entity = initHttpStringEntity(reqUrl, body, transactionType.getMethod()); - ResponseEntity responseEntity = requestTemplate.doExecuteEntity(reqUrl, entity, JSONObject.class, method); - 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())); - } - return responseBody; - } - - /** - * 发起请求 - * - * @param parameters 支付参数 - * @param order 订单 - * @return 请求响应 - */ - protected JSONObject doExecute(Map parameters, PayOrder order) { - TransactionType transactionType = order.getTransactionType(); - return doExecute(parameters, transactionType); } @@ -318,7 +278,7 @@ public class WxPayService extends BasePayService implements TransactionType transactionType = order.getTransactionType(); ((WxTransactionType) transactionType).setAttribute(parameters, order); - return doExecute(parameters, order); + return wxPayAssistService.doExecute(parameters, order); } @@ -334,33 +294,37 @@ public class WxPayService extends BasePayService implements ////统一下单 JSONObject result = unifiedOrder(order); - throw new PayErrorException(new WxPayError("", "等待作者实现")); - - } + //如果是扫码支付或者刷卡付无需处理,直接返回 + if (((WxTransactionType) order.getTransactionType()).isReturn()) { + return result; + } + Map params = new LinkedHashMap<>(); + String appId = payConfigStorage.getAppId(); + 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, prepayId); + String paySign = createSign(signText, payConfigStorage.getInputCharset()); + params.put(WxTransactionType.JSAPI.equals(order.getTransactionType()) ? "paySign" : "sign", paySign); + return params; - /** - * 初始化请求实体 - * 这里也做签名处理 - * - * @param body 请求内容体 - * @param method 请求方法 - * @return 请求实体 - */ - private HttpStringEntity initHttpStringEntity(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 = 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-Service")); - return entity; } @@ -398,8 +362,7 @@ public class WxPayService extends BasePayService implements private String getSpParameters() { Map attr = initSubMchId(null); setParameters(attr, WxConst.SP_MCH_ID, payConfigStorage.getSpMchId()); - String parameters = UriVariables.getMapToParameters(attr); - return parameters; + return UriVariables.getMapToParameters(attr); } @@ -510,7 +473,7 @@ public class WxPayService extends BasePayService implements transactionType = WxTransactionType.QUERY_OUT_TRADE_NO; uriVariable = outTradeNo; } - return doExecute(parameters, transactionType, uriVariable); + return wxPayAssistService.doExecute(parameters, transactionType, uriVariable); } @@ -524,7 +487,7 @@ public class WxPayService extends BasePayService implements @Override public Map close(String transactionId, String outTradeNo) { String parameters = getSpParameters(); - return doExecute(parameters, WxTransactionType.CLOSE, outTradeNo); + return wxPayAssistService.doExecute(parameters, WxTransactionType.CLOSE, outTradeNo); } /** @@ -553,7 +516,7 @@ public class WxPayService extends BasePayService implements } parameters.put("amount", refundAmount); setParameters(parameters, "amount", refundOrder); - return WxRefundResult.create(doExecute(parameters, WxTransactionType.REFUND)); + return WxRefundResult.create(wxPayAssistService.doExecute(parameters, WxTransactionType.REFUND)); } @@ -566,7 +529,7 @@ public class WxPayService extends BasePayService implements @Override public Map refundquery(RefundOrder refundOrder) { String parameters = UriVariables.getMapToParameters(initSubMchId(null)); - return doExecute(parameters, WxTransactionType.REFUND_QUERY, refundOrder.getRefundNo()); + return wxPayAssistService.doExecute(parameters, WxTransactionType.REFUND_QUERY, refundOrder.getRefundNo()); } /** @@ -608,10 +571,10 @@ public class WxPayService extends BasePayService implements initSubMchId(parameters).put("bill_type", billType.getType()); } String body = UriVariables.getMapToParameters(parameters); - JSONObject result = doExecute(body, WxTransactionType.valueOf(billType.getCustom())); + JSONObject result = wxPayAssistService.doExecute(body, WxTransactionType.valueOf(billType.getCustom())); String downloadUrl = result.getString("download_url"); MethodType methodType = MethodType.GET; - HttpStringEntity entity = initHttpStringEntity(downloadUrl, "", methodType.name()); + HttpEntity entity = wxPayAssistService.buildHttpEntity(downloadUrl, "", methodType.name()); ResponseEntity responseEntity = requestTemplate.doExecuteEntity(downloadUrl, entity, InputStream.class, methodType); InputStream inputStream = responseEntity.getBody(); int statusCode = responseEntity.getStatusCode(); @@ -637,18 +600,16 @@ public class WxPayService extends BasePayService implements * @param order 转账订单 *
      *
-     *                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               注意事项:
-     *                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               ◆ 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。
-     *                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               ◆ XML具有可扩展性,因此返回参数可能会有新增,而且顺序可能不完全遵循此文档规范,如果在解析回包的时候发生错误,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新回包字段,会更新到此API文档中。
-     *                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               ◆ 因为错误代码字段err_code的值后续可能会增加,所以商户如果遇到回包返回新的错误码,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新的错误码,会更新到此API文档中。
-     *                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               ◆ 错误代码描述字段err_code_des只供人工定位问题时做参考,系统实现时请不要依赖这个字段来做自动化处理。
-     *                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
+ * 注意事项: + * ◆ 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。 + * ◆ XML具有可扩展性,因此返回参数可能会有新增,而且顺序可能不完全遵循此文档规范,如果在解析回包的时候发生错误,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新回包字段,会更新到此API文档中。 + * ◆ 因为错误代码字段err_code的值后续可能会增加,所以商户如果遇到回包返回新的错误码,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新的错误码,会更新到此API文档中。 + * ◆ 错误代码描述字段err_code_des只供人工定位问题时做参考,系统实现时请不要依赖这个字段来做自动化处理。 + * * @return 对应的转账结果 */ @Override public Map transfer(TransferOrder order) { - - throw new PayErrorException(new WxPayError("", "等待作者实现")); } @@ -681,29 +642,5 @@ public class WxPayService extends BasePayService implements return WxPayMessage.create(message); } - /** - * 微信发红包 - * - * @param redpackOrder 红包实体 - * @return 返回发红包实体后的结果 - */ - @Override - public Map sendredpack(RedpackOrder redpackOrder) { - throw new PayErrorException(new WxPayError("", "等待作者实现")); - } - - - /** - * 查询红包记录 - * 用于商户对已发放的红包进行查询红包的具体信息,可支持普通红包和裂变包 - * 查询红包记录API只支持查询30天内的红包订单,30天之前的红包订单请登录商户平台查询。 - * - * @param mchBillno 商户发放红包的商户订单号 - * @return 返回查询结果 - */ - @Override - public Map gethbinfo(String mchBillno) { - throw new PayErrorException(new WxPayError("", "等待作者实现")); - } } 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 index 7bc13f2..eed1c5b 100644 --- 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 @@ -27,6 +27,10 @@ public class CertEnvironment { private String serialNumber; + + + + public CertEnvironment() { } 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 index 46ed6a4..7c94876 100644 --- 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 @@ -14,7 +14,7 @@ import com.egzosn.pay.wx.v3.bean.order.SceneInfo; import com.egzosn.pay.wx.v3.utils.WxConst; /** - * 微信交易类型 + * 微信V3交易类型 * * @author egan *

@@ -107,9 +107,7 @@ public enum WxTransactionType implements TransactionType { /** * 申请资金账单 */ - FUND_FLOW_BILL("bill/fundflowbill", MethodType.GET), - - + FUND_FLOW_BILL("bill/fundflowbill", MethodType.GET) ; WxTransactionType(String type, MethodType method) { 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 index 1e06084..1660d2c 100644 --- 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 @@ -2,14 +2,27 @@ 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.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 com.egzosn.pay.common.exception.PayErrorException; +import com.egzosn.pay.common.util.sign.encrypt.Base64; import com.egzosn.pay.wx.bean.WxPayError; import com.egzosn.pay.wx.v3.bean.CertEnvironment; @@ -21,18 +34,63 @@ import com.egzosn.pay.wx.v3.bean.CertEnvironment; * 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 { try { PKCS12_KEY_STORE = KeyStore.getInstance("PKCS12"); } catch (KeyStoreException e) { - throw new PayErrorException(new WxPayError("500", " keystore 初始化失败")); + 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 证书流 + */ + public static void loadCertificate(String serialNo, InputStream certificateStream) { + try { + Certificate certificate = CERTIFICATE_FACTORY.generateCertificate(certificateStream); + CERTIFICATE_MAP.put(serialNo, 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); + } /** @@ -56,11 +114,60 @@ public final class AntCertificationUtil { return new CertEnvironment(privateKey, publicKey, serialNumber); } catch (GeneralSecurityException e) { - throw new PayErrorException(new WxPayError("500", "获取公私钥失败"), e); + throw new PayErrorException(new WxPayError(WxConst.FAILURE, "获取公私钥失败"), e); } catch (IOException e) { - throw new PayErrorException(new WxPayError("500", "私钥证书流加载失败"), e); + throw new PayErrorException(new WxPayError(WxConst.FAILURE, "私钥证书流加载失败"), e); } } + + + /** + * 解密响应体. + * + * @param associatedData 相关数据 + * @param nonce 随机串 + * @param cipherText 需要解密的文本 + * @return the string + */ + 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 encrypt message + * @since 1.0.6.RELEASE + */ + public static String encryptToString(String message, Certificate certificate) { + try { + Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding", WxConst.BC_PROVIDER); + cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey()); + + byte[] data = message.getBytes(StandardCharsets.UTF_8); + byte[] cipherData = cipher.doFinal(data); + return Base64.encode(cipherData); + + } + catch (GeneralSecurityException e) { + throw new PayErrorException(new WxPayError(WxConst.FAILURE, e.getMessage()), e); + } + } + } diff --git a/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/utils/WxConst.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/utils/WxConst.java index fe0d1e0..e644019 100644 --- 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 @@ -20,6 +20,11 @@ public final class WxConst { * 证书别名 */ public static final String CERT_ALIAS = "Tenpay Certificate"; + /** + * 加密算法提供方 - BouncyCastle + */ + public static final String BC_PROVIDER = "BC"; + /** * 沙箱 */ @@ -38,4 +43,5 @@ public final class WxConst { 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"; } diff --git a/pay-java-wx/src/test/java/PayTest.java b/pay-java-wx/src/test/java/PayTest.java index 8137df0..4b42a60 100644 --- a/pay-java-wx/src/test/java/PayTest.java +++ b/pay-java-wx/src/test/java/PayTest.java @@ -19,8 +19,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 { -- Gitee From ea3bc29debbc5e8d6ba278200f271db68ea8175a Mon Sep 17 00:00:00 2001 From: egan Date: Mon, 9 Aug 2021 14:06:23 +0000 Subject: [PATCH 071/165] update README.md. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c7a2071..bad91f0 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ android 例子 [pay-java-android](https://gitee.com/egzosn/pay-java-android) 作者公众号(每周输出) ![公众号](https://egzosn.gitee.io/pay-java-parent/gzh.png "gzh.png") -E-Mail:egzosn@gmail.com +E-Mail:egan@egzosn.com **QQ群:** -- Gitee From 7b400692b657f81322a3bd2da7aa129f35caec0a Mon Sep 17 00:00:00 2001 From: egan Date: Sun, 15 Aug 2021 22:43:47 +0800 Subject: [PATCH 072/165] =?UTF-8?q?1.=E6=96=B0=E5=A2=9E=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E9=AA=8C=E7=AD=BE=E6=96=B9=E5=BC=8F=202.=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E6=9E=84=E9=80=A0=E7=B1=BB=203.REA=E8=AF=81?= =?UTF-8?q?=E4=B9=A6=E9=AA=8C=E7=AD=BE=E6=96=B0=E5=A2=9ECertificate?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egzosn/pay/common/api/BasePayService.java | 43 +++-- .../com/egzosn/pay/common/api/PayService.java | 23 ++- .../egzosn/pay/common/bean/NoticeParams.java | 13 ++ .../pay/common/bean/OrderParaStructure.java | 35 ++++ .../pay/common/util/sign/encrypt/RSA.java | 21 +- .../pay/common/util/sign/encrypt/RSA2.java | 180 ++++++++++-------- 6 files changed, 216 insertions(+), 99 deletions(-) create mode 100644 pay-java-common/src/main/java/com/egzosn/pay/common/bean/OrderParaStructure.java 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 187cbed..cc7213b 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 @@ -17,7 +17,10 @@ import org.slf4j.LoggerFactory; import com.alibaba.fastjson.JSON; 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.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; @@ -119,6 +122,7 @@ public abstract class BasePayService implements Pay return base64ClientID; } + /** * 创建签名 * @@ -395,6 +399,17 @@ public abstract class BasePayService implements Pay interceptors.add(interceptor); } + /** + * 将请求参数或者请求流转化为 Map + * + * @param request 通知请求 + * @return 获得回调的请求参数 + */ + @Override + public NoticeParams getNoticeParams(NoticeRequest request) { + return null; + } + /** * 将请求参数或者请求流转化为 Map * @@ -469,20 +484,26 @@ public abstract class BasePayService implements Pay 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 返回订单参数 + */ protected Map setParameters(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; + return OrderParaStructure.loadParameters(parameters, key, order); } 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 bf5256f..178d69c 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 @@ -7,6 +7,8 @@ import java.util.Map; 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; @@ -61,13 +63,21 @@ public interface PayService { /** * 回调校验 - * + * 已过时方法,详情{@link #verify(NoticeParams)} * @param params 回调回来的参数集 * @return 签名校验 true通过 */ - + @Deprecated boolean verify(Map params); + /** + * 回调校验 + * + * @param params 回调回来的参数集 + * @return 签名校验 true通过 + */ + boolean verify(NoticeParams params); + /** * 返回创建的订单信息 @@ -114,8 +124,17 @@ public interface PayService { * @param is 请求流 * @return 获得回调的请求参数 */ + @Deprecated Map getParameter2Map(Map parameterMap, InputStream is); + /** + * 将请求参数或者请求流转化为 Map + * + * @param request 通知请求 + * @return 获得回调的请求参数 + */ + NoticeParams getNoticeParams(NoticeRequest request); + /** * 获取输出消息,用户返回给支付端 * 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 index 71880b0..f89ac97 100644 --- 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 @@ -42,6 +42,11 @@ public class NoticeParams { */ private Map> headers; + /** + * 附加属性 + */ + private Map attr; + public NoticeParams(Map body) { this.body = body; @@ -82,4 +87,12 @@ public class NoticeParams { public void setHeaders(Map> headers) { this.headers = headers; } + + public Map getAttr() { + return attr; + } + + public void setAttr(Map attr) { + this.attr = attr; + } } 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 0000000..c651b25 --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/OrderParaStructure.java @@ -0,0 +1,35 @@ +package com.egzosn.pay.common.bean; + +import java.util.Map; + +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; + } + +} 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 ec060ac..4911def 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 @@ -12,6 +12,7 @@ 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; @@ -136,10 +137,7 @@ public class RSA { 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)); + return verify(content, sign, pubKey, signAlgorithms, characterEncoding); } catch (GeneralSecurityException e) { LOG.error("", e); @@ -176,6 +174,7 @@ public class RSA { return false; } + /** * RSA验签名检查 * @@ -204,6 +203,20 @@ public class RSA { 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); + } /** * 解密 * 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 46cae1c..d25251c 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)); + } } -- Gitee From 4bd02fa0c2fced9de4d436f0af6956666d6a96b2 Mon Sep 17 00:00:00 2001 From: egan Date: Sun, 15 Aug 2021 22:45:02 +0800 Subject: [PATCH 073/165] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E9=AA=8C=E7=AD=BE=E6=96=B9=E5=BC=8F=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/ali/api/AliPayService.java | 36 ++++++++---- .../egzosn/pay/baidu/api/BaiduPayService.java | 19 ++++++- .../egzosn/pay/common/api/BasePayService.java | 1 + .../egzosn/pay/fuiou/api/FuiouPayService.java | 17 +++++- .../pay/payoneer/api/PayoneerPayService.java | 15 ++++- .../pay/paypal/api/PayPalPayService.java | 9 +++ .../pay/paypal/v2/api/PayPalPayService.java | 11 +++- .../egzosn/pay/union/api/UnionPayService.java | 15 ++++- .../wx/youdian/api/WxYouDianPayService.java | 17 +++++- .../com/egzosn/pay/wx/api/WxPayService.java | 57 ++++++++++++------- 10 files changed, 154 insertions(+), 43 deletions(-) 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 1e94971..5c5568f 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 @@ -32,7 +32,9 @@ import com.egzosn.pay.ali.bean.OrderSettle; import com.egzosn.pay.common.api.BasePayService; 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; @@ -108,18 +110,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); return false; } return signVerify(params, (String) params.get(SIGN)) && verifySource((String) params.get("notify_id")); - } + /** * 根据反馈回来的信息,生成签名结果 * @@ -209,12 +223,12 @@ public class AliPayService extends BasePayService { private void setNotifyUrl(Map orderInfo, PayOrder order) { orderInfo.put(NOTIFY_URL, payConfigStorage.getNotifyUrl()); - setParameters(orderInfo, NOTIFY_URL, order); + OrderParaStructure.loadParameters(orderInfo, NOTIFY_URL, order); } private void setReturnUrl(Map orderInfo, PayOrder order) { orderInfo.put(RETURN_URL, payConfigStorage.getReturnUrl()); - setParameters(orderInfo, RETURN_URL, order); + OrderParaStructure.loadParameters(orderInfo, RETURN_URL, order); } /** @@ -235,7 +249,7 @@ public class AliPayService extends BasePayService { Map bizContent = new TreeMap<>(); bizContent.put("body", order.getBody()); - setParameters(bizContent, "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()); @@ -319,8 +333,8 @@ public class AliPayService extends BasePayService { protected void loadCertSn(Map orderInfo) { if (payConfigStorage.isCertSign()) { final CertEnvironment certEnvironment = payConfigStorage.getCertEnvironment(); - setParameters(orderInfo, "app_cert_sn", certEnvironment.getMerchantCertSN()); - setParameters(orderInfo, "alipay_root_cert_sn", certEnvironment.getRootCertSN()); + OrderParaStructure.loadParameters(orderInfo, "app_cert_sn", certEnvironment.getMerchantCertSN()); + OrderParaStructure.loadParameters(orderInfo, "alipay_root_cert_sn", certEnvironment.getRootCertSN()); } } @@ -493,7 +507,7 @@ public class AliPayService extends BasePayService { */ protected void setAppAuthToken(Map parameters, Map attrs) { setAppAuthToken(parameters); - setParameters(parameters, APP_AUTH_TOKEN, (String) attrs.remove(APP_AUTH_TOKEN)); + OrderParaStructure.loadParameters(parameters, APP_AUTH_TOKEN, (String) attrs.remove(APP_AUTH_TOKEN)); } /** @@ -502,7 +516,7 @@ public class AliPayService extends BasePayService { * @param parameters 参数 */ protected void setAppAuthToken(Map parameters) { - setParameters(parameters, APP_AUTH_TOKEN, payConfigStorage.getAppAuthToken()); + OrderParaStructure.loadParameters(parameters, APP_AUTH_TOKEN, payConfigStorage.getAppAuthToken()); } @@ -645,11 +659,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)); 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 7d0fcde..58487fe 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 @@ -27,6 +27,7 @@ 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; @@ -84,22 +85,34 @@ public class BaiduPayService extends BasePayService { * @param params 回调回来的参数集 * @return 结果 */ + @Deprecated @Override public boolean verify(Map params) { + + return verify(new NoticeParams(params)); + } + + /** + * 回调校验 + * + * @param noticeParams 回调回来的参数集 + * @return 签名校验 true通过 + */ + @Override + public boolean verify(NoticeParams noticeParams) { + final Map params = noticeParams.getBody(); if (!RESPONSE_SUCCESS.equals(params.get(RESPONSE_STATUS)) && !RESPONSE_SUCCESS.toString().equals(params.get(RESPONSE_STATUS))) { return false; } LOG.info("开始验证回调签名参数:" + params); try { - boolean checkSign = this.checkReturnSign(params, payConfigStorage.getKeyPublic(), (String) params.get(RSA_SIGN)); - return checkSign; + 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); 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 cc7213b..00b4b10 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 @@ -502,6 +502,7 @@ public abstract class BasePayService implements Pay * @param order 订单对象 * @return 返回订单参数 */ + @Deprecated protected Map setParameters(Map parameters, String key, Order order) { return OrderParaStructure.loadParameters(parameters, key, order); } 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 bb16674..f3127da 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 @@ -11,6 +11,7 @@ import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.common.api.BasePayService; 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.PayMessage; import com.egzosn.pay.common.bean.PayOrder; import com.egzosn.pay.common.bean.PayOutMessage; @@ -117,8 +118,21 @@ public class FuiouPayService extends BasePayService { * @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; @@ -128,11 +142,10 @@ public class FuiouPayService extends BasePayService { return (signVerify(params, (String) params.remove("md5")) && verifySource((String) params.get("order_id"))); } catch (PayErrorException e) { - e.printStackTrace(); + LOG.error("", e); } return false; } - /** * 回调签名校验 * 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 5561b40..a09043f 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 @@ -20,6 +20,7 @@ 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; @@ -141,8 +142,21 @@ public class PayoneerPayService extends BasePayService im * @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 != null && 0 == Integer.parseInt(params.get(CODE).toString())) { if (params.containsKey(OUT_TRADE_NO)) { return verifySource((String) params.get(OUT_TRADE_NO)); @@ -152,7 +166,6 @@ public class PayoneerPayService extends BasePayService im return false; } - /** * 支付宝需要,微信是否也需要再次校验来源,进行订单查询 * 校验数据来源 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 9da31e2..9b1b15f 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 @@ -24,6 +24,7 @@ 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; @@ -133,9 +134,17 @@ public class PayPalPayService extends BasePayService { } + @Deprecated @Override public boolean verify(Map params) { + return verify(new NoticeParams(params)); + + } + + @Override + 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")); 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 index 898b35c..5e54691 100644 --- 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 @@ -25,6 +25,7 @@ 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; @@ -155,8 +156,16 @@ public class PayPalPayService extends BasePayService implem * @param params 回调回来的参数集 * @return 是否成功 true成功 */ + @Deprecated @Override public boolean verify(Map params) { + return verify(new NoticeParams(params)); + + } + + @Override + public boolean verify(NoticeParams noticeParams) { + final Map params = noticeParams.getBody(); Object paymentStatus = params.get("payment_status"); if (!"Completed".equals(paymentStatus)) { LOG.warn("状态未完成:" + paymentStatus); @@ -166,8 +175,6 @@ public class PayPalPayService extends BasePayService implem return "VERIFIED".equals(resp); } - - /** * 获取授权请求头 * 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 1c10645..bfcf361 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 @@ -28,6 +28,7 @@ import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.common.api.BasePayService; 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.PayMessage; import com.egzosn.pay.common.bean.PayOrder; import com.egzosn.pay.common.bean.PayOutMessage; @@ -200,16 +201,28 @@ 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通过 + */ + 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; } return this.signVerify(result, (String) result.get(SDKConstants.param_signature)); } - /** * 签名校验 * 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 5139f48..859bcb5 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 @@ -16,6 +16,7 @@ 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; @@ -132,8 +133,22 @@ public class WxYouDianPayService extends BasePayService params) { + + return verify(new NoticeParams(params)); + } + + /** + * 回调校验 + * + * @param noticeParams 回调回来的参数集 + * @return 签名校验 true通过 + */ + public boolean verify(NoticeParams noticeParams) { + final Map params = noticeParams.getBody(); + if (!"SUCCESS".equals(params.get("return_code"))) { LOG.debug(String.format("友店微信支付异常:return_code=%s,参数集=%s", params.get("return_code"), params)); return false; @@ -151,7 +166,6 @@ public class WxYouDianPayService extends BasePayService downloadBill(Date billDate, BillType billType) { return Collections.emptyMap(); 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 2042622..58e622f 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 @@ -40,7 +40,9 @@ import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.common.api.BasePayService; 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; @@ -135,9 +137,22 @@ public class WxPayService extends BasePayService implements * @param params 回调回来的参数集 * @return 签名校验 true通过 */ + @Deprecated @Override public boolean verify(Map params) { + return verify(new NoticeParams(params)); + } + + + /** + * 回调校验 + * + * @param noticeParams 回调回来的参数集 + * @return 签名校验 true通过 + */ + public boolean verify(NoticeParams noticeParams) { + final Map params = noticeParams.getBody(); if (null == 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)); @@ -187,8 +202,8 @@ public class WxPayService extends BasePayService implements parameters.put(APPID, payConfigStorage.getAppId()); parameters.put(MCH_ID, payConfigStorage.getMchId()); //判断如果是服务商模式信息则加入 - setParameters(parameters, "sub_mch_id", payConfigStorage.getSubMchId()); - setParameters(parameters, "sub_appid", payConfigStorage.getSubAppId()); + OrderParaStructure.loadParameters(parameters, "sub_mch_id", payConfigStorage.getSubMchId()); + OrderParaStructure.loadParameters(parameters, "sub_appid", payConfigStorage.getSubAppId()); parameters.put(NONCE_STR, SignTextUtils.randomStr()); return parameters; @@ -209,13 +224,13 @@ 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()); + OrderParaStructure.loadParameters(parameters, "attach", order.getAddition()); initNotifyUrl(parameters, order); parameters.put("trade_type", order.getTransactionType().getType()); if (null != order.getExpirationTime()) { @@ -229,11 +244,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); @@ -514,8 +529,8 @@ public class WxPayService extends BasePayService implements private Map initNotifyUrl(Map parameters, Order order) { - setParameters(parameters, "notify_url", payConfigStorage.getNotifyUrl()); - setParameters(parameters, "notify_url", order); + OrderParaStructure.loadParameters(parameters, "notify_url", payConfigStorage.getNotifyUrl()); + OrderParaStructure.loadParameters(parameters, "notify_url", order); return parameters; } @@ -530,16 +545,16 @@ 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()); parameters.put("total_fee", Util.conversionCentAmount(refundOrder.getTotalAmount())); parameters.put("refund_fee", Util.conversionCentAmount(refundOrder.getRefundAmount())); 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()); //设置签名 @@ -559,9 +574,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); @@ -601,7 +616,7 @@ public class WxPayService extends BasePayService implements //目前只支持日账单 parameters.put("bill_date", DateUtils.formatDate(billDate, DateUtils.YYYYMMDD)); String fileType = billType.getFileType(); - setParameters(parameters, "tar_type", fileType); + OrderParaStructure.loadParameters(parameters, "tar_type", fileType); //设置签名 setSign(parameters); Map ret = new HashMap(3); @@ -758,8 +773,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); -- Gitee From e4129afe63a8a616f4d59942a3b156d19f06821b Mon Sep 17 00:00:00 2001 From: egan Date: Sun, 15 Aug 2021 22:45:29 +0800 Subject: [PATCH 074/165] =?UTF-8?q?pay=20java=20web=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pay-java-web-support/pom.xml | 42 ++++++++++ .../web/support/HttpRequestNoticeParams.java | 82 +++++++++++++++++++ pom.xml | 1 + 3 files changed, 125 insertions(+) create mode 100644 pay-java-web-support/pom.xml create mode 100644 pay-java-web-support/src/main/java/com/egzosn/pay/web/support/HttpRequestNoticeParams.java diff --git a/pay-java-web-support/pom.xml b/pay-java-web-support/pom.xml new file mode 100644 index 0000000..a0d1aa0 --- /dev/null +++ b/pay-java-web-support/pom.xml @@ -0,0 +1,42 @@ + + + + pay-java-parent + com.egzosn + 2.14.2-SNAPSHOT + + 4.0.0 + jar + pay-java-web-support + Pay Java Web 相关支持 + + 4.3.4.RELEASE + 4.10 + 4.3.6.Final + + + + + + com.egzosn + pay-java-common + + + javax.servlet + javax.servlet-api + provided + 3.1.0 + + + javax.servlet + jsp-api + 2.0 + provided + + + + + + \ 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 0000000..acf0753 --- /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/pom.xml b/pom.xml index 193a8d6..e041e7b 100644 --- a/pom.xml +++ b/pom.xml @@ -54,6 +54,7 @@ pay-java-common + pay-java-web-support pay-java-ali pay-java-wx pay-java-wx-youdian -- Gitee From 44a0c90413a8b7026d5d4416b8cc4a9d88d2f638 Mon Sep 17 00:00:00 2001 From: egan Date: Sun, 15 Aug 2021 23:34:36 +0800 Subject: [PATCH 075/165] =?UTF-8?q?=E5=BE=AE=E4=BF=A1V3=20=E5=9B=9E?= =?UTF-8?q?=E8=B0=83=E9=AA=8C=E7=AD=BE=E5=8A=9F=E8=83=BD=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=EF=BC=8C=E5=BE=AE=E4=BF=A1V3=E6=94=AF=E4=BB=98=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=9F=BA=E6=9C=AC=E5=AE=9E=E7=8E=B0,=E6=9A=82?= =?UTF-8?q?=E6=9C=AA=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../demo/controller/WxV3PayController.java | 335 ++++++++++++++++++ .../wx/v3/api/DefaultWxPayAssistService.java | 38 +- .../pay/wx/v3/api/WxParameterStructure.java | 131 +++++++ .../pay/wx/v3/api/WxPayConfigStorage.java | 15 + .../egzosn/pay/wx/v3/api/WxPayService.java | 207 ++++------- .../egzosn/pay/wx/v3/bean/order/Amount.java | 7 +- .../pay/wx/v3/utils/AntCertificationUtil.java | 7 +- .../egzosn/pay/yiji/api/YiJiPayService.java | 14 +- 8 files changed, 579 insertions(+), 175 deletions(-) create mode 100644 pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxV3PayController.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxParameterStructure.java 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 0000000..73791ef --- /dev/null +++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxV3PayController.java @@ -0,0 +1,335 @@ + +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.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.CertStoreType; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.RefundOrder; +import com.egzosn.pay.common.bean.TransferOrder; +import com.egzosn.pay.demo.request.QueryOrder; +import com.egzosn.pay.wx.bean.WxBank; +import com.egzosn.pay.wx.bean.WxTransferType; +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.response.WxRefundResult; + +/** + * 发起支付入口 + * + * @author egan + * email egzosn@gmail.com + * date 2016/11/18 0:25 + */ +@RestController +@RequestMapping("wxV3") +public class WxV3PayController { + + private WxPayService service = null; + + + + + + public WxV3PayController() { + + + } + + @PostConstruct + public void init() { + + WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage(); + wxPayConfigStorage.setAppId("wxc7b993ff15a9f27c"); + wxPayConfigStorage.setMchId("1602947766"); +// wxPayConfigStorage.setKeyPublic("转账公钥,转账时必填"); + wxPayConfigStorage.setSecretKey("9bd8f0e7af4841299d782406b7774f56"); + wxPayConfigStorage.setNotifyUrl("https://pay.egzosn.com"); + wxPayConfigStorage.setReturnUrl("https://pay.egzosn.com"); + wxPayConfigStorage.setInputCharset("utf-8"); + wxPayConfigStorage.setCertSign(true); + wxPayConfigStorage.setApiClientKeyP12("E:\\Documents\\gitee\\pay-java-parent\\pay-java-demo\\src\\main\\resources\\yifenli_mall.p12"); + wxPayConfigStorage.setCertStoreType(CertStoreType.PATH); + service = new WxPayService(wxPayConfigStorage); + //设置回调消息处理 + //TODO {@link com.egzosn.pay.demo.controller.WxPayController#payBack} +// service.setPayMessageHandler(new WxPayMessageHandler(null)); + } + + + /** + * 跳到支付页面 + * 针对实时支付 + * + * @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); + order.setSpbillCreateIp(request.getHeader("X-Real-IP")); + StringBuffer requestURL = request.getRequestURL(); + //设置网页地址 + order.setWapUrl(requestURL.substring(0, requestURL.indexOf("/") > 0 ? requestURL.indexOf("/") : requestURL.length())); + //设置网页名称 + order.setWapName("在线充值"); + +// Map orderInfo = service.orderInfo(order); +// return service.buildRequest(orderInfo, MethodType.POST); + return service.toPay(order); + } + + /** + * 公众号支付 + * + * @param openid openid + * @param price 金额 + * @return 返回jsapi所需参数 + */ + @RequestMapping(value = "jsapi") + public Map toPay(String openid, BigDecimal price) { + + PayOrder order = new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), WxTransactionType.JSAPI); + order.setOpenid(openid); + + Map orderInfo = service.orderInfo(order); + orderInfo.put("code", 0); + + return orderInfo; + } + + + /** + * 获取支付预订单信息 + * + * @return 支付预订单信息 + */ + @RequestMapping("app") + public Map app() { + Map data = new HashMap<>(); + data.put("code", 0); + PayOrder order = new PayOrder("订单title", "摘要", BigDecimal.valueOf(0.01), UUID.randomUUID().toString().replace("-", "")); + //App支付 + order.setTransactionType(WxTransactionType.APP); + data.put("orderInfo", service.app(order)); + return data; + } + + /** + * 获取二维码图像 + * 二维码支付 + * + * @param price 金额 + * @return 二维码图像 + * @throws IOException IOException + */ + @RequestMapping(value = "toQrPay.jpg", produces = "image/jpeg;charset=UTF-8") + public byte[] toWxQrPay(BigDecimal price) throws IOException { + //获取对应的支付账户操作工具(可根据账户id) + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(service.genQrPay(new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, System.currentTimeMillis() + "", WxTransactionType.NATIVE)), "JPEG", baos); + return baos.toByteArray(); + } + + /** + * 获取二维码地址 + * 二维码支付 + * + * @param price 金额 + * @return 二维码图像 + * @throws IOException IOException + */ + @RequestMapping(value = "getQrPay.json") + public String getQrPay(BigDecimal price) throws IOException { + //获取对应的支付账户操作工具(可根据账户id) + return service.getQrPay(new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, System.currentTimeMillis() + "", WxTransactionType.NATIVE)); + } + + + + /** + * 支付回调地址 方式一 + *

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

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

+ * 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler} + * @throws IOException IOException + */ + @RequestMapping(value = "payBack.json") + public String payBack(HttpServletRequest request) throws IOException { + //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() + return service.payBack(request.getParameterMap(), request.getInputStream()).toMessage(); + } + + + /** + * 查询 + * + * @param order 订单的请求体 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @RequestMapping("query") + public Map query(QueryOrder order) { + return service.query(order.getTradeNo(), order.getOutTradeNo()); + } + + + /** + * 交易关闭接口 + * + * @param order 订单的请求体 + * @return 返回支付方交易关闭后的结果 + */ + @RequestMapping("close") + public Map close(QueryOrder order) { + return service.close(order.getTradeNo(), order.getOutTradeNo()); + } + + /** + * 申请退款接口 + * + * @param order 订单的请求体 + * @return 返回支付方申请退款后的结果 + */ + @RequestMapping("refund") + public WxRefundResult 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()); + } + + + /** + * 转账到余额 + * + * @param order 转账订单 + * @return 对应的转账结果 + */ + @RequestMapping("transfer") + public Map transfer(TransferOrder order) { + order.setOutNo("partner_trade_no 商户转账订单号"); + order.setPayeeAccount("用户openid"); + order.setPayeeName("收款用户姓名, 非必填,如果填写将强制验证收款人姓名"); + order.setRemark("转账备注, 非必填"); + order.setAmount(new BigDecimal(10)); + + //转账到余额,这里默认值是转账到银行卡 + order.setTransferType(WxTransferType.TRANSFERS); + + return service.transfer(order); + } + + + /** + * 转账到银行卡 + * + * @param order 转账订单 + * @return 对应的转账结果 + */ + @RequestMapping("transferPayBank") + public Map transferPayBank(TransferOrder order) { + order.setOutNo("partner_trade_no 商户转账订单号"); + //采用标准RSA算法,公钥由微信侧提供,将公钥信息配置在PayConfigStorage#setKeyPublic(String) + order.setPayeeAccount("enc_bank_no 收款方银行卡号"); + order.setPayeeName("收款方用户名"); + order.setBank(WxBank.ABC); + order.setRemark("转账备注, 非必填"); + order.setAmount(new BigDecimal(10)); + //转账到银行卡,这里默认值是转账到银行卡 + order.setTransferType(WxTransferType.PAY_BANK); + + return service.transfer(order); + } + + /** + * 转账查询 + * + * @param outNo 商户转账订单号 + * @param wxTransferType 微信转账类型, + * .....这里没办法了只能这样写(┬_┬),请见谅 + * {@link WxTransferType#QUERY_BANK} + * {@link WxTransferType#GETTRANSFERINFO} + * + *

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

+ * @return 对应的转账订单 + */ + @RequestMapping("transferQuery") + public Map transferQuery(String outNo, String wxTransferType) { + //默认查询银行卡的记录 com.egzosn.pay.wx.v3.bean.WxTransferType#QUERY_BANK + return service.transferQuery(outNo, wxTransferType); + } + + +} 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 index 994335e..2ea5602 100644 --- 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 @@ -5,6 +5,7 @@ import java.nio.charset.StandardCharsets; import java.security.Signature; import java.security.cert.Certificate; import java.util.Map; +import java.util.TreeMap; import org.apache.http.HttpEntity; import org.apache.http.entity.ContentType; @@ -12,11 +13,14 @@ import org.apache.http.message.BasicHeader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +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.alibaba.fastjson.serializer.SerializerFeature; import com.egzosn.pay.common.bean.MethodType; +import com.egzosn.pay.common.bean.NoticeParams; import com.egzosn.pay.common.bean.PayOrder; import com.egzosn.pay.common.bean.TransactionType; import com.egzosn.pay.common.exception.PayErrorException; @@ -26,6 +30,9 @@ 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.sign.encrypt.Base64; +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.WxTransactionType; @@ -127,7 +134,8 @@ public class DefaultWxPayAssistService implements WxPayAssistService { 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-Service")); + entity.addHeader(new BasicHeader("User-Agent", "X-Pay-Service")); + entity.addHeader(new BasicHeader("Accept", APPLICATION_JSON.getMimeType())); return entity; } @@ -169,36 +177,16 @@ public class DefaultWxPayAssistService implements WxPayAssistService { */ @Override public Certificate getCertificate(String serialNo) { - final Certificate certificate = AntCertificationUtil.getCertificate(serialNo); - if (null == certificate){ + Certificate certificate = AntCertificationUtil.getCertificate(serialNo); + if (null == certificate) { refreshCertificate(); + certificate = AntCertificationUtil.getCertificate(serialNo); } - - return null; + return certificate; } -/* *//** - * 我方对响应验签,和应答签名做比较,使用微信平台证书. - * - * @param params the params - * @return the boolean - *//* - public boolean responseSignVerify(ResponseSignVerifyParams params) { - - String wechatpaySerial = params.getWechatpaySerial(); - if (CERTIFICATE_MAP.isEmpty() || !CERTIFICATE_MAP.containsKey(wechatpaySerial)) { - wechatMetaContainer.getTenantIds().forEach(this::refreshCertificate); - } - Certificate certificate = CERTIFICATE_MAP.get(wechatpaySerial); - - final String signatureStr = createSign(true, params.getWechatpayTimestamp(), params.getWechatpayNonce(), params.getBody()); - Signature signer = Signature.getInstance("SHA256withRSA"); - signer.initVerify(certificate); - signer.update(signatureStr.getBytes(StandardCharsets.UTF_8)); - return signer.verify(Base64Utils.decodeFromString(params.getWechatpaySignature())); - }*/ } 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 0000000..b6031fb --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxParameterStructure.java @@ -0,0 +1,131 @@ +package com.egzosn.pay.wx.v3.api; + +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; + + +import com.egzosn.pay.common.bean.Order; +import com.egzosn.pay.common.bean.OrderParaStructure; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.http.UriVariables; +import com.egzosn.pay.common.util.MapGen; +import com.egzosn.pay.common.util.str.StringUtils; +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; + } + + /** + * 获取公共参数 + * + * @return 公共参数 + */ + public Map getPublicParameters() { + + Map parameters = new TreeMap(); + parameters.put(WxConst.APPID, payConfigStorage.getAppId()); + parameters.put(WxConst.MCH_ID, payConfigStorage.getMchId()); + return parameters; + } + + + + + + + /** + * 加载结算信息 + * + * @param parameters 订单参数 + * @param order 支付订单 + * @return 订单参数 + */ + 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 订单信息 + * @return 订单参数 + */ + public void initNotifyUrl(Map parameters, Order order) { + OrderParaStructure.loadParameters(parameters, WxConst.NOTIFY_URL, payConfigStorage.getNotifyUrl()); + OrderParaStructure.loadParameters(parameters, WxConst.NOTIFY_URL, order); + } + + /** + * 获取服务商相关信息 + * + * @return 服务商相关信息 + */ + public String getSpParameters() { + Map attr = initSubMchId(null); + OrderParaStructure.loadParameters(attr, WxConst.SP_MCH_ID, payConfigStorage.getSpMchId()); + return UriVariables.getMapToParameters(attr); + } + /** + * 初始化商户相关信息 + * + * @param parameters 参数信息 + * @return 参数信息 + */ + public void initPartner(Map parameters) { + if (null == parameters) { + parameters = new HashMap<>(); + } + if (StringUtils.isNotEmpty(payConfigStorage.getSpAppId()) && StringUtils.isNotEmpty(payConfigStorage.getSpMchId())) { + payConfigStorage.setPartner(true); + parameters.put("sp_appid", payConfigStorage.getSpAppId()); + parameters.put(WxConst.SP_MCH_ID, payConfigStorage.getSpMchId()); + } + OrderParaStructure.loadParameters(parameters, "sub_appid", payConfigStorage.getSubAppId()); + initSubMchId(parameters); + } + + + /** + * 初始化商户相关信息 + * + * @param parameters 参数信息 + * @return 参数信息 + */ + public Map initSubMchId(Map parameters) { + if (null == parameters) { + parameters = new HashMap<>(); + } + if (StringUtils.isNotEmpty(payConfigStorage.getSubMchId())) { + payConfigStorage.setPartner(true); + parameters.put(WxConst.SUB_MCH_ID, payConfigStorage.getSubMchId()); + } + return parameters; + + } + +} 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 index e61f963..c765a91 100644 --- 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 @@ -70,6 +70,13 @@ public class WxPayConfigStorage extends BasePayConfigStorage { */ private CertEnvironment certEnvironment; + /** + * 是否为服务商模式, 默认为false + */ + private boolean partner = false; + + + @Deprecated @Override public String getAppid() { @@ -222,4 +229,12 @@ public class WxPayConfigStorage extends BasePayConfigStorage { throw new PayErrorException(new PayException("读取证书异常", e.getMessage())); } } + + public boolean isPartner() { + return partner; + } + + public void setPartner(boolean partner) { + this.partner = partner; + } } 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 index 7615270..1858643 100644 --- 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 @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URLEncoder; import java.security.PrivateKey; +import java.security.cert.Certificate; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; @@ -17,6 +18,7 @@ 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.SUCCESS; +import static com.egzosn.pay.wx.v3.utils.AntCertificationUtil.getCertificate; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; @@ -24,7 +26,8 @@ import com.egzosn.pay.common.api.BasePayService; 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.Order; +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; @@ -34,12 +37,10 @@ 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.XML; import com.egzosn.pay.common.util.sign.SignTextUtils; @@ -73,16 +74,20 @@ public class WxPayService extends BasePayService { * api服务地址,默认为国内 */ private String apiServerUrl = WxConst.URI; - /** - * 是否为服务商模式, 默认为false - */ - private boolean partner = false; + /** * 辅助api */ private volatile WxPayAssistService wxPayAssistService; + /** + * 微信参数构造器 + */ + private volatile WxParameterStructure wxParameterStructure; + + + public WxPayAssistService getAssistService() { if (null == wxPayAssistService) { wxPayAssistService = new DefaultWxPayAssistService(this); @@ -122,7 +127,7 @@ public class WxPayService extends BasePayService { public BasePayService setPayConfigStorage(WxPayConfigStorage payConfigStorage) { payConfigStorage.loadCertEnvironment(); this.payConfigStorage = payConfigStorage; - + wxParameterStructure = new WxParameterStructure(payConfigStorage); return this; } @@ -147,13 +152,14 @@ public class WxPayService extends BasePayService { public String getReqUrl(TransactionType transactionType) { String type = transactionType.getType(); String partnerStr = ""; - if (partner) { + if (payConfigStorage.isPartner()) { partnerStr = "/partner"; } type = type.replace("{partner}", partnerStr); return apiServerUrl + (payConfigStorage.isTest() ? SANDBOXNEW : "") + type; } + /** * 回调校验 * @@ -162,84 +168,32 @@ public class WxPayService extends BasePayService { */ @Override public boolean verify(Map params) { - throw new PayErrorException(new WxPayError("", "等待作者实现")); + throw new PayErrorException(new WxPayError("", "微信V3不支持方式")); } - /** - * 获取公共参数 + * 验签,使用微信平台证书. * - * @return 公共参数 + * @param noticeParams 通知参数 + * @return the boolean */ - private Map getPublicParameters() { - - Map parameters = new TreeMap(); - parameters.put(WxConst.APPID, payConfigStorage.getAppId()); - parameters.put(WxConst.MCH_ID, payConfigStorage.getMchId()); + public boolean verify(NoticeParams noticeParams) { - return parameters; - - - } - - - /** - * 初始化商户相关信息 - * - * @param parameters 参数信息 - * @return 参数信息 - */ - private void initPartner(Map parameters) { - if (null == parameters) { - parameters = new HashMap<>(); - } - if (StringUtils.isNotEmpty(payConfigStorage.getSpAppId()) && StringUtils.isNotEmpty(payConfigStorage.getSpMchId())) { - this.partner = true; - parameters.put("sp_appid", payConfigStorage.getSpAppId()); - parameters.put(WxConst.SP_MCH_ID, payConfigStorage.getSpMchId()); - } - setParameters(parameters, "sub_appid", payConfigStorage.getSubAppId()); - initSubMchId(parameters); - } - - /** - * 初始化商户相关信息 - * - * @param parameters 参数信息 - * @return 参数信息 - */ - private Map initSubMchId(Map parameters) { - if (null == parameters) { - parameters = new HashMap<>(); - } - if (StringUtils.isNotEmpty(payConfigStorage.getSubMchId())) { - this.partner = true; - parameters.put(WxConst.SUB_MCH_ID, payConfigStorage.getSubMchId()); - } - return parameters; - - } - - - /** - * 加载结算信息 - * - * @param parameters 订单参数 - * @param order 支付订单 - * @return 订单参数 - */ - private 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; - } - //结算信息 - setParameters(parameters, "settle_info", order); + //当前使用的微信平台证书序列号 + String serial = noticeParams.getHeader("Wechatpay-Serial"); + //微信服务器的时间戳 + String timestamp = noticeParams.getHeader("Wechatpay-Timestamp"); + //微信服务器提供的随机串 + String nonce = noticeParams.getHeader("Wechatpay-Nonce"); + //微信平台签名 + String signature = noticeParams.getHeader("Wechatpay-Signature"); + Certificate certificate = getCertificate(serial); + //签名信息 + String signText = StringUtils.joining("\n", timestamp, nonce, JSON.toJSONString(noticeParams.getBody())); + return RSA2.verify(signText, signature, certificate, payConfigStorage.getInputCharset()); } @@ -252,33 +206,33 @@ public class WxPayService extends BasePayService { public JSONObject unifiedOrder(PayOrder order) { //统一下单 - Map parameters = getPublicParameters(); - initPartner(parameters); + Map parameters = wxParameterStructure.getPublicParameters(); + wxParameterStructure.initPartner(parameters); // 商品描述 - setParameters(parameters, "description", order.getSubject()); - setParameters(parameters, "description", order.getBody()); + OrderParaStructure.loadParameters(parameters, "description", order.getSubject()); + OrderParaStructure.loadParameters(parameters, "description", order.getBody()); // 订单号 parameters.put(WxConst.OUT_TRADE_NO, order.getOutTradeNo()); //交易结束时间 if (null != order.getExpirationTime()) { parameters.put("time_expire", DateUtils.formatDate(order.getExpirationTime(), DateUtils.YYYYMMDDHHMMSS)); } - setParameters(parameters, "attach", order.getAddition()); - initNotifyUrl(parameters, order); + OrderParaStructure.loadParameters(parameters, "attach", order.getAddition()); + wxParameterStructure.initNotifyUrl(parameters, order); //订单优惠标记 - setParameters(parameters, "goods_tag", order); + OrderParaStructure.loadParameters(parameters, "goods_tag", order); parameters.put("amount", Amount.getAmount(order.getPrice(), order.getCurType())); //优惠功能 - setParameters(parameters, "detail", order); + OrderParaStructure.loadParameters(parameters, "detail", order); //支付场景描述 - setParameters(parameters, WxConst.SCENE_INFO, order); - loadSettleInfo(parameters, order); + OrderParaStructure.loadParameters(parameters, WxConst.SCENE_INFO, order); + wxParameterStructure.loadSettleInfo(parameters, order); TransactionType transactionType = order.getTransactionType(); ((WxTransactionType) transactionType).setAttribute(parameters, order); - return wxPayAssistService.doExecute(parameters, order); + return getAssistService().doExecute(parameters, order); } @@ -341,31 +295,6 @@ public class WxPayService extends BasePayService { return RSA2.sign(content, privateKey, characterEncoding); } - - /** - * 初始化通知URL必须为直接可访问的URL,不允许携带查询串,要求必须为https地址。 - * - * @param parameters 订单参数 - * @param order 订单信息 - * @return 订单参数 - */ - private void initNotifyUrl(Map parameters, Order order) { - setParameters(parameters, WxConst.NOTIFY_URL, payConfigStorage.getNotifyUrl()); - setParameters(parameters, WxConst.NOTIFY_URL, order); - } - - /** - * 获取服务商相关信息 - * - * @return 服务商相关信息 - */ - private String getSpParameters() { - Map attr = initSubMchId(null); - setParameters(attr, WxConst.SP_MCH_ID, payConfigStorage.getSpMchId()); - return UriVariables.getMapToParameters(attr); - } - - /** * 将请求参数或者请求流转化为 Map * @@ -466,14 +395,14 @@ public class WxPayService extends BasePayService { @Override public Map query(String transactionId, String outTradeNo) { - String parameters = getSpParameters(); + String parameters = wxParameterStructure.getSpParameters(); WxTransactionType transactionType = WxTransactionType.QUERY_TRANSACTION_ID; String uriVariable = transactionId; if (StringUtils.isNotEmpty(outTradeNo)) { transactionType = WxTransactionType.QUERY_OUT_TRADE_NO; uriVariable = outTradeNo; } - return wxPayAssistService.doExecute(parameters, transactionType, uriVariable); + return getAssistService().doExecute(parameters, transactionType, uriVariable); } @@ -486,8 +415,8 @@ public class WxPayService extends BasePayService { */ @Override public Map close(String transactionId, String outTradeNo) { - String parameters = getSpParameters(); - return wxPayAssistService.doExecute(parameters, WxTransactionType.CLOSE, outTradeNo); + String parameters = wxParameterStructure.getSpParameters(); + return getAssistService().doExecute(parameters, WxTransactionType.CLOSE, outTradeNo); } /** @@ -499,14 +428,14 @@ public class WxPayService extends BasePayService { @Override public WxRefundResult refund(RefundOrder refundOrder) { //获取公共参数 - Map parameters = initSubMchId(null); - - setParameters(parameters, "transaction_id", refundOrder.getTradeNo()); - setParameters(parameters, OUT_TRADE_NO, refundOrder.getOutTradeNo()); - setParameters(parameters, "out_refund_no", refundOrder.getRefundNo()); - setParameters(parameters, "reason", refundOrder.getDescription()); - setParameters(parameters, "funds_account", refundOrder); - initNotifyUrl(parameters, 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())); @@ -515,8 +444,8 @@ public class WxPayService extends BasePayService { refundAmount.setCurrency(curType.getType()); } parameters.put("amount", refundAmount); - setParameters(parameters, "amount", refundOrder); - return WxRefundResult.create(wxPayAssistService.doExecute(parameters, WxTransactionType.REFUND)); + OrderParaStructure.loadParameters(parameters, "amount", refundOrder); + return WxRefundResult.create(getAssistService().doExecute(parameters, WxTransactionType.REFUND)); } @@ -528,8 +457,8 @@ public class WxPayService extends BasePayService { */ @Override public Map refundquery(RefundOrder refundOrder) { - String parameters = UriVariables.getMapToParameters(initSubMchId(null)); - return wxPayAssistService.doExecute(parameters, WxTransactionType.REFUND_QUERY, refundOrder.getRefundNo()); + String parameters = UriVariables.getMapToParameters(wxParameterStructure.initSubMchId(null)); + return getAssistService().doExecute(parameters, WxTransactionType.REFUND_QUERY, refundOrder.getRefundNo()); } /** @@ -563,18 +492,18 @@ public class WxPayService extends BasePayService { //目前只支持日账单 parameters.put("bill_date", DateUtils.formatDate(billDate, DateUtils.YYYYMMDD)); String fileType = billType.getFileType(); - setParameters(parameters, "tar_type", fileType); + OrderParaStructure.loadParameters(parameters, "tar_type", fileType); if (billType instanceof WxAccountType) { - setParameters(parameters, "account_type", billType.getType()); + OrderParaStructure.loadParameters(parameters, "account_type", billType.getType()); } else { - initSubMchId(parameters).put("bill_type", billType.getType()); + wxParameterStructure.initSubMchId(parameters).put("bill_type", billType.getType()); } String body = UriVariables.getMapToParameters(parameters); - JSONObject result = wxPayAssistService.doExecute(body, WxTransactionType.valueOf(billType.getCustom())); + JSONObject result = getAssistService().doExecute(body, WxTransactionType.valueOf(billType.getCustom())); String downloadUrl = result.getString("download_url"); MethodType methodType = MethodType.GET; - HttpEntity entity = wxPayAssistService.buildHttpEntity(downloadUrl, "", methodType.name()); + HttpEntity entity = getAssistService().buildHttpEntity(downloadUrl, "", methodType.name()); ResponseEntity responseEntity = requestTemplate.doExecuteEntity(downloadUrl, entity, InputStream.class, methodType); InputStream inputStream = responseEntity.getBody(); int statusCode = responseEntity.getStatusCode(); @@ -598,14 +527,6 @@ public class WxPayService extends BasePayService { * 转账 * * @param order 转账订单 - *
-     *
-     *                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 注意事项:
-     *                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 ◆ 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。
-     *                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 ◆ XML具有可扩展性,因此返回参数可能会有新增,而且顺序可能不完全遵循此文档规范,如果在解析回包的时候发生错误,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新回包字段,会更新到此API文档中。
-     *                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 ◆ 因为错误代码字段err_code的值后续可能会增加,所以商户如果遇到回包返回新的错误码,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新的错误码,会更新到此API文档中。
-     *                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 ◆ 错误代码描述字段err_code_des只供人工定位问题时做参考,系统实现时请不要依赖这个字段来做自动化处理。
-     *                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
* @return 对应的转账结果 */ @Override 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 index 7c04136..8574451 100644 --- 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 @@ -4,6 +4,7 @@ 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; /** @@ -66,10 +67,10 @@ public class Amount { public static Amount getAmount(BigDecimal total, CurType curType ) { // 总金额单位为分 Amount amount = new Amount(Util.conversionCentAmount(total)); - if (null != curType) { - amount.setCurrency(curType.getType()); + 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/utils/AntCertificationUtil.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/utils/AntCertificationUtil.java index 1660d2c..1807787 100644 --- 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 @@ -67,14 +67,15 @@ public final class AntCertificationUtil { /** * 装载平台证书 - * - * @param serialNo 证书序列 + * @param serialNo 证书序列 * @param certificateStream 证书流 + * @return */ - public static void loadCertificate(String serialNo, InputStream certificateStream) { + 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); 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 d530673..cd3c66c 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 @@ -13,6 +13,7 @@ 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; @@ -91,13 +92,24 @@ public class YiJiPayService extends BasePayService { @Override public boolean verify(Map params) { + return verify(new NoticeParams(params)); + + } + + /** + * 回调校验 + * + * @param noticeParams 回调回来的参数集 + * @return 签名校验 true通过 + */ + public boolean verify(NoticeParams noticeParams){ + final Map params = noticeParams.getBody(); if (params.get(SIGN) == null) { LOG.debug("易极付支付异常:params:" + params); return false; } return signVerify(params, (String) params.get(SIGN)); - } /** -- Gitee From f761e9e2b8cfc8c2b0164128c0e413992b6b2800 Mon Sep 17 00:00:00 2001 From: egan Date: Mon, 16 Aug 2021 22:45:57 +0800 Subject: [PATCH 076/165] =?UTF-8?q?=E5=BE=AE=E4=BF=A1V3=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=9F=BA=E6=9C=AC=E5=AE=9E=E7=8E=B0,?= =?UTF-8?q?=E5=B9=B6=E5=B7=B2=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egzosn/pay/common/util/str/StringUtils.java | 6 ++---- .../pay/demo/controller/WxV3PayController.java | 14 +++++++------- .../pay/wx/v3/api/DefaultWxPayAssistService.java | 2 +- .../egzosn/pay/wx/v3/api/WxParameterStructure.java | 3 ++- .../java/com/egzosn/pay/wx/v3/utils/WxConst.java | 2 +- 5 files changed, 13 insertions(+), 14 deletions(-) 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 42ae96f..79a94e6 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 @@ -156,10 +156,8 @@ public class StringUtils { if (StringUtils.isEmpty(s)) { continue; } - if (builder.length() > 0) { - builder.append(separator); - } - builder.append(s); + + builder.append(s).append(separator); } return builder.toString(); } 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 index 73791ef..84880c4 100644 --- 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 @@ -54,15 +54,15 @@ public class WxV3PayController { public void init() { WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage(); - wxPayConfigStorage.setAppId("wxc7b993ff15a9f27c"); - wxPayConfigStorage.setMchId("1602947766"); + wxPayConfigStorage.setAppId("wxc7b993ff15a9f271"); + wxPayConfigStorage.setMchId("1602947765"); // wxPayConfigStorage.setKeyPublic("转账公钥,转账时必填"); - wxPayConfigStorage.setSecretKey("9bd8f0e7af4841299d782406b7774f56"); - wxPayConfigStorage.setNotifyUrl("https://pay.egzosn.com"); - wxPayConfigStorage.setReturnUrl("https://pay.egzosn.com"); + wxPayConfigStorage.setSecretKey("9bd8f0e7af4841299d782406b7774f52"); + wxPayConfigStorage.setNotifyUrl("https://pay.egzosn.com/payback"); + wxPayConfigStorage.setReturnUrl("https://pay.egzosn.com/payback"); wxPayConfigStorage.setInputCharset("utf-8"); wxPayConfigStorage.setCertSign(true); - wxPayConfigStorage.setApiClientKeyP12("E:\\Documents\\gitee\\pay-java-parent\\pay-java-demo\\src\\main\\resources\\yifenli_mall.p12"); + wxPayConfigStorage.setApiClientKeyP12("yifenli_mall.p12"); wxPayConfigStorage.setCertStoreType(CertStoreType.PATH); service = new WxPayService(wxPayConfigStorage); //设置回调消息处理 @@ -142,7 +142,7 @@ public class WxV3PayController { public byte[] toWxQrPay(BigDecimal price) throws IOException { //获取对应的支付账户操作工具(可根据账户id) ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ImageIO.write(service.genQrPay(new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, System.currentTimeMillis() + "", WxTransactionType.NATIVE)), "JPEG", baos); + ImageIO.write(service.genQrPay(new PayOrder("测试商品", "测试商品", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), WxTransactionType.NATIVE)), "JPEG", baos); return baos.toByteArray(); } 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 index 2ea5602..4e8d7e3 100644 --- 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 @@ -134,7 +134,7 @@ public class DefaultWxPayAssistService implements WxPayAssistService { 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", "X-Pay-Service")); + entity.addHeader(new BasicHeader("User-Agent", "Pay-Java-Parent")); entity.addHeader(new BasicHeader("Accept", APPLICATION_JSON.getMimeType())); return entity; } 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 index b6031fb..a0bba61 100644 --- 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 @@ -1,6 +1,7 @@ package com.egzosn.pay.wx.v3.api; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import java.util.TreeMap; @@ -35,7 +36,7 @@ public class WxParameterStructure { */ public Map getPublicParameters() { - Map parameters = new TreeMap(); + Map parameters = new LinkedHashMap<>(); parameters.put(WxConst.APPID, payConfigStorage.getAppId()); parameters.put(WxConst.MCH_ID, payConfigStorage.getMchId()); return parameters; 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 index e644019..978b790 100644 --- 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 @@ -30,7 +30,7 @@ public final class WxConst { */ public static final String SANDBOXNEW = "sandboxnew/"; public static final String APPID = "appid"; - public static final String MCH_ID = "mch_id"; + public static final String MCH_ID = "mchid"; 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"; -- Gitee From aae4ffe61dcdac764d4d070b037d0e1a6c1a6991 Mon Sep 17 00:00:00 2001 From: egan Date: Wed, 18 Aug 2021 22:49:57 +0800 Subject: [PATCH 077/165] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=9B=9E=E8=B0=83?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E5=AF=B9=E8=B1=A1=EF=BC=8C=E5=9B=9E=E8=B0=83?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egzosn/pay/common/api/BasePayService.java | 45 ++++++--- .../com/egzosn/pay/common/api/PayService.java | 14 ++- .../pay/common/bean/DefaultNoticeRequest.java | 95 +++++++++++++++++++ .../egzosn/pay/common/bean/NoticeParams.java | 2 + .../com/egzosn/pay/common/util/MapGen.java | 2 +- .../java/com/egzosn/pay/common/util/Util.java | 28 ++++-- pay-java-demo/pom.xml | 5 + .../pay/demo/controller/AliPayController.java | 31 ++++-- .../demo/controller/FuiouPayController.java | 25 ++++- .../pay/demo/controller/PayController.java | 41 ++++++-- .../demo/controller/PayPalPayController.java | 31 ++++-- .../controller/PayPalV2PayController.java | 36 ++++--- .../demo/controller/UnionPayController.java | 25 ++++- .../pay/demo/controller/WxPayController.java | 27 +++++- .../demo/controller/WxV3PayController.java | 38 ++------ .../egzosn/pay/wx/v3/api/WxPayService.java | 53 +++++++++-- .../com/egzosn/pay/wx/v3/utils/WxConst.java | 4 + pom.xml | 5 + 18 files changed, 396 insertions(+), 111 deletions(-) create mode 100644 pay-java-common/src/main/java/com/egzosn/pay/common/bean/DefaultNoticeRequest.java 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 00b4b10..028fbcf 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 @@ -16,6 +16,7 @@ import org.slf4j.LoggerFactory; import com.alibaba.fastjson.JSON; 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; @@ -56,7 +57,7 @@ public abstract class BasePayService implements Pay /** * 支付消息拦截器 */ - protected List interceptors = new ArrayList(); + protected List> interceptors = new ArrayList>(); ; /** @@ -191,6 +192,17 @@ 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(); for (Map.Entry entry : parameterMap.entrySet()) { @@ -212,10 +224,9 @@ public abstract class BasePayService implements Pay } params.put(name, valueStr); } - return params; + return new NoticeParams(params); } - /** * 交易查询接口,带处理器 * @@ -399,34 +410,38 @@ public abstract class BasePayService implements Pay interceptors.add(interceptor); } + + /** * 将请求参数或者请求流转化为 Map * - * @param request 通知请求 - * @return 获得回调的请求参数 + * @param parameterMap 请求参数 + * @param is 请求流 + * @return 获得回调响应信息 */ + @Deprecated @Override - public NoticeParams getNoticeParams(NoticeRequest request) { - return null; + public PayOutMessage payBack(Map parameterMap, InputStream is) { + return payBack(new DefaultNoticeRequest(parameterMap, is)); } + /** - * 将请求参数或者请求流转化为 Map + * 回调处理 * - * @param parameterMap 请求参数 - * @param is 请求流 + * @param request 请求参数 * @return 获得回调响应信息 */ @Override - public PayOutMessage payBack(Map parameterMap, InputStream is) { - Map data = getParameter2Map(parameterMap, is); + 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); + PayMessage payMessage = this.createMessage(noticeParams.getBody()); Map context = new HashMap(); for (PayMessageInterceptor interceptor : interceptors) { if (!interceptor.intercept(payMessage, context, this)) { 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 178d69c..0e0d03f 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 @@ -351,13 +351,21 @@ public interface PayService { 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); /** * 使用转换过的参数进行回调处理 @@ -371,7 +379,7 @@ public interface PayService { * 设置支付消息处理器,这里用于处理具体的支付业务 * * @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 }进行实现 */ @@ -381,7 +389,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 }进行实现 */ 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 0000000..b5f7819 --- /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/NoticeParams.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/NoticeParams.java index f89ac97..7cd494f 100644 --- 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 @@ -47,6 +47,8 @@ public class NoticeParams { */ private Map attr; + public NoticeParams() { + } public NoticeParams(Map body) { this.body = body; 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 index 45b8a89..4953c14 100644 --- 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 @@ -20,7 +20,7 @@ public class MapGen { keyValue(key, value); } - public MapGen keyValue(K key, V value) { + public MapGen keyValue(K key, V value) { attr.put(key, value); return this; } 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 7991c3c..53b9ae5 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; @@ -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; @@ -574,7 +581,7 @@ public class Util { /** * 一百 */ - public static final BigDecimal HUNDRED = new BigDecimal(100); + public static final BigDecimal HUNDRED = new BigDecimal(100); /** * 元转分 @@ -597,4 +604,13 @@ public class Util { } + 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-demo/pom.xml b/pay-java-demo/pom.xml index c83932b..7cf711f 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -31,6 +31,11 @@ pay-java-wx ${pay.version} + + com.egzosn + pay-java-web-support + ${pay.version} + com.egzosn pay-java-ali 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 867d7c5..f0a674c 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 @@ -26,6 +26,7 @@ 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.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; @@ -34,6 +35,7 @@ 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 com.egzosn.pay.web.support.HttpRequestNoticeParams; /** @@ -223,13 +225,13 @@ public class AliPayController { public String payBackBefore(HttpServletRequest request) throws IOException { //获取支付方返回的对应参数 - Map params = service.getParameter2Map(request.getParameterMap(), request.getInputStream()); - if (null == params) { + NoticeParams noticeParams = service.getNoticeParams(new HttpRequestNoticeParams(request)); + if (null == noticeParams) { return service.getPayOutMessage("fail", "失败").toMessage(); } //校验 - if (service.verify(params)) { + if (service.verify(noticeParams)) { //这里处理业务逻辑 //......业务逻辑处理块........ return service.successPayOutMessage(null).toMessage(); @@ -242,19 +244,36 @@ public class AliPayController { * 支付回调地址 * * @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(); + } /** * 查询 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 046001e..0ea09e5 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; @@ -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 d6aa4d0..3e23fd6 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 @@ -27,6 +27,7 @@ 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.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; @@ -41,6 +42,7 @@ 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; /** @@ -319,32 +321,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 * @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);*/ @@ -379,8 +401,9 @@ public class PayController { *

* 如果未设置 {@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(); 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 934b8b1..e40d037 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 @@ -22,6 +22,7 @@ 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 com.egzosn.pay.web.support.HttpRequestNoticeParams; /** * 发起支付入口 @@ -129,17 +130,35 @@ public class PayPalPayController { * 支付回调地址 * * @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 - * 业务处理在对应的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.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(); + } } 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 index 94917e7..0febd38 100644 --- 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 @@ -23,6 +23,7 @@ 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; /** * 发起支付入口 @@ -138,26 +139,39 @@ public class PayPalV2PayController { return "failure"; } - /* */ - /** * 支付回调地址 - * 注意:这里不是异步回调的通知 IPN 地址设置的路径:https://developer.paypal.com/developer/ipnSimulator/ - * 参数解析与校验 https://developer.paypal.com/docs/api-basics/notifications/ipn/IPNIntro/#id08CKFJ00JYK * * @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 - * 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link 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.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() - // 参数解析与校验 https://developer.paypal.com/docs/api-basics/notifications/ipn/IPNIntro/#id08CKFJ00JYK 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/UnionPayController.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/UnionPayController.java index 5cb3135..297bd06 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 @@ -15,6 +15,8 @@ import com.egzosn.pay.union.api.UnionPayConfigStorage; import com.egzosn.pay.union.api.UnionPayService; import com.egzosn.pay.union.bean.UnionRefundResult; import com.egzosn.pay.union.bean.UnionTransactionType; +import com.egzosn.pay.web.support.HttpRequestNoticeParams; + import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -231,18 +233,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(); } 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 fc0bef7..3ac4464 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.*; @@ -245,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(); @@ -271,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(); + } /** 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 index 84880c4..080ea19 100644 --- 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 @@ -17,10 +17,13 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.egzosn.pay.common.bean.CertStoreType; +import com.egzosn.pay.common.bean.NoticeRequest; import com.egzosn.pay.common.bean.PayOrder; import com.egzosn.pay.common.bean.RefundOrder; import com.egzosn.pay.common.bean.TransferOrder; import com.egzosn.pay.demo.request.QueryOrder; +import com.egzosn.pay.demo.service.handler.WxPayMessageHandler; +import com.egzosn.pay.web.support.HttpRequestNoticeParams; import com.egzosn.pay.wx.bean.WxBank; import com.egzosn.pay.wx.bean.WxTransferType; import com.egzosn.pay.wx.v3.api.WxPayConfigStorage; @@ -67,7 +70,7 @@ public class WxV3PayController { service = new WxPayService(wxPayConfigStorage); //设置回调消息处理 //TODO {@link com.egzosn.pay.demo.controller.WxPayController#payBack} -// service.setPayMessageHandler(new WxPayMessageHandler(null)); + service.setPayMessageHandler(new WxPayMessageHandler(null)); } @@ -162,35 +165,6 @@ public class WxV3PayController { - /** - * 支付回调地址 方式一 - *

- * 方式二,{@link #payBack(HttpServletRequest)} 是属于简化方式, 试用与简单的业务场景 - * - * @param request 请求 - * @return 是否成功 - * @throws IOException IOException - * @see #payBack(HttpServletRequest) - */ - @Deprecated - @RequestMapping(value = "payBackBefore.json") - public String payBackBefore(HttpServletRequest request) throws IOException { - - //获取支付方返回的对应参数 - Map params = service.getParameter2Map(request.getParameterMap(), request.getInputStream()); - if (null == params) { - return service.getPayOutMessage("fail", "失败").toMessage(); - } - - //校验 - if (service.verify(params)) { - //这里处理业务逻辑 - //......业务逻辑处理块........ - return service.successPayOutMessage(null).toMessage(); - } - - return service.getPayOutMessage("fail", "失败").toMessage(); - } /** * 支付回调地址 @@ -204,9 +178,9 @@ public class WxV3PayController { * @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-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 index 1858643..a5bd1a3 100644 --- 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 @@ -5,11 +5,13 @@ import java.io.InputStream; import java.net.URLEncoder; import java.security.PrivateKey; import java.security.cert.Certificate; +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.TreeMap; import org.apache.http.HttpEntity; @@ -19,6 +21,7 @@ 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.SUCCESS; import static com.egzosn.pay.wx.v3.utils.AntCertificationUtil.getCertificate; +import static com.egzosn.pay.wx.v3.utils.WxConst.FAILURE; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; @@ -27,6 +30,7 @@ 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; @@ -41,8 +45,8 @@ 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.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.RSA2; @@ -82,12 +86,11 @@ public class WxPayService extends BasePayService { private volatile WxPayAssistService wxPayAssistService; /** - * 微信参数构造器 + * 微信参数构造器 */ private volatile WxParameterStructure wxParameterStructure; - public WxPayAssistService getAssistService() { if (null == wxPayAssistService) { wxPayAssistService = new DefaultWxPayAssistService(this); @@ -190,8 +193,17 @@ public class WxPayService extends BasePayService { String signature = noticeParams.getHeader("Wechatpay-Signature"); Certificate certificate = getCertificate(serial); + + Map attr = noticeParams.getAttr(); + + if (Util.isEmpty(attr)) { + throw new PayErrorException(new WxPayError(FAILURE, "请勿置空NoticeParams.attr中的数据")); + } + + //这里为微信回调时的请求内容体,原值数据 + String body = (String) attr.get(WxConst.RESP_BODY); //签名信息 - String signText = StringUtils.joining("\n", timestamp, nonce, JSON.toJSONString(noticeParams.getBody())); + String signText = StringUtils.joining("\n", timestamp, nonce, body); return RSA2.verify(signText, signature, certificate, payConfigStorage.getInputCharset()); } @@ -304,14 +316,35 @@ public class WxPayService extends BasePayService { */ @Override public Map getParameter2Map(Map parameterMap, InputStream is) { - TreeMap map = new TreeMap(); - try { - return XML.inputStream2Map(is, map); + throw new PayErrorException(new WxPayError(FAILURE, "微信V3不支持方式")); + + } + + /** + * 将请求参数或者请求流转化为 Map + * + * @param request 通知请求 + * @return 获得回调的请求参数 + */ + @Override + public NoticeParams getNoticeParams(NoticeRequest request) { + NoticeParams noticeParams = new NoticeParams(); + 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); + try (InputStream is = request.getInputStream()) { + String body = IOUtils.toString(is); + noticeParams.setAttr(new MapGen(WxConst.RESP_BODY, body).getAttr()); + noticeParams.setBody(JSON.parseObject(body)); } catch (IOException e) { - throw new PayErrorException(new PayException("IOException", e.getMessage())); + LOG.error("获取回调参数异常", e); } - + return super.getNoticeParams(request); } /** 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 index 978b790..2055d49 100644 --- 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 @@ -1,5 +1,7 @@ package com.egzosn.pay.wx.v3.utils; +import com.egzosn.pay.wx.v3.api.WxPayService; + /** * 微信所需常量 * @@ -44,4 +46,6 @@ public final class WxConst { 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"; } diff --git a/pom.xml b/pom.xml index e041e7b..44f98e1 100644 --- a/pom.xml +++ b/pom.xml @@ -87,6 +87,11 @@ pay-java-common ${pay.version} + + com.egzosn + pay-java-web-support + ${pay.version} + -- Gitee From b731154e681fee248b5611c7ecfdcd720fd64bba Mon Sep 17 00:00:00 2001 From: egan Date: Wed, 18 Aug 2021 23:03:51 +0800 Subject: [PATCH 078/165] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=9B=9E=E8=B0=83?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E5=AF=B9=E8=B1=A1=EF=BC=8C=E5=9B=9E=E8=B0=83?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/demo/controller/WxV3PayController.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 index 080ea19..4d65fc2 100644 --- 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 @@ -60,12 +60,14 @@ public class WxV3PayController { wxPayConfigStorage.setAppId("wxc7b993ff15a9f271"); wxPayConfigStorage.setMchId("1602947765"); // wxPayConfigStorage.setKeyPublic("转账公钥,转账时必填"); - wxPayConfigStorage.setSecretKey("9bd8f0e7af4841299d782406b7774f52"); + //V3密钥 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_2.shtml + wxPayConfigStorage.setSecretKey("V3密钥"); wxPayConfigStorage.setNotifyUrl("https://pay.egzosn.com/payback"); wxPayConfigStorage.setReturnUrl("https://pay.egzosn.com/payback"); wxPayConfigStorage.setInputCharset("utf-8"); wxPayConfigStorage.setCertSign(true); - wxPayConfigStorage.setApiClientKeyP12("yifenli_mall.p12"); + //商户API证书 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_1.shtml + wxPayConfigStorage.setApiClientKeyP12("商户API证书.p12"); wxPayConfigStorage.setCertStoreType(CertStoreType.PATH); service = new WxPayService(wxPayConfigStorage); //设置回调消息处理 -- Gitee From 1b10bbd7879100ba343232f94bc73dbde2f426dd Mon Sep 17 00:00:00 2001 From: egzosn Date: Mon, 4 Oct 2021 18:45:50 +0800 Subject: [PATCH 079/165] =?UTF-8?q?=E5=BE=AE=E4=BF=A1V3=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E6=8E=A5=E5=85=A5=E7=9B=B4=E8=BF=9E=E6=8E=A5=E5=8F=A3=EF=BC=8C?= =?UTF-8?q?=E8=87=AA=E6=B5=8B=E9=80=9A=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egzosn/pay/common/api/BasePayService.java | 8 +- .../com/egzosn/pay/common/util/MapGen.java | 5 + .../pay/common/util/str/StringUtils.java | 2 +- .../demo/controller/WxV3PayController.java | 27 +- .../handler/WxV3PayMessageHandler.java | 29 ++ .../src/main/resources/applicationContext.xml | 4 +- pay-java-web-support/pom.xml | 8 +- .../wx/v3/api/DefaultWxPayAssistService.java | 2 +- .../pay/wx/v3/api/WxPayConfigStorage.java | 35 +- .../egzosn/pay/wx/v3/api/WxPayService.java | 73 ++-- .../pay/wx/v3/bean/WxTransactionType.java | 12 + .../pay/wx/v3/bean/order/SceneInfo.java | 5 +- .../pay/wx/v3/bean/response/Resource.java | 80 +++++ .../wx/v3/bean/response/WxNoticeParams.java | 108 ++++++ .../pay/wx/v3/bean/response/WxPayMessage.java | 311 ++++++++++++++++++ .../pay/wx/v3/bean/response/order/Amount.java | 39 +++ .../v3/bean/response/order/GoodsDetail.java | 81 +++++ .../pay/wx/v3/bean/response/order/Payer.java | 24 ++ .../bean/response/order/PromotionDetail.java | 176 ++++++++++ .../wx/v3/bean/response/order/TradeState.java | 54 +++ pom.xml | 4 +- 21 files changed, 1025 insertions(+), 62 deletions(-) create mode 100644 pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/WxV3PayMessageHandler.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/Resource.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/WxNoticeParams.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/WxPayMessage.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/Amount.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/GoodsDetail.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/Payer.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/PromotionDetail.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/TradeState.java 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 028fbcf..4e6d1b5 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 @@ -58,7 +58,7 @@ public abstract class BasePayService implements Pay * 支付消息拦截器 */ protected List> interceptors = new ArrayList>(); - ; + /** * 设置支付配置 @@ -101,9 +101,15 @@ public abstract class BasePayService implements Pay public BasePayService(PC payConfigStorage, HttpConfigStorage configStorage) { setPayConfigStorage(payConfigStorage); setRequestTemplateConfigStorage(configStorage); + initAfter(); } + /** + * 初始化之后执行 + */ + protected void initAfter(){ + } /** * Generate a Base64 encoded String from user , password * 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 index 4953c14..545dae8 100644 --- 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 @@ -1,5 +1,7 @@ package com.egzosn.pay.common.util; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; /** @@ -21,6 +23,9 @@ public class MapGen { } public MapGen keyValue(K key, V value) { + if (null == attr){ + attr = new LinkedHashMap<>(); + } attr.put(key, value); return this; } 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 79a94e6..9bba1a2 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 @@ -153,7 +153,7 @@ public class StringUtils { public static String joining(String separator, String... str) { StringBuilder builder = new StringBuilder(); for (String s : str) { - if (StringUtils.isEmpty(s)) { + if (null == s) { continue; } 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 index 4d65fc2..8adc70e 100644 --- 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 @@ -17,12 +17,11 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.egzosn.pay.common.bean.CertStoreType; -import com.egzosn.pay.common.bean.NoticeRequest; import com.egzosn.pay.common.bean.PayOrder; import com.egzosn.pay.common.bean.RefundOrder; import com.egzosn.pay.common.bean.TransferOrder; import com.egzosn.pay.demo.request.QueryOrder; -import com.egzosn.pay.demo.service.handler.WxPayMessageHandler; +import com.egzosn.pay.demo.service.handler.WxV3PayMessageHandler; import com.egzosn.pay.web.support.HttpRequestNoticeParams; import com.egzosn.pay.wx.bean.WxBank; import com.egzosn.pay.wx.bean.WxTransferType; @@ -48,31 +47,27 @@ public class WxV3PayController { - public WxV3PayController() { - - } - - @PostConstruct + @PostConstruct //没有证书的情况下注释掉,避免启动报错 public void init() { - + System.out.println("v3 init"); WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage(); - wxPayConfigStorage.setAppId("wxc7b993ff15a9f271"); - wxPayConfigStorage.setMchId("1602947765"); -// wxPayConfigStorage.setKeyPublic("转账公钥,转账时必填"); + wxPayConfigStorage.setAppId("wxc7b993ff15a9f27c"); + wxPayConfigStorage.setMchId("1602947766"); //V3密钥 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_2.shtml - wxPayConfigStorage.setSecretKey("V3密钥"); - wxPayConfigStorage.setNotifyUrl("https://pay.egzosn.com/payback"); - wxPayConfigStorage.setReturnUrl("https://pay.egzosn.com/payback"); + wxPayConfigStorage.setV3ApiKey("9bd8f0e7af4841299d782406b7774f56"); + 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("商户API证书.p12"); + wxPayConfigStorage.setApiClientKeyP12("E:\\Documents\\gitee\\支付\\yifenli_mall.p12"); wxPayConfigStorage.setCertStoreType(CertStoreType.PATH); service = new WxPayService(wxPayConfigStorage); //设置回调消息处理 //TODO {@link com.egzosn.pay.demo.controller.WxPayController#payBack} - service.setPayMessageHandler(new WxPayMessageHandler(null)); + service.setPayMessageHandler(new WxV3PayMessageHandler()); } 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 0000000..64e9381 --- /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/resources/applicationContext.xml b/pay-java-demo/src/main/resources/applicationContext.xml index 1bea38a..3352d53 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-web-support/pom.xml b/pay-java-web-support/pom.xml index a0d1aa0..7389396 100644 --- a/pay-java-web-support/pom.xml +++ b/pay-java-web-support/pom.xml @@ -26,15 +26,9 @@ javax.servlet javax.servlet-api - provided 3.1.0 - - javax.servlet - jsp-api - 2.0 - provided - + 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 index 4e8d7e3..a6d0e1b 100644 --- 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 @@ -162,7 +162,7 @@ public class DefaultWxPayAssistService implements WxPayAssistService { String associatedData = encryptCertificate.getString("associated_data"); String nonce = encryptCertificate.getString("nonce"); String ciphertext = encryptCertificate.getString("ciphertext"); - String publicKey = AntCertificationUtil.decryptToString(associatedData, nonce, ciphertext, payConfigStorage.getSecretKey(), payConfigStorage.getInputCharset()); + 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); } 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 index c765a91..66ccf53 100644 --- 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 @@ -51,6 +51,10 @@ public class WxPayConfigStorage extends BasePayConfigStorage { * 微信支付分配的子商户号,开发者模式下必填 合作者id */ private String subMchId; + /** + * V2 Api密钥 + */ + private String apiKey; /** * 商户API证书 @@ -111,19 +115,33 @@ public class WxPayConfigStorage extends BasePayConfigStorage { public void setMchId(String mchId) { this.mchId = mchId; + addAttr("mchId", mchId); } /** * 为商户平台设置的密钥key * - * @return 微信密钥 + * @return 微信v2密钥 */ - public String getSecretKey() { - return getKeyPrivate(); + public String getApiKey() { + return apiKey; } - public void setSecretKey(String secretKey) { - setKeyPrivate(secretKey); + 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) { @@ -147,6 +165,7 @@ public class WxPayConfigStorage extends BasePayConfigStorage { public void setSubAppId(String subAppId) { this.subAppId = subAppId; + addAttr("subAppId", subAppId); } public String getSpAppId() { @@ -155,6 +174,7 @@ public class WxPayConfigStorage extends BasePayConfigStorage { public void setSpAppId(String spAppId) { this.spAppId = spAppId; + addAttr("spAppId", spAppId); } public String getSpMchId() { @@ -163,6 +183,7 @@ public class WxPayConfigStorage extends BasePayConfigStorage { public void setSpMchId(String spMchId) { this.spMchId = spMchId; + addAttr("spMchId", spMchId); } /** @@ -180,6 +201,7 @@ public class WxPayConfigStorage extends BasePayConfigStorage { @Deprecated public void setSubAppid(String subAppid) { this.subAppId = subAppid; + addAttr("subAppId", subAppId); } @@ -189,6 +211,7 @@ public class WxPayConfigStorage extends BasePayConfigStorage { public void setSubMchId(String subMchId) { this.subMchId = subMchId; + addAttr("subMchId", subMchId); } public Object getApiClientKeyP12() { @@ -197,6 +220,7 @@ public class WxPayConfigStorage extends BasePayConfigStorage { public void setApiClientKeyP12(Object apiClientKeyP12) { this.apiClientKeyP12 = apiClientKeyP12; + addAttr("apiClientKeyP12", apiClientKeyP12); } public CertStoreType getCertStoreType() { @@ -205,6 +229,7 @@ public class WxPayConfigStorage extends BasePayConfigStorage { public void setCertStoreType(CertStoreType certStoreType) { this.certStoreType = certStoreType; + addAttr("certStoreType", certStoreType); } public CertEnvironment getCertEnvironment() { 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 index a5bd1a3..a72958d 100644 --- 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 @@ -20,7 +20,6 @@ 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.SUCCESS; -import static com.egzosn.pay.wx.v3.utils.AntCertificationUtil.getCertificate; import static com.egzosn.pay.wx.v3.utils.WxConst.FAILURE; import com.alibaba.fastjson.JSON; @@ -52,14 +51,17 @@ import com.egzosn.pay.common.util.sign.SignUtils; import com.egzosn.pay.common.util.sign.encrypt.RSA2; import com.egzosn.pay.common.util.str.StringUtils; import com.egzosn.pay.wx.bean.WxPayError; -import com.egzosn.pay.wx.bean.WxPayMessage; +import com.egzosn.pay.wx.v3.bean.response.WxPayMessage; import com.egzosn.pay.wx.bean.WxTransferType; 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.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.WxRefundResult; +import com.egzosn.pay.wx.v3.utils.AntCertificationUtil; import com.egzosn.pay.wx.v3.utils.WxConst; /** @@ -94,6 +96,8 @@ public class WxPayService extends BasePayService { public WxPayAssistService getAssistService() { if (null == wxPayAssistService) { wxPayAssistService = new DefaultWxPayAssistService(this); + //在这预先进行初始化 + wxPayAssistService.refreshCertificate(); } return wxPayAssistService; } @@ -121,17 +125,26 @@ public class WxPayService extends BasePayService { super(payConfigStorage, configStorage); } + /** - * 设置支付配置 - * - * @param payConfigStorage 支付配置 + * 初始化之后执行 */ @Override - public BasePayService setPayConfigStorage(WxPayConfigStorage payConfigStorage) { - payConfigStorage.loadCertEnvironment(); - this.payConfigStorage = payConfigStorage; - wxParameterStructure = new WxParameterStructure(payConfigStorage); - return this; + protected void initAfter() { + new Thread(() -> { + payConfigStorage.loadCertEnvironment(); + wxParameterStructure = new WxParameterStructure(payConfigStorage); + //在这预先进行初始化 + try { + Thread.sleep(10); + } + catch (InterruptedException e) { + + } + getAssistService(); + }).start(); + + } /** @@ -184,15 +197,15 @@ public class WxPayService extends BasePayService { public boolean verify(NoticeParams noticeParams) { //当前使用的微信平台证书序列号 - String serial = noticeParams.getHeader("Wechatpay-Serial"); + String serial = noticeParams.getHeader("wechatpay-serial"); //微信服务器的时间戳 - String timestamp = noticeParams.getHeader("Wechatpay-Timestamp"); + String timestamp = noticeParams.getHeader("wechatpay-timestamp"); //微信服务器提供的随机串 - String nonce = noticeParams.getHeader("Wechatpay-Nonce"); + String nonce = noticeParams.getHeader("wechatpay-nonce"); //微信平台签名 - String signature = noticeParams.getHeader("Wechatpay-Signature"); + String signature = noticeParams.getHeader("wechatpay-signature"); - Certificate certificate = getCertificate(serial); + Certificate certificate = getAssistService().getCertificate(serial); Map attr = noticeParams.getAttr(); @@ -328,23 +341,29 @@ public class WxPayService extends BasePayService { */ @Override public NoticeParams getNoticeParams(NoticeRequest request) { - NoticeParams noticeParams = new NoticeParams(); - 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); + 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.setBody(JSON.parseObject(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) { LOG.error("获取回调参数异常", e); } - return super.getNoticeParams(request); + 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; } /** @@ -356,7 +375,7 @@ public class WxPayService extends BasePayService { */ @Override public PayOutMessage getPayOutMessage(String code, String message) { - return PayOutMessage.XML().code(code.toUpperCase()).content(message).build(); + return PayOutMessage.JSON().content("code", code).content("message", message).build(); } @@ -369,7 +388,7 @@ public class WxPayService extends BasePayService { */ @Override public PayOutMessage successPayOutMessage(PayMessage payMessage) { - return PayOutMessage.XML().code("SUCCESS").content("成功").build(); + return getPayOutMessage("SUCCESS", "成功"); } 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 index 7c94876..70a6ee8 100644 --- 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 @@ -73,6 +73,18 @@ public enum WxTransactionType implements TransactionType { } } + }, + /** + * H5支付 + * 兼容 后期会抛弃 + */ + @Deprecated + MWEB("pay{partner}/transactions/h5", MethodType.POST, true) { + @Override + public void setAttribute(Map parameters, PayOrder order) { + H5.setAttribute(parameters, order); + } + }, /** 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 index 055cf35..952a3c9 100644 --- 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 @@ -3,9 +3,12 @@ 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 { @@ -15,7 +18,7 @@ public class SceneInfo { @JSONField(name = "payer_client_ip") private String payerClientIp; /** - * 商户端设备号 + * 商户端设备号(门店号或收银设备ID)。 */ @JSONField(name = "device_id") private String deviceId; 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 0000000..a6910de --- /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 0000000..9c7ab19 --- /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 0000000..8aa4008 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/WxPayMessage.java @@ -0,0 +1,311 @@ +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 = "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 static final 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/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 0000000..c6cf950 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/Amount.java @@ -0,0 +1,39 @@ +package com.egzosn.pay.wx.v3.bean.response.order; + +import java.math.BigDecimal; + +/** + * 回调中的订单金额信息 + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/10/4
+ * 
+ */ +public class Amount extends com.egzosn.pay.wx.v3.bean.order.Amount{ + + /** + * 用户支付金额,单位为分。 + */ + private BigDecimal payerTotal; + /** + * 用户支付币种 + */ + private String payerCurrency; + + 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; + } +} 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 0000000..d48675c --- /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 0000000..623b97d --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/Payer.java @@ -0,0 +1,24 @@ +package com.egzosn.pay.wx.v3.bean.response.order; + +/** + * 支付者信息 + * @author Egan + *
+ * email egan@egzosn.com
+ * date 2021/10/4
+ * 
+ */ +public class Payer { + /** + * 用户在直连商户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 0000000..697355e --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/PromotionDetail.java @@ -0,0 +1,176 @@ +package com.egzosn.pay.wx.v3.bean.response.order; + +import java.util.List; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * 优惠功能 + * + * @author felord.cn + * @since 1.0.0.RELEASE + */ + +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 0000000..6b6afe6 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/TradeState.java @@ -0,0 +1,54 @@ +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, + /** + * 已撤销(付款码支付) + * + */ + REVOKED, + /** + * 用户支付中(付款码支付) + */ + USERPAYING, + /** + * 支付失败(其他原因,如银行返回失败) + */ + PAYERROR, + /** + * 已接收,等待扣款 + * + */ + ACCEPT, +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 44f98e1..a1d07ce 100644 --- a/pom.xml +++ b/pom.xml @@ -147,8 +147,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.7 - 1.7 + 1.8 + 1.8 utf-8 -- Gitee From 76a27a0105a1e42445bd32232d29591281f01ed5 Mon Sep 17 00:00:00 2001 From: egzosn Date: Mon, 4 Oct 2021 19:24:54 +0800 Subject: [PATCH 080/165] =?UTF-8?q?=E5=BE=AE=E4=BF=A1V3=E7=89=88=E6=9C=ACH?= =?UTF-8?q?5=E8=B7=B3=E8=BD=AC=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/wx/v3/api/WxPayService.java | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) 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 index a72958d..49da334 100644 --- 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 @@ -2,7 +2,6 @@ package com.egzosn.pay.wx.v3.api; import java.io.IOException; import java.io.InputStream; -import java.net.URLEncoder; import java.security.PrivateKey; import java.security.cert.Certificate; import java.util.Collections; @@ -16,10 +15,7 @@ import java.util.Map; import org.apache.http.HttpEntity; import static com.egzosn.pay.wx.api.WxConst.OUT_TRADE_NO; -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.SUCCESS; import static com.egzosn.pay.wx.v3.utils.WxConst.FAILURE; import com.alibaba.fastjson.JSON; @@ -51,7 +47,6 @@ import com.egzosn.pay.common.util.sign.SignUtils; import com.egzosn.pay.common.util.sign.encrypt.RSA2; import com.egzosn.pay.common.util.str.StringUtils; import com.egzosn.pay.wx.bean.WxPayError; -import com.egzosn.pay.wx.v3.bean.response.WxPayMessage; import com.egzosn.pay.wx.bean.WxTransferType; import com.egzosn.pay.wx.v3.bean.WxAccountType; import com.egzosn.pay.wx.v3.bean.WxBillType; @@ -60,6 +55,7 @@ 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.utils.AntCertificationUtil; import com.egzosn.pay.wx.v3.utils.WxConst; @@ -402,14 +398,8 @@ public class WxPayService extends BasePayService { */ @Override public String buildRequest(Map orderInfo, MethodType method) { - if (!SUCCESS.equals(orderInfo.get(RETURN_CODE))) { - throw new PayErrorException(new WxPayError((String) orderInfo.get(RETURN_CODE), (String) orderInfo.get(RETURN_MSG_CODE))); - } - if (WxTransactionType.H5.name().equals(orderInfo.get("trade_type"))) { - return String.format("", orderInfo.get("mweb_url"), StringUtils.isEmpty(payConfigStorage.getReturnUrl()) ? "" : "&redirect_url=" + URLEncoder.encode(payConfigStorage.getReturnUrl())); - } - throw new UnsupportedOperationException(); - + String redirectUrl = StringUtils.isEmpty(payConfigStorage.getReturnUrl()) ? "" : "&redirect_url=" + UriVariables.urlEncoder(payConfigStorage.getReturnUrl()); + return String.format("", orderInfo.get("h5_url"), redirectUrl); } /** -- Gitee From d4096194f143b03137f3d76a1b1d883ed0af6d46 Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 6 Oct 2021 15:45:41 +0800 Subject: [PATCH 081/165] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E5=85=B3=E9=97=AD=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/ali/api/AliPayService.java | 23 +++-- .../egzosn/pay/baidu/api/BaiduPayService.java | 12 ++- .../com/egzosn/pay/common/api/PayService.java | 10 ++ .../egzosn/pay/common/bean/CloseOrder.java | 92 +++++++++++++++++++ .../pay/common/bean/OrderParaStructure.java | 8 ++ .../com/egzosn/pay/common/bean/PayOrder.java | 2 + .../com/egzosn/pay/common/util/DateUtils.java | 1 + .../egzosn/pay/fuiou/api/FuiouPayService.java | 12 ++- .../pay/payoneer/api/PayoneerPayService.java | 12 ++- .../pay/paypal/api/PayPalPayService.java | 12 ++- .../pay/paypal/v2/api/PayPalPayService.java | 12 ++- .../egzosn/pay/union/api/UnionPayService.java | 12 ++- .../wx/youdian/api/WxYouDianPayService.java | 12 ++- .../com/egzosn/pay/wx/api/WxPayService.java | 12 +++ .../egzosn/pay/yiji/api/YiJiPayService.java | 12 ++- 15 files changed, 226 insertions(+), 18 deletions(-) create mode 100644 pay-java-common/src/main/java/com/egzosn/pay/common/bean/CloseOrder.java 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 5c5568f..1b9d724 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 @@ -31,6 +31,7 @@ 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.BillType; +import com.egzosn.pay.common.bean.CloseOrder; import com.egzosn.pay.common.bean.MethodType; import com.egzosn.pay.common.bean.NoticeParams; import com.egzosn.pay.common.bean.Order; @@ -130,7 +131,7 @@ public class AliPayService extends BasePayService { return false; } - return signVerify(params, (String) params.get(SIGN)) && verifySource((String) params.get("notify_id")); + return signVerify(params, (String) params.get(SIGN)); } @@ -182,15 +183,6 @@ public class AliPayService extends BasePayService { return (String) respMap.get(ALIPAY_CERT_SN_FIELD); } - /** - * 校验数据来源 - * - * @param id 业务id, 数据的真实性. - * @return true通过 - */ - protected boolean verifySource(String id) { - return true; - } /** @@ -484,6 +476,17 @@ public class AliPayService extends BasePayService { return secondaryInterface(tradeNo, outTradeNo, AliTransactionType.CLOSE); } + /** + * 交易关闭接口 + * + * @param closeOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 + */ + @Override + public Map close(CloseOrder closeOrder){ + return secondaryInterface(closeOrder.getTradeNo(), closeOrder.getOutTradeNo(), AliTransactionType.CLOSE); + } + /** * 支付交易返回失败或支付系统超时,调用该接口撤销交易。 * 如果此订单用户支付失败,支付宝系统会将此订单关闭;如果用户支付成功,支付宝系统会将此订单资金退还给用户。 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 58487fe..3b7f3af 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 @@ -25,6 +25,7 @@ import com.egzosn.pay.baidu.util.Asserts; import com.egzosn.pay.common.api.BasePayService; import com.egzosn.pay.common.bean.BaseRefundResult; import com.egzosn.pay.common.bean.BillType; +import com.egzosn.pay.common.bean.CloseOrder; import com.egzosn.pay.common.bean.CurType; import com.egzosn.pay.common.bean.MethodType; import com.egzosn.pay.common.bean.NoticeParams; @@ -438,7 +439,16 @@ public class BaiduPayService extends BasePayService { public Map close(String tradeNo, String outTradeNo) { throw new UnsupportedOperationException("不支持该操作"); } - + /** + * 交易关闭接口 + * + * @param closeOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 + */ + @Override + public Map close(CloseOrder closeOrder){ + throw new UnsupportedOperationException("不支持该操作"); + } /** * 退款 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 0e0d03f..6ee633d 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 @@ -6,6 +6,7 @@ import java.util.Date; import java.util.Map; import com.egzosn.pay.common.bean.BillType; +import com.egzosn.pay.common.bean.CloseOrder; import com.egzosn.pay.common.bean.MethodType; import com.egzosn.pay.common.bean.NoticeParams; import com.egzosn.pay.common.bean.NoticeRequest; @@ -218,8 +219,17 @@ public interface PayService { * @param tradeNo 支付平台订单号 * @param outTradeNo 商户单号 * @return 返回支付方交易关闭后的结果 + * @see #close(CloseOrder) */ + @Deprecated Map close(String tradeNo, String outTradeNo); + /** + * 交易关闭接口 + * + * @param closeOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 + */ + Map close(CloseOrder closeOrder); /** 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 0000000..b7e975a --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/CloseOrder.java @@ -0,0 +1,92 @@ +package com.egzosn.pay.common.bean; + +import java.util.Map; + +/** + * 关闭订单 + * @author Egan + * @email egan@egzosn.com + * @date 2021/10/6 + */ +public class CloseOrder implements Order { + + /** + * 支付平台订单号,交易号 + */ + private String tradeNo; + /** + * 商户单号 + */ + private String outTradeNo; + + + /** + * 订单附加信息,可用于预设未提供的参数,这里会覆盖以上所有的订单信息, + */ + private Map attr; + + public CloseOrder() { + } + + public CloseOrder(String outTradeNo) { + this.outTradeNo = outTradeNo; + } + + + + /** + * 添加订单信息 + * + * @param key key + * @param value 值 + */ + @Override + public void addAttr(String key, Object value) { + attr.put(key, value); + } + + /** + * 获取属性 这里可用做覆盖已设置的信息属性,订单信息在签名前进行覆盖。 + * + * @return 属性 + */ + @Override + public Map getAttrs() { + return attr; + } + + /** + * 获取属性 这里可用做覆盖已设置的订单信息属性,订单信息在签名前进行覆盖。 + * + * @param key 属性名 + * @return 属性 + */ + @Override + public Object getAttr(String key) { + return attr.get(key); + } + + 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 Map getAttr() { + return attr; + } + + public void setAttr(Map attr) { + this.attr = attr; + } +} 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 index c651b25..0453306 100644 --- 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 @@ -1,11 +1,14 @@ 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
@@ -32,4 +35,9 @@ public final class OrderParaStructure {
         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/PayOrder.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/PayOrder.java
index 92995ab..1db5c6e 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
@@ -48,10 +48,12 @@ public class PayOrder implements Order {
     /**
      * 设备信息
      */
+    @Deprecated
     private String deviceInfo;
     /**
      * 支付创建ip
      */
+    @Deprecated
     private String spbillCreateIp;
     /**
      * 付款条码串,人脸凭证,有关支付代码相关的,
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 f5397da..1691321 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
@@ -58,6 +58,7 @@ 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 = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
     public static final String YYYY_MM_DD = "yyyy-MM-dd";
     public static final String YYYYMMDD = "yyyyMMdd";
     public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
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 f3127da..94f6dc8 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
@@ -10,6 +10,7 @@ import java.util.Map;
 import com.alibaba.fastjson.JSONObject;
 import com.egzosn.pay.common.api.BasePayService;
 import com.egzosn.pay.common.bean.BillType;
+import com.egzosn.pay.common.bean.CloseOrder;
 import com.egzosn.pay.common.bean.MethodType;
 import com.egzosn.pay.common.bean.NoticeParams;
 import com.egzosn.pay.common.bean.PayMessage;
@@ -412,7 +413,16 @@ public class FuiouPayService extends BasePayService {
     public Map close(String tradeNo, String outTradeNo) {
         return Collections.EMPTY_MAP;
     }
-
+    /**
+     * 交易关闭接口
+     *
+     * @param closeOrder    关闭订单
+     * @return 返回支付方交易关闭后的结果
+     */
+    @Override
+    public Map close(CloseOrder closeOrder){
+        throw new UnsupportedOperationException("不支持该操作");
+    }
 
     /**
      * 申请退款接口
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 a09043f..d1572b3 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
@@ -17,6 +17,7 @@ import com.alibaba.fastjson.JSONObject;
 import com.egzosn.pay.common.api.BasePayService;
 import com.egzosn.pay.common.bean.BaseRefundResult;
 import com.egzosn.pay.common.bean.BillType;
+import com.egzosn.pay.common.bean.CloseOrder;
 import com.egzosn.pay.common.bean.CurType;
 import com.egzosn.pay.common.bean.DefaultCurType;
 import com.egzosn.pay.common.bean.MethodType;
@@ -305,7 +306,16 @@ public class PayoneerPayService extends BasePayService im
     public Map close(String tradeNo, String outTradeNo) {
         return secondaryInterface(tradeNo, outTradeNo, PayoneerTransactionType.CHARGE_CANCEL);
     }
-
+    /**
+     * 交易关闭接口
+     *
+     * @param closeOrder    关闭订单
+     * @return 返回支付方交易关闭后的结果
+     */
+    @Override
+    public Map close(CloseOrder closeOrder){
+        return secondaryInterface(closeOrder.getTradeNo(), closeOrder.getOutTradeNo(), PayoneerTransactionType.CHARGE_CANCEL);
+    }
     /**
      * 交易交易撤销
      *
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 9b1b15f..6d44c1c 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
@@ -21,6 +21,7 @@ import com.alibaba.fastjson.JSONObject;
 import com.egzosn.pay.common.api.BasePayService;
 import com.egzosn.pay.common.bean.BaseRefundResult;
 import com.egzosn.pay.common.bean.BillType;
+import com.egzosn.pay.common.bean.CloseOrder;
 import com.egzosn.pay.common.bean.CurType;
 import com.egzosn.pay.common.bean.DefaultCurType;
 import com.egzosn.pay.common.bean.MethodType;
@@ -271,7 +272,16 @@ public class PayPalPayService extends BasePayService {
         return null;
     }
 
-
+    /**
+     * 交易关闭接口
+     *
+     * @param closeOrder    关闭订单
+     * @return 返回支付方交易关闭后的结果
+     */
+    @Override
+    public Map close(CloseOrder closeOrder){
+        throw new UnsupportedOperationException("不支持该操作");
+    }
     /**
      * 申请退款接口
      *
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
index 5e54691..c932770 100644
--- 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
@@ -22,6 +22,7 @@ import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.egzosn.pay.common.api.BasePayService;
 import com.egzosn.pay.common.bean.BillType;
+import com.egzosn.pay.common.bean.CloseOrder;
 import com.egzosn.pay.common.bean.CurType;
 import com.egzosn.pay.common.bean.DefaultCurType;
 import com.egzosn.pay.common.bean.MethodType;
@@ -354,7 +355,16 @@ public class PayPalPayService extends BasePayService implem
     public Map close(String tradeNo, String outTradeNo) {
         return null;
     }
-
+    /**
+     * 交易关闭接口
+     *
+     * @param closeOrder    关闭订单
+     * @return 返回支付方交易关闭后的结果
+     */
+    @Override
+    public Map close(CloseOrder closeOrder){
+        throw new UnsupportedOperationException("不支持该操作");
+    }
     /**
      * 注意:最好在付款成功之后回调时进行调用
      * 确认订单并返回确认后订单信息
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 bfcf361..7576302 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
@@ -27,6 +27,7 @@ import java.util.TreeMap;
 import com.alibaba.fastjson.JSONObject;
 import com.egzosn.pay.common.api.BasePayService;
 import com.egzosn.pay.common.bean.BillType;
+import com.egzosn.pay.common.bean.CloseOrder;
 import com.egzosn.pay.common.bean.MethodType;
 import com.egzosn.pay.common.bean.NoticeParams;
 import com.egzosn.pay.common.bean.PayMessage;
@@ -656,7 +657,16 @@ public class UnionPayService extends BasePayService {
     public Map close(String tradeNo, String outTradeNo) {
         return Collections.emptyMap();
     }
-
+    /**
+     * 交易关闭接口
+     *
+     * @param closeOrder    关闭订单
+     * @return 返回支付方交易关闭后的结果
+     */
+    @Override
+    public Map close(CloseOrder closeOrder){
+        return Collections.emptyMap();
+    }
 
     @Override
     public UnionRefundResult refund(RefundOrder refundOrder) {
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 859bcb5..5eec170 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
@@ -14,6 +14,7 @@ import com.alibaba.fastjson.JSONObject;
 import com.egzosn.pay.common.api.BasePayService;
 import com.egzosn.pay.common.bean.BaseRefundResult;
 import com.egzosn.pay.common.bean.BillType;
+import com.egzosn.pay.common.bean.CloseOrder;
 import com.egzosn.pay.common.bean.CurType;
 import com.egzosn.pay.common.bean.MethodType;
 import com.egzosn.pay.common.bean.NoticeParams;
@@ -422,7 +423,16 @@ public class WxYouDianPayService extends BasePayService close(String tradeNo, String outTradeNo) {
         return Collections.emptyMap();
     }
-
+    /**
+     * 交易关闭接口
+     *
+     * @param closeOrder    关闭订单
+     * @return 返回支付方交易关闭后的结果
+     */
+    @Override
+    public Map close(CloseOrder closeOrder){
+        return Collections.emptyMap();
+    }
 
     /**
      * 申请退款接口
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 58e622f..570feb1 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
@@ -39,6 +39,7 @@ 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.BillType;
+import com.egzosn.pay.common.bean.CloseOrder;
 import com.egzosn.pay.common.bean.MethodType;
 import com.egzosn.pay.common.bean.NoticeParams;
 import com.egzosn.pay.common.bean.Order;
@@ -514,6 +515,17 @@ public class WxPayService extends BasePayService implements
 
         return secondaryInterface(transactionId, outTradeNo, WxTransactionType.CLOSE);
     }
+    /**
+     * 交易关闭接口
+     *
+     * @param closeOrder    关闭订单
+     * @return 返回支付方交易关闭后的结果
+     */
+    @Override
+    public Map close(CloseOrder closeOrder){
+        return secondaryInterface(closeOrder.getTradeNo(), closeOrder.getOutTradeNo(), WxTransactionType.CLOSE);
+    }
+
 
     /**
      * 交易交易撤销
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 cd3c66c..adae363 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
@@ -10,6 +10,7 @@ import com.alibaba.fastjson.JSONObject;
 import com.egzosn.pay.common.api.BasePayService;
 import com.egzosn.pay.common.bean.BaseRefundResult;
 import com.egzosn.pay.common.bean.BillType;
+import com.egzosn.pay.common.bean.CloseOrder;
 import com.egzosn.pay.common.bean.CurType;
 import com.egzosn.pay.common.bean.DefaultCurType;
 import com.egzosn.pay.common.bean.MethodType;
@@ -298,7 +299,16 @@ public class YiJiPayService extends BasePayService {
     public Map close(String tradeNo, String outTradeNo) {
         return Collections.emptyMap();
     }
-
+    /**
+     * 交易关闭接口
+     *
+     * @param closeOrder    关闭订单
+     * @return 返回支付方交易关闭后的结果
+     */
+    @Override
+    public Map close(CloseOrder closeOrder){
+        return Collections.emptyMap();
+    }
 
     /**
      * 申请退款接口
-- 
Gitee


From 6885cacfc54f914e5a620bc1243319b1dfebba5e Mon Sep 17 00:00:00 2001
From: egzosn 
Date: Wed, 6 Oct 2021 15:46:21 +0800
Subject: [PATCH 082/165] =?UTF-8?q?=E5=BE=AE=E4=BF=A1V3=E5=AE=9E=E7=8E=B0?=
 =?UTF-8?q?=E5=90=88=E5=8D=95=E6=94=AF=E4=BB=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../pay/wx/v3/api/WxCombinePayService.java    | 159 +++++++++++++++
 .../egzosn/pay/wx/v3/api/WxPayService.java    |  68 ++++---
 .../pay/wx/v3/bean/WxTransactionType.java     |  62 ++++--
 .../pay/wx/v3/bean/combine/CombineAmount.java |  34 ++++
 .../wx/v3/bean/combine/CombineCloseOrder.java |  30 +++
 .../wx/v3/bean/combine/CombinePayMessage.java | 115 +++++++++++
 .../wx/v3/bean/combine/CombinePayOrder.java   |  92 +++++++++
 .../wx/v3/bean/combine/CombineSubOrder.java   |  42 ++++
 .../pay/wx/v3/bean/order/SettleInfo.java      |  45 +++++
 .../egzosn/pay/wx/v3/bean/order/SubOrder.java | 187 ++++++++++++++++++
 .../pay/wx/v3/bean/response/WxPayMessage.java |  67 ++++++-
 .../pay/wx/v3/bean/response/order/Amount.java |  34 +++-
 .../pay/wx/v3/bean/response/order/Payer.java  |   1 +
 .../wx/v3/bean/response/order/TradeState.java |  19 +-
 .../com/egzosn/pay/wx/v3/utils/WxConst.java   |  10 +-
 15 files changed, 902 insertions(+), 63 deletions(-)
 create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxCombinePayService.java
 create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombineAmount.java
 create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombineCloseOrder.java
 create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombinePayMessage.java
 create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombinePayOrder.java
 create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombineSubOrder.java
 create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/SettleInfo.java
 create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/SubOrder.java

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 0000000..4c99699
--- /dev/null
+++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxCombinePayService.java
@@ -0,0 +1,159 @@
+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.CloseOrder;
+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 egzosn@gmail.com
+ * date 2016-5-18 14:09:01
+ * 
+ */ +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 订单信息 + * @return 订单参数 + */ + 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 下单结果 + */ + 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); + OrderParaStructure.loadDateParameters(parameters, WxConst.TIME_EXPIRE, order, DateUtils.YYYY_MM_DD_T_HH_MM_SS); + initNotifyUrl(parameters, order); + //支付场景描述 + OrderParaStructure.loadParameters(parameters, WxConst.SCENE_INFO, order); + //子单信息 最多支持子单条数:50 + OrderParaStructure.loadParameters(parameters, WxConst.SUB_ORDERS, order); + //支付者信息 + if (StringUtils.isNotEmpty(order.getOpenid())) { + parameters.put("combine_payer_info", new MapGen<>("openid", order.getOpenid()).getAttr()); + } + + return getAssistService().doExecute(parameters, order); + } + + + /** + * 交易查询接口 + * + * @param transactionId 微信支付平台订单号 + * @param outTradeNo 商户单号 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(String transactionId, String outTradeNo) { + return getAssistService().doExecute("", WxTransactionType.COMBINE_TRANSACTION, outTradeNo); + } + + + /** + * 交易关闭接口 + * + * @param transactionId 支付平台订单号 + * @param outTradeNo 商户单号 + * @return 返回支付方交易关闭后的结果 + */ + @Override + public Map close(String transactionId, String outTradeNo) { + throw new PayErrorException(new PayException("failure", "合单关闭必须要有子单")); + } + + /** + * 交易关闭接口 + * + * @param closeOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 + */ + @Override + public Map close(CloseOrder closeOrder) { + Map parameters = new MapGen(WxConst.COMBINE_APPID, payConfigStorage.getAppId()) + .keyValue(WxConst.SUB_ORDERS, closeOrder.getAttr(WxConst.SUB_ORDERS)) + .getAttr(); + String requestBody = JSON.toJSONString(parameters, SerializerFeature.WriteMapNullValue); + return getAssistService().doExecute(requestBody, WxTransactionType.COMBINE_CLOSE, closeOrder.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/WxPayService.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayService.java index 49da334..80c6679 100644 --- 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 @@ -22,6 +22,7 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.common.api.BasePayService; import com.egzosn.pay.common.bean.BillType; +import com.egzosn.pay.common.bean.CloseOrder; import com.egzosn.pay.common.bean.CurType; import com.egzosn.pay.common.bean.MethodType; import com.egzosn.pay.common.bean.NoticeParams; @@ -81,27 +82,13 @@ public class WxPayService extends BasePayService { /** * 辅助api */ - private volatile WxPayAssistService wxPayAssistService; + private volatile WxPayAssistService assistService; /** * 微信参数构造器 */ private volatile WxParameterStructure wxParameterStructure; - - public WxPayAssistService getAssistService() { - if (null == wxPayAssistService) { - wxPayAssistService = new DefaultWxPayAssistService(this); - //在这预先进行初始化 - wxPayAssistService.refreshCertificate(); - } - return wxPayAssistService; - } - - public void setAssistService(WxPayAssistService wxPayAssistService) { - this.wxPayAssistService = wxPayAssistService; - } - /** * 创建支付服务 * @@ -123,25 +110,23 @@ public class WxPayService extends BasePayService { /** - * 初始化之后执行 + * 辅助api + * @return 辅助api */ - @Override - protected void initAfter() { - new Thread(() -> { - payConfigStorage.loadCertEnvironment(); - wxParameterStructure = new WxParameterStructure(payConfigStorage); + public WxPayAssistService getAssistService() { + if (null == assistService) { + assistService = new DefaultWxPayAssistService(this); //在这预先进行初始化 - try { - Thread.sleep(10); - } - catch (InterruptedException e) { + assistService.refreshCertificate(); + } + return assistService; + } - } - getAssistService(); - }).start(); + public void setAssistService(WxPayAssistService assistService) { + this.assistService = assistService; + } - } /** * 设置api服务器地址 @@ -154,6 +139,14 @@ public class WxPayService extends BasePayService { return this; } + public String getApiServerUrl() { + return apiServerUrl; + } + + public WxParameterStructure getWxParameterStructure() { + return wxParameterStructure; + } + /** * 根据交易类型获取url * @@ -323,6 +316,7 @@ public class WxPayService extends BasePayService { * @param is 请求流 * @return 获得回调的请求参数 */ + @Deprecated @Override public Map getParameter2Map(Map parameterMap, InputStream is) { throw new PayErrorException(new WxPayError(FAILURE, "微信V3不支持方式")); @@ -350,7 +344,7 @@ public class WxPayService extends BasePayService { noticeParams.setBody(JSON.parseObject(data)); } catch (IOException e) { - LOG.error("获取回调参数异常", e); + throw new PayErrorException(new WxPayError(FAILURE, "获取回调参数异常"), e); } Map> headers = new HashMap<>(); Enumeration headerNames = request.getHeaderNames(); @@ -457,8 +451,20 @@ public class WxPayService extends BasePayService { */ @Override public Map close(String transactionId, String outTradeNo) { + return close(new CloseOrder(outTradeNo)); + } + + + /** + * 交易关闭接口 + * + * @param closeOrder 关闭订单 + * @return 返回支付方交易关闭后的结果 + */ + @Override + public Map close(CloseOrder closeOrder) { String parameters = wxParameterStructure.getSpParameters(); - return getAssistService().doExecute(parameters, WxTransactionType.CLOSE, outTradeNo); + return getAssistService().doExecute(parameters, WxTransactionType.CLOSE, closeOrder.getOutTradeNo()); } /** 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 index 70a6ee8..948d3f0 100644 --- 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 @@ -25,11 +25,14 @@ public enum WxTransactionType implements TransactionType { /** * 获取证书. */ - CERT("certificates", MethodType.GET), + CERT("/v3/certificates", MethodType.GET), + + //----------------------------------------------------------------- + //以下为直连与服务商支付方式 /** - * 公众号支付 + * 微信公众号支付或者小程序支付 */ - JSAPI("pay{partner}/transactions/jsapi", MethodType.POST) { + 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"; @@ -40,15 +43,15 @@ public enum WxTransactionType implements TransactionType { /** * 二维码支付 */ - NATIVE("pay{partner}/transactions/native", MethodType.POST, true), + NATIVE("/v3/pay{partner}/transactions/native", MethodType.POST, true), /** * 移动支付 */ - APP("pay{partner}/transactions/app", MethodType.POST), + APP("/v3/pay{partner}/transactions/app", MethodType.POST), /** * H5支付 */ - H5("pay{partner}/transactions/h5", MethodType.POST, true) { + H5("/v3/pay{partner}/transactions/h5", MethodType.POST, true) { @Override public void setAttribute(Map parameters, PayOrder order) { Object sceneInfoObj = parameters.get(WxConst.SCENE_INFO); @@ -79,7 +82,7 @@ public enum WxTransactionType implements TransactionType { * 兼容 后期会抛弃 */ @Deprecated - MWEB("pay{partner}/transactions/h5", MethodType.POST, true) { + MWEB("/v3/pay{partner}/transactions/h5", MethodType.POST, true) { @Override public void setAttribute(Map parameters, PayOrder order) { H5.setAttribute(parameters, order); @@ -91,35 +94,64 @@ public enum WxTransactionType implements TransactionType { * 查询订单 * 兼容V2的方式,通过入参来决定 */ - QUERY("pay{partner}/transactions/", MethodType.GET), + QUERY("/v3/pay{partner}/transactions/", MethodType.GET), /** * 微信支付订单号查询 */ - QUERY_TRANSACTION_ID("pay{partner}/transactions/id/{transaction_id}", MethodType.GET), + QUERY_TRANSACTION_ID("/v3/pay{partner}/transactions/id/{transaction_id}", MethodType.GET), /** * 商户订单号查询 */ - QUERY_OUT_TRADE_NO("pay{partner}/transactions/out-trade-no/{out_trade_no}", MethodType.GET), + QUERY_OUT_TRADE_NO("/v3/pay{partner}/transactions/out-trade-no/{out_trade_no}", MethodType.GET), /** * 关闭订单 */ - CLOSE("pay{partner}/transactions/out-trade-no/{out_trade_no}/close", MethodType.POST), + CLOSE("/v3/pay{partner}/transactions/out-trade-no/{out_trade_no}/close", MethodType.POST), /** * 申请退款 */ - REFUND("refund/domestic/refunds", MethodType.POST), + REFUND("/v3/refund/domestic/refunds", MethodType.POST), /** * 查询退款 */ - REFUND_QUERY("refund/domestic/refunds/{out_refund_no}", MethodType.GET), + REFUND_QUERY("/v3/refund/domestic/refunds/{out_refund_no}", MethodType.GET), /** * 申请交易账单 */ - TRADE_BILL("bill/tradebill", MethodType.GET), + TRADE_BILL("/v3/bill/tradebill", MethodType.GET), /** * 申请资金账单 */ - FUND_FLOW_BILL("bill/fundflowbill", MethodType.GET) + FUND_FLOW_BILL("/v3/bill/fundflowbill", MethodType.GET), + + //----------------------------------------------------------------- + //以下为合并支付 + /** + * 合单下单-APP支付API. + */ + COMBINE_APP("/v3/combine-transactions/app", MethodType.POST), + + /** + * 合单下单-微信公众号支付或者小程序支付. + */ + COMBINE_JSAPI("/v3/combine-transactions/jsapi", MethodType.POST), + /** + * 合单下单-H5支付API. + */ + COMBINE_H5("/v3/combine-transactions/h5", MethodType.POST, true), + /** + * 合单下单-Native支付API. + */ + COMBINE_NATIVE("/v3/combine-transactions/native", MethodType.POST, true), + /** + * 合单查询订单API. + */ + COMBINE_TRANSACTION("/v3/combine-transactions/out-trade-no/{combine_out_trade_no}", MethodType.GET), + + /** + * 合单关闭订单API. + */ + COMBINE_CLOSE("/v3/combine-transactions/out-trade-no/{combine_out_trade_no}/close", MethodType.POST), ; WxTransactionType(String type, MethodType method) { 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 0000000..f3d812b --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombineAmount.java @@ -0,0 +1,34 @@ +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 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 0000000..cef6267 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombineCloseOrder.java @@ -0,0 +1,30 @@ +package com.egzosn.pay.wx.v3.bean.combine; + +import java.util.List; + +import com.egzosn.pay.common.bean.CloseOrder; +import com.egzosn.pay.wx.v3.utils.WxConst; + +/** + * 微信合单关闭订单 + * @author Egan + * @email egan@egzosn.com + * @date 2021/10/6 + */ +public class CombineCloseOrder extends CloseOrder { + + /** + * 子单信息,必填,最多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 0000000..cda2d61 --- /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 0000000..280aef3 --- /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 0000000..ee66a24 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/combine/CombineSubOrder.java @@ -0,0 +1,42 @@ +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; + + /** + * 商户系统内部订单号,要求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 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/SettleInfo.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/SettleInfo.java new file mode 100644 index 0000000..4ce0a7c --- /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/SubOrder.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/SubOrder.java new file mode 100644 index 0000000..0b0010d --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/SubOrder.java @@ -0,0 +1,187 @@ +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 = "sub_mchid") + private String subMchid; + + /** + * 结算信息,选填 + */ + @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 String getSubMchid() { + return subMchid; + } + + public void setSubMchid(String subMchid) { + this.subMchid = subMchid; + } + + 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/response/WxPayMessage.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/WxPayMessage.java index 8aa4008..f02ffc4 100644 --- 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 @@ -16,7 +16,7 @@ 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
@@ -99,8 +99,36 @@ public class WxPayMessage extends PayMessage {
      */
     @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;
     /**
      * 交易状态描述
      * 示例值:支付成功
@@ -121,7 +149,7 @@ public class WxPayMessage extends PayMessage {
     private String attach;
 
     /**
-     * 支付完成时间,遵循rfc3339标准格式,
+     * 支付完成时间|| 退款完成时间,遵循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秒。
@@ -301,6 +329,37 @@ public class WxPayMessage extends PayMessage {
         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 final WxPayMessage create(Map message) {
         WxPayMessage payMessage = new JSONObject(message).toJavaObject(WxPayMessage.class);
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
index c6cf950..b134a8c 100644
--- 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
@@ -2,25 +2,41 @@ 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{ +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; } @@ -36,4 +52,20 @@ public class Amount extends com.egzosn.pay.wx.v3.bean.order.Amount{ 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/Payer.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/response/order/Payer.java index 623b97d..7f359f1 100644 --- 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 @@ -11,6 +11,7 @@ package com.egzosn.pay.wx.v3.bean.response.order; public class Payer { /** * 用户在直连商户appid下的唯一标识。 + * 使用合单appid获取的对应用户openid。是用户在商户appid下的唯一标识。 */ private String openid; 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 index 6b6afe6..9d49a4c 100644 --- 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 @@ -1,7 +1,8 @@ package com.egzosn.pay.wx.v3.bean.response.order; /** - * 微信侧返回交易状态 + * 微信侧返回交易状态,兼容退款状态 + * * @author Egan *
  * email egan@egzosn.com
@@ -11,31 +12,28 @@ package com.egzosn.pay.wx.v3.bean.response.order;
 public enum TradeState {
     /**
      * 支付成功
-     *
-     * 
+     * 退款成功
      */
     SUCCESS,
     /**
      * 转入退款
-     *
-     * 
      */
     REFUND,
     /**
      * 未支付
-     *
-     * 
      */
     NOTPAY,
     /**
      * 已关闭
-     *
-     * 
+     * 退款关闭
      */
     CLOSED,
+    /**
+     * 退款异常.
+     */
+    ABNORMAL,
     /**
      * 已撤销(付款码支付)
-     *
      */
     REVOKED,
     /**
@@ -48,7 +46,6 @@ public enum TradeState {
     PAYERROR,
     /**
      * 已接收,等待扣款
-     *
      */
     ACCEPT,
 }
\ No newline at end of file
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
index 2055d49..941ff10 100644
--- 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
@@ -17,7 +17,7 @@ public final class WxConst {
     /**
      * 微信默认请求地址
      */
-    public static final String URI = "https://api.mch.weixin.qq.com/v3/";
+    public static final String URI = "https://api.mch.weixin.qq.com";
     /**
      * 证书别名
      */
@@ -31,12 +31,20 @@ public final class WxConst {
      * 沙箱
      */
     public static final String SANDBOXNEW = "sandboxnew/";
+    public static final String COMBINE = "combine_";
     public static final String APPID = "appid";
+
+    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 TOKEN_PATTERN = "mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"";
 
-- 
Gitee


From 175fa4297712944e287b04fd761463866e1ee462 Mon Sep 17 00:00:00 2001
From: egzosn 
Date: Wed, 6 Oct 2021 16:35:32 +0800
Subject: [PATCH 083/165] =?UTF-8?q?=E5=BE=AE=E4=BF=A1V3=E6=94=AF=E4=BB=98?=
 =?UTF-8?q?=E4=B8=8E=E5=BE=AE=E4=BF=A1V3=E5=90=88=E5=8D=95=E6=94=AF?=
 =?UTF-8?q?=E4=BB=98=E6=A1=88=E4=BE=8B=E5=AE=9E=E7=8E=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../controller/WxV3CombinePayController.java  | 307 ++++++++++++++++++
 .../demo/controller/WxV3PayController.java    |  78 +----
 .../handler/WxV3CombinePayMessageHandler.java |  29 ++
 .../pay/wx/v3/bean/combine/CombineAmount.java |   6 +
 .../wx/v3/bean/combine/CombineSubOrder.java   |  17 +
 .../egzosn/pay/wx/v3/bean/order/Amount.java   |   2 +-
 .../egzosn/pay/wx/v3/bean/order/H5Info.java   |   5 +
 .../egzosn/pay/wx/v3/bean/order/SubOrder.java |  17 +-
 8 files changed, 368 insertions(+), 93 deletions(-)
 create mode 100644 pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxV3CombinePayController.java
 create mode 100644 pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/WxV3CombinePayMessageHandler.java

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 0000000..0bfd3ce
--- /dev/null
+++ b/pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxV3CombinePayController.java
@@ -0,0 +1,307 @@
+
+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.CertStoreType;
+import com.egzosn.pay.common.bean.RefundOrder;
+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;
+import com.egzosn.pay.wx.v3.bean.response.WxRefundResult;
+
+/**
+ * 微信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("wxc7b993ff15a9f27c");
+        wxPayConfigStorage.setMchId("1602947766");
+        //V3密钥 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_2.shtml
+        wxPayConfigStorage.setV3ApiKey("9bd8f0e7af4841299d782406b7774f56");
+        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("E:\\Documents\\gitee\\支付\\yifenli_mall.p12");
+        wxPayConfigStorage.setCertStoreType(CertStoreType.PATH);
+        service = new WxCombinePayService(wxPayConfigStorage);
+        //设置回调消息处理
+        //TODO {@link com.egzosn.pay.demo.controller.WxPayController#payBack}
+        service.setPayMessageHandler(new WxV3CombinePayMessageHandler());
+    }
+
+
+    /**
+     * 跳到支付页面
+     * 针对实时支付
+     *
+     * @return 跳到支付页面
+     */
+    @RequestMapping(value = "toPay.html", produces = "text/html;charset=UTF-8")
+    public String toPay() {
+        CombinePayOrder order = new CombinePayOrder();
+        SceneInfo sceneInfo = new SceneInfo();
+        sceneInfo.setPayerClientIp("用户终端IP ");
+        sceneInfo.setDeviceId("终端设备号(门店号或收银设备ID) 。为了方便问题定位,H5支付场景下,该字段必填");
+        sceneInfo.setH5Info(new H5Info("场景类型,枚举值:\n" +
+                "iOS:IOS移动应用;\n" +
+                "Android:安卓移动应用;\n" +
+                "Wap:WAP网站应用;"));
+        order.setSceneInfo(sceneInfo);
+        order.setCombineOutTradeNo("合单商户订单号");
+        //子单信息,最多50单.
+        List subOrders = new ArrayList<>();
+        SubOrder subOrder = new SubOrder();
+        subOrder.setMchid("子单商户号");
+        subOrder.setAttach("附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。 ");
+        //"子单金额,单位为分。 "
+        subOrder.setAmount(new CombineAmount(121));
+        subOrder.setOutTradeNo("子单商户订单号 ");
+        subOrder.setDescription("商品描述");
+        subOrder.setSubMchid("二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 ");
+        subOrders.add(subOrder);
+        order.setSubOrders(subOrders);
+        order.setTransactionType(WxTransactionType.COMBINE_H5);
+        return service.toPay(order);
+    }
+
+    /**
+     * 公众号支付,小程序
+     *
+     * @return 返回jsapi所需参数
+     */
+    @RequestMapping(value = "jsapi")
+    public Map jsapi() {
+
+        CombinePayOrder order = new CombinePayOrder();
+        order.setTransactionType(WxTransactionType.COMBINE_JSAPI);
+        order.setCombineOutTradeNo("合单商户订单号");
+        order.setOpenid("使用合单appid获取的对应用户openid。是用户在商户appid下的唯一标识。 ");
+        //子单信息,最多50单.
+        List subOrders = new ArrayList<>();
+        SubOrder subOrder = new SubOrder();
+        subOrder.setMchid("子单商户号");
+        subOrder.setAttach("附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。 ");
+        //"子单金额,单位为分。 "
+        subOrder.setAmount(new CombineAmount(111));
+        subOrder.setOutTradeNo("子单商户订单号 ");
+        subOrder.setDescription("商品描述");
+        subOrder.setSubMchid("二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 ");
+        subOrders.add(subOrder);
+        order.setSubOrders(subOrders);
+        Map orderInfo = service.orderInfo(order);
+        orderInfo.put("code", 0);
+        return orderInfo;
+    }
+
+
+    /**
+     * 获取支付预订单信息
+     *
+     * @return 支付预订单信息
+     */
+    @RequestMapping("app")
+    public Map app() {
+
+        CombinePayOrder order = new CombinePayOrder();
+        order.setTransactionType(WxTransactionType.COMBINE_APP);
+        order.setCombineOutTradeNo("合单商户订单号");
+        //子单信息,最多50单.
+        List subOrders = new ArrayList<>();
+        SubOrder subOrder = new SubOrder();
+        subOrder.setMchid("子单商户号");
+        subOrder.setAttach("附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。 ");
+        //"子单金额,单位为分。 "
+        subOrder.setAmount(new CombineAmount(211));
+        subOrder.setOutTradeNo("子单商户订单号 ");
+        subOrder.setDescription("商品描述");
+        subOrder.setSubMchid("二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 ");
+        subOrders.add(subOrder);
+        order.setSubOrders(subOrders);
+        Map orderInfo = service.orderInfo(order);
+        orderInfo.put("code", 0);
+        return orderInfo;
+    }
+
+    /**
+     * 获取二维码图像
+     * 二维码支付
+     *
+     * @return 二维码图像
+     * @throws IOException IOException
+     */
+    @RequestMapping(value = "toQrPay.jpg", produces = "image/jpeg;charset=UTF-8")
+    public byte[] toWxQrPay() throws IOException {
+        CombinePayOrder order = new CombinePayOrder();
+        order.setTransactionType(WxTransactionType.COMBINE_NATIVE);
+        order.setCombineOutTradeNo("合单商户订单号");
+        //子单信息,最多50单.
+        List subOrders = new ArrayList<>();
+        SubOrder subOrder = new SubOrder();
+        subOrder.setMchid("子单商户号");
+        subOrder.setAttach("附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。 ");
+        //"子单金额,单位为分。 "
+        subOrder.setAmount(new CombineAmount(131));
+        subOrder.setOutTradeNo("子单商户订单号 ");
+        subOrder.setDescription("商品描述");
+        subOrder.setSubMchid("二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 ");
+        subOrders.add(subOrder);
+        order.setSubOrders(subOrders);
+
+        //获取对应的支付账户操作工具(可根据账户id)
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        ImageIO.write(service.genQrPay(order), "JPEG", baos);
+        return baos.toByteArray();
+    }
+
+    /**
+     * 获取二维码地址
+     * 二维码支付
+     *
+     * @return 二维码图像
+     * @throws IOException IOException
+     */
+    @RequestMapping(value = "getQrPay.json")
+    public String getQrPay() {
+        CombinePayOrder order = new CombinePayOrder();
+        order.setTransactionType(WxTransactionType.COMBINE_NATIVE);
+        order.setCombineOutTradeNo("合单商户订单号");
+        //子单信息,最多50单.
+        List subOrders = new ArrayList<>();
+        SubOrder subOrder = new SubOrder();
+        subOrder.setMchid("子单商户号");
+        subOrder.setAttach("附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。1 ");
+        //"子单金额,单位为分。 "
+        subOrder.setAmount(new CombineAmount(115));
+        subOrder.setOutTradeNo("子单商户订单号 ");
+        subOrder.setDescription("商品描述");
+        subOrder.setSubMchid("二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 ");
+        subOrders.add(subOrder);
+        order.setSubOrders(subOrders);
+        //获取对应的支付账户操作工具(可根据账户id)
+        return service.getQrPay(order);
+    }
+
+
+    /**
+     * 支付回调地址
+     *
+     * @param request 请求
+     * @return 是否成功
+     * 

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

+ * 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler} + * @throws IOException IOException + */ + @RequestMapping(value = "payBack.json") + public String payBack(HttpServletRequest request) { + //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() + return service.payBack(new HttpRequestNoticeParams(request)).toMessage(); + } + + + /** + * 查询 + * + * @param order 订单的请求体 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @RequestMapping("query") + public Map query(QueryOrder order) { + return service.query(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 WxRefundResult 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 index 8adc70e..5554c00 100644 --- 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 @@ -19,19 +19,16 @@ import org.springframework.web.bind.annotation.RestController; 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.TransferOrder; 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.bean.WxBank; -import com.egzosn.pay.wx.bean.WxTransferType; 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.response.WxRefundResult; /** - * 发起支付入口 + * 微信V3发起支付入口 * * @author egan * email egzosn@gmail.com @@ -44,13 +41,8 @@ public class WxV3PayController { private WxPayService service = null; - - - - @PostConstruct //没有证书的情况下注释掉,避免启动报错 public void init() { - System.out.println("v3 init"); WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage(); wxPayConfigStorage.setAppId("wxc7b993ff15a9f27c"); wxPayConfigStorage.setMchId("1602947766"); @@ -161,8 +153,6 @@ public class WxV3PayController { } - - /** * 支付回调地址 * @@ -175,7 +165,7 @@ public class WxV3PayController { * @throws IOException IOException */ @RequestMapping(value = "payBack.json") - public String payBack(HttpServletRequest request) { + public String payBack(HttpServletRequest request) { //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() return service.payBack(new HttpRequestNoticeParams(request)).toMessage(); } @@ -239,68 +229,4 @@ public class WxV3PayController { } - /** - * 转账到余额 - * - * @param order 转账订单 - * @return 对应的转账结果 - */ - @RequestMapping("transfer") - public Map transfer(TransferOrder order) { - order.setOutNo("partner_trade_no 商户转账订单号"); - order.setPayeeAccount("用户openid"); - order.setPayeeName("收款用户姓名, 非必填,如果填写将强制验证收款人姓名"); - order.setRemark("转账备注, 非必填"); - order.setAmount(new BigDecimal(10)); - - //转账到余额,这里默认值是转账到银行卡 - order.setTransferType(WxTransferType.TRANSFERS); - - return service.transfer(order); - } - - - /** - * 转账到银行卡 - * - * @param order 转账订单 - * @return 对应的转账结果 - */ - @RequestMapping("transferPayBank") - public Map transferPayBank(TransferOrder order) { - order.setOutNo("partner_trade_no 商户转账订单号"); - //采用标准RSA算法,公钥由微信侧提供,将公钥信息配置在PayConfigStorage#setKeyPublic(String) - order.setPayeeAccount("enc_bank_no 收款方银行卡号"); - order.setPayeeName("收款方用户名"); - order.setBank(WxBank.ABC); - order.setRemark("转账备注, 非必填"); - order.setAmount(new BigDecimal(10)); - //转账到银行卡,这里默认值是转账到银行卡 - order.setTransferType(WxTransferType.PAY_BANK); - - return service.transfer(order); - } - - /** - * 转账查询 - * - * @param outNo 商户转账订单号 - * @param wxTransferType 微信转账类型, - * .....这里没办法了只能这样写(┬_┬),请见谅 - * {@link WxTransferType#QUERY_BANK} - * {@link WxTransferType#GETTRANSFERINFO} - * - *

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

- * @return 对应的转账订单 - */ - @RequestMapping("transferQuery") - public Map transferQuery(String outNo, String wxTransferType) { - //默认查询银行卡的记录 com.egzosn.pay.wx.v3.bean.WxTransferType#QUERY_BANK - return service.transferQuery(outNo, wxTransferType); - } - - } 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 0000000..cdbcd0c --- /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.combine.CombinePayMessage; + +/** + * 微信合单支付回调处理器 + * Created by ZaoSheng on 2016/6/1. + */ +public class WxV3CombinePayMessageHandler implements PayMessageHandler { + + private final Logger LOG = LoggerFactory.getLogger(DefaultPayMessageHandler.class); + + @Override + public PayOutMessage handle(CombinePayMessage payMessage, Map context, PayService payService) throws PayErrorException { + LOG.info("回调支付消息处理器,回调消息:{}", JSON.toJSONString(payMessage)); + return payService.successPayOutMessage(payMessage); + } +} 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 index f3d812b..8a677f6 100644 --- 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 @@ -21,6 +21,12 @@ public class CombineAmount extends Amount { @JSONField(name = "total_amount") private Integer totalAmount; + public CombineAmount() { + } + + public CombineAmount(Integer totalAmount) { + this.totalAmount = totalAmount; + } public Integer getTotalAmount() { return totalAmount; 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 index ee66a24..3fd4f14 100644 --- 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 @@ -16,6 +16,15 @@ public class CombineSubOrder { * 子单发起方商户号,必填,必须与发起方appid有绑定关系。 */ private String mchid; + /** + * 二级商户商户号,由微信支付生成并下发。 + *

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

+ * 直连商户不用传二级商户号。 + */ + @JSONField(name = "sub_mchid") + private String subMchid; /** * 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。 @@ -32,6 +41,14 @@ public class CombineSubOrder { this.mchid = mchid; } + public String getSubMchid() { + return subMchid; + } + + public void setSubMchid(String subMchid) { + this.subMchid = subMchid; + } + public String getOutTradeNo() { return 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 index 8574451..ae15d18 100644 --- 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 @@ -23,7 +23,7 @@ public class Amount { * 货币类型 CNY:人民币,境内商户号仅支持人民币。 * {@link com.egzosn.pay.common.bean.CurType} */ - private String currency; + private String currency = DefaultCurType.CNY.getType(); public Amount() { } 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 index 03e5f71..5568533 100644 --- 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 @@ -41,10 +41,15 @@ public class H5Info { @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(); 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 index 0b0010d..5a579d8 100644 --- 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 @@ -32,15 +32,7 @@ public class SubOrder extends CombineSubOrder { private String description; - /** - * 二级商户商户号,由微信支付生成并下发。 - *

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

- * 直连商户不用传二级商户号。 - */ - @JSONField(name = "sub_mchid") - private String subMchid; + /** * 结算信息,选填 @@ -113,13 +105,6 @@ public class SubOrder extends CombineSubOrder { } - public String getSubMchid() { - return subMchid; - } - - public void setSubMchid(String subMchid) { - this.subMchid = subMchid; - } public SettleInfo getSettleInfo() { return settleInfo; -- Gitee From fa4d2d90078f07e9f44b2ea15b38bd7f3a355ef2 Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 6 Oct 2021 16:37:28 +0800 Subject: [PATCH 084/165] =?UTF-8?q?=E5=BE=AE=E4=BF=A1V3=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E4=B8=8E=E5=BE=AE=E4=BF=A1V3=E5=90=88=E5=8D=95=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E6=A1=88=E4=BE=8B=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pay/demo/controller/WxV3CombinePayController.java | 8 ++++---- .../com/egzosn/pay/demo/controller/WxV3PayController.java | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) 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 index 0bfd3ce..d9fcbc4 100644 --- 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 @@ -48,17 +48,17 @@ public class WxV3CombinePayController { @PostConstruct //没有证书的情况下注释掉,避免启动报错 public void init() { WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage(); - wxPayConfigStorage.setAppId("wxc7b993ff15a9f27c"); - wxPayConfigStorage.setMchId("1602947766"); + wxPayConfigStorage.setAppId("wxc7b993ff15a9f26c"); + wxPayConfigStorage.setMchId("1602947765"); //V3密钥 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_2.shtml - wxPayConfigStorage.setV3ApiKey("9bd8f0e7af4841299d782406b7774f56"); + wxPayConfigStorage.setV3ApiKey("9bd8f0e7af4841299d782406b7774f57"); wxPayConfigStorage.setNotifyUrl("http://sailinmu.iok.la/wxV3/payBack.json"); wxPayConfigStorage.setReturnUrl("http://sailinmu.iok.la/wxV3/payBack.json"); wxPayConfigStorage.setInputCharset("utf-8"); //使用证书时设置为true wxPayConfigStorage.setCertSign(true); //商户API证书 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_1.shtml - wxPayConfigStorage.setApiClientKeyP12("E:\\Documents\\gitee\\支付\\yifenli_mall.p12"); + wxPayConfigStorage.setApiClientKeyP12("yifenli_mall.p12"); wxPayConfigStorage.setCertStoreType(CertStoreType.PATH); service = new WxCombinePayService(wxPayConfigStorage); //设置回调消息处理 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 index 5554c00..fbc4f97 100644 --- 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 @@ -44,17 +44,17 @@ public class WxV3PayController { @PostConstruct //没有证书的情况下注释掉,避免启动报错 public void init() { WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage(); - wxPayConfigStorage.setAppId("wxc7b993ff15a9f27c"); - wxPayConfigStorage.setMchId("1602947766"); + wxPayConfigStorage.setAppId("wxc7b993ff15a9f26c"); + wxPayConfigStorage.setMchId("1602947765"); //V3密钥 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_2.shtml - wxPayConfigStorage.setV3ApiKey("9bd8f0e7af4841299d782406b7774f56"); + wxPayConfigStorage.setV3ApiKey("9bd8f0e7af4841299d782406b7774f57"); wxPayConfigStorage.setNotifyUrl("http://sailinmu.iok.la/wxV3/payBack.json"); wxPayConfigStorage.setReturnUrl("http://sailinmu.iok.la/wxV3/payBack.json"); wxPayConfigStorage.setInputCharset("utf-8"); //使用证书时设置为true wxPayConfigStorage.setCertSign(true); //商户API证书 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_1.shtml - wxPayConfigStorage.setApiClientKeyP12("E:\\Documents\\gitee\\支付\\yifenli_mall.p12"); + wxPayConfigStorage.setApiClientKeyP12("yifenli_mall.p12"); wxPayConfigStorage.setCertStoreType(CertStoreType.PATH); service = new WxPayService(wxPayConfigStorage); //设置回调消息处理 -- Gitee From 5d47e3e0fabe38458bb0c2aa8b020f1679048ad4 Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 6 Oct 2021 16:54:01 +0800 Subject: [PATCH 085/165] 1.8 --- pay-java-demo/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index 7cf711f..a95cbba 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -103,8 +103,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.7 - 1.7 + 1.8 + 1.8 UTF-8 -- Gitee From 9c02712ae9ea8fc0dd605d962303485ee3af250d Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 6 Oct 2021 17:14:59 +0800 Subject: [PATCH 086/165] 2.14.2 --- README.md | 2 +- pay-java-ali/pom.xml | 2 +- pay-java-baidu/pom.xml | 2 +- pay-java-common/pom.xml | 3 +-- .../com/egzosn/pay/common/bean/CloseOrder.java | 6 ++++-- pay-java-demo/pom.xml | 2 +- pay-java-fuiou/pom.xml | 2 +- pay-java-payoneer/pom.xml | 2 +- pay-java-paypal/pom.xml | 2 +- pay-java-union/pom.xml | 2 +- pay-java-web-support/pom.xml | 2 +- pay-java-wx-youdian/pom.xml | 2 +- pay-java-wx/pom.xml | 2 +- .../wx/v3/api/DefaultWxPayAssistService.java | 2 +- .../pay/wx/v3/api/WxCombinePayService.java | 1 - .../pay/wx/v3/api/WxParameterStructure.java | 3 --- .../pay/wx/v3/api/WxPayAssistService.java | 1 + .../wx/v3/bean/combine/CombineCloseOrder.java | 6 ++++-- .../pay/wx/v3/bean/response/WxPayMessage.java | 2 +- .../bean/response/order/PromotionDetail.java | 6 ++++-- .../pay/wx/v3/utils/AntCertificationUtil.java | 18 ++++++++++-------- pay-java-yiji/pom.xml | 2 +- pom.xml | 10 +++++----- 23 files changed, 43 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index bad91f0..ff69992 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ com.egzosn {module-name} - 2.14.1-b + 2.14.2 ``` diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index 7e4683a..7d743ce 100644 --- a/pay-java-ali/pom.xml +++ b/pay-java-ali/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.2-SNAPSHOT + 2.14.2 4.0.0 pay-java-ali diff --git a/pay-java-baidu/pom.xml b/pay-java-baidu/pom.xml index 8861985..7fbb57d 100644 --- a/pay-java-baidu/pom.xml +++ b/pay-java-baidu/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.2-SNAPSHOT + 2.14.2 4.0.0 pay-java-baidu diff --git a/pay-java-common/pom.xml b/pay-java-common/pom.xml index fb24df8..f19fd5b 100644 --- a/pay-java-common/pom.xml +++ b/pay-java-common/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.2-SNAPSHOT + 2.14.2 4.0.0 jar @@ -30,7 +30,6 @@ org.slf4j slf4j-api - 1.7.30 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 index b7e975a..4d50e86 100644 --- 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 @@ -5,8 +5,10 @@ import java.util.Map; /** * 关闭订单 * @author Egan - * @email egan@egzosn.com - * @date 2021/10/6 + *

+ * email egan@egzosn.com
+ * date 2021/10/6
+ * 
*/ public class CloseOrder implements Order { diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index a95cbba..125b476 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.2-SNAPSHOT + 2.14.2 4.0.0 war diff --git a/pay-java-fuiou/pom.xml b/pay-java-fuiou/pom.xml index 4ba3acd..8685c30 100644 --- a/pay-java-fuiou/pom.xml +++ b/pay-java-fuiou/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.2-SNAPSHOT + 2.14.2 4.0.0 pay-java-fuiou diff --git a/pay-java-payoneer/pom.xml b/pay-java-payoneer/pom.xml index a8e8334..c920ab0 100644 --- a/pay-java-payoneer/pom.xml +++ b/pay-java-payoneer/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.2-SNAPSHOT + 2.14.2 4.0.0 pay-java-payoneer diff --git a/pay-java-paypal/pom.xml b/pay-java-paypal/pom.xml index ce4d4a7..4e0f8bd 100644 --- a/pay-java-paypal/pom.xml +++ b/pay-java-paypal/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.2-SNAPSHOT + 2.14.2 4.0.0 diff --git a/pay-java-union/pom.xml b/pay-java-union/pom.xml index c1ca7f9..2a00ed4 100644 --- a/pay-java-union/pom.xml +++ b/pay-java-union/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.2-SNAPSHOT + 2.14.2 4.0.0 diff --git a/pay-java-web-support/pom.xml b/pay-java-web-support/pom.xml index 7389396..539f476 100644 --- a/pay-java-web-support/pom.xml +++ b/pay-java-web-support/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.2-SNAPSHOT + 2.14.2 4.0.0 jar diff --git a/pay-java-wx-youdian/pom.xml b/pay-java-wx-youdian/pom.xml index e88c55c..8f3827a 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.14.2-SNAPSHOT + 2.14.2 4.0.0 pay-java-wx-youdian diff --git a/pay-java-wx/pom.xml b/pay-java-wx/pom.xml index b0d3669..d9ad6ff 100644 --- a/pay-java-wx/pom.xml +++ b/pay-java-wx/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.2-SNAPSHOT + 2.14.2 4.0.0 pay-java-wx 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 index a6d0e1b..906a529 100644 --- 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 @@ -47,7 +47,6 @@ import com.egzosn.pay.wx.v3.utils.WxConst; * date 2021/8/7 */ public class DefaultWxPayAssistService implements WxPayAssistService { - protected final Logger LOG = LoggerFactory.getLogger(getClass()); private WxPayConfigStorage payConfigStorage; private HttpRequestTemplate requestTemplate; @@ -118,6 +117,7 @@ public class DefaultWxPayAssistService implements WxPayAssistService { * 构建请求实体 * 这里也做签名处理 * + * @param url url * @param body 请求内容体 * @param method 请求方法 * @return 请求实体 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 index 4c99699..0a19bf0 100644 --- 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 @@ -69,7 +69,6 @@ public class WxCombinePayService extends WxPayService { * * @param parameters 订单参数 * @param order 订单信息 - * @return 订单参数 */ public void initNotifyUrl(Map parameters, Order order) { OrderParaStructure.loadParameters(parameters, WxConst.NOTIFY_URL, payConfigStorage.getNotifyUrl()); 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 index a0bba61..b949b8b 100644 --- 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 @@ -52,7 +52,6 @@ public class WxParameterStructure { * * @param parameters 订单参数 * @param order 支付订单 - * @return 订单参数 */ public void loadSettleInfo(Map parameters, PayOrder order) { Object profitSharing = order.getAttr("profit_sharing"); @@ -74,7 +73,6 @@ public class WxParameterStructure { * * @param parameters 订单参数 * @param order 订单信息 - * @return 订单参数 */ public void initNotifyUrl(Map parameters, Order order) { OrderParaStructure.loadParameters(parameters, WxConst.NOTIFY_URL, payConfigStorage.getNotifyUrl()); @@ -95,7 +93,6 @@ public class WxParameterStructure { * 初始化商户相关信息 * * @param parameters 参数信息 - * @return 参数信息 */ public void initPartner(Map parameters) { if (null == 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 index 8bf20e9..58bfe74 100644 --- 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 @@ -53,6 +53,7 @@ public interface WxPayAssistService { * 构建请求实体 * 这里也做签名处理 * + * @param url url * @param body 请求内容体 * @param method 请求方法 * @return 请求实体 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 index cef6267..dfe9c84 100644 --- 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 @@ -8,8 +8,10 @@ import com.egzosn.pay.wx.v3.utils.WxConst; /** * 微信合单关闭订单 * @author Egan - * @email egan@egzosn.com - * @date 2021/10/6 + *
+ * email egan@egzosn.com
+ * date 2021/10/6
+ * 
*/ public class CombineCloseOrder extends CloseOrder { 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 index f02ffc4..e058663 100644 --- 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 @@ -113,7 +113,7 @@ public class WxPayMessage extends PayMessage { * 退款状态,枚举值: * SUCCESS:退款成功 * CLOSE:退款关闭 - * ABNORMAL:退款异常,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往【商户平台—>交易中心】,手动处理此笔退款 + * ABNORMAL: 退款异常,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往【商户平台—交易中心】,手动处理此笔退款 * 示例值:SUCCESS */ @JSONField(name = "refund_status") 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 index 697355e..2d6e6e6 100644 --- 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 @@ -7,8 +7,10 @@ import com.alibaba.fastjson.annotation.JSONField; /** * 优惠功能 * - * @author felord.cn - * @since 1.0.0.RELEASE + *
+ * email egan@egzosn.com
+ * date 2021/10/6
+ * 
*/ public class PromotionDetail { 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 index 1807787..2c4cedb 100644 --- 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 @@ -67,9 +67,10 @@ public final class AntCertificationUtil { /** * 装载平台证书 - * @param serialNo 证书序列 + * + * @param serialNo 证书序列 * @param certificateStream 证书流 - * @return + * @return 平台证书 */ public static Certificate loadCertificate(String serialNo, InputStream certificateStream) { try { @@ -127,10 +128,12 @@ public final class AntCertificationUtil { /** * 解密响应体. * - * @param associatedData 相关数据 - * @param nonce 随机串 - * @param cipherText 需要解密的文本 - * @return the string + * @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) { @@ -153,8 +156,7 @@ public final class AntCertificationUtil { * * @param message the message * @param certificate the certificate - * @return encrypt message - * @since 1.0.6.RELEASE + * @return 加密后的内容 */ public static String encryptToString(String message, Certificate certificate) { try { diff --git a/pay-java-yiji/pom.xml b/pay-java-yiji/pom.xml index 2458fcf..c8e4428 100644 --- a/pay-java-yiji/pom.xml +++ b/pay-java-yiji/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.2-SNAPSHOT + 2.14.2 4.0.0 diff --git a/pom.xml b/pom.xml index a1d07ce..9c6be50 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.egzosn pay-java-parent pom - 2.14.2-SNAPSHOT + 2.14.2 Pay Java - Parent Pay Java Parent @@ -70,7 +70,7 @@ - 2.14.2-SNAPSHOT + 2.14.2 4.5.4 1.2.17 1.2.73 @@ -110,9 +110,9 @@ - log4j - log4j - ${log4j.version} + org.slf4j + slf4j-api + 1.7.30 -- Gitee From 4d00aae8a914fe97dfbb13965ccdabf9e2096457 Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 6 Oct 2021 17:51:57 +0800 Subject: [PATCH 087/165] =?UTF-8?q?2.14.3=E5=BC=80=E5=A7=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pay-java-ali/pom.xml | 2 +- .../main/java/com/egzosn/pay/ali/api/AliPayService.java | 7 +++++++ pay-java-baidu/pom.xml | 2 +- pay-java-common/pom.xml | 2 +- pay-java-demo/pom.xml | 2 +- pay-java-fuiou/pom.xml | 2 +- pay-java-payoneer/pom.xml | 2 +- pay-java-paypal/pom.xml | 2 +- pay-java-union/pom.xml | 2 +- pay-java-web-support/pom.xml | 2 +- pay-java-wx-youdian/pom.xml | 2 +- pay-java-wx/pom.xml | 2 +- pay-java-yiji/pom.xml | 2 +- pom.xml | 4 ++-- 14 files changed, 21 insertions(+), 14 deletions(-) diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index 7d743ce..aa08115 100644 --- a/pay-java-ali/pom.xml +++ b/pay-java-ali/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.2 + 2.14.3-SNAPSHOT 4.0.0 pay-java-ali 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 1b9d724..3112792 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 @@ -253,7 +253,12 @@ public class AliPayService extends BasePayService { break; case WAP: bizContent.put(PASSBACK_PARAMS, order.getAddition()); + //产品码。 + //商家和支付宝签约的产品码。 枚举值(点击查看签约情况): + //QUICK_WAP_WAY:无线快捷支付产品。 + //默认值为QUICK_WAP_PAY。 bizContent.put(PRODUCT_CODE, "QUICK_WAP_PAY"); + OrderParaStructure.loadParameters(bizContent, PRODUCT_CODE, order); setReturnUrl(orderInfo, order); break; case APP: @@ -274,6 +279,8 @@ public class AliPayService extends BasePayService { break; } + + setExpirationTime(bizContent, order); bizContent.putAll(order.getAttrs()); diff --git a/pay-java-baidu/pom.xml b/pay-java-baidu/pom.xml index 7fbb57d..e9f0b16 100644 --- a/pay-java-baidu/pom.xml +++ b/pay-java-baidu/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.2 + 2.14.3-SNAPSHOT 4.0.0 pay-java-baidu diff --git a/pay-java-common/pom.xml b/pay-java-common/pom.xml index f19fd5b..e20e284 100644 --- a/pay-java-common/pom.xml +++ b/pay-java-common/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.2 + 2.14.3-SNAPSHOT 4.0.0 jar diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index 125b476..30f7350 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.2 + 2.14.3-SNAPSHOT 4.0.0 war diff --git a/pay-java-fuiou/pom.xml b/pay-java-fuiou/pom.xml index 8685c30..9d1672c 100644 --- a/pay-java-fuiou/pom.xml +++ b/pay-java-fuiou/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.2 + 2.14.3-SNAPSHOT 4.0.0 pay-java-fuiou diff --git a/pay-java-payoneer/pom.xml b/pay-java-payoneer/pom.xml index c920ab0..45fdcfb 100644 --- a/pay-java-payoneer/pom.xml +++ b/pay-java-payoneer/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.2 + 2.14.3-SNAPSHOT 4.0.0 pay-java-payoneer diff --git a/pay-java-paypal/pom.xml b/pay-java-paypal/pom.xml index 4e0f8bd..c4a49d9 100644 --- a/pay-java-paypal/pom.xml +++ b/pay-java-paypal/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.2 + 2.14.3-SNAPSHOT 4.0.0 diff --git a/pay-java-union/pom.xml b/pay-java-union/pom.xml index 2a00ed4..64b8020 100644 --- a/pay-java-union/pom.xml +++ b/pay-java-union/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.2 + 2.14.3-SNAPSHOT 4.0.0 diff --git a/pay-java-web-support/pom.xml b/pay-java-web-support/pom.xml index 539f476..36f8dbb 100644 --- a/pay-java-web-support/pom.xml +++ b/pay-java-web-support/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.2 + 2.14.3-SNAPSHOT 4.0.0 jar diff --git a/pay-java-wx-youdian/pom.xml b/pay-java-wx-youdian/pom.xml index 8f3827a..ca68448 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.14.2 + 2.14.3-SNAPSHOT 4.0.0 pay-java-wx-youdian diff --git a/pay-java-wx/pom.xml b/pay-java-wx/pom.xml index d9ad6ff..52dd4cf 100644 --- a/pay-java-wx/pom.xml +++ b/pay-java-wx/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.2 + 2.14.3-SNAPSHOT 4.0.0 pay-java-wx diff --git a/pay-java-yiji/pom.xml b/pay-java-yiji/pom.xml index c8e4428..b736fa5 100644 --- a/pay-java-yiji/pom.xml +++ b/pay-java-yiji/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.2 + 2.14.3-SNAPSHOT 4.0.0 diff --git a/pom.xml b/pom.xml index 9c6be50..2189a4a 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.egzosn pay-java-parent pom - 2.14.2 + 2.14.3-SNAPSHOT Pay Java - Parent Pay Java Parent @@ -70,7 +70,7 @@ - 2.14.2 + 2.14.3-SNAPSHOT 4.5.4 1.2.17 1.2.73 -- Gitee From 9db3f5a76d3a1bae3cd39005971be2d0e281ac7a Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 6 Oct 2021 23:29:54 +0800 Subject: [PATCH 088/165] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E8=BE=85=E5=8A=A9?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E5=AE=9E=E4=BD=93=E5=B9=B6=E5=AF=B9=E5=85=B6?= =?UTF-8?q?=E6=89=80=E6=9C=89=E6=94=AF=E4=BB=98=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/ali/api/AliPayService.java | 23 +++- .../egzosn/pay/baidu/api/BaiduPayService.java | 17 ++- .../egzosn/pay/common/api/BasePayService.java | 28 +--- .../com/egzosn/pay/common/api/PayService.java | 42 ++++-- .../egzosn/pay/common/bean/AssistOrder.java | 126 ++++++++++++++++++ .../egzosn/pay/common/bean/CloseOrder.java | 84 +----------- .../com/egzosn/pay/common/bean/PayOrder.java | 121 ++--------------- .../egzosn/pay/common/bean/RefundOrder.java | 66 +-------- .../controller/WxV3CombinePayController.java | 1 - .../demo/controller/WxV3PayController.java | 1 - .../egzosn/pay/fuiou/api/FuiouPayService.java | 43 ++++-- .../pay/payoneer/api/PayoneerPayService.java | 20 ++- .../pay/paypal/api/PayPalPayService.java | 26 ++-- .../pay/paypal/v2/api/PayPalPayService.java | 51 +++---- .../egzosn/pay/union/api/UnionPayService.java | 27 +++- .../wx/youdian/api/WxYouDianPayService.java | 35 +++-- .../java/com/egzosn/pay/wx/api/WxConst.java | 2 +- .../com/egzosn/pay/wx/api/WxPayService.java | 35 +++-- .../egzosn/pay/yiji/api/YiJiPayService.java | 18 ++- 19 files changed, 372 insertions(+), 394 deletions(-) create mode 100644 pay-java-common/src/main/java/com/egzosn/pay/common/bean/AssistOrder.java 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 3112792..126b386 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 @@ -30,8 +30,9 @@ 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.AssistOrder; import com.egzosn.pay.common.bean.BillType; -import com.egzosn.pay.common.bean.CloseOrder; + import com.egzosn.pay.common.bean.MethodType; import com.egzosn.pay.common.bean.NoticeParams; import com.egzosn.pay.common.bean.Order; @@ -256,7 +257,7 @@ public class AliPayService extends BasePayService { //产品码。 //商家和支付宝签约的产品码。 枚举值(点击查看签约情况): //QUICK_WAP_WAY:无线快捷支付产品。 - //默认值为QUICK_WAP_PAY。 + //默认值为QUICK_WAP_PAY。 bizContent.put(PRODUCT_CODE, "QUICK_WAP_PAY"); OrderParaStructure.loadParameters(bizContent, PRODUCT_CODE, order); setReturnUrl(orderInfo, order); @@ -470,6 +471,18 @@ public class AliPayService extends BasePayService { } + /** + * 交易查询接口 + * + * @param assistOrder 查询条件 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(AssistOrder assistOrder) { + return secondaryInterface(assistOrder.getTradeNo(), assistOrder.getOutTradeNo(), AliTransactionType.QUERY); + } + + /** * 交易关闭接口 @@ -486,12 +499,12 @@ public class AliPayService extends BasePayService { /** * 交易关闭接口 * - * @param closeOrder 关闭订单 + * @param assistOrder 关闭订单 * @return 返回支付方交易关闭后的结果 */ @Override - public Map close(CloseOrder closeOrder){ - return secondaryInterface(closeOrder.getTradeNo(), closeOrder.getOutTradeNo(), AliTransactionType.CLOSE); + public Map close(AssistOrder assistOrder){ + return secondaryInterface(assistOrder.getTradeNo(), assistOrder.getOutTradeNo(), AliTransactionType.CLOSE); } /** 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 3b7f3af..e529eba 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 @@ -23,9 +23,9 @@ import com.egzosn.pay.baidu.bean.BaiduTransactionType; import com.egzosn.pay.baidu.bean.type.AuditStatus; import com.egzosn.pay.baidu.util.Asserts; import com.egzosn.pay.common.api.BasePayService; +import com.egzosn.pay.common.bean.AssistOrder; import com.egzosn.pay.common.bean.BaseRefundResult; import com.egzosn.pay.common.bean.BillType; -import com.egzosn.pay.common.bean.CloseOrder; import com.egzosn.pay.common.bean.CurType; import com.egzosn.pay.common.bean.MethodType; import com.egzosn.pay.common.bean.NoticeParams; @@ -428,6 +428,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); + } + /** * 百度不支持该操作 * @@ -442,11 +453,11 @@ public class BaiduPayService extends BasePayService { /** * 交易关闭接口 * - * @param closeOrder 关闭订单 + * @param assistOrder 关闭订单 * @return 返回支付方交易关闭后的结果 */ @Override - public Map close(CloseOrder closeOrder){ + public Map close(AssistOrder assistOrder){ throw new UnsupportedOperationException("不支持该操作"); } 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 4e6d1b5..bd8b5bc 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 @@ -257,6 +257,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)); @@ -431,7 +432,6 @@ public abstract class BasePayService implements Pay return payBack(new DefaultNoticeRequest(parameterMap, is)); } - /** * 回调处理 * @@ -448,31 +448,7 @@ public abstract class BasePayService implements Pay return getPayOutMessage("fail", "失败"); } PayMessage payMessage = this.createMessage(noticeParams.getBody()); - Map context = new HashMap(); - for (PayMessageInterceptor interceptor : interceptors) { - if (!interceptor.intercept(payMessage, context, this)) { - return successPayOutMessage(payMessage); - } - } - return getPayMessageHandler().handle(payMessage, context, this); - } - - /** - * 使用转换过的参数进行回调处理 - * - * @param data 转化后的参数Map - * @return 获得回调响应信息 - */ - @Override - public PayOutMessage payBack(Map data) { - if (LOG.isDebugEnabled()) { - LOG.debug("回调响应:" + JSON.toJSONString(data)); - } - if (!verify(data)) { - return getPayOutMessage("fail", "失败"); - } - PayMessage payMessage = this.createMessage(data); - Map context = new HashMap(); + Map context = new HashMap<>(); for (PayMessageInterceptor interceptor : interceptors) { if (!interceptor.intercept(payMessage, context, this)) { return successPayOutMessage(payMessage); 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 6ee633d..cc1f708 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 @@ -5,8 +5,9 @@ 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.CloseOrder; + import com.egzosn.pay.common.bean.MethodType; import com.egzosn.pay.common.bean.NoticeParams; import com.egzosn.pay.common.bean.NoticeRequest; @@ -67,6 +68,7 @@ public interface PayService { * 已过时方法,详情{@link #verify(NoticeParams)} * @param params 回调回来的参数集 * @return 签名校验 true通过 + * @see #verify(NoticeParams) */ @Deprecated boolean verify(Map params); @@ -124,6 +126,7 @@ public interface PayService { * @param parameterMap 请求参数 * @param is 请求流 * @return 获得回调的请求参数 + * @see #getNoticeParams(NoticeRequest) */ @Deprecated Map getParameter2Map(Map parameterMap, InputStream is); @@ -199,7 +202,9 @@ public interface PayService { * @param tradeNo 支付平台订单号 * @param outTradeNo 商户单号 * @return 返回查询回来的结果集,支付方原值返回 + * @see #query(AssistOrder) */ + @Deprecated Map query(String tradeNo, String outTradeNo); /** @@ -211,25 +216,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(CloseOrder) + * @see #close(AssistOrder) */ @Deprecated Map close(String tradeNo, String outTradeNo); /** * 交易关闭接口 * - * @param closeOrder 关闭订单 + * @param assistOrder 关闭订单 * @return 返回支付方交易关闭后的结果 */ - Map close(CloseOrder closeOrder); + Map close(AssistOrder assistOrder); /** @@ -241,8 +259,10 @@ public interface PayService { * @param 返回类型 * @return 返回支付方交易关闭后的结果 */ + @Deprecated T close(String tradeNo, String outTradeNo, Callback callback); + /** * 交易交易撤销 * @@ -261,6 +281,7 @@ public interface PayService { * @param 返回类型 * @return 返回支付方交易撤销后的结果 */ + @Deprecated T cancel(String tradeNo, String outTradeNo, Callback callback); @@ -280,6 +301,7 @@ public interface PayService { * @param 返回类型 * @return 返回支付方申请退款后的结果 */ + @Deprecated T refund(RefundOrder refundOrder, Callback callback); @@ -299,6 +321,7 @@ public interface PayService { * @param 返回类型 * @return 返回支付方查询退款后的结果 */ + @Deprecated T refundquery(RefundOrder refundOrder, Callback callback); /** @@ -337,6 +360,7 @@ public interface PayService { * @param 返回类型 * @return 对应的转账结果 */ + @Deprecated T transfer(TransferOrder order, Callback callback); @@ -358,6 +382,7 @@ public interface PayService { * @param 返回类型 * @return 对应的转账订单 */ + @Deprecated T transferQuery(String outNo, String tradeNo, Callback callback); /** @@ -377,13 +402,7 @@ public interface PayService { */ PayOutMessage payBack(NoticeRequest request); - /** - * 使用转换过的参数进行回调处理 - * - * @param data 转化后的参数Map - * @return 获得回调响应信息 - */ - PayOutMessage payBack(Map data); + /** * 设置支付消息处理器,这里用于处理具体的支付业务 @@ -431,6 +450,7 @@ public interface PayService { * @param 预订单类型 * @return 处理后订单信息 */ + @Deprecated Map preOrderHandler(Map orderInfo, O payOrder); } 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 0000000..67f95f5 --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/AssistOrder.java @@ -0,0 +1,126 @@ +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; + /** + * 订单附加信息,可用于预设未提供的参数,这里会覆盖以上所有的订单信息, + */ + @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); + } + + +} 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 index 4d50e86..9ea3277 100644 --- 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 @@ -1,7 +1,5 @@ package com.egzosn.pay.common.bean; -import java.util.Map; - /** * 关闭订单 * @author Egan @@ -10,85 +8,7 @@ import java.util.Map; * date 2021/10/6 *
*/ -public class CloseOrder implements Order { - - /** - * 支付平台订单号,交易号 - */ - private String tradeNo; - /** - * 商户单号 - */ - private String outTradeNo; - - - /** - * 订单附加信息,可用于预设未提供的参数,这里会覆盖以上所有的订单信息, - */ - private Map attr; - - public CloseOrder() { - } - - public CloseOrder(String outTradeNo) { - this.outTradeNo = outTradeNo; - } - - - - /** - * 添加订单信息 - * - * @param key key - * @param value 值 - */ - @Override - public void addAttr(String key, Object value) { - attr.put(key, value); - } - - /** - * 获取属性 这里可用做覆盖已设置的信息属性,订单信息在签名前进行覆盖。 - * - * @return 属性 - */ - @Override - public Map getAttrs() { - return attr; - } - - /** - * 获取属性 这里可用做覆盖已设置的订单信息属性,订单信息在签名前进行覆盖。 - * - * @param key 属性名 - * @return 属性 - */ - @Override - public Object getAttr(String key) { - return attr.get(key); - } - - 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 Map getAttr() { - return attr; - } +@Deprecated +public class CloseOrder extends AssistOrder { - public void setAttr(Map attr) { - this.attr = attr; - } } 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 1db5c6e..1a88e0f 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; + /** * 银行卡类型 */ @@ -63,12 +54,13 @@ public class PayOrder implements Order { * 微信专用,,,, * WAP支付链接 */ + @Deprecated private String wapUrl; /** * 微信专用,,,, * WAP支付网页名称 */ - + @Deprecated private String wapName; /** * 用户唯一标识 @@ -76,10 +68,7 @@ public class PayOrder implements Order { * 支付宝 buyer_id */ private String openid; - /** - * 交易类型 - */ - private TransactionType transactionType; + /** * 支付币种 */ @@ -89,11 +78,6 @@ public class PayOrder implements Order { */ private Date expirationTime; - /** - * 订单附加信息,可用于预设未提供的参数,这里会覆盖以上所有的订单信息, - */ - private volatile Map attr; - public PayOrder() { } @@ -107,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); } @@ -152,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; @@ -260,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/RefundOrder.java b/pay-java-common/src/main/java/com/egzosn/pay/common/bean/RefundOrder.java index 30e5959..f3cb108 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; /** * 退款金额 */ @@ -54,10 +44,6 @@ public class RefundOrder implements Order { */ private String userId; - /** - * 订单附加信息,可用于预设未提供的参数,这里会覆盖以上所有的订单信息, - */ - private Map attr; public String getRefundNo() { return refundNo; @@ -67,22 +53,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 +106,23 @@ 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); - } - - - /** - * 添加订单信息 - * - * @param key key - * @param value 值 - */ - @Override - public void addAttr(String key, Object value) { - getAttrs().put(key, value); - } } 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 index d9fcbc4..6431bb2 100644 --- 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 @@ -30,7 +30,6 @@ 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; -import com.egzosn.pay.wx.v3.bean.response.WxRefundResult; /** * 微信V3合单发起支付入口 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 index fbc4f97..f987656 100644 --- 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 @@ -25,7 +25,6 @@ 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.response.WxRefundResult; /** * 微信V3发起支付入口 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 94f6dc8..d46bc98 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,6 +1,5 @@ package com.egzosn.pay.fuiou.api; -import java.io.InputStream; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -9,10 +8,11 @@ import java.util.Map; 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.CloseOrder; 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; @@ -125,6 +125,7 @@ public class FuiouPayService extends BasePayService { return verify(new NoticeParams(params)); } + /** * 回调校验 * @@ -143,10 +144,11 @@ public class FuiouPayService extends BasePayService { return (signVerify(params, (String) params.remove("md5")) && verifySource((String) params.get("order_id"))); } catch (PayErrorException e) { - LOG.error("", e); + LOG.error("", e); } return false; } + /** * 回调签名校验 * @@ -254,15 +256,16 @@ 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) { + 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"); @@ -273,7 +276,7 @@ public class FuiouPayService extends BasePayService { conversion(parameterMap, params, "resv1"); conversion(parameterMap, params, "fy_ssn"); conversion(parameterMap, params, "md5"); - return params; + return new NoticeParams(params); } /** @@ -393,12 +396,23 @@ 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("order_id", assistOrder.getOutTradeNo()); params.put("md5", createSign(SignTextUtils.parameters2Md5Str(params, "|"), payConfigStorage.getInputCharset())); - JSONObject resultJson = getHttpRequestTemplate().postForObject(getReqUrl() + URL_FuiouSmpAQueryGate + "?" + UriVariables.getMapToParameters(params), null, JSONObject.class); - return resultJson; + return getHttpRequestTemplate().postForObject(getReqUrl() + URL_FuiouSmpAQueryGate + "?" + UriVariables.getMapToParameters(params), null, JSONObject.class); } @@ -411,16 +425,17 @@ public class FuiouPayService extends BasePayService { */ @Override public Map close(String tradeNo, String outTradeNo) { - return Collections.EMPTY_MAP; + throw new UnsupportedOperationException("不支持该操作"); } + /** * 交易关闭接口 * - * @param closeOrder 关闭订单 + * @param assistOrder 关闭订单 * @return 返回支付方交易关闭后的结果 */ @Override - public Map close(CloseOrder closeOrder){ + public Map close(AssistOrder assistOrder) { throw new UnsupportedOperationException("不支持该操作"); } 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 d1572b3..965b67e 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 @@ -15,9 +15,10 @@ 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.AssistOrder; import com.egzosn.pay.common.bean.BaseRefundResult; import com.egzosn.pay.common.bean.BillType; -import com.egzosn.pay.common.bean.CloseOrder; + import com.egzosn.pay.common.bean.CurType; import com.egzosn.pay.common.bean.DefaultCurType; import com.egzosn.pay.common.bean.MethodType; @@ -294,6 +295,17 @@ 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); + } + /** * 交易关闭接口 @@ -309,12 +321,12 @@ public class PayoneerPayService extends BasePayService im /** * 交易关闭接口 * - * @param closeOrder 关闭订单 + * @param assistOrder 关闭订单 * @return 返回支付方交易关闭后的结果 */ @Override - public Map close(CloseOrder closeOrder){ - return secondaryInterface(closeOrder.getTradeNo(), closeOrder.getOutTradeNo(), PayoneerTransactionType.CHARGE_CANCEL); + public Map close(AssistOrder assistOrder){ + return secondaryInterface(assistOrder.getTradeNo(), assistOrder.getOutTradeNo(), PayoneerTransactionType.CHARGE_CANCEL); } /** * 交易交易撤销 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 6d44c1c..9d26cf5 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 @@ -19,9 +19,9 @@ 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.AssistOrder; import com.egzosn.pay.common.bean.BaseRefundResult; import com.egzosn.pay.common.bean.BillType; -import com.egzosn.pay.common.bean.CloseOrder; import com.egzosn.pay.common.bean.CurType; import com.egzosn.pay.common.bean.DefaultCurType; import com.egzosn.pay.common.bean.MethodType; @@ -263,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 @@ -275,13 +286,14 @@ public class PayPalPayService extends BasePayService { /** * 交易关闭接口 * - * @param closeOrder 关闭订单 + * @param assistOrder 关闭订单 * @return 返回支付方交易关闭后的结果 */ @Override - public Map close(CloseOrder closeOrder){ + public Map close(AssistOrder assistOrder) { throw new UnsupportedOperationException("不支持该操作"); } + /** * 申请退款接口 * @@ -363,12 +375,10 @@ 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, BillType billType) { return Collections.emptyMap(); 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 index c932770..8cab6f9 100644 --- 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 @@ -21,8 +21,9 @@ 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.CloseOrder; + import com.egzosn.pay.common.bean.CurType; import com.egzosn.pay.common.bean.DefaultCurType; import com.egzosn.pay.common.bean.MethodType; @@ -351,6 +352,17 @@ public class PayPalPayService extends BasePayService implem 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; @@ -358,11 +370,11 @@ public class PayPalPayService extends BasePayService implem /** * 交易关闭接口 * - * @param closeOrder 关闭订单 + * @param assistOrder 关闭订单 * @return 返回支付方交易关闭后的结果 */ @Override - public Map close(CloseOrder closeOrder){ + public Map close(AssistOrder assistOrder){ throw new UnsupportedOperationException("不支持该操作"); } /** @@ -512,38 +524,7 @@ public class PayPalPayService extends BasePayService implem } - /** - * 将请求参数或者请求流转化为 Map - * - * @param parameterMap 请求参数 - * @param is 请求流 - * @return 获得回调的请求参数 - */ - @Override - public Map getParameter2Map(Map parameterMap, InputStream is) { - - Map params = new LinkedHashMap<>(); - for (Map.Entry entry : parameterMap.entrySet()) { - String name = entry.getKey(); - String[] values = entry.getValue(); - String valueStr = ""; - for (int i = 0, len = values.length; i < len; i++) { - valueStr += (i == len - 1) ? values[i] : values[i] + ","; - } - 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); - } - } - params.put(name, valueStr); - } - return params; - } + } 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 7576302..d236603 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 @@ -26,8 +26,9 @@ import java.util.TreeMap; import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.common.api.BasePayService; +import com.egzosn.pay.common.bean.AssistOrder; import com.egzosn.pay.common.bean.BillType; -import com.egzosn.pay.common.bean.CloseOrder; + import com.egzosn.pay.common.bean.MethodType; import com.egzosn.pay.common.bean.NoticeParams; import com.egzosn.pay.common.bean.PayMessage; @@ -224,6 +225,7 @@ public class UnionPayService extends BasePayService { } return this.signVerify(result, (String) result.get(SDKConstants.param_signature)); } + /** * 签名校验 * @@ -581,25 +583,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())); - } @@ -657,14 +669,15 @@ public class UnionPayService extends BasePayService { public Map close(String tradeNo, String outTradeNo) { return Collections.emptyMap(); } + /** * 交易关闭接口 * - * @param closeOrder 关闭订单 + * @param assistOrder 关闭订单 * @return 返回支付方交易关闭后的结果 */ @Override - public Map close(CloseOrder closeOrder){ + public Map close(AssistOrder assistOrder) { return Collections.emptyMap(); } 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 5eec170..516696c 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 @@ -12,12 +12,14 @@ 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.AssistOrder; import com.egzosn.pay.common.bean.BaseRefundResult; import com.egzosn.pay.common.bean.BillType; -import com.egzosn.pay.common.bean.CloseOrder; + 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.PayMessage; import com.egzosn.pay.common.bean.PayOrder; import com.egzosn.pay.common.bean.PayOutMessage; @@ -298,15 +300,17 @@ public class WxYouDianPayService extends BasePayService getParameter2Map(Map parameterMap, InputStream is) { + public NoticeParams getNoticeParams(NoticeRequest request) { + final Map parameterMap = request.getParameterMap(); Map params = new TreeMap(); for (Iterator iter = parameterMap.keySet().iterator(); iter.hasNext(); ) { String name = (String) iter.next(); @@ -318,7 +322,7 @@ public class WxYouDianPayService extends BasePayService query(String tradeNo, String outTradeNo) { + 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()); - if (StringUtils.isEmpty(tradeNo)) { - data.put("order_sn", outTradeNo); + if (StringUtils.isEmpty(assistOrder.getTradeNo())) { + data.put("order_sn", assistOrder.getOutTradeNo()); } else { - data.put("order_sn", tradeNo); + data.put("order_sn", assistOrder.getTradeNo()); } String sign = createSign(SignTextUtils.parameterText(data, "") + apbNonce, payConfigStorage.getInputCharset()); String queryParam = SignTextUtils.parameterText(data) + "&apb_nonce=" + apbNonce + "&sign=" + sign; @@ -426,11 +441,11 @@ public class WxYouDianPayService extends BasePayService close(CloseOrder closeOrder){ + public Map close(AssistOrder assistOrder){ return Collections.emptyMap(); } 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 52ea2e9..f41eab5 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 @@ -33,6 +33,6 @@ public interface WxConst { String NONCE_STR = "nonce_str"; String OUT_TRADE_NO = "out_trade_no"; String GZIP = "GZIP"; - + String BILL_DATE = "bill_date"; } 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 570feb1..2bee0d8 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 @@ -38,10 +38,12 @@ 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.AssistOrder; import com.egzosn.pay.common.bean.BillType; -import com.egzosn.pay.common.bean.CloseOrder; + 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; @@ -389,25 +391,25 @@ public class WxPayService extends BasePayService implements return signType.createSign(content + "&key=" + (signType == SignUtils.MD5 ? "" : keyPrivate), keyPrivate, characterEncoding).toUpperCase(); } + /** * 将请求参数或者请求流转化为 Map * - * @param parameterMap 请求参数 - * @param is 请求流 + * @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); + return new NoticeParams(XML.inputStream2Map(request.getInputStream(), map)); } catch (IOException e) { throw new PayErrorException(new PayException("IOException", e.getMessage())); } } - /** * 获取输出消息,用户返回给支付端 * @@ -502,6 +504,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); + } + /** * 交易关闭接口 @@ -518,12 +531,12 @@ public class WxPayService extends BasePayService implements /** * 交易关闭接口 * - * @param closeOrder 关闭订单 + * @param assistOrder 关闭订单 * @return 返回支付方交易关闭后的结果 */ @Override - public Map close(CloseOrder closeOrder){ - return secondaryInterface(closeOrder.getTradeNo(), closeOrder.getOutTradeNo(), WxTransactionType.CLOSE); + public Map close(AssistOrder assistOrder){ + return secondaryInterface(assistOrder.getTradeNo(), assistOrder.getOutTradeNo(), WxTransactionType.CLOSE); } @@ -626,7 +639,7 @@ public class WxPayService extends BasePayService implements Map parameters = getPublicParameters(); parameters.put("bill_type", billType); //目前只支持日账单 - parameters.put("bill_date", DateUtils.formatDate(billDate, DateUtils.YYYYMMDD)); + parameters.put(WxConst.BILL_DATE, DateUtils.formatDate(billDate, DateUtils.YYYYMMDD)); String fileType = billType.getFileType(); OrderParaStructure.loadParameters(parameters, "tar_type", fileType); //设置签名 @@ -753,7 +766,7 @@ public class WxPayService extends BasePayService implements Map parameters = getPublicParameters(); parameters.put("bill_type", billType); //目前只支持日账单 - parameters.put("bill_date", DateUtils.formatDate(billDate, DateUtils.YYYYMMDD)); + parameters.put(WxConst.BILL_DATE, DateUtils.formatDate(billDate, DateUtils.YYYYMMDD)); if (tarType) { parameters.put("tar_type", "GZIP"); } 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 adae363..5f1f973 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 @@ -8,9 +8,10 @@ import java.util.TreeMap; import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.common.api.BasePayService; +import com.egzosn.pay.common.bean.AssistOrder; import com.egzosn.pay.common.bean.BaseRefundResult; import com.egzosn.pay.common.bean.BillType; -import com.egzosn.pay.common.bean.CloseOrder; + import com.egzosn.pay.common.bean.CurType; import com.egzosn.pay.common.bean.DefaultCurType; import com.egzosn.pay.common.bean.MethodType; @@ -287,6 +288,17 @@ public class YiJiPayService extends BasePayService { return Collections.emptyMap(); } + /** + * 交易查询接口 + * + * @param assistOrder 查询条件 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(AssistOrder assistOrder) { + return Collections.emptyMap(); + } + /** * 交易关闭接口 @@ -302,11 +314,11 @@ public class YiJiPayService extends BasePayService { /** * 交易关闭接口 * - * @param closeOrder 关闭订单 + * @param assistOrder 关闭订单 * @return 返回支付方交易关闭后的结果 */ @Override - public Map close(CloseOrder closeOrder){ + public Map close(AssistOrder assistOrder){ return Collections.emptyMap(); } -- Gitee From a888fb58b1d281ffc1462f0a5b225507dcbabf21 Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 6 Oct 2021 23:31:37 +0800 Subject: [PATCH 089/165] =?UTF-8?q?=E5=88=86=E8=B4=A6=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pay/wx/v3/api/ProfitSharingService.java | 36 ++ .../pay/wx/v3/api/WxCombinePayService.java | 27 +- .../pay/wx/v3/api/WxParameterStructure.java | 4 +- .../egzosn/pay/wx/v3/api/WxPayService.java | 58 +- .../pay/wx/v3/api/WxProfitSharingService.java | 499 ++++++++++++++++++ .../bean/WxProfitSharingTransactionType.java | 79 +++ .../pay/wx/v3/bean/WxTransactionType.java | 15 +- .../wx/v3/bean/combine/CombineCloseOrder.java | 5 +- .../pay/wx/v3/bean/response/WxPayMessage.java | 2 +- .../wx/v3/bean/response/order/TradeState.java | 9 + .../bean/sharing/ProfitSharingBillType.java | 73 +++ .../v3/bean/sharing/ProfitSharingOrder.java | 106 ++++ .../bean/sharing/ProfitSharingPayMessage.java | 119 +++++ .../sharing/ProfitSharingReturnOrder.java | 29 + .../pay/wx/v3/bean/sharing/Receiver.java | 84 +++ .../pay/wx/v3/bean/sharing/ReceiverType.java | 26 + .../wx/v3/bean/sharing/ReceiversOrder.java | 120 +++++ .../pay/wx/v3/bean/sharing/RelationType.java | 53 ++ .../sharing/WxProfitSharingReturnResult.java | 304 +++++++++++ .../com/egzosn/pay/wx/v3/utils/WxConst.java | 13 + 20 files changed, 1624 insertions(+), 37 deletions(-) create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/ProfitSharingService.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxProfitSharingService.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxProfitSharingTransactionType.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ProfitSharingBillType.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ProfitSharingOrder.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ProfitSharingPayMessage.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ProfitSharingReturnOrder.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/Receiver.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ReceiverType.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/ReceiversOrder.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/RelationType.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/sharing/WxProfitSharingReturnResult.java 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 0000000..bdae1f3 --- /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 index 0a19bf0..e009e7b 100644 --- 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 @@ -6,7 +6,7 @@ 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.CloseOrder; +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; @@ -26,8 +26,8 @@ import com.egzosn.pay.wx.v3.utils.WxConst; * * @author egan *
- * email egzosn@gmail.com
- * date 2016-5-18 14:09:01
+ * email egan@egzosn.com
+ * date 2021/10/6
  * 
*/ public class WxCombinePayService extends WxPayService { @@ -114,9 +114,18 @@ public class WxCombinePayService extends WxPayService { */ @Override public Map query(String transactionId, String outTradeNo) { - return getAssistService().doExecute("", WxTransactionType.COMBINE_TRANSACTION, outTradeNo); + return query(new AssistOrder(outTradeNo)); + } + /** + * 交易查询接口 + * + * @param assistOrder 查询条件 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @Override + public Map query(AssistOrder assistOrder) { + return getAssistService().doExecute("", WxTransactionType.COMBINE_TRANSACTION, assistOrder.getOutTradeNo()); } - /** * 交易关闭接口 @@ -133,16 +142,16 @@ public class WxCombinePayService extends WxPayService { /** * 交易关闭接口 * - * @param closeOrder 关闭订单 + * @param assistOrder 关闭订单 * @return 返回支付方交易关闭后的结果 */ @Override - public Map close(CloseOrder closeOrder) { + public Map close(AssistOrder assistOrder) { Map parameters = new MapGen(WxConst.COMBINE_APPID, payConfigStorage.getAppId()) - .keyValue(WxConst.SUB_ORDERS, closeOrder.getAttr(WxConst.SUB_ORDERS)) + .keyValue(WxConst.SUB_ORDERS, assistOrder.getAttr(WxConst.SUB_ORDERS)) .getAttr(); String requestBody = JSON.toJSONString(parameters, SerializerFeature.WriteMapNullValue); - return getAssistService().doExecute(requestBody, WxTransactionType.COMBINE_CLOSE, closeOrder.getOutTradeNo()); + return getAssistService().doExecute(requestBody, WxTransactionType.COMBINE_CLOSE, assistOrder.getOutTradeNo()); } /** 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 index b949b8b..3f3596b 100644 --- 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 @@ -3,8 +3,6 @@ package com.egzosn.pay.wx.v3.api; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -import java.util.TreeMap; - import com.egzosn.pay.common.bean.Order; import com.egzosn.pay.common.bean.OrderParaStructure; @@ -56,7 +54,7 @@ public class WxParameterStructure { public void loadSettleInfo(Map parameters, PayOrder order) { Object profitSharing = order.getAttr("profit_sharing"); if (null != profitSharing) { - Map settleInfo = new MapGen("profit_sharing", profitSharing).getAttr(); + Map settleInfo = new MapGen<>("profit_sharing", profitSharing).getAttr(); parameters.put("settle_info", settleInfo); return; } 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 index 80c6679..75d5f99 100644 --- 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 @@ -21,8 +21,9 @@ 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.bean.AssistOrder; import com.egzosn.pay.common.bean.BillType; -import com.egzosn.pay.common.bean.CloseOrder; + import com.egzosn.pay.common.bean.CurType; import com.egzosn.pay.common.bean.MethodType; import com.egzosn.pay.common.bean.NoticeParams; @@ -32,6 +33,7 @@ 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; @@ -66,8 +68,8 @@ import com.egzosn.pay.wx.v3.utils.WxConst; * * @author egan *
- * email egzosn@gmail.com
- * date 2016-5-18 14:09:01
+ * email egan@egzosn.com
+ * date 2021/10/6
  * 
*/ public class WxPayService extends BasePayService { @@ -111,6 +113,7 @@ public class WxPayService extends BasePayService { /** * 辅助api + * * @return 辅助api */ public WxPayAssistService getAssistService() { @@ -126,7 +129,18 @@ public class WxPayService extends BasePayService { this.assistService = assistService; } + /** + * 初始化之后执行 + */ + @Override + protected void initAfter() { + new Thread(() -> { + payConfigStorage.loadCertEnvironment(); + wxParameterStructure = new WxParameterStructure(payConfigStorage); + getAssistService(); + }).start(); + } /** * 设置api服务器地址 @@ -223,8 +237,8 @@ public class WxPayService extends BasePayService { Map parameters = wxParameterStructure.getPublicParameters(); wxParameterStructure.initPartner(parameters); // 商品描述 - OrderParaStructure.loadParameters(parameters, "description", order.getSubject()); - OrderParaStructure.loadParameters(parameters, "description", order.getBody()); + OrderParaStructure.loadParameters(parameters, WxConst.DESCRIPTION, order.getSubject()); + OrderParaStructure.loadParameters(parameters, WxConst.DESCRIPTION, order.getBody()); // 订单号 parameters.put(WxConst.OUT_TRADE_NO, order.getOutTradeNo()); //交易结束时间 @@ -430,7 +444,19 @@ public class WxPayService extends BasePayService { */ @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 = wxParameterStructure.getSpParameters(); WxTransactionType transactionType = WxTransactionType.QUERY_TRANSACTION_ID; String uriVariable = transactionId; @@ -438,7 +464,9 @@ public class WxPayService extends BasePayService { transactionType = WxTransactionType.QUERY_OUT_TRADE_NO; uriVariable = outTradeNo; } + return getAssistService().doExecute(parameters, transactionType, uriVariable); + } @@ -451,22 +479,22 @@ public class WxPayService extends BasePayService { */ @Override public Map close(String transactionId, String outTradeNo) { - return close(new CloseOrder(outTradeNo)); + return close(new AssistOrder(outTradeNo)); } - /** * 交易关闭接口 * - * @param closeOrder 关闭订单 + * @param assistOrder 关闭订单 * @return 返回支付方交易关闭后的结果 */ @Override - public Map close(CloseOrder closeOrder) { + public Map close(AssistOrder assistOrder) { String parameters = wxParameterStructure.getSpParameters(); - return getAssistService().doExecute(parameters, WxTransactionType.CLOSE, closeOrder.getOutTradeNo()); + return getAssistService().doExecute(parameters, WxTransactionType.CLOSE, assistOrder.getOutTradeNo()); } + /** * 申请退款接口 * @@ -474,7 +502,7 @@ public class WxPayService extends BasePayService { * @return 返回支付方申请退款后的结果 */ @Override - public WxRefundResult refund(RefundOrder refundOrder) { + public RefundResult refund(RefundOrder refundOrder) { //获取公共参数 Map parameters = wxParameterStructure.initSubMchId(null); @@ -538,7 +566,7 @@ public class WxPayService extends BasePayService { Map parameters = new HashMap<>(5); //目前只支持日账单 - parameters.put("bill_date", DateUtils.formatDate(billDate, DateUtils.YYYYMMDD)); + 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) { @@ -567,7 +595,7 @@ public class WxPayService extends BasePayService { } Map data = new HashMap<>(); data.put("file", inputStream); - return result; + return data; } @@ -579,7 +607,7 @@ public class WxPayService extends BasePayService { */ @Override public Map transfer(TransferOrder order) { - throw new PayErrorException(new WxPayError("", "等待作者实现")); + throw new PayErrorException(new WxPayError("", "V3不支持转账")); } @@ -596,7 +624,7 @@ public class WxPayService extends BasePayService { */ @Override public Map transferQuery(String outNo, String wxTransferType) { - throw new PayErrorException(new WxPayError("", "等待作者实现")); + throw new PayErrorException(new WxPayError("", "V3不支持转账查询")); } 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 0000000..c38ec42 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxProfitSharingService.java @@ -0,0 +1,499 @@ +package com.egzosn.pay.wx.v3.api; + +import java.io.InputStream; +import java.security.PrivateKey; +import java.util.Date; +import java.util.Map; + +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.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.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.UriVariables; +import com.egzosn.pay.common.util.DateUtils; +import com.egzosn.pay.common.util.MapGen; +import com.egzosn.pay.common.util.sign.encrypt.RSA2; +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(); + getAssistService(); + }).start(); + + } + + /** + * 回调校验 + * + * @param params 回调回来的参数集 + * @return 签名校验 true通过 + */ + @Override + public boolean verify(Map params) { + throw new PayErrorException(new WxPayError("", "分账不支持方式")); + + } + + /** + * 验签,使用微信平台证书. + * + * @param noticeParams 通知参数 + * @return the boolean + */ + public boolean verify(NoticeParams noticeParams) { + throw new PayErrorException(new WxPayError("", "分账不支持方式")); + } + + + /** + * 微信统一下单接口 + * + * @param order 支付订单集 + * @return 下单结果 + */ + 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); + } + } + + + /** + * 签名 + * + * @param content 需要签名的内容 不包含key + * @param characterEncoding 字符编码 + * @return 签名结果 + */ + @Override + public String createSign(String content, String characterEncoding) { + PrivateKey privateKey = payConfigStorage.getCertEnvironment().getPrivateKey(); + return RSA2.sign(content, privateKey, characterEncoding); + } + + /** + * 将请求参数或者请求流转化为 Map + * + * @param parameterMap 请求参数 + * @param is 请求流 + * @return 获得回调的请求参数 + */ + @Deprecated + @Override + public Map getParameter2Map(Map parameterMap, InputStream is) { + throw new PayErrorException(new WxPayError("", "分账不支持方式")); + + } + + /** + * 将请求参数或者请求流转化为 Map + * + * @param request 通知请求 + * @return 获得回调的请求参数 + */ + @Override + public NoticeParams getNoticeParams(NoticeRequest request) { + throw new PayErrorException(new WxPayError("", "分账不支持方式")); + } + + /** + * 获取输出消息,用户返回给支付端 + * + * @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) { + 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的输入流 + */ + 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/WxProfitSharingTransactionType.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxProfitSharingTransactionType.java new file mode 100644 index 0000000..e7fb075 --- /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.POST), + /** + * 请求分账回退 + */ + 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 index 948d3f0..ead42a1 100644 --- 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 @@ -17,9 +17,10 @@ import com.egzosn.pay.wx.v3.utils.WxConst; * 微信V3交易类型 * * @author egan - *

- * email egzosn@gmail.com + *

+ * email egan@egzosn.com
  * date 2016/10/19 22:58
+ * 
*/ public enum WxTransactionType implements TransactionType { /** @@ -127,7 +128,7 @@ public enum WxTransactionType implements TransactionType { //----------------------------------------------------------------- //以下为合并支付 /** - * 合单下单-APP支付API. + * 合单下单-APP支付 */ COMBINE_APP("/v3/combine-transactions/app", MethodType.POST), @@ -136,20 +137,20 @@ public enum WxTransactionType implements TransactionType { */ COMBINE_JSAPI("/v3/combine-transactions/jsapi", MethodType.POST), /** - * 合单下单-H5支付API. + * 合单下单-H5支付 */ COMBINE_H5("/v3/combine-transactions/h5", MethodType.POST, true), /** - * 合单下单-Native支付API. + * 合单下单-Native支付 */ COMBINE_NATIVE("/v3/combine-transactions/native", MethodType.POST, true), /** - * 合单查询订单API. + * 合单查询订单 */ COMBINE_TRANSACTION("/v3/combine-transactions/out-trade-no/{combine_out_trade_no}", MethodType.GET), /** - * 合单关闭订单API. + * 合单关闭订单 */ COMBINE_CLOSE("/v3/combine-transactions/out-trade-no/{combine_out_trade_no}/close", MethodType.POST), ; 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 index dfe9c84..ae328bd 100644 --- 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 @@ -2,7 +2,8 @@ package com.egzosn.pay.wx.v3.bean.combine; import java.util.List; -import com.egzosn.pay.common.bean.CloseOrder; +import com.egzosn.pay.common.bean.AssistOrder; + import com.egzosn.pay.wx.v3.utils.WxConst; /** @@ -13,7 +14,7 @@ import com.egzosn.pay.wx.v3.utils.WxConst; * date 2021/10/6 * */ -public class CombineCloseOrder extends CloseOrder { +public class CombineCloseOrder extends AssistOrder { /** * 子单信息,必填,最多50单 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 index e058663..2dccdeb 100644 --- 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 @@ -361,7 +361,7 @@ public class WxPayMessage extends PayMessage { this.userReceivedAccount = userReceivedAccount; } - public static final WxPayMessage create(Map message) { + public static WxPayMessage create(Map message) { WxPayMessage payMessage = new JSONObject(message).toJavaObject(WxPayMessage.class); // payMessage.setPayType(""); payMessage.setPayMessage(message); 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 index 9d49a4c..260c2ae 100644 --- 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 @@ -48,4 +48,13 @@ public enum TradeState { * 已接收,等待扣款 */ 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 0000000..487e16f --- /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 0000000..2e6112b --- /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, receivers); + } +} 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 0000000..18ff526 --- /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 0000000..8252120 --- /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 0000000..cd62f34 --- /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 0000000..00e73c3 --- /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 0000000..d435611 --- /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 0000000..1511892 --- /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 0000000..8f51a5d --- /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/utils/WxConst.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/utils/WxConst.java index 941ff10..06d43f4 100644 --- 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 @@ -33,6 +33,17 @@ public final class WxConst { public static final String SANDBOXNEW = "sandboxnew/"; public static final String COMBINE = "combine_"; public static final String APPID = "appid"; + public static final String SUB_APPID = "sub_appid"; + public static final String TRANSACTION_ID = "transaction_id"; + public static final String OUT_ORDER_NO = "out_order_no"; + public static final String TYPE = "type"; + public static final String ACCOUNT = "account"; + public static final String NAME = "name"; + public static final String RELATION_TYPE = "relationType"; + public static final String CUSTOM_RELATION = "customRelation"; + public static final String DESCRIPTION = "description"; + public static final String BILL_DATE = "bill_date"; + public static final String TAR_TYPE = "tar_type"; public static final String COMBINE_APPID = COMBINE + APPID; public static final String MCH_ID = "mchid"; @@ -45,6 +56,8 @@ public final class WxConst { 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\""; -- Gitee From 973a4698508c7065acad89e7438497c0a970c9bc Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 6 Oct 2021 23:32:18 +0800 Subject: [PATCH 090/165] =?UTF-8?q?=E6=A0=B9=E6=8D=AE=E6=9C=80=E6=96=B0?= =?UTF-8?q?=E7=9A=84=E6=8E=A5=E5=8F=A3=E5=8A=9F=E8=83=BD=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E6=A1=88=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pay/demo/controller/AliPayController.java | 5 +-- .../pay/demo/controller/PayController.java | 5 +-- .../controller/PayoneerPayController.java | 5 +-- .../demo/controller/UnionPayController.java | 35 +++++++++---------- .../pay/demo/controller/WxPayController.java | 4 +-- .../controller/WxV3CombinePayController.java | 22 ++++++------ .../demo/controller/WxV3PayController.java | 8 +++-- .../handler/WxV3CombinePayMessageHandler.java | 6 ++-- 8 files changed, 48 insertions(+), 42 deletions(-) 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 f0a674c..8911a83 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 @@ -25,6 +25,7 @@ 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; @@ -283,7 +284,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())); } /** @@ -311,7 +312,7 @@ 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())); } /** 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 3e23fd6..93b09e7 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 @@ -26,6 +26,7 @@ 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.AssistOrder; import com.egzosn.pay.common.bean.MethodType; import com.egzosn.pay.common.bean.NoticeParams; import com.egzosn.pay.common.bean.PayMessage; @@ -419,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())); } /** * 查询 @@ -444,7 +445,7 @@ 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())); } /** 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 e6580b2..caaee05 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 @@ -13,6 +13,7 @@ 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; @@ -160,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())); } @@ -172,7 +173,7 @@ 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())); } /** 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 297bd06..46e1b4a 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,9 +2,23 @@ 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; @@ -17,21 +31,6 @@ import com.egzosn.pay.union.bean.UnionRefundResult; import com.egzosn.pay.union.bean.UnionTransactionType; import com.egzosn.pay.web.support.HttpRequestNoticeParams; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.PostConstruct; -import javax.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; - /** * 银联相关 * @@ -271,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())); } 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 3ac4464..879494e 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 @@ -303,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())); } @@ -315,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())); } /** 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 index 6431bb2..ce65d86 100644 --- 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 @@ -15,8 +15,10 @@ 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; @@ -51,8 +53,8 @@ public class WxV3CombinePayController { wxPayConfigStorage.setMchId("1602947765"); //V3密钥 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_2.shtml wxPayConfigStorage.setV3ApiKey("9bd8f0e7af4841299d782406b7774f57"); - wxPayConfigStorage.setNotifyUrl("http://sailinmu.iok.la/wxV3/payBack.json"); - wxPayConfigStorage.setReturnUrl("http://sailinmu.iok.la/wxV3/payBack.json"); + wxPayConfigStorage.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); @@ -93,7 +95,7 @@ public class WxV3CombinePayController { subOrder.setAmount(new CombineAmount(121)); subOrder.setOutTradeNo("子单商户订单号 "); subOrder.setDescription("商品描述"); - subOrder.setSubMchid("二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 "); + subOrder.setSubMchid("服务商必填----二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 "); subOrders.add(subOrder); order.setSubOrders(subOrders); order.setTransactionType(WxTransactionType.COMBINE_H5); @@ -121,7 +123,7 @@ public class WxV3CombinePayController { subOrder.setAmount(new CombineAmount(111)); subOrder.setOutTradeNo("子单商户订单号 "); subOrder.setDescription("商品描述"); - subOrder.setSubMchid("二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 "); + subOrder.setSubMchid("服务商必填----二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 "); subOrders.add(subOrder); order.setSubOrders(subOrders); Map orderInfo = service.orderInfo(order); @@ -150,7 +152,7 @@ public class WxV3CombinePayController { subOrder.setAmount(new CombineAmount(211)); subOrder.setOutTradeNo("子单商户订单号 "); subOrder.setDescription("商品描述"); - subOrder.setSubMchid("二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 "); + subOrder.setSubMchid("服务商必填----二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 "); subOrders.add(subOrder); order.setSubOrders(subOrders); Map orderInfo = service.orderInfo(order); @@ -179,7 +181,7 @@ public class WxV3CombinePayController { subOrder.setAmount(new CombineAmount(131)); subOrder.setOutTradeNo("子单商户订单号 "); subOrder.setDescription("商品描述"); - subOrder.setSubMchid("二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 "); + subOrder.setSubMchid("服务商必填----二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 "); subOrders.add(subOrder); order.setSubOrders(subOrders); @@ -210,7 +212,7 @@ public class WxV3CombinePayController { subOrder.setAmount(new CombineAmount(115)); subOrder.setOutTradeNo("子单商户订单号 "); subOrder.setDescription("商品描述"); - subOrder.setSubMchid("二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 "); + subOrder.setSubMchid("服务商必填----二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 "); subOrders.add(subOrder); order.setSubOrders(subOrders); //获取对应的支付账户操作工具(可根据账户id) @@ -244,7 +246,7 @@ public class WxV3CombinePayController { */ @RequestMapping("query") public Map query(QueryOrder order) { - return service.query(order.getTradeNo(), order.getOutTradeNo()); + return service.query(new AssistOrder(order.getTradeNo(), order.getOutTradeNo())); } @@ -262,7 +264,7 @@ public class WxV3CombinePayController { CombineSubOrder subOrder = new CombineSubOrder(); subOrder.setMchid("子单商户号"); subOrder.setOutTradeNo("子单商户订单号 "); - subOrder.setSubMchid("二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 "); + subOrder.setSubMchid("服务商必填----二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 "); subOrders.add(subOrder); order.setSubOrders(subOrders); return service.close(order); @@ -275,7 +277,7 @@ public class WxV3CombinePayController { * @return 返回支付方申请退款后的结果 */ @RequestMapping("refund") - public WxRefundResult refund(RefundOrder order) { + public RefundResult refund(RefundOrder order) { return service.refund(order); } 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 index f987656..621c83f 100644 --- 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 @@ -16,9 +16,11 @@ 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; @@ -178,7 +180,7 @@ public class WxV3PayController { */ @RequestMapping("query") public Map query(QueryOrder order) { - return service.query(order.getTradeNo(), order.getOutTradeNo()); + return service.query(new AssistOrder(order.getTradeNo(), order.getOutTradeNo())); } @@ -190,7 +192,7 @@ public class WxV3PayController { */ @RequestMapping("close") public Map close(QueryOrder order) { - return service.close(order.getTradeNo(), order.getOutTradeNo()); + return service.close(new AssistOrder(order.getTradeNo(), order.getOutTradeNo())); } /** @@ -200,7 +202,7 @@ public class WxV3PayController { * @return 返回支付方申请退款后的结果 */ @RequestMapping("refund") - public WxRefundResult refund(RefundOrder order) { + public RefundResult refund(RefundOrder order) { return service.refund(order); } 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 index cdbcd0c..ef6c341 100644 --- 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 @@ -11,18 +11,18 @@ 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.combine.CombinePayMessage; +import com.egzosn.pay.wx.v3.bean.sharing.ProfitSharingPayMessage; /** * 微信合单支付回调处理器 * Created by ZaoSheng on 2016/6/1. */ -public class WxV3CombinePayMessageHandler implements PayMessageHandler { +public class WxV3CombinePayMessageHandler implements PayMessageHandler { private final Logger LOG = LoggerFactory.getLogger(DefaultPayMessageHandler.class); @Override - public PayOutMessage handle(CombinePayMessage payMessage, Map context, PayService payService) throws PayErrorException { + public PayOutMessage handle(ProfitSharingPayMessage payMessage, Map context, PayService payService) throws PayErrorException { LOG.info("回调支付消息处理器,回调消息:{}", JSON.toJSONString(payMessage)); return payService.successPayOutMessage(payMessage); } -- Gitee From 9b82bcabefed0bb584a8c1889e70e26acb1ac770 Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 6 Oct 2021 23:32:37 +0800 Subject: [PATCH 091/165] =?UTF-8?q?=E5=88=86=E8=B4=A6=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WxV3ProfitSharingController.java | 264 ++++++++++++++++++ .../WxV3ProfitSharingMessageHandler.java | 29 ++ 2 files changed, 293 insertions(+) create mode 100644 pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxV3ProfitSharingController.java create mode 100644 pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/WxV3ProfitSharingMessageHandler.java 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 0000000..299fea1 --- /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/service/handler/WxV3ProfitSharingMessageHandler.java b/pay-java-demo/src/main/java/com/egzosn/pay/demo/service/handler/WxV3ProfitSharingMessageHandler.java new file mode 100644 index 0000000..ad083cc --- /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); + } +} -- Gitee From 6d05d587e6d64037258614577a1dd9ce5b990953 Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 6 Oct 2021 23:37:56 +0800 Subject: [PATCH 092/165] =?UTF-8?q?=E5=88=86=E8=B4=A6=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pay/wx/v3/api/WxProfitSharingService.java | 37 ------------------- 1 file changed, 37 deletions(-) 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 index c38ec42..275b034 100644 --- 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 @@ -173,43 +173,6 @@ public class WxProfitSharingService extends WxPayService implements ProfitSharin } - /** - * 将请求参数或者请求流转化为 Map - * - * @param request 通知请求 - * @return 获得回调的请求参数 - */ - @Override - public NoticeParams getNoticeParams(NoticeRequest request) { - throw new PayErrorException(new WxPayError("", "分账不支持方式")); - } - - /** - * 获取输出消息,用户返回给支付端 - * - * @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端 * -- Gitee From b9a7cff0a22f3626e92807f7e6b06149dec05d4e Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 6 Oct 2021 23:44:33 +0800 Subject: [PATCH 093/165] 2.14.3 --- README.md | 2 +- pay-java-ali/pom.xml | 2 +- pay-java-baidu/pom.xml | 2 +- pay-java-common/pom.xml | 2 +- pay-java-demo/pom.xml | 2 +- pay-java-fuiou/pom.xml | 2 +- pay-java-payoneer/pom.xml | 2 +- pay-java-paypal/pom.xml | 2 +- pay-java-union/pom.xml | 2 +- pay-java-web-support/pom.xml | 2 +- pay-java-wx-youdian/pom.xml | 2 +- pay-java-wx/pom.xml | 2 +- pay-java-yiji/pom.xml | 2 +- pom.xml | 4 ++-- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index ff69992..cd46d5d 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ com.egzosn {module-name} - 2.14.2 + 2.14.3 ``` diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index aa08115..2cf7477 100644 --- a/pay-java-ali/pom.xml +++ b/pay-java-ali/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-SNAPSHOT + 2.14.3 4.0.0 pay-java-ali diff --git a/pay-java-baidu/pom.xml b/pay-java-baidu/pom.xml index e9f0b16..0939ce4 100644 --- a/pay-java-baidu/pom.xml +++ b/pay-java-baidu/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-SNAPSHOT + 2.14.3 4.0.0 pay-java-baidu diff --git a/pay-java-common/pom.xml b/pay-java-common/pom.xml index e20e284..6bddee0 100644 --- a/pay-java-common/pom.xml +++ b/pay-java-common/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-SNAPSHOT + 2.14.3 4.0.0 jar diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index 30f7350..6ad2995 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-SNAPSHOT + 2.14.3 4.0.0 war diff --git a/pay-java-fuiou/pom.xml b/pay-java-fuiou/pom.xml index 9d1672c..cfc08fc 100644 --- a/pay-java-fuiou/pom.xml +++ b/pay-java-fuiou/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-SNAPSHOT + 2.14.3 4.0.0 pay-java-fuiou diff --git a/pay-java-payoneer/pom.xml b/pay-java-payoneer/pom.xml index 45fdcfb..26784ed 100644 --- a/pay-java-payoneer/pom.xml +++ b/pay-java-payoneer/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-SNAPSHOT + 2.14.3 4.0.0 pay-java-payoneer diff --git a/pay-java-paypal/pom.xml b/pay-java-paypal/pom.xml index c4a49d9..2a042ca 100644 --- a/pay-java-paypal/pom.xml +++ b/pay-java-paypal/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-SNAPSHOT + 2.14.3 4.0.0 diff --git a/pay-java-union/pom.xml b/pay-java-union/pom.xml index 64b8020..9bf99b8 100644 --- a/pay-java-union/pom.xml +++ b/pay-java-union/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-SNAPSHOT + 2.14.3 4.0.0 diff --git a/pay-java-web-support/pom.xml b/pay-java-web-support/pom.xml index 36f8dbb..af39355 100644 --- a/pay-java-web-support/pom.xml +++ b/pay-java-web-support/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-SNAPSHOT + 2.14.3 4.0.0 jar diff --git a/pay-java-wx-youdian/pom.xml b/pay-java-wx-youdian/pom.xml index ca68448..c92c64b 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.14.3-SNAPSHOT + 2.14.3 4.0.0 pay-java-wx-youdian diff --git a/pay-java-wx/pom.xml b/pay-java-wx/pom.xml index 52dd4cf..d48f95c 100644 --- a/pay-java-wx/pom.xml +++ b/pay-java-wx/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-SNAPSHOT + 2.14.3 4.0.0 pay-java-wx diff --git a/pay-java-yiji/pom.xml b/pay-java-yiji/pom.xml index b736fa5..4ce9397 100644 --- a/pay-java-yiji/pom.xml +++ b/pay-java-yiji/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-SNAPSHOT + 2.14.3 4.0.0 diff --git a/pom.xml b/pom.xml index 2189a4a..63391c0 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.egzosn pay-java-parent pom - 2.14.3-SNAPSHOT + 2.14.3 Pay Java - Parent Pay Java Parent @@ -70,7 +70,7 @@ - 2.14.3-SNAPSHOT + 2.14.3 4.5.4 1.2.17 1.2.73 -- Gitee From d74b1f79eada2f3869f25c5b5a0974edaff05b60 Mon Sep 17 00:00:00 2001 From: egzosn Date: Wed, 6 Oct 2021 23:57:09 +0800 Subject: [PATCH 094/165] 2.14.3 --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cd46d5d..8387ad3 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提供的各种支付相关的功能 ### 特性 @@ -41,6 +41,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) -- Gitee From 50244c67b1836f62907e76c6ca919531f07b4b51 Mon Sep 17 00:00:00 2001 From: egzosn Date: Mon, 18 Oct 2021 21:07:37 +0800 Subject: [PATCH 095/165] =?UTF-8?q?=E5=AF=B9=E5=BF=85=E9=A1=BB=E8=A6=81?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E8=AF=81=E4=B9=A6=E7=9A=84=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E6=96=B9=E5=BC=8F=E7=A7=BB=E9=99=A4isCertSign=E5=88=A4?= =?UTF-8?q?=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pay-java-ali/pom.xml | 2 +- .../src/main/java/com/egzosn/pay/ali/api/AliPayService.java | 2 +- .../main/java/com/egzosn/pay/union/api/UnionPayService.java | 6 ++---- .../java/com/egzosn/pay/wx/v3/api/WxPayConfigStorage.java | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index 2cf7477..aac8ea0 100644 --- a/pay-java-ali/pom.xml +++ b/pay-java-ali/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3 + 2.14.3-b 4.0.0 pay-java-ali 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 126b386..274ef1d 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 @@ -128,7 +128,7 @@ public class AliPayService extends BasePayService { 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; } 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 d236603..1cda4f4 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 @@ -108,13 +108,11 @@ public class UnionPayService extends BasePayService { @Override public UnionPayService setPayConfigStorage(UnionPayConfigStorage payConfigStorage) { this.payConfigStorage = payConfigStorage; - if (!payConfigStorage.isCertSign()) { + if (null != certDescriptor) { return this; } - if (null == certDescriptor) { - certDescriptor = new CertDescriptor(); - } try { + certDescriptor = new CertDescriptor(); certDescriptor.initPrivateSignCert(payConfigStorage.getKeyPrivateCertInputStream(), payConfigStorage.getKeyPrivateCertPwd(), "PKCS12"); certDescriptor.initPublicCert(payConfigStorage.getAcpMiddleCertInputStream()); certDescriptor.initRootCert(payConfigStorage.getAcpRootCertInputStream()); 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 index 66ccf53..b668a63 100644 --- 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 @@ -244,7 +244,7 @@ public class WxPayConfigStorage extends BasePayConfigStorage { * 初始化证书信息 */ public void loadCertEnvironment() { - if (!isCertSign() || null != this.certEnvironment) { + if (null != this.certEnvironment) { return; } try (InputStream apiKeyCert = certStoreType.getInputStream(getApiClientKeyP12())) { -- Gitee From f4d689d6ba124528d73278fa1dcbddf38b9cf68c Mon Sep 17 00:00:00 2001 From: egzosn Date: Mon, 18 Oct 2021 21:09:20 +0800 Subject: [PATCH 096/165] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9C=AA=E4=BD=BF?= =?UTF-8?q?=E7=94=A8pay-java-web-support=E7=BB=84=E4=BB=B6=E7=9A=84?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E5=AF=B9=E8=B1=A1=E6=97=B6json=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E5=8C=96=E7=A9=BA=E6=8C=87=E9=92=88=E5=BC=82=E5=B8=B8?= =?UTF-8?q?bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/egzosn/pay/common/api/BasePayService.java | 4 ++-- .../main/java/com/egzosn/pay/common/bean/NoticeParams.java | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) 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 bd8b5bc..df18742 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 @@ -350,7 +350,7 @@ public abstract class BasePayService implements Pay */ @Override public Map transfer(TransferOrder order) { - return new HashMap<>(0); + return Collections.emptyMap(); } /** @@ -362,7 +362,7 @@ public abstract class BasePayService implements Pay */ @Override public Map transferQuery(String outNo, String tradeNo) { - return new HashMap<>(0); + return Collections.emptyMap(); } /** 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 index 7cd494f..54f59a6 100644 --- 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 @@ -70,6 +70,9 @@ public class NoticeParams { } public Enumeration getHeaderNames() { + if (null == headers){ + return Collections.enumeration(Collections.emptySet()); + } return Collections.enumeration(this.headers.keySet()); } -- Gitee From 6e75b724280efd2cca1ac1e71060f884622c43ac Mon Sep 17 00:00:00 2001 From: egzosn Date: Mon, 18 Oct 2021 21:10:47 +0800 Subject: [PATCH 097/165] =?UTF-8?q?=E5=BE=AE=E4=BF=A1V2=E9=80=80=E6=AC=BE?= =?UTF-8?q?=E3=80=90=E9=80=80=E6=AC=BE=E4=BB=A3=E9=87=91=E5=88=B8ID?= =?UTF-8?q?=E3=80=91=E7=B1=BB=E5=9E=8B=E9=94=99=E8=AF=AF=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pay-java-web-support/pom.xml | 12 +++++------- .../java/com/egzosn/pay/wx/bean/WxRefundResult.java | 6 +++--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/pay-java-web-support/pom.xml b/pay-java-web-support/pom.xml index af39355..c8bf104 100644 --- a/pay-java-web-support/pom.xml +++ b/pay-java-web-support/pom.xml @@ -5,19 +5,17 @@ pay-java-parent com.egzosn - 2.14.3 + 2.14.3-b 4.0.0 jar pay-java-web-support Pay Java Web 相关支持 + - 4.3.4.RELEASE - 4.10 - 4.3.6.Final + 4.0.1 - com.egzosn @@ -26,10 +24,10 @@ javax.servlet javax.servlet-api - 3.1.0 + provided + ${servlet-api.version} - 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 index ce3ade7..7fadc2e 100644 --- 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 @@ -199,7 +199,7 @@ public class WxRefundResult extends BaseRefundResult { * 这里只接收0的,其余请自行获取 */ @JSONField(name = "coupon_refund_id_0") - private Integer couponRefundId0; + private String couponRefundId0; /** * 获取退款请求结果状态码 * @@ -480,11 +480,11 @@ public class WxRefundResult extends BaseRefundResult { this.couponRefundCount = couponRefundCount; } - public Integer getCouponRefundId0() { + public String getCouponRefundId0() { return couponRefundId0; } - public void setCouponRefundId0(Integer couponRefundId0) { + public void setCouponRefundId0(String couponRefundId0) { this.couponRefundId0 = couponRefundId0; } -- Gitee From d264723d4c4bcbc1f37395a52024eb3fcf7239cc Mon Sep 17 00:00:00 2001 From: egzosn Date: Mon, 18 Oct 2021 21:11:11 +0800 Subject: [PATCH 098/165] 2.14.3-b --- README.md | 5 +-- pay-java-baidu/pom.xml | 8 ++--- .../pay/baidu/api/BaiduPayServiceTest.java | 12 +++---- pay-java-common/pom.xml | 2 +- pay-java-demo/pom.xml | 31 +++++++------------ pay-java-fuiou/pom.xml | 2 +- pay-java-payoneer/pom.xml | 2 +- pay-java-paypal/pom.xml | 2 +- pay-java-union/pom.xml | 2 +- pay-java-wx-youdian/pom.xml | 2 +- pay-java-wx/pom.xml | 2 +- pay-java-yiji/pom.xml | 2 +- pom.xml | 15 ++++----- 13 files changed, 37 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 8387ad3..33e6d9d 100644 --- a/README.md +++ b/README.md @@ -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.14.3 + 2.14.3-b ``` diff --git a/pay-java-baidu/pom.xml b/pay-java-baidu/pom.xml index 0939ce4..4fd5363 100644 --- a/pay-java-baidu/pom.xml +++ b/pay-java-baidu/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3 + 2.14.3-b 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/test/java/com/egzosn/pay/baidu/api/BaiduPayServiceTest.java b/pay-java-baidu/src/test/java/com/egzosn/pay/baidu/api/BaiduPayServiceTest.java index 6f9b5c2..43f31a1 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 6bddee0..5ea672b 100644 --- a/pay-java-common/pom.xml +++ b/pay-java-common/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3 + 2.14.3-b 4.0.0 jar diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index 6ad2995..769a561 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3 + 2.14.3-b 4.0.0 war @@ -16,9 +16,9 @@ 4.3.4.RELEASE 4.10 4.3.6.Final + 4.0.1 - @@ -31,11 +31,7 @@ pay-java-wx ${pay.version} - - com.egzosn - pay-java-web-support - ${pay.version} - + com.egzosn pay-java-ali @@ -63,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 @@ -87,7 +73,12 @@ - + + javax.servlet + javax.servlet-api + provided + ${servlet-api.version} + com.fasterxml.jackson.core jackson-databind diff --git a/pay-java-fuiou/pom.xml b/pay-java-fuiou/pom.xml index cfc08fc..e8dc77d 100644 --- a/pay-java-fuiou/pom.xml +++ b/pay-java-fuiou/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3 + 2.14.3-b 4.0.0 pay-java-fuiou diff --git a/pay-java-payoneer/pom.xml b/pay-java-payoneer/pom.xml index 26784ed..89b6f58 100644 --- a/pay-java-payoneer/pom.xml +++ b/pay-java-payoneer/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3 + 2.14.3-b 4.0.0 pay-java-payoneer diff --git a/pay-java-paypal/pom.xml b/pay-java-paypal/pom.xml index 2a042ca..5ec9d96 100644 --- a/pay-java-paypal/pom.xml +++ b/pay-java-paypal/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3 + 2.14.3-b 4.0.0 diff --git a/pay-java-union/pom.xml b/pay-java-union/pom.xml index 9bf99b8..7c6eaca 100644 --- a/pay-java-union/pom.xml +++ b/pay-java-union/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3 + 2.14.3-b 4.0.0 diff --git a/pay-java-wx-youdian/pom.xml b/pay-java-wx-youdian/pom.xml index c92c64b..228e9b1 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.14.3 + 2.14.3-b 4.0.0 pay-java-wx-youdian diff --git a/pay-java-wx/pom.xml b/pay-java-wx/pom.xml index d48f95c..f636f1a 100644 --- a/pay-java-wx/pom.xml +++ b/pay-java-wx/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3 + 2.14.3-b 4.0.0 pay-java-wx diff --git a/pay-java-yiji/pom.xml b/pay-java-yiji/pom.xml index 4ce9397..d8abf2e 100644 --- a/pay-java-yiji/pom.xml +++ b/pay-java-yiji/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3 + 2.14.3-b 4.0.0 diff --git a/pom.xml b/pom.xml index 63391c0..4b558b7 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.egzosn pay-java-parent pom - 2.14.3 + 2.14.3-b Pay Java - Parent Pay Java Parent @@ -70,12 +70,12 @@ - 2.14.3 + 2.14.3-b 4.5.4 1.2.17 1.2.73 3.3.1 - 5.5.1 + 4.0.1 @@ -107,7 +107,6 @@ - org.slf4j @@ -122,10 +121,12 @@ core ${zxing.version} + - org.junit.jupiter - junit-jupiter - ${junit.version} + javax.servlet + javax.servlet-api + provided + ${servlet-api.version} -- Gitee From 2d74c2a959cbeed67a39be6fa8ee1ecd7aa6d19c Mon Sep 17 00:00:00 2001 From: egzosn Date: Thu, 28 Oct 2021 13:00:03 +0800 Subject: [PATCH 099/165] =?UTF-8?q?H5=E6=94=AF=E4=BB=98=E9=81=97=E6=BC=8F?= =?UTF-8?q?=E5=9C=BA=E6=99=AF=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pay-java-ali/pom.xml | 4 ++-- pay-java-baidu/pom.xml | 2 +- pay-java-common/pom.xml | 2 +- pay-java-demo/pom.xml | 2 +- pay-java-fuiou/pom.xml | 2 +- pay-java-payoneer/pom.xml | 2 +- pay-java-paypal/pom.xml | 2 +- pay-java-union/pom.xml | 2 +- pay-java-web-support/pom.xml | 2 +- pay-java-wx-youdian/pom.xml | 2 +- pay-java-wx/pom.xml | 8 ++++++-- .../java/com/egzosn/pay/wx/v3/bean/WxTransactionType.java | 1 + pay-java-yiji/pom.xml | 2 +- pom.xml | 5 +++-- 14 files changed, 22 insertions(+), 16 deletions(-) diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index aac8ea0..467543a 100644 --- a/pay-java-ali/pom.xml +++ b/pay-java-ali/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b + 2.14.3-b1 4.0.0 pay-java-ali @@ -22,7 +22,7 @@ org.bouncycastle bcprov-jdk15on - 1.59 + ${bcprov-jdk15on.version} diff --git a/pay-java-baidu/pom.xml b/pay-java-baidu/pom.xml index 4fd5363..23af590 100644 --- a/pay-java-baidu/pom.xml +++ b/pay-java-baidu/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b + 2.14.3-b1 4.0.0 pay-java-baidu diff --git a/pay-java-common/pom.xml b/pay-java-common/pom.xml index 5ea672b..042a30c 100644 --- a/pay-java-common/pom.xml +++ b/pay-java-common/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b + 2.14.3-b1 4.0.0 jar diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index 769a561..3ddbb47 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b + 2.14.3-b1 4.0.0 war diff --git a/pay-java-fuiou/pom.xml b/pay-java-fuiou/pom.xml index e8dc77d..03c375b 100644 --- a/pay-java-fuiou/pom.xml +++ b/pay-java-fuiou/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b + 2.14.3-b1 4.0.0 pay-java-fuiou diff --git a/pay-java-payoneer/pom.xml b/pay-java-payoneer/pom.xml index 89b6f58..a6ac61a 100644 --- a/pay-java-payoneer/pom.xml +++ b/pay-java-payoneer/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b + 2.14.3-b1 4.0.0 pay-java-payoneer diff --git a/pay-java-paypal/pom.xml b/pay-java-paypal/pom.xml index 5ec9d96..37b129e 100644 --- a/pay-java-paypal/pom.xml +++ b/pay-java-paypal/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b + 2.14.3-b1 4.0.0 diff --git a/pay-java-union/pom.xml b/pay-java-union/pom.xml index 7c6eaca..5024eec 100644 --- a/pay-java-union/pom.xml +++ b/pay-java-union/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b + 2.14.3-b1 4.0.0 diff --git a/pay-java-web-support/pom.xml b/pay-java-web-support/pom.xml index c8bf104..03c1aab 100644 --- a/pay-java-web-support/pom.xml +++ b/pay-java-web-support/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b + 2.14.3-b1 4.0.0 jar diff --git a/pay-java-wx-youdian/pom.xml b/pay-java-wx-youdian/pom.xml index 228e9b1..ac48d3d 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.14.3-b + 2.14.3-b1 4.0.0 pay-java-wx-youdian diff --git a/pay-java-wx/pom.xml b/pay-java-wx/pom.xml index f636f1a..13a2089 100644 --- a/pay-java-wx/pom.xml +++ b/pay-java-wx/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b + 2.14.3-b1 4.0.0 pay-java-wx @@ -20,7 +20,11 @@ - + + org.bouncycastle + bcprov-jdk15on + ${bcprov-jdk15on.version} + 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 index ead42a1..ea883a7 100644 --- 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 @@ -75,6 +75,7 @@ public enum WxTransactionType implements TransactionType { if (null == h5Info) { sceneInfo.setH5Info(new H5Info(order.getWapName(), order.getWapUrl())); } + parameters.put("scene_info", sceneInfo); } }, diff --git a/pay-java-yiji/pom.xml b/pay-java-yiji/pom.xml index d8abf2e..b52209c 100644 --- a/pay-java-yiji/pom.xml +++ b/pay-java-yiji/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b + 2.14.3-b1 4.0.0 diff --git a/pom.xml b/pom.xml index 4b558b7..e5a6f99 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.egzosn pay-java-parent pom - 2.14.3-b + 2.14.3-b1 Pay Java - Parent Pay Java Parent @@ -70,12 +70,13 @@ - 2.14.3-b + 2.14.3-b1 4.5.4 1.2.17 1.2.73 3.3.1 4.0.1 + 1.59 -- Gitee From e10812cacc11db2d7e0b8dc9603a578aed157c48 Mon Sep 17 00:00:00 2001 From: egzosn Date: Sun, 31 Oct 2021 21:01:27 +0800 Subject: [PATCH 100/165] =?UTF-8?q?=E7=BD=91=E9=A1=B5=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=EF=BC=8C=E5=A6=82=E6=9E=9C=E6=9C=89=E5=87=BA?= =?UTF-8?q?=E7=8E=B0'(=E5=8D=95=E5=BC=95=E5=8F=B7)=E5=88=99=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E5=89=94=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/egzosn/pay/common/api/BasePayService.java | 6 ++++++ .../com/egzosn/pay/demo/controller/AliPayController.java | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) 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 df18742..52ccdbb 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 @@ -162,6 +162,12 @@ public abstract class BasePayService implements Pay */ @Override public String toPay(O order) { + if (StringUtils.isNotEmpty(order.getSubject()) && order.getSubject().contains("'")){ + order.setSubject(order.getSubject().replace("'","")); + } + if (StringUtils.isNotEmpty(order.getBody()) && order.getBody().contains("'")){ + order.setBody(order.getBody().replace("'","")); + } Map orderInfo = orderInfo(order); return buildRequest(orderInfo, MethodType.POST); } 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 8911a83..df8f2e6 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 @@ -123,7 +123,7 @@ public class AliPayController { @RequestMapping(value = "toPay.html", produces = "text/html;charset=UTF-8") public String toPay(BigDecimal price) { //及时收款 - PayOrder order = new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(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 ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), AliTransactionType.WAP); @@ -200,7 +200,7 @@ public class AliPayController { //支付结果 Map params = service.microPay(order); //校验 - if (service.verify(params)) { + if (service.verify(new NoticeParams(params))) { //支付校验通过后的处理 //......业务逻辑处理块........ -- Gitee From 7acb047c45122ecc71a09c2161dc9ee2dc35293b Mon Sep 17 00:00:00 2001 From: egzosn Date: Sun, 31 Oct 2021 21:14:36 +0800 Subject: [PATCH 101/165] =?UTF-8?q?=E5=8F=91=E7=89=88=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 33e6d9d..42a6764 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ com.egzosn {module-name} - 2.14.3-b + 2.14.3-b1 ``` -- Gitee From 556c3e766ec9f412da72182396c6132a0a2fe760 Mon Sep 17 00:00:00 2001 From: egzosn Date: Fri, 12 Nov 2021 20:58:24 +0800 Subject: [PATCH 102/165] =?UTF-8?q?=E5=8A=A0=E5=85=A5TLSv1.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/egzosn/pay/common/http/HttpRequestTemplate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 218a26e..ba60626 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 @@ -153,7 +153,7 @@ public class HttpRequestTemplate { //指定TLS版本 sslsf = new SSLConnectionSocketFactory( - sslcontext, new String[]{"TLSv1"}, null, + sslcontext, new String[]{"TLSv1","TLSv1.2"}, null, new DefaultHostnameVerifier()); return sslsf; -- Gitee From e4ca2a0e7db87fd395250c1f43002a07703e56c9 Mon Sep 17 00:00:00 2001 From: egzosn Date: Fri, 12 Nov 2021 20:59:08 +0800 Subject: [PATCH 103/165] =?UTF-8?q?=E5=8A=A0=E5=85=A5TLSv1.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pay-java-ali/pom.xml | 2 +- pay-java-baidu/pom.xml | 2 +- pay-java-common/pom.xml | 2 +- pay-java-demo/pom.xml | 2 +- pay-java-fuiou/pom.xml | 2 +- pay-java-payoneer/pom.xml | 2 +- pay-java-paypal/pom.xml | 2 +- pay-java-union/pom.xml | 2 +- pay-java-web-support/pom.xml | 2 +- pay-java-wx-youdian/pom.xml | 2 +- pay-java-wx/pom.xml | 2 +- pay-java-yiji/pom.xml | 2 +- pom.xml | 4 ++-- 13 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index 467543a..566487f 100644 --- a/pay-java-ali/pom.xml +++ b/pay-java-ali/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b1 + 2.14.3-b2 4.0.0 pay-java-ali diff --git a/pay-java-baidu/pom.xml b/pay-java-baidu/pom.xml index 23af590..4d1c45f 100644 --- a/pay-java-baidu/pom.xml +++ b/pay-java-baidu/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b1 + 2.14.3-b2 4.0.0 pay-java-baidu diff --git a/pay-java-common/pom.xml b/pay-java-common/pom.xml index 042a30c..7da4d06 100644 --- a/pay-java-common/pom.xml +++ b/pay-java-common/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b1 + 2.14.3-b2 4.0.0 jar diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index 3ddbb47..348317b 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b1 + 2.14.3-b2 4.0.0 war diff --git a/pay-java-fuiou/pom.xml b/pay-java-fuiou/pom.xml index 03c375b..08cea48 100644 --- a/pay-java-fuiou/pom.xml +++ b/pay-java-fuiou/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b1 + 2.14.3-b2 4.0.0 pay-java-fuiou diff --git a/pay-java-payoneer/pom.xml b/pay-java-payoneer/pom.xml index a6ac61a..86ed571 100644 --- a/pay-java-payoneer/pom.xml +++ b/pay-java-payoneer/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b1 + 2.14.3-b2 4.0.0 pay-java-payoneer diff --git a/pay-java-paypal/pom.xml b/pay-java-paypal/pom.xml index 37b129e..da3ae52 100644 --- a/pay-java-paypal/pom.xml +++ b/pay-java-paypal/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b1 + 2.14.3-b2 4.0.0 diff --git a/pay-java-union/pom.xml b/pay-java-union/pom.xml index 5024eec..47d0abc 100644 --- a/pay-java-union/pom.xml +++ b/pay-java-union/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b1 + 2.14.3-b2 4.0.0 diff --git a/pay-java-web-support/pom.xml b/pay-java-web-support/pom.xml index 03c1aab..021cb2c 100644 --- a/pay-java-web-support/pom.xml +++ b/pay-java-web-support/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b1 + 2.14.3-b2 4.0.0 jar diff --git a/pay-java-wx-youdian/pom.xml b/pay-java-wx-youdian/pom.xml index ac48d3d..3f2bd4a 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.14.3-b1 + 2.14.3-b2 4.0.0 pay-java-wx-youdian diff --git a/pay-java-wx/pom.xml b/pay-java-wx/pom.xml index 13a2089..e6dd416 100644 --- a/pay-java-wx/pom.xml +++ b/pay-java-wx/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b1 + 2.14.3-b2 4.0.0 pay-java-wx diff --git a/pay-java-yiji/pom.xml b/pay-java-yiji/pom.xml index b52209c..c0f4aa7 100644 --- a/pay-java-yiji/pom.xml +++ b/pay-java-yiji/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b1 + 2.14.3-b2 4.0.0 diff --git a/pom.xml b/pom.xml index e5a6f99..3c1d50c 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.egzosn pay-java-parent pom - 2.14.3-b1 + 2.14.3-b2 Pay Java - Parent Pay Java Parent @@ -70,7 +70,7 @@ - 2.14.3-b1 + 2.14.3-b2 4.5.4 1.2.17 1.2.73 -- Gitee From 6ab618ca44bd460ae18f1e152f7943c292698d12 Mon Sep 17 00:00:00 2001 From: egzosn Date: Sat, 20 Nov 2021 23:49:58 +0800 Subject: [PATCH 104/165] =?UTF-8?q?1.=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=83=A8=E5=88=86jdk=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E8=AF=81=E4=B9=A6=E9=97=AE=E9=A2=98=202.?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E4=B8=8B=E8=BD=BD=E5=AF=B9=E8=B4=A6=E5=8D=95?= =?UTF-8?q?=E6=8A=A5=E6=97=A0=E6=B3=95=E7=B1=BB=E5=9E=8B=E8=BD=AC=E6=8D=A2?= =?UTF-8?q?=E9=97=AE=E9=A2=98=203.=E5=BE=AE=E4=BF=A1=E9=80=80=E6=AC=BE?= =?UTF-8?q?=E9=9D=9E=E5=BF=85=E5=A1=AB=E5=8F=82=E6=95=B0=E5=BF=BD=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pay-java-ali/pom.xml | 1 - .../pay/ali/utils/AntCertificationUtil.java | 32 +++++----- .../pay/common/http/ClientHttpRequest.java | 61 ++++++++++--------- .../controller/WxV3CombinePayController.java | 2 +- .../demo/controller/WxV3PayController.java | 12 ++-- .../WxV3ProfitSharingController.java | 2 +- pay-java-wx/pom.xml | 2 +- .../wx/v3/api/DefaultWxPayAssistService.java | 3 +- .../egzosn/pay/wx/v3/api/WxPayService.java | 4 +- .../pay/wx/v3/utils/AntCertificationUtil.java | 11 +++- pom.xml | 6 +- 11 files changed, 76 insertions(+), 60 deletions(-) diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index 566487f..9014156 100644 --- a/pay-java-ali/pom.xml +++ b/pay-java-ali/pom.xml @@ -22,7 +22,6 @@ org.bouncycastle bcprov-jdk15on - ${bcprov-jdk15on.version} 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 index b3057cd..e21af86 100644 --- 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 @@ -24,9 +24,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.bouncycastle.jce.provider.BouncyCastleProvider; import com.egzosn.pay.common.bean.result.PayException; import com.egzosn.pay.common.exception.PayErrorException; @@ -38,17 +38,19 @@ import com.egzosn.pay.common.util.str.StringUtils; * 证书文件可信校验 * * @author junying.wjy - * @version $Id: AntCertificationUtil.java, v 0.1 2019-07-29 下午04:46 junying.wjy Exp $ - * * @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 { - Security.removeProvider("SunEC"); - Security.addProvider(new BouncyCastleProvider()); + if (null == Security.getProvider("BC")) { + Security.removeProvider("SunEC"); + Security.addProvider(new BouncyCastleProvider()); + } } + /** * 验证证书是否可信 * @@ -141,7 +143,7 @@ public class AntCertificationUtil { //验证证书链 for (int i = 1; i < certs.length; i++) { X509Certificate cert = certs[i]; - if (!checkValidity(cert)){ + if (!checkValidity(cert)) { return false; } verifySignature(prev.getPublicKey(), cert); @@ -155,7 +157,7 @@ public class AntCertificationUtil { /** * 验证证书链是否是信任证书库中证书签发的 * - * @param cert 目标验证证书 + * @param cert 目标验证证书 * @return 验证结果 */ private static boolean checkValidity(X509Certificate cert) { @@ -172,13 +174,11 @@ public class AntCertificationUtil { } - - private static void verifySignature(PublicKey publicKey, X509Certificate cert){ + private static void verifySignature(PublicKey publicKey, X509Certificate cert) { try { cert.verify(publicKey); - } - catch (GeneralSecurityException e) { - throw new PayErrorException(new PayException("证书校验失败", e.getMessage())); + } catch (GeneralSecurityException e) { + throw new PayErrorException(new PayException("证书校验失败", e.getMessage())); } } @@ -281,7 +281,7 @@ public class AntCertificationUtil { addressingDown(issuerMap, certChain, subject); } - private static X509Certificate[] readPemCertChain(String cert){ + private static X509Certificate[] readPemCertChain(String cert) { ByteArrayInputStream inputStream = new ByteArrayInputStream(cert.getBytes()); CertificateFactory factory = null; try { @@ -305,7 +305,7 @@ public class AntCertificationUtil { String rootCertSN = null; try { X509Certificate[] x509Certificates = readPemCertChain(rootCertContent); - if (null == x509Certificates){ + if (null == x509Certificates) { return null; } MessageDigest md = MessageDigest.getInstance("MD5"); @@ -383,8 +383,6 @@ public class AntCertificationUtil { } - - public static String readFromInputStream(InputStream cert) { try { return new String(IOUtils.toByteArray(cert), StandardCharsets.UTF_8); 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 49681ed..99ec689 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 @@ -317,6 +317,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"; @@ -359,35 +391,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-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 index ce65d86..2724601 100644 --- 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 @@ -46,7 +46,7 @@ public class WxV3CombinePayController { private WxCombinePayService service = null; - @PostConstruct //没有证书的情况下注释掉,避免启动报错 +// @PostConstruct //没有证书的情况下注释掉,避免启动报错 public void init() { WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage(); wxPayConfigStorage.setAppId("wxc7b993ff15a9f26c"); 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 index 621c83f..e9fbd6f 100644 --- 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 @@ -27,6 +27,9 @@ 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.order.H5Info; +import com.egzosn.pay.wx.v3.bean.order.SceneInfo; +import com.egzosn.pay.wx.v3.utils.WxConst; /** * 微信V3发起支付入口 @@ -75,12 +78,11 @@ public class WxV3PayController { @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); - order.setSpbillCreateIp(request.getHeader("X-Real-IP")); StringBuffer requestURL = request.getRequestURL(); - //设置网页地址 - order.setWapUrl(requestURL.substring(0, requestURL.indexOf("/") > 0 ? requestURL.indexOf("/") : requestURL.length())); - //设置网页名称 - order.setWapName("在线充值"); + 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); 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 index 299fea1..793864d 100644 --- 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 @@ -44,7 +44,7 @@ public class WxV3ProfitSharingController { private WxProfitSharingService service = null; - @PostConstruct //没有证书的情况下注释掉,避免启动报错 +// @PostConstruct //没有证书的情况下注释掉,避免启动报错 public void init() { WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage(); wxPayConfigStorage.setAppId("wxc7b993ff15a9f26c"); diff --git a/pay-java-wx/pom.xml b/pay-java-wx/pom.xml index e6dd416..b4ecdce 100644 --- a/pay-java-wx/pom.xml +++ b/pay-java-wx/pom.xml @@ -23,7 +23,7 @@ org.bouncycastle bcprov-jdk15on - ${bcprov-jdk15on.version} + 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 index 906a529..9d6ab0d 100644 --- 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 @@ -70,7 +70,8 @@ public class DefaultWxPayAssistService implements WxPayAssistService { * @return 响应内容体 */ public JSONObject doExecute(Map parameters, TransactionType transactionType) { - String requestBody = JSON.toJSONString(parameters, SerializerFeature.WriteMapNullValue); +// String requestBody = JSON.toJSONString(parameters, SerializerFeature.WriteMapNullValue); + String requestBody = JSON.toJSONString(parameters); return doExecute(requestBody, transactionType); } 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 index 75d5f99..be3e5e1 100644 --- 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 @@ -302,7 +302,7 @@ public class WxPayService extends BasePayService { params.put("prepayid", prepayId); params.put("package", "Sign=WXPay"); } - String signText = StringUtils.joining("\n", appId, timeStamp, prepayId); + 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; @@ -525,6 +525,8 @@ public class WxPayService extends BasePayService { } + + /** * 查询退款 * 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 index 2c4cedb..46c5bfc 100644 --- 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 @@ -10,6 +10,7 @@ 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; @@ -21,6 +22,8 @@ import javax.crypto.Cipher; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + import com.egzosn.pay.common.exception.PayErrorException; import com.egzosn.pay.common.util.sign.encrypt.Base64; import com.egzosn.pay.wx.bean.WxPayError; @@ -49,6 +52,10 @@ public final class AntCertificationUtil { static { try { + if (null == Security.getProvider("BC")) { + Security.removeProvider("SunEC"); + Security.addProvider(new BouncyCastleProvider()); + } PKCS12_KEY_STORE = KeyStore.getInstance("PKCS12"); } catch (KeyStoreException e) { @@ -133,7 +140,7 @@ public final class AntCertificationUtil { * @param cipherText 需要解密的文本 * @param secretKey 密钥 * @param characterEncoding 编码类型 - * @return 解密后的信息 + * @return 解密后的信息 */ public static String decryptToString(String associatedData, String nonce, String cipherText, String secretKey, String characterEncoding) { @@ -156,7 +163,7 @@ public final class AntCertificationUtil { * * @param message the message * @param certificate the certificate - * @return 加密后的内容 + * @return 加密后的内容 */ public static String encryptToString(String message, Certificate certificate) { try { diff --git a/pom.xml b/pom.xml index 3c1d50c..0cca8c9 100644 --- a/pom.xml +++ b/pom.xml @@ -106,7 +106,11 @@ fastjson 1.2.73 - + + org.bouncycastle + bcprov-jdk15on + ${bcprov-jdk15on.version} + -- Gitee From f40afda8904ab605f4693795f4992633deac0a3d Mon Sep 17 00:00:00 2001 From: egzosn Date: Tue, 23 Nov 2021 09:36:53 +0800 Subject: [PATCH 105/165] 2.14.3-b2 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 42a6764..154d076 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ com.egzosn {module-name} - 2.14.3-b1 + 2.14.3-b2 ``` -- Gitee From 213c8e84a3dd5745cda7a790fccac10e19d4399e Mon Sep 17 00:00:00 2001 From: egzosn Date: Tue, 23 Nov 2021 22:52:59 +0800 Subject: [PATCH 106/165] =?UTF-8?q?2.14.3-b3=E5=BC=80=E5=A7=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pay-java-ali/pom.xml | 2 +- pay-java-baidu/pom.xml | 2 +- pay-java-common/pom.xml | 2 +- pay-java-demo/pom.xml | 2 +- pay-java-fuiou/pom.xml | 2 +- pay-java-payoneer/pom.xml | 2 +- pay-java-paypal/pom.xml | 2 +- pay-java-union/pom.xml | 2 +- pay-java-web-support/pom.xml | 2 +- pay-java-wx-youdian/pom.xml | 2 +- pay-java-wx/pom.xml | 2 +- pay-java-yiji/pom.xml | 2 +- pom.xml | 4 ++-- 13 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index 9014156..863e118 100644 --- a/pay-java-ali/pom.xml +++ b/pay-java-ali/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b2 + 2.14.3-b3 4.0.0 pay-java-ali diff --git a/pay-java-baidu/pom.xml b/pay-java-baidu/pom.xml index 4d1c45f..54da1d4 100644 --- a/pay-java-baidu/pom.xml +++ b/pay-java-baidu/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b2 + 2.14.3-b3 4.0.0 pay-java-baidu diff --git a/pay-java-common/pom.xml b/pay-java-common/pom.xml index 7da4d06..055aed4 100644 --- a/pay-java-common/pom.xml +++ b/pay-java-common/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b2 + 2.14.3-b3 4.0.0 jar diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index 348317b..78644eb 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b2 + 2.14.3-b3 4.0.0 war diff --git a/pay-java-fuiou/pom.xml b/pay-java-fuiou/pom.xml index 08cea48..a941445 100644 --- a/pay-java-fuiou/pom.xml +++ b/pay-java-fuiou/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b2 + 2.14.3-b3 4.0.0 pay-java-fuiou diff --git a/pay-java-payoneer/pom.xml b/pay-java-payoneer/pom.xml index 86ed571..0a7817c 100644 --- a/pay-java-payoneer/pom.xml +++ b/pay-java-payoneer/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b2 + 2.14.3-b3 4.0.0 pay-java-payoneer diff --git a/pay-java-paypal/pom.xml b/pay-java-paypal/pom.xml index da3ae52..d74b445 100644 --- a/pay-java-paypal/pom.xml +++ b/pay-java-paypal/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b2 + 2.14.3-b3 4.0.0 diff --git a/pay-java-union/pom.xml b/pay-java-union/pom.xml index 47d0abc..d5b733d 100644 --- a/pay-java-union/pom.xml +++ b/pay-java-union/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b2 + 2.14.3-b3 4.0.0 diff --git a/pay-java-web-support/pom.xml b/pay-java-web-support/pom.xml index 021cb2c..126b206 100644 --- a/pay-java-web-support/pom.xml +++ b/pay-java-web-support/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b2 + 2.14.3-b3 4.0.0 jar diff --git a/pay-java-wx-youdian/pom.xml b/pay-java-wx-youdian/pom.xml index 3f2bd4a..2bea82d 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.14.3-b2 + 2.14.3-b3 4.0.0 pay-java-wx-youdian diff --git a/pay-java-wx/pom.xml b/pay-java-wx/pom.xml index b4ecdce..fddcf5b 100644 --- a/pay-java-wx/pom.xml +++ b/pay-java-wx/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b2 + 2.14.3-b3 4.0.0 pay-java-wx diff --git a/pay-java-yiji/pom.xml b/pay-java-yiji/pom.xml index c0f4aa7..ee63230 100644 --- a/pay-java-yiji/pom.xml +++ b/pay-java-yiji/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b2 + 2.14.3-b3 4.0.0 diff --git a/pom.xml b/pom.xml index 0cca8c9..06f14ba 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.egzosn pay-java-parent pom - 2.14.3-b2 + 2.14.3-b3 Pay Java - Parent Pay Java Parent @@ -70,7 +70,7 @@ - 2.14.3-b2 + 2.14.3-b3 4.5.4 1.2.17 1.2.73 -- Gitee From 7a80e5a09cbcd75ae477f50e655f8c2a9a632051 Mon Sep 17 00:00:00 2001 From: egzosn Date: Tue, 23 Nov 2021 22:53:12 +0800 Subject: [PATCH 107/165] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=9B=B4=E8=BF=9E?= =?UTF-8?q?=E5=95=86=E6=88=B7=E6=9F=A5=E8=AF=A2=E8=AE=A2=E5=8D=95=E4=B8=8E?= =?UTF-8?q?=E5=85=B3=E9=97=AD=E8=AE=A2=E5=8D=95=E6=8F=90=E7=A4=BAMchId?= =?UTF-8?q?=E5=BF=85=E5=A1=AB=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pay/wx/v3/api/WxParameterStructure.java | 53 ++++++++++--------- .../pay/wx/v3/api/WxPayConfigStorage.java | 8 +-- .../egzosn/pay/wx/v3/api/WxPayService.java | 13 +++-- 3 files changed, 42 insertions(+), 32 deletions(-) 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 index 3f3596b..bd0b101 100644 --- 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 @@ -9,11 +9,11 @@ import com.egzosn.pay.common.bean.OrderParaStructure; import com.egzosn.pay.common.bean.PayOrder; import com.egzosn.pay.common.http.UriVariables; import com.egzosn.pay.common.util.MapGen; -import com.egzosn.pay.common.util.str.StringUtils; import com.egzosn.pay.wx.v3.utils.WxConst; /** * 微信参数构造器 + * * @author Egan *

  * email egzosn@gmail.com
@@ -32,19 +32,19 @@ public class WxParameterStructure {
      *
      * @return 公共参数
      */
-    public Map getPublicParameters() {
-
-        Map parameters = new LinkedHashMap<>();
+    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;
     }
 
 
-
-
-
-
     /**
      * 加载结算信息
      *
@@ -65,7 +65,6 @@ public class WxParameterStructure {
     }
 
 
-
     /**
      * 初始化通知URL必须为直接可访问的URL,不允许携带查询串,要求必须为https地址。
      *
@@ -77,32 +76,38 @@ public class WxParameterStructure {
         OrderParaStructure.loadParameters(parameters, WxConst.NOTIFY_URL, order);
     }
 
+
     /**
-     * 获取服务商相关信息
+     * 获取商户相关信息
      *
-     * @return 服务商相关信息
+     * @return 商户相关信息
      */
-    public String getSpParameters() {
+    public String getMchParameters() {
         Map attr = initSubMchId(null);
-        OrderParaStructure.loadParameters(attr, WxConst.SP_MCH_ID, payConfigStorage.getSpMchId());
+        OrderParaStructure.loadParameters(attr, payConfigStorage.isPartner() ? WxConst.SP_MCH_ID : WxConst.MCH_ID, payConfigStorage.getMchId());
         return UriVariables.getMapToParameters(attr);
     }
+
     /**
      * 初始化商户相关信息
      *
      * @param parameters 参数信息
      */
-    public void initPartner(Map parameters) {
+    public Map initPartner(Map parameters) {
         if (null == parameters) {
-            parameters = new HashMap<>();
+            parameters = new LinkedHashMap<>();
         }
-        if (StringUtils.isNotEmpty(payConfigStorage.getSpAppId()) && StringUtils.isNotEmpty(payConfigStorage.getSpMchId())) {
-            payConfigStorage.setPartner(true);
-            parameters.put("sp_appid", payConfigStorage.getSpAppId());
-            parameters.put(WxConst.SP_MCH_ID, payConfigStorage.getSpMchId());
+        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;
         }
-        OrderParaStructure.loadParameters(parameters, "sub_appid", payConfigStorage.getSubAppId());
-        initSubMchId(parameters);
+
+        parameters.put(WxConst.APPID, payConfigStorage.getAppId());
+        parameters.put(WxConst.MCH_ID, payConfigStorage.getMchId());
+        return parameters;
     }
 
 
@@ -116,10 +121,10 @@ public class WxParameterStructure {
         if (null == parameters) {
             parameters = new HashMap<>();
         }
-        if (StringUtils.isNotEmpty(payConfigStorage.getSubMchId())) {
-            payConfigStorage.setPartner(true);
-            parameters.put(WxConst.SUB_MCH_ID, payConfigStorage.getSubMchId());
+        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/WxPayConfigStorage.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayConfigStorage.java
index b668a63..63af5eb 100644
--- 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
@@ -169,20 +169,20 @@ public class WxPayConfigStorage extends BasePayConfigStorage {
     }
 
     public String getSpAppId() {
-        return spAppId;
+        return getAppId();
     }
 
     public void setSpAppId(String spAppId) {
-        this.spAppId = spAppId;
+        setAppId(spAppId);
         addAttr("spAppId", spAppId);
     }
 
     public String getSpMchId() {
-        return spMchId;
+        return getMchId();
     }
 
     public void setSpMchId(String spMchId) {
-        this.spMchId = spMchId;
+        setMchId(spMchId);
         addAttr("spMchId", spMchId);
     }
 
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
index be3e5e1..f175615 100644
--- 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
@@ -134,6 +134,7 @@ public class WxPayService extends BasePayService {
      */
     @Override
     protected void initAfter() {
+        payConfigStorage.setPartner(StringUtils.isNotEmpty(payConfigStorage.getSubMchId()));
         new Thread(() -> {
             payConfigStorage.loadCertEnvironment();
             wxParameterStructure = new WxParameterStructure(payConfigStorage);
@@ -234,8 +235,9 @@ public class WxPayService extends BasePayService {
     public JSONObject unifiedOrder(PayOrder order) {
 
         //统一下单
-        Map parameters = wxParameterStructure.getPublicParameters();
-        wxParameterStructure.initPartner(parameters);
+        Map parameters =  wxParameterStructure.initPartner(null);
+       ;
+//        wxParameterStructure.getPublicParameters(parameters);
         // 商品描述
         OrderParaStructure.loadParameters(parameters, WxConst.DESCRIPTION, order.getSubject());
         OrderParaStructure.loadParameters(parameters, WxConst.DESCRIPTION, order.getBody());
@@ -283,6 +285,9 @@ public class WxPayService extends BasePayService {
 
         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");
@@ -457,7 +462,7 @@ public class WxPayService extends BasePayService {
     public Map query(AssistOrder assistOrder) {
         String transactionId = assistOrder.getTradeNo();
         String outTradeNo = assistOrder.getOutTradeNo();
-        String parameters = wxParameterStructure.getSpParameters();
+        String parameters = wxParameterStructure.getMchParameters();
         WxTransactionType transactionType = WxTransactionType.QUERY_TRANSACTION_ID;
         String uriVariable = transactionId;
         if (StringUtils.isNotEmpty(outTradeNo)) {
@@ -490,7 +495,7 @@ public class WxPayService extends BasePayService {
      */
     @Override
     public Map close(AssistOrder assistOrder) {
-        String parameters = wxParameterStructure.getSpParameters();
+        String parameters = wxParameterStructure.getMchParameters();
         return getAssistService().doExecute(parameters, WxTransactionType.CLOSE, assistOrder.getOutTradeNo());
     }
 
-- 
Gitee


From 999c446bd45926037c63306f687aa800fe4ef3ed Mon Sep 17 00:00:00 2001
From: egzosn 
Date: Sun, 28 Nov 2021 22:23:45 +0800
Subject: [PATCH 108/165] =?UTF-8?q?v3=E9=80=80=E6=AC=BEint=E7=B1=BB?=
 =?UTF-8?q?=E5=9E=8B=E5=AF=BC=E8=87=B4=E9=BB=98=E8=AE=A4=E5=80=BC=E5=87=BA?=
 =?UTF-8?q?=E7=8E=B0=E7=AD=BE=E5=90=8D=E5=A4=B1=E8=B4=A5=E6=94=B9=E7=94=A8?=
 =?UTF-8?q?Integer?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../egzosn/pay/wx/v3/bean/order/Detail.java   |  6 ++--
 .../com/egzosn/pay/wx/v3/bean/order/From.java |  6 ++--
 .../pay/wx/v3/bean/order/GoodsDetail.java     | 12 +++----
 .../pay/wx/v3/bean/order/RefundAmount.java    | 36 +++++++++----------
 4 files changed, 30 insertions(+), 30 deletions(-)

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
index ede9be6..d11875d 100644
--- 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
@@ -15,7 +15,7 @@ public class Detail {
      * 订单原价
      */
     @JSONField(name = "cost_price")
-    private int costPrice;
+    private Integer costPrice;
     /**
      * 商家小票
      */
@@ -25,11 +25,11 @@ public class Detail {
     @JSONField(name = "goods_detail")
     private List goodsDetail;
 
-    public int getCostPrice() {
+    public Integer getCostPrice() {
         return costPrice;
     }
 
-    public void setCostPrice(int costPrice) {
+    public void setCostPrice(Integer costPrice) {
         this.costPrice = costPrice;
     }
 
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
index 84812ae..7c7a9e0 100644
--- 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
@@ -19,7 +19,7 @@ public class From {
     /**
      * 对应账户出资金额
      */
-    private int amount;
+    private Integer amount;
 
     public String getAccount() {
         return account;
@@ -29,11 +29,11 @@ public class From {
         this.account = account;
     }
 
-    public int getAmount() {
+    public Integer getAmount() {
         return amount;
     }
 
-    public void setAmount(int 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
index e1155d0..ad09ccb 100644
--- 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
@@ -33,13 +33,13 @@ public class GoodsDetail {
      * 商品数量
      */
 
-    private int quantity;
+    private Integer quantity;
     /**
      * 商品单价
      * 商品单价,单位为分
      */
     @JSONField(name = "unit_price")
-    private int unitPrice ;
+    private Integer unitPrice ;
 
 
     public String getMerchantGoodsId() {
@@ -66,19 +66,19 @@ public class GoodsDetail {
         this.goodsName = goodsName;
     }
 
-    public int getQuantity() {
+    public Integer getQuantity() {
         return quantity;
     }
 
-    public void setQuantity(int quantity) {
+    public void setQuantity(Integer quantity) {
         this.quantity = quantity;
     }
 
-    public int getUnitPrice() {
+    public Integer getUnitPrice() {
         return unitPrice;
     }
 
-    public void setUnitPrice(int 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/RefundAmount.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/order/RefundAmount.java
index fbac901..acc691f 100644
--- 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
@@ -17,7 +17,7 @@ public class RefundAmount extends Amount {
     /**
      * 退款金额,单位分
      */
-    private int refund;
+    private Integer refund;
 
     /**
      * 退款出资的账户类型及金额信息
@@ -28,38 +28,38 @@ public class RefundAmount extends Amount {
      * 用户支付金额,单位分
      */
     @JSONField(name = "payer_total")
-    private int payerTotal;
+    private Integer payerTotal;
     /**
      * 用户退款金额
      * 退款给用户的金额,不包含所有优惠券金额
      */
     @JSONField(name = "payer_refund")
-    private int payerRefund;
+    private Integer payerRefund;
     /**
      * 应结退款金额
      * 去掉非充值代金券退款金额后的退款金额,单位为分,退款金额=申请退款金额-非充值代金券退款金额,退款金额<=申请退款金额
      */
     @JSONField(name = "settlement_refund")
-    private int settlementRefund;
+    private Integer settlementRefund;
     /**
      * 应结订单金额
      * 应结订单金额=订单金额-免充值代金券金额,应结订单金额<=订单金额,单位为分
      */
     @JSONField(name = "settlement_total")
-    private int settlementTotal;
+    private Integer settlementTotal;
     /**
      * 优惠退款金额
      * 优惠退款金额<=退款金额,退款金额-代金券或立减优惠退款金额为现金,说明详见代金券或立减优惠,单位为分
      */
     @JSONField(name = "discount_refund")
-    private int discountRefund;
+    private Integer discountRefund;
 
 
-    public int getRefund() {
+    public Integer getRefund() {
         return refund;
     }
 
-    public void setRefund(int refund) {
+    public void setRefund(Integer refund) {
         this.refund = refund;
     }
 
@@ -71,43 +71,43 @@ public class RefundAmount extends Amount {
         this.from = from;
     }
 
-    public int getPayerTotal() {
+    public Integer getPayerTotal() {
         return payerTotal;
     }
 
-    public void setPayerTotal(int payerTotal) {
+    public void setPayerTotal(Integer payerTotal) {
         this.payerTotal = payerTotal;
     }
 
-    public int getPayerRefund() {
+    public Integer getPayerRefund() {
         return payerRefund;
     }
 
-    public void setPayerRefund(int payerRefund) {
+    public void setPayerRefund(Integer payerRefund) {
         this.payerRefund = payerRefund;
     }
 
-    public int getSettlementRefund() {
+    public Integer getSettlementRefund() {
         return settlementRefund;
     }
 
-    public void setSettlementRefund(int settlementRefund) {
+    public void setSettlementRefund(Integer settlementRefund) {
         this.settlementRefund = settlementRefund;
     }
 
-    public int getSettlementTotal() {
+    public Integer getSettlementTotal() {
         return settlementTotal;
     }
 
-    public void setSettlementTotal(int settlementTotal) {
+    public void setSettlementTotal(Integer settlementTotal) {
         this.settlementTotal = settlementTotal;
     }
 
-    public int getDiscountRefund() {
+    public Integer getDiscountRefund() {
         return discountRefund;
     }
 
-    public void setDiscountRefund(int discountRefund) {
+    public void setDiscountRefund(Integer discountRefund) {
         this.discountRefund = discountRefund;
     }
 }
-- 
Gitee


From 1656a55ce6e0369e3e48c52910dd391a70ba3d3e Mon Sep 17 00:00:00 2001
From: egzosn 
Date: Mon, 29 Nov 2021 22:02:25 +0800
Subject: [PATCH 109/165] =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=AE=9D=E5=AE=8C?=
 =?UTF-8?q?=E5=96=84=E9=80=80=E6=AC=BE=E6=A1=88=E4=BE=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 pay-java-ali/README.md                        | 27 ++++++++++++-------
 .../com/egzosn/pay/ali/api/AliPayService.java |  6 ++++-
 .../egzosn/pay/common/bean/RefundOrder.java   | 11 ++++++++
 .../pay/demo/controller/AliPayController.java | 10 +++++--
 4 files changed, 42 insertions(+), 12 deletions(-)

diff --git a/pay-java-ali/README.md b/pay-java-ali/README.md
index 166131d..d48313c 100644
--- a/pay-java-ali/README.md
+++ b/pay-java-ali/README.md
@@ -269,7 +269,14 @@
 
 #### 查询退款
   ```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();
             
 ```
 
@@ -282,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/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 274ef1d..c80b21b 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
@@ -587,7 +587,11 @@ public class AliPayService extends BasePayService {
         if (!StringUtils.isEmpty(refundOrder.getRefundNo())) {
             bizContent.put("out_request_no", refundOrder.getRefundNo());
         }
-        bizContent.putAll(refundOrder.getAttrs());
+        OrderParaStructure.loadParameters(parameters, "biz_type", refundOrder);
+        OrderParaStructure.loadParameters(parameters, "refund_reason", refundOrder.getDescription());
+        OrderParaStructure.loadParameters(parameters, "store_id", refundOrder);
+        OrderParaStructure.loadParameters(parameters, "terminal_id", refundOrder);
+//        bizContent.putAll(refundOrder.getAttrs());
         //设置请求参数的集合
         parameters.put(BIZ_CONTENT, JSON.toJSONString(bizContent));
         //设置签名
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 f3cb108..4bc0fa9 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
@@ -44,6 +44,10 @@ public class RefundOrder extends AssistOrder {
      */
     private String userId;
 
+    /**
+     * 退款URL
+     */
+    private String refundUrl;
 
     public String getRefundNo() {
         return refundNo;
@@ -125,4 +129,11 @@ public class RefundOrder extends AssistOrder {
         this.totalAmount = totalAmount;
     }
 
+    public String getRefundUrl() {
+        return refundUrl;
+    }
+
+    public void setRefundUrl(String refundUrl) {
+        this.refundUrl = refundUrl;
+    }
 }
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 df8f2e6..f88f71a 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
@@ -340,11 +340,17 @@ public class AliPayController {
     /**
      * 查询退款
      *
-     * @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);
     }
 
-- 
Gitee


From 496db0b51f596e54f72d8abcf6219940afc72387 Mon Sep 17 00:00:00 2001
From: egzosn 
Date: Mon, 29 Nov 2021 22:05:06 +0800
Subject: [PATCH 110/165] =?UTF-8?q?v3=E6=8F=90=E4=BE=9B=E6=B5=B7=E5=A4=96?=
 =?UTF-8?q?=E6=94=AF=E4=BB=98=E7=9A=84=E4=BD=BF=E7=94=A8=E6=A1=88=E4=BE=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../com/egzosn/pay/demo/controller/WxV3PayController.java     | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

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
index e9fbd6f..7511364 100644
--- 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
@@ -56,11 +56,13 @@ public class WxV3PayController {
         wxPayConfigStorage.setReturnUrl("http://sailinmu.iok.la/wxV3/payBack.json");
         wxPayConfigStorage.setInputCharset("utf-8");
         //使用证书时设置为true
-        wxPayConfigStorage.setCertSign(true);
+//        wxPayConfigStorage.setCertSign(true);
         //商户API证书 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_1.shtml
         wxPayConfigStorage.setApiClientKeyP12("yifenli_mall.p12");
         wxPayConfigStorage.setCertStoreType(CertStoreType.PATH);
         service = new WxPayService(wxPayConfigStorage);
+        //微信海外支付:东南亚
+//        service.setApiServerUrl("https://apihk.mch.weixin.qq.com");
         //设置回调消息处理
         //TODO {@link com.egzosn.pay.demo.controller.WxPayController#payBack}
         service.setPayMessageHandler(new WxV3PayMessageHandler());
-- 
Gitee


From 5b6af98ac8cda018307cfa1610f0fd7835e8177a Mon Sep 17 00:00:00 2001
From: egzosn 
Date: Fri, 3 Dec 2021 23:16:04 +0800
Subject: [PATCH 111/165] =?UTF-8?q?V3=20=E8=AF=81=E4=B9=A6=E5=8A=A0?=
 =?UTF-8?q?=E8=BD=BD=E5=85=BC=E5=AE=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../com/egzosn/pay/wx/v3/utils/AntCertificationUtil.java    | 6 ++++++
 1 file changed, 6 insertions(+)

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
index 46c5bfc..4d8c324 100644
--- 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
@@ -51,6 +51,11 @@ public final class AntCertificationUtil {
     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");
+        }
+
         try {
             if (null == Security.getProvider("BC")) {
                 Security.removeProvider("SunEC");
@@ -180,4 +185,5 @@ public final class AntCertificationUtil {
         }
     }
 
+
 }
-- 
Gitee


From 7a989a7a15db359e87ce6951b0df4f1d0ab59e36 Mon Sep 17 00:00:00 2001
From: egzosn 
Date: Sat, 4 Dec 2021 22:02:55 +0800
Subject: [PATCH 112/165] =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=AE=9D=E9=80=80?=
 =?UTF-8?q?=E6=AC=BE=E5=85=BC=E5=AE=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../com/egzosn/pay/ali/api/AliPayService.java | 24 ++++++++-----------
 .../com/egzosn/pay/ali/bean/AliPayConst.java  |  8 +++++++
 .../pay/ali/bean/AliTransactionType.java      |  5 ++++
 3 files changed, 23 insertions(+), 14 deletions(-)

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 c80b21b..3b39bd8 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
@@ -32,7 +32,6 @@ import com.egzosn.pay.ali.bean.OrderSettle;
 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.MethodType;
 import com.egzosn.pay.common.bean.NoticeParams;
 import com.egzosn.pay.common.bean.Order;
@@ -499,11 +498,11 @@ public class AliPayService extends BasePayService {
     /**
      * 交易关闭接口
      *
-     * @param assistOrder    关闭订单
+     * @param assistOrder 关闭订单
      * @return 返回支付方交易关闭后的结果
      */
     @Override
-    public Map close(AssistOrder assistOrder){
+    public Map close(AssistOrder assistOrder) {
         return secondaryInterface(assistOrder.getTradeNo(), assistOrder.getOutTradeNo(), AliTransactionType.CLOSE);
     }
 
@@ -554,12 +553,14 @@ public class AliPayService extends BasePayService {
         //获取公共参数
         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));
         //设置签名
@@ -584,13 +585,8 @@ 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());
-        }
-        OrderParaStructure.loadParameters(parameters, "biz_type", refundOrder);
-        OrderParaStructure.loadParameters(parameters, "refund_reason", refundOrder.getDescription());
-        OrderParaStructure.loadParameters(parameters, "store_id", refundOrder);
-        OrderParaStructure.loadParameters(parameters, "terminal_id", refundOrder);
+        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));
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
index a56db71..edd824b 100644
--- 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
@@ -60,4 +60,12 @@ public final class AliPayConst {
      * 收款方信息
      */
     public static final String ALIPAY_CERT_SN_FIELD = "alipay_cert_sn";
+    /**
+     * 业务扩展参数
+     */
+    public static final String EXTEND_PARAMS = "extend_params";
+    public static final String BIZ_TYPE = "biz_type";
+    public static final String REFUND_REASON = "refund_reason";
+    public static final String QUERY_OPTIONS = "query_options";
+    public static final String OUT_REQUEST_NO = "out_request_no";
 }
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 13e4f12..10ae405 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"),
     /**
      * 下载对账单
      */
-- 
Gitee


From cd2294e9502aad04eb434475c85f1831ab5a18f6 Mon Sep 17 00:00:00 2001
From: egzosn 
Date: Sat, 4 Dec 2021 22:03:24 +0800
Subject: [PATCH 113/165] =?UTF-8?q?=E5=BE=AE=E4=BF=A1V3=E6=94=AF=E4=BB=98?=
 =?UTF-8?q?=EF=BC=8C=E8=BF=87=E6=9C=9F=E6=97=B6=E9=97=B4=E6=A0=BC=E5=BC=8F?=
 =?UTF-8?q?=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../src/main/java/com/egzosn/pay/common/util/DateUtils.java   | 3 ++-
 .../java/com/egzosn/pay/wx/v3/api/WxCombinePayService.java    | 4 ++--
 .../src/main/java/com/egzosn/pay/wx/v3/api/WxPayService.java  | 2 +-
 3 files changed, 5 insertions(+), 4 deletions(-)

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 1691321..cddec8b 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
@@ -58,7 +58,8 @@ 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 = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
+    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";
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
index e009e7b..a1dc98a 100644
--- 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
@@ -89,8 +89,8 @@ public class WxCombinePayService extends WxPayService {
         // 订单号
         parameters.put(WxConst.COMBINE_OUT_TRADE_NO, order.getOutTradeNo());
 
-        OrderParaStructure.loadDateParameters(parameters, WxConst.TIME_START, order, DateUtils.YYYY_MM_DD_T_HH_MM_SS);
-        OrderParaStructure.loadDateParameters(parameters, WxConst.TIME_EXPIRE, order, DateUtils.YYYY_MM_DD_T_HH_MM_SS);
+        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);
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
index f175615..e73284d 100644
--- 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
@@ -245,7 +245,7 @@ public class WxPayService extends BasePayService {
         parameters.put(WxConst.OUT_TRADE_NO, order.getOutTradeNo());
         //交易结束时间
         if (null != order.getExpirationTime()) {
-            parameters.put("time_expire", DateUtils.formatDate(order.getExpirationTime(), DateUtils.YYYYMMDDHHMMSS));
+            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);
-- 
Gitee


From 74c33ca341881c44446b456b3d49274f4a31f3a2 Mon Sep 17 00:00:00 2001
From: egzosn 
Date: Sun, 5 Dec 2021 12:40:14 +0800
Subject: [PATCH 114/165] =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=AE=9D=E6=96=B0?=
 =?UTF-8?q?=E5=A2=9E=20=E7=BD=91=E9=A1=B5=E6=94=AF=E4=BB=98=20=E4=B8=AD?=
 =?UTF-8?q?=E9=80=94=E5=8F=96=E6=B6=88=E6=94=AF=E4=BB=98=E5=9B=9E=E8=B0=83?=
 =?UTF-8?q?=E9=A1=B5=E9=9D=A2=EF=BC=8C=E6=96=B0=E5=A2=9E=20=E6=94=B6?=
 =?UTF-8?q?=E5=8D=95=E9=80=80=E6=AC=BE=E5=86=B2=E9=80=80=E5=AE=8C=E6=88=90?=
 =?UTF-8?q?=E9=80=9A=E7=9F=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../com/egzosn/pay/ali/api/AliPayService.java |  76 ++++++++--
 .../egzosn/pay/ali/api/AliPayServiceInf.java  |  22 +++
 .../com/egzosn/pay/ali/bean/AliPayConst.java  |   9 ++
 .../RefundDepositBackCompletedNotify.java     | 139 ++++++++++++++++++
 4 files changed, 236 insertions(+), 10 deletions(-)
 create mode 100644 pay-java-ali/src/main/java/com/egzosn/pay/ali/api/AliPayServiceInf.java
 create mode 100644 pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/RefundDepositBackCompletedNotify.java

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 3b39bd8..82011b8 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
@@ -10,6 +10,7 @@ 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;
@@ -61,7 +62,7 @@ import com.egzosn.pay.common.util.str.StringUtils;
  * email egzosn@gmail.com
  * date 2017-2-22 20:09
  */
-public class AliPayService extends BasePayService {
+public class AliPayService extends BasePayService implements AliPayServiceInf {
 
 
     /**
@@ -184,7 +185,6 @@ public class AliPayService extends BasePayService {
     }
 
 
-
     /**
      * 生成并设置签名
      *
@@ -249,6 +249,8 @@ public class AliPayService extends BasePayService {
             case PAGE:
                 bizContent.put(PASSBACK_PARAMS, order.getAddition());
                 bizContent.put(PRODUCT_CODE, "FAST_INSTANT_TRADE_PAY");
+                bizContent.put(AliPayConst.REQUEST_FROM_URL, payConfigStorage.getReturnUrl());
+                OrderParaStructure.loadParameters(bizContent, AliPayConst.REQUEST_FROM_URL, order);
                 setReturnUrl(orderInfo, order);
                 break;
             case WAP:
@@ -259,6 +261,9 @@ public class AliPayService extends BasePayService {
                 //默认值为QUICK_WAP_PAY。
                 bizContent.put(PRODUCT_CODE, "QUICK_WAP_PAY");
                 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:
@@ -314,12 +319,24 @@ public class AliPayService extends BasePayService {
      * @return 放回公共请求参数
      */
     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("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;
     }
@@ -482,7 +499,6 @@ public class AliPayService extends BasePayService {
     }
 
 
-
     /**
      * 交易关闭接口
      *
@@ -544,12 +560,19 @@ public class AliPayService extends BasePayService {
 
     /**
      * 申请退款接口
+     * 兼容 收单退款冲退完成通知 {@link #refundDepositBackCompleted(RefundOrder)} 与 {@link com.egzosn.pay.ali.bean.RefundDepositBackCompletedNotify}
      *
      * @param refundOrder 退款订单信息
      * @return 返回支付方申请退款后的结果
      */
     @Override
     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());
@@ -557,9 +580,9 @@ public class AliPayService extends BasePayService {
         Map bizContent = getBizContent(refundOrder.getTradeNo(), refundOrder.getOutTradeNo(), null);
         OrderParaStructure.loadParameters(bizContent, AliPayConst.OUT_REQUEST_NO, refundOrder.getRefundNo());
         bizContent.put("refund_amount", Util.conversionAmount(refundOrder.getRefundAmount()));
-        OrderParaStructure.loadParameters(bizContent,  AliPayConst.REFUND_REASON, refundOrder.getDescription());
-        OrderParaStructure.loadParameters(bizContent,  AliPayConst.REFUND_REASON, refundOrder);
-        OrderParaStructure.loadParameters(bizContent,"refund_royalty_parameters", refundOrder);
+        OrderParaStructure.loadParameters(bizContent, AliPayConst.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));
@@ -586,7 +609,7 @@ public class AliPayService extends BasePayService {
         setAppAuthToken(parameters, refundOrder.getAttrs());
         Map bizContent = getBizContent(refundOrder.getTradeNo(), refundOrder.getOutTradeNo(), null);
         OrderParaStructure.loadParameters(bizContent, AliPayConst.OUT_REQUEST_NO, refundOrder.getRefundNo());
-        OrderParaStructure.loadParameters(bizContent,  AliPayConst.QUERY_OPTIONS, refundOrder);
+        OrderParaStructure.loadParameters(bizContent, AliPayConst.QUERY_OPTIONS, refundOrder);
 //        bizContent.putAll(refundOrder.getAttrs());
         //设置请求参数的集合
         parameters.put(BIZ_CONTENT, JSON.toJSONString(bizContent));
@@ -782,4 +805,37 @@ 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;
+    }
+
+
 }
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 0000000..c425c45
--- /dev/null
+++ b/pay-java-ali/src/main/java/com/egzosn/pay/ali/api/AliPayServiceInf.java
@@ -0,0 +1,22 @@
+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); +} 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 index edd824b..dd09e40 100644 --- 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 @@ -68,4 +68,13 @@ public final class AliPayConst { public static final String REFUND_REASON = "refund_reason"; public static final String QUERY_OPTIONS = "query_options"; public static final String OUT_REQUEST_NO = "out_request_no"; + /** + * 用户付款中途退出返回商户网站的地址 + */ + public static final String QUIT_URL = "quit_url"; + /** + * 请求来源地址。如果使用ALIAPP的集成方式,用户中途取消支付会返回该地址。 + */ + public static final String REQUEST_FROM_URL = "request_from_url"; + public static final String DBACK_AMOUNT = "dback_amount"; } diff --git a/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/RefundDepositBackCompletedNotify.java b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/RefundDepositBackCompletedNotify.java new file mode 100644 index 0000000..33f1978 --- /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); + } +} -- Gitee From c78182d711f29af5ffdcb2d618b0f9de6e552363 Mon Sep 17 00:00:00 2001 From: egzosn Date: Sun, 5 Dec 2021 13:05:06 +0800 Subject: [PATCH 115/165] 2.14.3-b3 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 154d076..01dac15 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ com.egzosn {module-name} - 2.14.3-b2 + 2.14.3-b3 ``` -- Gitee From 2c2e0977857e3f29c9bc31df2e002809fcd85a8a Mon Sep 17 00:00:00 2001 From: egzosn Date: Sun, 2 Jan 2022 20:41:18 +0800 Subject: [PATCH 116/165] =?UTF-8?q?2.14.3-b4=E5=BC=80=E5=A7=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pay-java-ali/pom.xml | 2 +- pay-java-baidu/pom.xml | 2 +- pay-java-common/pom.xml | 2 +- pay-java-demo/pom.xml | 2 +- pay-java-fuiou/pom.xml | 2 +- pay-java-payoneer/pom.xml | 2 +- pay-java-paypal/pom.xml | 2 +- pay-java-union/pom.xml | 2 +- pay-java-web-support/pom.xml | 2 +- pay-java-wx-youdian/pom.xml | 2 +- pay-java-wx/pom.xml | 2 +- pay-java-yiji/pom.xml | 2 +- pom.xml | 4 ++-- 13 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index 863e118..e222896 100644 --- a/pay-java-ali/pom.xml +++ b/pay-java-ali/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b3 + 2.14.3-b4 4.0.0 pay-java-ali diff --git a/pay-java-baidu/pom.xml b/pay-java-baidu/pom.xml index 54da1d4..c8372c4 100644 --- a/pay-java-baidu/pom.xml +++ b/pay-java-baidu/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b3 + 2.14.3-b4 4.0.0 pay-java-baidu diff --git a/pay-java-common/pom.xml b/pay-java-common/pom.xml index 055aed4..fab77ed 100644 --- a/pay-java-common/pom.xml +++ b/pay-java-common/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b3 + 2.14.3-b4 4.0.0 jar diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index 78644eb..91ed777 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b3 + 2.14.3-b4 4.0.0 war diff --git a/pay-java-fuiou/pom.xml b/pay-java-fuiou/pom.xml index a941445..76e3587 100644 --- a/pay-java-fuiou/pom.xml +++ b/pay-java-fuiou/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b3 + 2.14.3-b4 4.0.0 pay-java-fuiou diff --git a/pay-java-payoneer/pom.xml b/pay-java-payoneer/pom.xml index 0a7817c..4e67128 100644 --- a/pay-java-payoneer/pom.xml +++ b/pay-java-payoneer/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b3 + 2.14.3-b4 4.0.0 pay-java-payoneer diff --git a/pay-java-paypal/pom.xml b/pay-java-paypal/pom.xml index d74b445..f4de33c 100644 --- a/pay-java-paypal/pom.xml +++ b/pay-java-paypal/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b3 + 2.14.3-b4 4.0.0 diff --git a/pay-java-union/pom.xml b/pay-java-union/pom.xml index d5b733d..1dfb536 100644 --- a/pay-java-union/pom.xml +++ b/pay-java-union/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b3 + 2.14.3-b4 4.0.0 diff --git a/pay-java-web-support/pom.xml b/pay-java-web-support/pom.xml index 126b206..630aef4 100644 --- a/pay-java-web-support/pom.xml +++ b/pay-java-web-support/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b3 + 2.14.3-b4 4.0.0 jar diff --git a/pay-java-wx-youdian/pom.xml b/pay-java-wx-youdian/pom.xml index 2bea82d..60f911f 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.14.3-b3 + 2.14.3-b4 4.0.0 pay-java-wx-youdian diff --git a/pay-java-wx/pom.xml b/pay-java-wx/pom.xml index fddcf5b..ebdb3e6 100644 --- a/pay-java-wx/pom.xml +++ b/pay-java-wx/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b3 + 2.14.3-b4 4.0.0 pay-java-wx diff --git a/pay-java-yiji/pom.xml b/pay-java-yiji/pom.xml index ee63230..19ca7d2 100644 --- a/pay-java-yiji/pom.xml +++ b/pay-java-yiji/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b3 + 2.14.3-b4 4.0.0 diff --git a/pom.xml b/pom.xml index 06f14ba..3dc1670 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.egzosn pay-java-parent pom - 2.14.3-b3 + 2.14.3-b4 Pay Java - Parent Pay Java Parent @@ -70,7 +70,7 @@ - 2.14.3-b3 + 2.14.3-b4 4.5.4 1.2.17 1.2.73 -- Gitee From 70461510d4e06e46680f3aa7bfc7f737ce23cec5 Mon Sep 17 00:00:00 2001 From: egzosn Date: Sun, 2 Jan 2022 20:41:38 +0800 Subject: [PATCH 117/165] =?UTF-8?q?=E7=8A=B6=E6=80=81=E4=B8=8D=E5=8C=BA?= =?UTF-8?q?=E5=88=86=E5=A4=A7=E5=B0=8F=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/egzosn/pay/paypal/v2/api/PayPalPayService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 8cab6f9..9dc566f 100644 --- 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 @@ -297,7 +297,7 @@ public class PayPalPayService extends BasePayService implem header.addHeader(new BasicHeader("prefer", "return=representation")); entity.setHeaders(header); JSONObject resp = getHttpRequestTemplate().postForObject(getReqUrl(order.getTransactionType()), entity, JSONObject.class); - if ("created".equals(resp.getString("state")) && StringUtils.isNotEmpty(resp.getString("id"))) { + if ("created".equalsIgnoreCase(resp.getString("state")) && StringUtils.isNotEmpty(resp.getString("id"))) { order.setTradeNo(resp.getString("id")); } return preOrderHandler(resp, order); -- Gitee From d1e26df6265bac463366419977ff67aeccbe8c90 Mon Sep 17 00:00:00 2001 From: egzosn Date: Sun, 9 Jan 2022 15:18:34 +0800 Subject: [PATCH 118/165] =?UTF-8?q?=E5=BE=AE=E4=BF=A1V3=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E5=85=B3=E9=97=AD=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/egzosn/pay/wx/v3/api/WxParameterStructure.java | 5 +++-- .../src/main/java/com/egzosn/pay/wx/v3/api/WxPayService.java | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) 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 index bd0b101..d144801 100644 --- 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 @@ -82,12 +82,13 @@ public class WxParameterStructure { * * @return 商户相关信息 */ - public String getMchParameters() { + public Map getMchParameters() { Map attr = initSubMchId(null); OrderParaStructure.loadParameters(attr, payConfigStorage.isPartner() ? WxConst.SP_MCH_ID : WxConst.MCH_ID, payConfigStorage.getMchId()); - return UriVariables.getMapToParameters(attr); + return attr; } + /** * 初始化商户相关信息 * 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 index e73284d..17c21db 100644 --- 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 @@ -462,7 +462,7 @@ public class WxPayService extends BasePayService { public Map query(AssistOrder assistOrder) { String transactionId = assistOrder.getTradeNo(); String outTradeNo = assistOrder.getOutTradeNo(); - String parameters = wxParameterStructure.getMchParameters(); + String parameters = UriVariables.getMapToParameters(wxParameterStructure.getMchParameters()); WxTransactionType transactionType = WxTransactionType.QUERY_TRANSACTION_ID; String uriVariable = transactionId; if (StringUtils.isNotEmpty(outTradeNo)) { @@ -495,7 +495,7 @@ public class WxPayService extends BasePayService { */ @Override public Map close(AssistOrder assistOrder) { - String parameters = wxParameterStructure.getMchParameters(); + String parameters = JSON.toJSONString(wxParameterStructure.getMchParameters()); return getAssistService().doExecute(parameters, WxTransactionType.CLOSE, assistOrder.getOutTradeNo()); } -- Gitee From 47b2bf9b3e56c417afff0175ef2f19676ec5f728 Mon Sep 17 00:00:00 2001 From: egzosn Date: Mon, 10 Jan 2022 23:31:37 +0800 Subject: [PATCH 119/165] 2.14.3-b4 --- README.md | 2 +- .../egzosn/pay/baidu/api/BaiduPayService.java | 25 +++---------------- .../pay/demo/controller/AliPayController.java | 2 +- 3 files changed, 6 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 01dac15..fbe4f56 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ com.egzosn {module-name} - 2.14.3-b3 + 2.14.3-b4 ``` 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 e529eba..468a46e 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 @@ -136,11 +136,10 @@ public class BaiduPayService extends BasePayService { * * @param publicKey 公钥原始字符串 * @return X509标准公钥 - * @throws InvalidKeySpecException - * @throws NoSuchAlgorithmException + * @throws InvalidKeySpecException InvalidKeySpecException + * @throws NoSuchAlgorithmException NoSuchAlgorithmException */ - private static PublicKey getPublicKeyX509(String publicKey) throws InvalidKeySpecException, - NoSuchAlgorithmException { + private static PublicKey getPublicKeyX509(String publicKey) throws InvalidKeySpecException, NoSuchAlgorithmException { if (StringUtils.isEmpty(publicKey)) { return null; } @@ -154,7 +153,7 @@ public class BaiduPayService extends BasePayService { * * @param params 待签名参数集合 * @return 待签名内容 - * @throws UnsupportedEncodingException + * @throws UnsupportedEncodingException UnsupportedEncodingException */ private String signContent(Map params) throws UnsupportedEncodingException { Map sortedParams = new TreeMap<>(new Comparator() { @@ -198,22 +197,6 @@ public class BaiduPayService extends BasePayService { return StringUtils.isNotBlank(key) && !SIGN_KEY.equalsIgnoreCase(key); } - - /** - * 验证签名 - * - * @param params 参数集 - * @param sign 签名原文 - * @return 结果 - */ - 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); - } - - /** * 返回创建的订单信息 * 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 f88f71a..7b7b4f7 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 @@ -76,8 +76,8 @@ public class AliPayController { //设置证书存储方式,这里为路径 aliPayConfigStorage.setCertStoreType(CertStoreType.CLASS_PATH); aliPayConfigStorage.setMerchantCert("ali/appCertPublicKey_2016080400165436.crt"); - aliPayConfigStorage.setAliPayCert("ali/alipayCertPublicKey_RSA2.crt"); aliPayConfigStorage.setAliPayRootCert("ali/alipayRootCert.crt"); + aliPayConfigStorage.setAliPayCert("ali/alipayCertPublicKey_RSA2.crt"); } @PostConstruct -- Gitee From 0faa3014f047edd0402a3fcb5ef73cd10d029064 Mon Sep 17 00:00:00 2001 From: egan Date: Sun, 20 Mar 2022 23:43:17 +0800 Subject: [PATCH 120/165] =?UTF-8?q?=E6=8F=90=E4=BE=9B=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E7=9B=B4=E6=8E=A5=E8=AE=BE=E7=BD=AE=E5=BC=82?= =?UTF-8?q?=E6=AD=A5=E5=9B=9E=E8=B0=83=E5=9C=B0=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egzosn/pay/common/bean/AssistOrder.java | 12 ++++++++++ .../egzosn/pay/fuiou/api/FuiouPayService.java | 15 +++++++++--- .../egzosn/pay/union/api/UnionPayService.java | 24 +++++++++++++++---- 3 files changed, 44 insertions(+), 7 deletions(-) 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 index 67f95f5..52cb462 100644 --- 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 @@ -29,6 +29,12 @@ public class AssistOrder implements Order { * 交易类型 */ private TransactionType transactionType; + + /** + * 异步回调通知 + */ + private String notifyUrl; + /** * 订单附加信息,可用于预设未提供的参数,这里会覆盖以上所有的订单信息, */ @@ -122,5 +128,11 @@ public class AssistOrder implements Order { getAttrs().put(key, value); } + public String getNotifyUrl() { + return notifyUrl; + } + public void setNotifyUrl(String notifyUrl) { + this.notifyUrl = notifyUrl; + } } 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 d46bc98..e942664 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 @@ -13,6 +13,7 @@ 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; @@ -72,6 +73,10 @@ 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"; /** @@ -199,7 +204,12 @@ public class FuiouPayService extends BasePayService { 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; + } /** * 按序添加请求参数 * @@ -222,8 +232,7 @@ public class FuiouPayService extends BasePayService { //商户接受支付结果通知地址 parameters.put("page_notify_url", payConfigStorage.getReturnUrl()); //商户接受的支付结果后台通知地址 //非必填 - parameters.put("back_notify_url", StringUtils.isBlank(payConfigStorage.getNotifyUrl()) ? "" : payConfigStorage.getNotifyUrl()); - + initNotifyUrl(parameters, order); if (null != order.getExpirationTime()) { parameters.put("order_valid_time", DateUtils.minutesRemaining(order.getExpirationTime()) + "m"); } 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 1cda4f4..42f82f3 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 @@ -31,6 +31,7 @@ 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; @@ -166,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自行选择外其他不需修改 @@ -182,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:平台商户) @@ -282,7 +298,7 @@ public class UnionPayService extends BasePayService { Map params = this.getCommonParam(); UnionTransactionType type = (UnionTransactionType) order.getTransactionType(); - + initNotifyUrl(params, order); //设置交易类型相关的参数 type.convertMap(params); @@ -645,7 +661,7 @@ public class UnionPayService extends BasePayService { String responseStr = getHttpRequestTemplate().postForObject(this.getBackTransUrl(), params, String.class); JSONObject response = UriVariables.getParametersToMap(responseStr); - if (this.verify(response)) { + if (this.verify(new NoticeParams(response))) { final UnionRefundResult refundResult = UnionRefundResult.create(response); if (SDKConstants.OK_RESP_CODE.equals(refundResult.getRespCode())) { return refundResult; -- Gitee From d9ced7ea050ca8dbed0ab24d82cba632639f1cf7 Mon Sep 17 00:00:00 2001 From: egan Date: Sun, 20 Mar 2022 23:44:23 +0800 Subject: [PATCH 121/165] =?UTF-8?q?=E6=8F=90=E4=BE=9B=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E7=9B=B4=E6=8E=A5=E8=AE=BE=E7=BD=AE=E5=BC=82?= =?UTF-8?q?=E6=AD=A5=E5=9B=9E=E8=B0=83=E5=9C=B0=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/egzosn/pay/ali/api/AliPayService.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 82011b8..4d4ecfd 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 @@ -213,8 +213,10 @@ public class AliPayService extends BasePayService implement return setSign(getOrder(order)); } - private void setNotifyUrl(Map orderInfo, PayOrder order) { - orderInfo.put(NOTIFY_URL, payConfigStorage.getNotifyUrl()); + 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); } -- Gitee From ab775eefd21604f8253c42998e82eb83faf04a4f Mon Sep 17 00:00:00 2001 From: egan Date: Sun, 20 Mar 2022 23:45:07 +0800 Subject: [PATCH 122/165] =?UTF-8?q?AES=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pay/ali/utils/AntCertificationUtil.java | 15 +--- .../java/com/egzosn/pay/common/util/XML.java | 5 +- .../pay/common/util/sign/encrypt/AES.java | 77 +++++++++++++++++++ 3 files changed, 81 insertions(+), 16 deletions(-) create mode 100644 pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/AES.java 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 index e21af86..4d1ffbc 100644 --- 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 @@ -10,9 +10,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.PublicKey; -import java.security.Security; import java.security.cert.Certificate; -import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateFactory; import java.security.cert.CertificateNotYetValidException; @@ -24,7 +22,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,12 +41,7 @@ import com.egzosn.pay.common.util.str.StringUtils; public class AntCertificationUtil { private static final Logger LOGGER = LoggerFactory.getLogger(AntCertificationUtil.class); - static { - if (null == Security.getProvider("BC")) { - Security.removeProvider("SunEC"); - Security.addProvider(new BouncyCastleProvider()); - } - } + /** * 验证证书是否可信 @@ -283,12 +275,11 @@ public class AntCertificationUtil { private static X509Certificate[] readPemCertChain(String cert) { ByteArrayInputStream inputStream = new ByteArrayInputStream(cert.getBytes()); - CertificateFactory factory = null; try { - factory = CertificateFactory.getInstance("X.509"); + CertificateFactory factory = CertificateFactory.getInstance("X.509", "BC");; Collection certificates = factory.generateCertificates(inputStream); return certificates.toArray(new X509Certificate[certificates.size()]); - } catch (CertificateException e) { + } catch (GeneralSecurityException e) { LOGGER.error("提取根证书失败", e); } return null; 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 5ebf06c..3c18fe8 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 @@ -240,10 +240,7 @@ public class XML { } } } - catch (ParserConfigurationException e) { - throw new PayErrorException(new PayException("XML failure", "XML解析失败\n" + e.getMessage())); - } - catch (SAXException e) { + catch (ParserConfigurationException | SAXException e) { throw new PayErrorException(new PayException("XML failure", "XML解析失败\n" + e.getMessage())); } finally { 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 0000000..bd4bf64 --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/AES.java @@ -0,0 +1,77 @@ +package com.egzosn.pay.common.util.sign.encrypt; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.Security; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.codec.digest.DigestUtils; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +/** + * 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 { + if (null == Security.getProvider("BC")) { + Security.removeProvider("SunEC"); + Security.addProvider(new BouncyCastleProvider()); + } + } + + /** + * 解密 + * + * @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); + } + +} -- Gitee From a4140dab5c72a77016161e559954b2f876d38fc2 Mon Sep 17 00:00:00 2001 From: egan Date: Sun, 20 Mar 2022 23:45:40 +0800 Subject: [PATCH 123/165] =?UTF-8?q?=E8=AF=B7=E6=B1=82=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=97=A0=E5=86=85=E5=AE=B9=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E6=97=B6=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/egzosn/pay/common/http/ClientHttpRequest.java | 3 +++ 1 file changed, 3 insertions(+) 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 99ec689..060a6f6 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 @@ -284,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"}; -- Gitee From be3b66e025c3c8fc21b8628356e0c7d14a02b157 Mon Sep 17 00:00:00 2001 From: egan Date: Sun, 20 Mar 2022 23:47:12 +0800 Subject: [PATCH 124/165] =?UTF-8?q?paypal=20v1=E9=A2=84=E8=AE=BE=E5=95=86?= =?UTF-8?q?=E6=88=B7=E5=8D=95=E5=8F=B7=EF=BC=9Bv2=20=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E5=88=A4=E6=96=AD=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egzosn/pay/demo/controller/PayPalV2PayController.java | 8 +++++++- .../java/com/egzosn/pay/paypal/api/PayPalPayService.java | 2 +- .../com/egzosn/pay/paypal/v2/api/PayPalPayService.java | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) 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 index 0febd38..13f5eeb 100644 --- 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 @@ -165,7 +165,13 @@ public class PayPalV2PayController { * 业务处理在对应的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 + * 付款之后不会进行扣款,需要调用 {@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 + * */ @RequestMapping(value = "payBack.json") public String payBack(HttpServletRequest request) { 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 9d26cf5..d375df6 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 @@ -195,7 +195,7 @@ public class PayPalPayService extends BasePayService { transaction.setDescription(order.getBody()); } transaction.setAmount(amount); - + transaction.setCustom(order.getOutTradeNo()); List transactions = new ArrayList<>(); transactions.add(transaction); 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 index 9dc566f..7219f41 100644 --- 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 @@ -297,7 +297,7 @@ public class PayPalPayService extends BasePayService implem 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("state")) && StringUtils.isNotEmpty(resp.getString("id"))) { + if ("created".equalsIgnoreCase(resp.getString("status")) && StringUtils.isNotEmpty(resp.getString("id"))) { order.setTradeNo(resp.getString("id")); } return preOrderHandler(resp, order); @@ -390,7 +390,7 @@ public class PayPalPayService extends BasePayService implem */ @Override public Map ordersCapture(String tradeNo) { - JSONObject ordersCaptureInfo = getHttpRequestTemplate().getForObject(getReqUrl(PayPalTransactionType.ORDERS_CAPTURE), authHeader(), JSONObject.class, tradeNo); + JSONObject ordersCaptureInfo = getHttpRequestTemplate().postForObject(getReqUrl(PayPalTransactionType.ORDERS_CAPTURE), authHeader(), JSONObject.class, tradeNo); // String captureId = ordersCaptureInfo.getJSONArray("purchaseUnits").getJSONObject(0).getJSONObject("payments").getJSONArray("captures").getJSONObject(0).getString("id"); return ordersCaptureInfo; } -- Gitee From 80ce0950ea82813bdc9d60be5792460e6d34b0f0 Mon Sep 17 00:00:00 2001 From: egan Date: Sun, 20 Mar 2022 23:48:47 +0800 Subject: [PATCH 125/165] =?UTF-8?q?v3=E5=88=A0=E9=99=A4=E5=88=86=E8=B4=A6?= =?UTF-8?q?=E6=8E=A5=E6=94=B6=E6=96=B9=E8=AF=B7=E6=B1=82url=E5=9C=B0?= =?UTF-8?q?=E5=9D=80=E9=94=99=E8=AF=AF=20#88=20=E8=AE=A2=E5=8D=95=E5=85=B3?= =?UTF-8?q?=E9=97=AD=E6=A0=B9=E6=8D=AE=E7=8A=B6=E6=80=81=E7=A0=81=E8=BF=9B?= =?UTF-8?q?=E8=A1=8C=E5=88=A4=E6=96=AD=E6=88=90=E5=8A=9F=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=20=E7=A7=BB=E9=99=A4=E5=BC=82=E6=AD=A5=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E8=AF=81=E4=B9=A6=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wx/v3/api/DefaultWxPayAssistService.java | 24 ++++++++++------- .../pay/wx/v3/api/WxParameterStructure.java | 6 ++--- .../pay/wx/v3/api/WxPayAssistService.java | 12 ++++++++- .../pay/wx/v3/api/WxPayConfigStorage.java | 3 ++- .../egzosn/pay/wx/v3/api/WxPayService.java | 27 ++++++++++--------- .../bean/WxProfitSharingTransactionType.java | 14 +++++----- .../v3/bean/sharing/ProfitSharingOrder.java | 2 +- .../pay/wx/v3/utils/AntCertificationUtil.java | 10 +++---- 8 files changed, 57 insertions(+), 41 deletions(-) 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 index 9d6ab0d..e9daaf6 100644 --- 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 @@ -2,25 +2,19 @@ package com.egzosn.pay.wx.v3.api; import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; -import java.security.Signature; import java.security.cert.Certificate; import java.util.Map; -import java.util.TreeMap; import org.apache.http.HttpEntity; import org.apache.http.entity.ContentType; import org.apache.http.message.BasicHeader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; 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.alibaba.fastjson.serializer.SerializerFeature; import com.egzosn.pay.common.bean.MethodType; -import com.egzosn.pay.common.bean.NoticeParams; import com.egzosn.pay.common.bean.PayOrder; import com.egzosn.pay.common.bean.TransactionType; import com.egzosn.pay.common.exception.PayErrorException; @@ -30,9 +24,6 @@ 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.sign.encrypt.Base64; -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.WxTransactionType; @@ -84,7 +75,7 @@ public class DefaultWxPayAssistService implements WxPayAssistService { * @param uriVariables 用于匹配表达式 * @return 响应内容体 */ - public JSONObject doExecute(String body, TransactionType transactionType, Object... uriVariables) { + 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)) { @@ -93,6 +84,19 @@ public class DefaultWxPayAssistService implements WxPayAssistService { } 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 响应内容体 + */ + 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) { 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 index d144801..24c2553 100644 --- 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 @@ -4,10 +4,9 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -import com.egzosn.pay.common.bean.Order; +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.http.UriVariables; import com.egzosn.pay.common.util.MapGen; import com.egzosn.pay.wx.v3.utils.WxConst; @@ -71,8 +70,9 @@ public class WxParameterStructure { * @param parameters 订单参数 * @param order 订单信息 */ - public void initNotifyUrl(Map parameters, Order 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); } 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 index 58bfe74..92e45ef 100644 --- 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 @@ -8,7 +8,7 @@ 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.HttpStringEntity; +import com.egzosn.pay.common.http.ResponseEntity; /** * 微信支付辅助服务 @@ -40,6 +40,16 @@ public interface WxPayAssistService { */ JSONObject doExecute(String body, TransactionType transactionType, Object... uriVariables); + /** + * 发起请求 + * + * @param body 请求内容 + * @param transactionType 交易类型 + * @param uriVariables 用于匹配表达式 + * @return 响应内容体 + */ + ResponseEntity doExecuteEntity(String body, TransactionType transactionType, Object... uriVariables); + /** * 发起请求 * 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 index 63af5eb..02e0747 100644 --- 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 @@ -72,7 +72,7 @@ public class WxPayConfigStorage extends BasePayConfigStorage { /** * 证书信息 */ - private CertEnvironment certEnvironment; + private volatile CertEnvironment certEnvironment; /** * 是否为服务商模式, 默认为false @@ -233,6 +233,7 @@ public class WxPayConfigStorage extends BasePayConfigStorage { } public CertEnvironment getCertEnvironment() { + loadCertEnvironment(); return certEnvironment; } 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 index 17c21db..8d7b3f6 100644 --- 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 @@ -135,11 +135,11 @@ public class WxPayService extends BasePayService { @Override protected void initAfter() { payConfigStorage.setPartner(StringUtils.isNotEmpty(payConfigStorage.getSubMchId())); - new Thread(() -> { - payConfigStorage.loadCertEnvironment(); - wxParameterStructure = new WxParameterStructure(payConfigStorage); - getAssistService(); - }).start(); +// new Thread(() -> { + payConfigStorage.loadCertEnvironment(); + wxParameterStructure = new WxParameterStructure(payConfigStorage); + getAssistService(); +// }).start(); } @@ -235,8 +235,8 @@ public class WxPayService extends BasePayService { public JSONObject unifiedOrder(PayOrder order) { //统一下单 - Map parameters = wxParameterStructure.initPartner(null); - ; + Map parameters = wxParameterStructure.initPartner(null); + ; // wxParameterStructure.getPublicParameters(parameters); // 商品描述 OrderParaStructure.loadParameters(parameters, WxConst.DESCRIPTION, order.getSubject()); @@ -261,7 +261,8 @@ public class WxPayService extends BasePayService { TransactionType transactionType = order.getTransactionType(); ((WxTransactionType) transactionType).setAttribute(parameters, order); - + // 订单附加信息,可用于预设未提供的参数,这里会覆盖以上所有的订单信息, + parameters.putAll(order.getAttrs()); return getAssistService().doExecute(parameters, order); } @@ -285,7 +286,7 @@ public class WxPayService extends BasePayService { Map params = new LinkedHashMap<>(); String appId = payConfigStorage.getAppId(); - if (payConfigStorage.isPartner() && StringUtils.isNotEmpty(payConfigStorage.getSubAppId())){ + if (payConfigStorage.isPartner() && StringUtils.isNotEmpty(payConfigStorage.getSubAppId())) { appId = payConfigStorage.getSubAppId(); } String timeStamp = String.valueOf(DateUtils.toEpochSecond()); @@ -496,7 +497,11 @@ public class WxPayService extends BasePayService { @Override public Map close(AssistOrder assistOrder) { String parameters = JSON.toJSONString(wxParameterStructure.getMchParameters()); - return getAssistService().doExecute(parameters, WxTransactionType.CLOSE, assistOrder.getOutTradeNo()); + final ResponseEntity responseEntity = getAssistService().doExecuteEntity(parameters, WxTransactionType.CLOSE, assistOrder.getOutTradeNo()); + if (responseEntity.getStatusCode() == 204) { + return new MapGen("statusCode", responseEntity.getStatusCode()).getAttr(); + } + return responseEntity.getBody(); } @@ -530,8 +535,6 @@ public class WxPayService extends BasePayService { } - - /** * 查询退款 * 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 index e7fb075..edb9b91 100644 --- 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 @@ -16,15 +16,15 @@ public enum WxProfitSharingTransactionType implements TransactionType { /** * 请求分账 */ - ORDERS("/v3/profitsharing/orders ", MethodType.POST), + ORDERS("/v3/profitsharing/orders", MethodType.POST), /** * 查询分账结果 */ - ORDERS_RESULT("/v3/profitsharing/orders/{out_order_no} ", MethodType.POST), + ORDERS_RESULT("/v3/profitsharing/orders/{out_order_no}", MethodType.GET), /** * 请求分账回退 */ - RETURN_ORDERS("/v3/profitsharing/return-orders ", MethodType.POST), + RETURN_ORDERS("/v3/profitsharing/return-orders", MethodType.POST), /** * 查询分账回退结果 */ @@ -32,19 +32,19 @@ public enum WxProfitSharingTransactionType implements TransactionType { /** * 解冻剩余资金 */ - ORDERS_UNFREEZE("/v3/profitsharing/orders/unfreeze ", MethodType.POST), + ORDERS_UNFREEZE("/v3/profitsharing/orders/unfreeze", MethodType.POST), /** * 查询剩余待分金额 */ - AMOUNTS("/v3/profitsharing/transactions/{transaction_id}/amounts ", MethodType.GET), + AMOUNTS("/v3/profitsharing/transactions/{transaction_id}/amounts", MethodType.GET), /** * 服务商专用-查询最大分账比例 */ - MCH_CONFIG("/v3/profitsharing/merchant-configs/{sub_mchid} ", MethodType.GET), + MCH_CONFIG("/v3/profitsharing/merchant-configs/{sub_mchid}", MethodType.GET), /** * 添加分账接收方 */ - RECEIVERS_ADD("/v3/profitsharing/receivers/add ", MethodType.POST), + RECEIVERS_ADD("/v3/profitsharing/receivers/add", MethodType.POST), /** * 删除分账接收方 */ 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 index 2e6112b..4cbdabd 100644 --- 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 @@ -101,6 +101,6 @@ public class ProfitSharingOrder extends PayOrder { public void setUnfreezeUnsplit(Boolean unfreezeUnsplit) { this.unfreezeUnsplit = unfreezeUnsplit; - addAttr(WxConst.UNFREEZE_UNSPLIT, receivers); + addAttr(WxConst.UNFREEZE_UNSPLIT, unfreezeUnsplit); } } 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 index 4d8c324..b494c62 100644 --- 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 @@ -21,8 +21,7 @@ import java.util.concurrent.ConcurrentHashMap; import javax.crypto.Cipher; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; - -import org.bouncycastle.jce.provider.BouncyCastleProvider; +import javax.management.openmbean.InvalidKeyException; import com.egzosn.pay.common.exception.PayErrorException; import com.egzosn.pay.common.util.sign.encrypt.Base64; @@ -57,10 +56,6 @@ public final class AntCertificationUtil { } try { - if (null == Security.getProvider("BC")) { - Security.removeProvider("SunEC"); - Security.addProvider(new BouncyCastleProvider()); - } PKCS12_KEY_STORE = KeyStore.getInstance("PKCS12"); } catch (KeyStoreException e) { @@ -127,6 +122,9 @@ public final class AntCertificationUtil { 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); } -- Gitee From 4baa476df8c1a70a3ace689816aa946cc79122bd Mon Sep 17 00:00:00 2001 From: egan Date: Sun, 20 Mar 2022 23:49:14 +0800 Subject: [PATCH 126/165] =?UTF-8?q?=E5=BE=AE=E4=BF=A1V2=E9=80=80=E6=AC=BE?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E5=A4=84=E7=90=86=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/egzosn/pay/wx/api/WxConst.java | 2 + .../com/egzosn/pay/wx/api/WxPayService.java | 68 ++++++++++++++----- 2 files changed, 53 insertions(+), 17 deletions(-) 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 f41eab5..fe3d4e4 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 @@ -34,5 +34,7 @@ public interface WxConst { 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/WxPayService.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/api/WxPayService.java index 2bee0d8..2f2e7f3 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 @@ -25,6 +25,7 @@ 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; @@ -40,7 +41,6 @@ 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.MethodType; import com.egzosn.pay.common.bean.NoticeParams; import com.egzosn.pay.common.bean.NoticeRequest; @@ -65,6 +65,7 @@ import com.egzosn.pay.common.util.Util; import com.egzosn.pay.common.util.XML; import com.egzosn.pay.common.util.sign.SignTextUtils; import com.egzosn.pay.common.util.sign.SignUtils; +import com.egzosn.pay.common.util.sign.encrypt.AES; import com.egzosn.pay.common.util.sign.encrypt.RSA2; import com.egzosn.pay.common.util.str.StringUtils; import com.egzosn.pay.wx.bean.RedpackOrder; @@ -156,6 +157,11 @@ public class WxPayService extends BasePayService implements */ public boolean verify(NoticeParams noticeParams) { final Map params = noticeParams.getBody(); + //如果为退款不需要校验, 直接返回, + if (params.containsKey(REQ_INFO)){ + return true; + } + if (null == 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)); @@ -247,7 +253,7 @@ public class WxPayService extends BasePayService implements ((WxTransactionType) order.getTransactionType()).setAttribute(parameters, order); //可覆盖参数 -/* OrderParaStructure.loadParameters(parameters, "notify_url", order); +/* OrderParaStructure.loadParameters(parameters, NOTIFY_URL, order); OrderParaStructure.loadParameters(parameters, "goods_tag", order); OrderParaStructure.loadParameters(parameters, "limit_pay", order); OrderParaStructure.loadParameters(parameters, "receipt", order); @@ -286,7 +292,7 @@ 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; @@ -392,6 +398,29 @@ public class WxPayService extends BasePayService implements } + /** + * 将请求参数或者请求流转化为 Map + * + * @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 * @@ -403,13 +432,16 @@ public class WxPayService extends BasePayService implements TreeMap map = new TreeMap(); try { - return new NoticeParams(XML.inputStream2Map(request.getInputStream(), map)); + 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())); } } + /** * 获取输出消息,用户返回给支付端 * @@ -528,14 +560,15 @@ public class WxPayService extends BasePayService implements return secondaryInterface(transactionId, outTradeNo, WxTransactionType.CLOSE); } + /** * 交易关闭接口 * - * @param assistOrder 关闭订单 + * @param assistOrder 关闭订单 * @return 返回支付方交易关闭后的结果 */ @Override - public Map close(AssistOrder assistOrder){ + public Map close(AssistOrder assistOrder) { return secondaryInterface(assistOrder.getTradeNo(), assistOrder.getOutTradeNo(), WxTransactionType.CLOSE); } @@ -553,9 +586,10 @@ public class WxPayService extends BasePayService implements } - private Map initNotifyUrl(Map parameters, Order order) { - OrderParaStructure.loadParameters(parameters, "notify_url", payConfigStorage.getNotifyUrl()); - OrderParaStructure.loadParameters(parameters, "notify_url", order); + 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; } @@ -620,7 +654,7 @@ public class WxPayService extends BasePayService implements * @return 返回支付方下载对账单的结果 */ public Map downloadBill(Date billDate, String billType) { - return downloadBill(billDate, WxPayBillType.valueOf(billType)); + return downloadBill(billDate, WxPayBillType.valueOf(billType)); } /** @@ -637,7 +671,7 @@ public class WxPayService extends BasePayService implements public Map downloadBill(Date billDate, BillType billType) { //获取公共参数 Map parameters = getPublicParameters(); - parameters.put("bill_type", billType); + parameters.put("bill_type", billType.getType()); //目前只支持日账单 parameters.put(WxConst.BILL_DATE, DateUtils.formatDate(billDate, DateUtils.YYYYMMDD)); String fileType = billType.getFileType(); @@ -811,12 +845,12 @@ public class WxPayService extends BasePayService implements * @param order 转账订单 *

      *
-     *                                                     注意事项:
-     *                                                     ◆ 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。
-     *                                                     ◆ XML具有可扩展性,因此返回参数可能会有新增,而且顺序可能不完全遵循此文档规范,如果在解析回包的时候发生错误,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新回包字段,会更新到此API文档中。
-     *                                                     ◆ 因为错误代码字段err_code的值后续可能会增加,所以商户如果遇到回包返回新的错误码,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新的错误码,会更新到此API文档中。
-     *                                                     ◆ 错误代码描述字段err_code_des只供人工定位问题时做参考,系统实现时请不要依赖这个字段来做自动化处理。
-     *                                                     
+ * 注意事项: + * ◆ 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。 + * ◆ XML具有可扩展性,因此返回参数可能会有新增,而且顺序可能不完全遵循此文档规范,如果在解析回包的时候发生错误,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新回包字段,会更新到此API文档中。 + * ◆ 因为错误代码字段err_code的值后续可能会增加,所以商户如果遇到回包返回新的错误码,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新的错误码,会更新到此API文档中。 + * ◆ 错误代码描述字段err_code_des只供人工定位问题时做参考,系统实现时请不要依赖这个字段来做自动化处理。 + *
* @return 对应的转账结果 */ @Override -- Gitee From 592a619c909d4fd834df767770fe6228a4941d5e Mon Sep 17 00:00:00 2001 From: egan Date: Sun, 20 Mar 2022 23:53:31 +0800 Subject: [PATCH 127/165] =?UTF-8?q?2.14.4=E5=8F=91=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- pay-java-ali/pom.xml | 2 +- pay-java-baidu/pom.xml | 2 +- pay-java-common/pom.xml | 6 +++++- pay-java-demo/pom.xml | 2 +- pay-java-fuiou/pom.xml | 2 +- pay-java-payoneer/pom.xml | 2 +- pay-java-paypal/pom.xml | 2 +- pay-java-union/pom.xml | 2 +- pay-java-web-support/pom.xml | 2 +- pay-java-wx-youdian/pom.xml | 2 +- pay-java-wx/pom.xml | 2 +- pay-java-yiji/pom.xml | 2 +- pom.xml | 6 +++--- 14 files changed, 20 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index fbe4f56..d24b69d 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ com.egzosn {module-name} - 2.14.3-b4 + 2.14.4 ``` diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index e222896..e8540ec 100644 --- a/pay-java-ali/pom.xml +++ b/pay-java-ali/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b4 + 2.14.4 4.0.0 pay-java-ali diff --git a/pay-java-baidu/pom.xml b/pay-java-baidu/pom.xml index c8372c4..34f8eb6 100644 --- a/pay-java-baidu/pom.xml +++ b/pay-java-baidu/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b4 + 2.14.4 4.0.0 pay-java-baidu diff --git a/pay-java-common/pom.xml b/pay-java-common/pom.xml index fab77ed..e3dfd1d 100644 --- a/pay-java-common/pom.xml +++ b/pay-java-common/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b4 + 2.14.4 4.0.0 jar @@ -38,6 +38,10 @@ com.google.zxing core
+ + org.bouncycastle + bcprov-jdk15on + diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index 91ed777..7bd2b09 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b4 + 2.14.4 4.0.0 war diff --git a/pay-java-fuiou/pom.xml b/pay-java-fuiou/pom.xml index 76e3587..b07c340 100644 --- a/pay-java-fuiou/pom.xml +++ b/pay-java-fuiou/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b4 + 2.14.4 4.0.0 pay-java-fuiou diff --git a/pay-java-payoneer/pom.xml b/pay-java-payoneer/pom.xml index 4e67128..968ecdf 100644 --- a/pay-java-payoneer/pom.xml +++ b/pay-java-payoneer/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b4 + 2.14.4 4.0.0 pay-java-payoneer diff --git a/pay-java-paypal/pom.xml b/pay-java-paypal/pom.xml index f4de33c..fca02e4 100644 --- a/pay-java-paypal/pom.xml +++ b/pay-java-paypal/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b4 + 2.14.4 4.0.0 diff --git a/pay-java-union/pom.xml b/pay-java-union/pom.xml index 1dfb536..6a2584a 100644 --- a/pay-java-union/pom.xml +++ b/pay-java-union/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b4 + 2.14.4 4.0.0 diff --git a/pay-java-web-support/pom.xml b/pay-java-web-support/pom.xml index 630aef4..4093bdc 100644 --- a/pay-java-web-support/pom.xml +++ b/pay-java-web-support/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b4 + 2.14.4 4.0.0 jar diff --git a/pay-java-wx-youdian/pom.xml b/pay-java-wx-youdian/pom.xml index 60f911f..db19f8a 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.14.3-b4 + 2.14.4 4.0.0 pay-java-wx-youdian diff --git a/pay-java-wx/pom.xml b/pay-java-wx/pom.xml index ebdb3e6..d9a1c21 100644 --- a/pay-java-wx/pom.xml +++ b/pay-java-wx/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b4 + 2.14.4 4.0.0 pay-java-wx diff --git a/pay-java-yiji/pom.xml b/pay-java-yiji/pom.xml index 19ca7d2..1993eb7 100644 --- a/pay-java-yiji/pom.xml +++ b/pay-java-yiji/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.3-b4 + 2.14.4 4.0.0 diff --git a/pom.xml b/pom.xml index 3dc1670..a919890 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.egzosn pay-java-parent pom - 2.14.3-b4 + 2.14.4 Pay Java - Parent Pay Java Parent @@ -64,13 +64,13 @@ pay-java-paypal pay-java-yiji pay-java-baidu - pay-java-demo +
- 2.14.3-b4 + 2.14.4 4.5.4 1.2.17 1.2.73 -- Gitee From 7073d904fee980a99a31b18fb9aa57880f0c0995 Mon Sep 17 00:00:00 2001 From: egan Date: Thu, 5 May 2022 22:10:02 +0800 Subject: [PATCH 128/165] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=B7=B2=E7=9F=A5bug?= =?UTF-8?q?=20=202.14.4=E7=89=88=E6=9C=AC=20=E5=BE=AE=E4=BF=A1=E6=94=AF?= =?UTF-8?q?=E4=BB=98=20v3=20=E5=90=AF=E5=8A=A8=E6=8A=A5=E9=94=99=20#90=20?= =?UTF-8?q?=20=E5=BE=AE=E4=BF=A1V3=E5=88=9D=E5=A7=8B=E5=8C=96apiServerUrl?= =?UTF-8?q?=E4=B8=BAnull?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- pay-java-ali/pom.xml | 2 +- .../pay/ali/utils/AntCertificationUtil.java | 50 +++++++++++++------ pay-java-baidu/pom.xml | 2 +- pay-java-common/pom.xml | 2 +- .../egzosn/pay/common/api/BasePayService.java | 9 ++-- .../pay/common/http/HttpRequestTemplate.java | 2 +- .../pay/common/util/sign/SignUtils.java | 32 +++++------- .../pay/common/util/sign/encrypt/AES.java | 10 ++-- pay-java-demo/pom.xml | 2 +- .../pay/demo/controller/AliPayController.java | 8 +-- pay-java-fuiou/pom.xml | 2 +- pay-java-payoneer/pom.xml | 2 +- pay-java-paypal/pom.xml | 2 +- pay-java-union/pom.xml | 2 +- pay-java-web-support/pom.xml | 2 +- pay-java-wx-youdian/pom.xml | 2 +- pay-java-wx/pom.xml | 2 +- .../egzosn/pay/wx/v3/api/WxPayService.java | 6 ++- .../pay/wx/v3/api/WxProfitSharingService.java | 4 +- .../pay/wx/v3/utils/AntCertificationUtil.java | 3 +- pay-java-yiji/pom.xml | 2 +- pom.xml | 9 +--- 23 files changed, 81 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index d24b69d..1ac066e 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ com.egzosn {module-name} - 2.14.4 + 2.14.4-fix ``` diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index e8540ec..53896ee 100644 --- a/pay-java-ali/pom.xml +++ b/pay-java-ali/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.4 + 2.14.4-fix 4.0.0 pay-java-ali 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 index 4d1ffbc..e574d20 100644 --- 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 @@ -28,6 +28,7 @@ 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; @@ -41,7 +42,9 @@ import com.egzosn.pay.common.util.str.StringUtils; public class AntCertificationUtil { private static final Logger LOGGER = LoggerFactory.getLogger(AntCertificationUtil.class); - + static { + SignUtils.initBc(); + } /** * 验证证书是否可信 @@ -54,7 +57,8 @@ public class AntCertificationUtil { X509Certificate[] certificates; try { certificates = readPemCertChain(certContent); - } catch (Exception e) { + } + catch (Exception e) { LOGGER.error("读取证书失败", e); throw new RuntimeException(e); } @@ -63,7 +67,8 @@ public class AntCertificationUtil { try { X509Certificate[] certs = readPemCertChain(rootCertContent); rootCerts.addAll(Arrays.asList(certs)); - } catch (Exception e) { + } + catch (Exception e) { LOGGER.error("读取根证书失败", e); throw new RuntimeException(e); } @@ -81,10 +86,12 @@ public class AntCertificationUtil { private static boolean verifyCert(X509Certificate cert, X509Certificate[] rootCerts) { try { cert.checkValidity(); - } catch (CertificateExpiredException e) { + } + catch (CertificateExpiredException e) { LOGGER.error("证书已经过期", e); return false; - } catch (CertificateNotYetValidException e) { + } + catch (CertificateNotYetValidException e) { LOGGER.error("证书未激活", e); return false; } @@ -104,7 +111,8 @@ public class AntCertificationUtil { try { PublicKey publicKey = issuer.getPublicKey(); verifySignature(publicKey, cert); - } catch (PayErrorException e) { + } + catch (PayErrorException e) { LOGGER.error("证书链验证失败", e); return false; } @@ -155,10 +163,12 @@ public class AntCertificationUtil { private static boolean checkValidity(X509Certificate cert) { try { cert.checkValidity(); - } catch (CertificateExpiredException e) { + } + catch (CertificateExpiredException e) { LOGGER.error("证书已经过期"); return false; - } catch (CertificateNotYetValidException e) { + } + catch (CertificateNotYetValidException e) { LOGGER.error("证书未激活"); return false; } @@ -169,7 +179,8 @@ public class AntCertificationUtil { private static void verifySignature(PublicKey publicKey, X509Certificate cert) { try { cert.verify(publicKey); - } catch (GeneralSecurityException e) { + } + catch (GeneralSecurityException e) { throw new PayErrorException(new PayException("证书校验失败", e.getMessage())); } } @@ -276,10 +287,12 @@ public class AntCertificationUtil { private static X509Certificate[] readPemCertChain(String cert) { ByteArrayInputStream inputStream = new ByteArrayInputStream(cert.getBytes()); try { - CertificateFactory factory = CertificateFactory.getInstance("X.509", "BC");; + CertificateFactory factory = CertificateFactory.getInstance("X.509", "BC"); + ; Collection certificates = factory.generateCertificates(inputStream); return certificates.toArray(new X509Certificate[certificates.size()]); - } catch (GeneralSecurityException e) { + } + catch (GeneralSecurityException e) { LOGGER.error("提取根证书失败", e); } return null; @@ -308,13 +321,15 @@ public class AntCertificationUtil { certSN = fillMD5(certSN); if (StringUtils.isEmpty(rootCertSN)) { rootCertSN = certSN; - } else { + } + else { rootCertSN = rootCertSN + "_" + certSN; } } } - } catch (NoSuchAlgorithmException e) { + } + catch (NoSuchAlgorithmException e) { LOGGER.error("提取根证书失败", e); } return rootCertSN; @@ -332,7 +347,8 @@ public class AntCertificationUtil { CertificateFactory factory = CertificateFactory.getInstance("X.509", "BC"); X509Certificate cert = (X509Certificate) factory.generateCertificate(inputStream); return md5((cert.getIssuerX500Principal().getName() + cert.getSerialNumber()).getBytes()); - } catch (GeneralSecurityException e) { + } + catch (GeneralSecurityException e) { throw new PayErrorException(new PayException(" 获取公钥证书序列号异常", e.getMessage())); } } @@ -368,7 +384,8 @@ public class AntCertificationUtil { CertificateFactory factory = CertificateFactory.getInstance("X.509", "BC"); X509Certificate cert = (X509Certificate) factory.generateCertificate(inputStream); return Base64.encode(cert.getPublicKey().getEncoded()); - } catch (GeneralSecurityException e) { + } + catch (GeneralSecurityException e) { throw new PayErrorException(new PayException(" 提取公钥证书中的公钥异常", e.getMessage())); } } @@ -377,7 +394,8 @@ public class AntCertificationUtil { public static String readFromInputStream(InputStream cert) { try { return new String(IOUtils.toByteArray(cert), StandardCharsets.UTF_8); - } catch (IOException e) { + } + catch (IOException e) { throw new PayErrorException(new PayException("读取证书异常", e.getMessage())); } } diff --git a/pay-java-baidu/pom.xml b/pay-java-baidu/pom.xml index 34f8eb6..576a6f5 100644 --- a/pay-java-baidu/pom.xml +++ b/pay-java-baidu/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.4 + 2.14.4-fix 4.0.0 pay-java-baidu diff --git a/pay-java-common/pom.xml b/pay-java-common/pom.xml index e3dfd1d..37a7b9d 100644 --- a/pay-java-common/pom.xml +++ b/pay-java-common/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.4 + 2.14.4-fix 4.0.0 jar 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 52ccdbb..50bade5 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 @@ -94,6 +94,7 @@ public abstract class BasePayService implements Pay } + public BasePayService(PC payConfigStorage) { this(payConfigStorage, null); } @@ -101,15 +102,11 @@ public abstract class BasePayService implements Pay public BasePayService(PC payConfigStorage, HttpConfigStorage configStorage) { setPayConfigStorage(payConfigStorage); setRequestTemplateConfigStorage(configStorage); - initAfter(); + } - /** - * 初始化之后执行 - */ - protected void initAfter(){ - } + /** * Generate a Base64 encoded String from user , password * 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 ba60626..d53f372 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 @@ -428,7 +428,7 @@ public class HttpRequestTemplate { return new ResponseEntity<>(statusCode, allHeaders, body); } catch (IOException e) { - throw new PayErrorException(new PayException("IOException", e.getLocalizedMessage())); + throw new PayErrorException(new PayException("IOException", e.getLocalizedMessage()), e); } finally { httpRequest.releaseConnection(); 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 0085513..69b7090 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,33 +1,15 @@ package com.egzosn.pay.common.util.sign; -import java.io.UnsupportedEncodingException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; +import java.security.Security; import java.util.Map; -import java.util.Set; -import java.util.SortedMap; -import java.util.UUID; -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.apache.http.message.BasicNameValuePair; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import static com.egzosn.pay.common.util.sign.SignTextUtils.parameterText; import com.egzosn.pay.common.bean.SignType; -import com.egzosn.pay.common.bean.result.PayException; -import com.egzosn.pay.common.exception.PayErrorException; import com.egzosn.pay.common.util.sign.encrypt.HmacSha256; -import com.egzosn.pay.common.util.str.StringUtils; /** * 签名 工具 @@ -209,5 +191,15 @@ public enum SignUtils implements SignType { return this.verify(parameterText(params), sign, key, characterEncoding); } + /** + * 初始化BC + */ + public static void initBc() { + if (null == Security.getProvider("BC")) { + Security.removeProvider("SunEC"); + Security.addProvider(new BouncyCastleProvider()); + } + } + } diff --git a/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/AES.java b/pay-java-common/src/main/java/com/egzosn/pay/common/util/sign/encrypt/AES.java index bd4bf64..9d9bb7e 100644 --- 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 @@ -2,13 +2,13 @@ package com.egzosn.pay.common.util.sign.encrypt; import java.io.IOException; import java.security.GeneralSecurityException; -import java.security.Security; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.digest.DigestUtils; -import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import com.egzosn.pay.common.util.sign.SignUtils; /** * AES 加解密 @@ -30,12 +30,10 @@ public class AES { private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS7Padding"; static { - if (null == Security.getProvider("BC")) { - Security.removeProvider("SunEC"); - Security.addProvider(new BouncyCastleProvider()); - } + SignUtils.initBc(); } + /** * 解密 * diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index 7bd2b09..8e14d35 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.4 + 2.14.4-fix 4.0.0 war 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 7b7b4f7..1ca3cc2 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 @@ -87,10 +87,10 @@ public class AliPayController { 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=="); + 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.RSA2.name()); diff --git a/pay-java-fuiou/pom.xml b/pay-java-fuiou/pom.xml index b07c340..c5e6c90 100644 --- a/pay-java-fuiou/pom.xml +++ b/pay-java-fuiou/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.4 + 2.14.4-fix 4.0.0 pay-java-fuiou diff --git a/pay-java-payoneer/pom.xml b/pay-java-payoneer/pom.xml index 968ecdf..e935206 100644 --- a/pay-java-payoneer/pom.xml +++ b/pay-java-payoneer/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.4 + 2.14.4-fix 4.0.0 pay-java-payoneer diff --git a/pay-java-paypal/pom.xml b/pay-java-paypal/pom.xml index fca02e4..6f7ba60 100644 --- a/pay-java-paypal/pom.xml +++ b/pay-java-paypal/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.4 + 2.14.4-fix 4.0.0 diff --git a/pay-java-union/pom.xml b/pay-java-union/pom.xml index 6a2584a..28795c7 100644 --- a/pay-java-union/pom.xml +++ b/pay-java-union/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.4 + 2.14.4-fix 4.0.0 diff --git a/pay-java-web-support/pom.xml b/pay-java-web-support/pom.xml index 4093bdc..9995c94 100644 --- a/pay-java-web-support/pom.xml +++ b/pay-java-web-support/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.4 + 2.14.4-fix 4.0.0 jar diff --git a/pay-java-wx-youdian/pom.xml b/pay-java-wx-youdian/pom.xml index db19f8a..11bd564 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.14.4 + 2.14.4-fix 4.0.0 pay-java-wx-youdian diff --git a/pay-java-wx/pom.xml b/pay-java-wx/pom.xml index d9a1c21..660c134 100644 --- a/pay-java-wx/pom.xml +++ b/pay-java-wx/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.4 + 2.14.4-fix 4.0.0 pay-java-wx 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 index 8d7b3f6..0900af2 100644 --- 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 @@ -98,6 +98,7 @@ public class WxPayService extends BasePayService { */ public WxPayService(WxPayConfigStorage payConfigStorage) { super(payConfigStorage); + } /** @@ -109,7 +110,9 @@ public class WxPayService extends BasePayService { public WxPayService(WxPayConfigStorage payConfigStorage, HttpConfigStorage configStorage) { super(payConfigStorage, configStorage); } - + { + initAfter(); + } /** * 辅助api @@ -132,7 +135,6 @@ public class WxPayService extends BasePayService { /** * 初始化之后执行 */ - @Override protected void initAfter() { payConfigStorage.setPartner(StringUtils.isNotEmpty(payConfigStorage.getSubMchId())); // new Thread(() -> { 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 index 275b034..cc9d1a9 100644 --- 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 @@ -69,10 +69,10 @@ public class WxProfitSharingService extends WxPayService implements ProfitSharin */ @Override protected void initAfter() { - new Thread(() -> { +// new Thread(() -> { payConfigStorage.loadCertEnvironment(); getAssistService(); - }).start(); +// }).start(); } 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 index b494c62..e29e163 100644 --- 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 @@ -24,6 +24,7 @@ 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; @@ -54,7 +55,7 @@ public final class AntCertificationUtil { if (javaVersion.contains("1.8") || javaVersion.startsWith("8")){ Security.setProperty("crypto.policy", "unlimited"); } - + SignUtils.initBc(); try { PKCS12_KEY_STORE = KeyStore.getInstance("PKCS12"); } diff --git a/pay-java-yiji/pom.xml b/pay-java-yiji/pom.xml index 1993eb7..c49d41e 100644 --- a/pay-java-yiji/pom.xml +++ b/pay-java-yiji/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.4 + 2.14.4-fix 4.0.0 diff --git a/pom.xml b/pom.xml index a919890..8006320 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.egzosn pay-java-parent pom - 2.14.4 + 2.14.4-fix Pay Java - Parent Pay Java Parent @@ -39,11 +39,6 @@ hocgin@gmail.com https://github.com/hocgin - - Menjoe - 1030009014@qq.com - https://gitee.com/menjoe-z - scm:git:https://github.com/egzosn/pay-java-parent.git @@ -70,7 +65,7 @@ - 2.14.4 + 2.14.4-fix 4.5.4 1.2.17 1.2.73 -- Gitee From 739c0cc03e6bffec572ab36162ade31e04f1e46a Mon Sep 17 00:00:00 2001 From: egan Date: Thu, 5 May 2022 22:10:25 +0800 Subject: [PATCH 129/165] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=B7=B2=E7=9F=A5bug?= =?UTF-8?q?=20=202.14.4=E7=89=88=E6=9C=AC=20=E5=BE=AE=E4=BF=A1=E6=94=AF?= =?UTF-8?q?=E4=BB=98=20v3=20=E5=90=AF=E5=8A=A8=E6=8A=A5=E9=94=99=20#90=20?= =?UTF-8?q?=20=E5=BE=AE=E4=BF=A1V3=E5=88=9D=E5=A7=8B=E5=8C=96apiServerUrl?= =?UTF-8?q?=E4=B8=BAnull?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8006320..65aa5fb 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ pay-java-paypal pay-java-yiji pay-java-baidu - + pay-java-demo -- Gitee From 29db0f99e2e7f11f969f2e8cb4cec7b1421dfb64 Mon Sep 17 00:00:00 2001 From: egan Date: Sat, 2 Jul 2022 21:29:46 +0800 Subject: [PATCH 130/165] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pay-java-ali/pom.xml | 2 +- .../com/egzosn/pay/ali/api/AliPayService.java | 2 + pay-java-baidu/pom.xml | 2 +- .../egzosn/pay/baidu/api/BaiduPayService.java | 2 + pay-java-common/pom.xml | 2 +- .../egzosn/pay/common/api/BasePayService.java | 54 ++++---- .../egzosn/pay/common/http/UriVariables.java | 5 +- pay-java-demo/pom.xml | 8 +- .../demo/controller/WxV3PayController.java | 2 +- pay-java-fuiou/pom.xml | 2 +- pay-java-payoneer/pom.xml | 2 +- pay-java-paypal/pom.xml | 2 +- .../pay/paypal/v2/api/PayPalPayService.java | 2 +- pay-java-union/pom.xml | 2 +- .../egzosn/pay/union/api/UnionPayService.java | 2 + pay-java-web-support/pom.xml | 2 +- pay-java-wx-youdian/pom.xml | 2 +- .../wx/youdian/api/WxYouDianPayService.java | 126 ++++++------------ pay-java-wx/pom.xml | 2 +- .../pay/wx/v3/api/WxCombinePayService.java | 1 + .../egzosn/pay/wx/v3/api/WxPayService.java | 3 +- .../pay/wx/v3/api/WxProfitSharingService.java | 3 + pay-java-yiji/pom.xml | 2 +- .../egzosn/pay/yiji/api/YiJiPayService.java | 14 +- pom.xml | 4 +- 25 files changed, 115 insertions(+), 135 deletions(-) diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index 53896ee..feabf14 100644 --- a/pay-java-ali/pom.xml +++ b/pay-java-ali/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.4-fix + 2.14.5 4.0.0 pay-java-ali 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 4d4ecfd..f43b76f 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 @@ -628,6 +628,7 @@ public class AliPayService extends BasePayService implement * @param billType 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; * @return 返回支付方下载对账单的结果 */ + @Override public Map downloadBill(Date billDate, String billType) { return this.downloadBill(billDate, "trade".equals(billType) ? AliPayBillType.TRADE_DAY : AliPayBillType.SIGNCUSTOMER_DAY); @@ -640,6 +641,7 @@ public class AliPayService extends BasePayService implement * @param billType 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单; * @return 返回支付方下载对账单的结果 */ + @Override public Map downloadBill(Date billDate, BillType billType) { //获取公共参数 Map parameters = getPublicParameters(AliTransactionType.DOWNLOADBILL); diff --git a/pay-java-baidu/pom.xml b/pay-java-baidu/pom.xml index 576a6f5..e2723cd 100644 --- a/pay-java-baidu/pom.xml +++ b/pay-java-baidu/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.4-fix + 2.14.5 4.0.0 pay-java-baidu 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 468a46e..42c064c 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 @@ -542,6 +542,7 @@ public class BaiduPayService extends BasePayService { * @param accessToken 用户token * @return 对账单 */ + @Override public Map downloadBill(Date billDate, String accessToken) { return downloadBill(billDate, new BaiduBillType(accessToken, BaiduTransactionType.DOWNLOAD_ORDER_BILL.name())); } @@ -553,6 +554,7 @@ public class BaiduPayService extends BasePayService { * @param billType 账单类型 {@link BaiduBillType} * @return 返回支付方下载对账单的结果 */ + @Override public Map downloadBill(Date billDate, BillType billType) { Map parameters = new HashMap<>(); parameters.put("access_token", billType.getCustom()); diff --git a/pay-java-common/pom.xml b/pay-java-common/pom.xml index 37a7b9d..c7d3e72 100644 --- a/pay-java-common/pom.xml +++ b/pay-java-common/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.4-fix + 2.14.5 4.0.0 jar 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 50bade5..1b54d95 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 @@ -3,6 +3,7 @@ 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; @@ -11,6 +12,7 @@ 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; @@ -59,6 +61,7 @@ public abstract class BasePayService implements Pay */ protected List> interceptors = new ArrayList>(); + private Charset inputCharset = Consts.UTF_8; /** * 设置支付配置 @@ -68,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; } @@ -94,7 +101,6 @@ public abstract class BasePayService implements Pay } - public BasePayService(PC payConfigStorage) { this(payConfigStorage, null); } @@ -106,7 +112,6 @@ public abstract class BasePayService implements Pay } - /** * Generate a Base64 encoded String from user , password * @@ -159,11 +164,11 @@ public abstract class BasePayService implements Pay */ @Override public String toPay(O order) { - if (StringUtils.isNotEmpty(order.getSubject()) && order.getSubject().contains("'")){ - order.setSubject(order.getSubject().replace("'","")); + 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("'","")); + if (StringUtils.isNotEmpty(order.getBody()) && order.getBody().contains("'")) { + order.setBody(order.getBody().replace("'", "")); } Map orderInfo = orderInfo(order); return buildRequest(orderInfo, MethodType.POST); @@ -203,6 +208,7 @@ public abstract class BasePayService implements Pay public Map getParameter2Map(Map parameterMap, InputStream is) { return getNoticeParams(new DefaultNoticeRequest(parameterMap, is)).getBody(); } + /** * 将请求参数或者请求流转化为 Map * @@ -213,22 +219,18 @@ public abstract class BasePayService implements Pay 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); @@ -329,9 +331,10 @@ public abstract class BasePayService implements Pay * @return 返回支付方下载对账单的结果 */ @Override - public Map downloadBill(Date billDate, String billType){ + public Map downloadBill(Date billDate, String billType) { return Collections.emptyMap(); } + /** * 转账 * @@ -421,7 +424,6 @@ public abstract class BasePayService implements Pay } - /** * 将请求参数或者请求流转化为 Map * @@ -432,7 +434,7 @@ public abstract class BasePayService implements Pay @Deprecated @Override public PayOutMessage payBack(Map parameterMap, InputStream is) { - return payBack(new DefaultNoticeRequest(parameterMap, is)); + return payBack(new DefaultNoticeRequest(parameterMap, is)); } /** @@ -445,7 +447,7 @@ public abstract class BasePayService implements Pay public PayOutMessage payBack(NoticeRequest request) { final NoticeParams noticeParams = getNoticeParams(request); if (LOG.isDebugEnabled()) { - LOG.debug("回调响应:{}" , JSON.toJSONString(noticeParams)); + LOG.debug("回调响应:{}", JSON.toJSONString(noticeParams)); } if (!verify(noticeParams)) { return getPayOutMessage("fail", "失败"); @@ -480,26 +482,30 @@ public abstract class BasePayService implements Pay * @param orderInfo 订单信息 * @return 处理后订单信息 */ + @Override public Map preOrderHandler(Map orderInfo, O payOrder) { return orderInfo; } /** * 过时 + * * @param parameters 参数map - * @param key key - * @param value 值 + * @param key key + * @param value 值 * @return 返回订单参数 */ @Deprecated protected Map setParameters(Map parameters, String key, String value) { return OrderParaStructure.loadParameters(parameters, key, value); } + /** * 过时 + * * @param parameters 参数map - * @param key key - * @param order 订单对象 + * @param key key + * @param order 订单对象 * @return 返回订单参数 */ @Deprecated 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 cff3608..5da5719 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 @@ -146,7 +146,8 @@ public final 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 @@ -177,7 +178,7 @@ public final class UriVariables { } } if (curChar == '&' && !isOpen) {// 如果读取到&分割符,同时这个分割符不是值域,这时将map里添加 - putKeyValueToMap(temp, isKey, key, map); + putKeyValueToMap(temp, false, key, map); temp.setLength(0); isKey = true; } diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index 8e14d35..ddc9ab0 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.4-fix + 2.14.5 4.0.0 war @@ -84,7 +84,11 @@ jackson-databind 2.9.10.6 - + + org.slf4j + slf4j-log4j12 + 1.7.30 + 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 index 7511364..a3bc5da 100644 --- 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 @@ -45,7 +45,7 @@ public class WxV3PayController { private WxPayService service = null; - @PostConstruct //没有证书的情况下注释掉,避免启动报错 +// @PostConstruct //没有证书的情况下注释掉,避免启动报错 public void init() { WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage(); wxPayConfigStorage.setAppId("wxc7b993ff15a9f26c"); diff --git a/pay-java-fuiou/pom.xml b/pay-java-fuiou/pom.xml index c5e6c90..514bcb9 100644 --- a/pay-java-fuiou/pom.xml +++ b/pay-java-fuiou/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.4-fix + 2.14.5 4.0.0 pay-java-fuiou diff --git a/pay-java-payoneer/pom.xml b/pay-java-payoneer/pom.xml index e935206..415950d 100644 --- a/pay-java-payoneer/pom.xml +++ b/pay-java-payoneer/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.4-fix + 2.14.5 4.0.0 pay-java-payoneer diff --git a/pay-java-paypal/pom.xml b/pay-java-paypal/pom.xml index 6f7ba60..efcb5e2 100644 --- a/pay-java-paypal/pom.xml +++ b/pay-java-paypal/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.4-fix + 2.14.5 4.0.0 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 index 7219f41..5eb631d 100644 --- 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 @@ -518,7 +518,7 @@ public class PayPalPayService extends BasePayService implem 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-union/pom.xml b/pay-java-union/pom.xml index 28795c7..7058352 100644 --- a/pay-java-union/pom.xml +++ b/pay-java-union/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.4-fix + 2.14.5 4.0.0 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 42f82f3..97c913d 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 @@ -231,6 +231,7 @@ public class UnionPayService extends BasePayService { * @param noticeParams 回调回来的参数集 * @return 签名校验 true通过 */ + @Override public boolean verify(NoticeParams noticeParams) { final Map result = noticeParams.getBody(); if (null == result || result.get(SDKConstants.param_signature) == null) { @@ -719,6 +720,7 @@ public class UnionPayService extends BasePayService { * @param fileType 文件类型 文件类型,一般商户填写00即可 * @return 返回fileContent 请自行将数据落地 */ + @Override public Map downloadBill(Date billDate, String fileType) { return downloadBill(billDate, new UnionPayBillType(fileType)); } diff --git a/pay-java-web-support/pom.xml b/pay-java-web-support/pom.xml index 9995c94..8ddb16d 100644 --- a/pay-java-web-support/pom.xml +++ b/pay-java-web-support/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.4-fix + 2.14.5 4.0.0 jar diff --git a/pay-java-wx-youdian/pom.xml b/pay-java-wx-youdian/pom.xml index 11bd564..6d1afc9 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.14.4-fix + 2.14.5 4.0.0 pay-java-wx-youdian 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 516696c..d16cdd8 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,10 +1,8 @@ package com.egzosn.pay.wx.youdian.api; -import java.io.InputStream; import java.math.BigDecimal; import java.util.Collections; import java.util.Date; -import java.util.Iterator; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.locks.Lock; @@ -15,11 +13,9 @@ import com.egzosn.pay.common.api.BasePayService; import com.egzosn.pay.common.bean.AssistOrder; import com.egzosn.pay.common.bean.BaseRefundResult; import com.egzosn.pay.common.bean.BillType; - import com.egzosn.pay.common.bean.CurType; import com.egzosn.pay.common.bean.MethodType; import com.egzosn.pay.common.bean.NoticeParams; -import com.egzosn.pay.common.bean.NoticeRequest; import com.egzosn.pay.common.bean.PayMessage; import com.egzosn.pay.common.bean.PayOrder; import com.egzosn.pay.common.bean.PayOutMessage; @@ -47,21 +43,19 @@ import com.egzosn.pay.wx.youdian.bean.YoudianTransactionType; */ 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); } /** @@ -71,7 +65,7 @@ public class WxYouDianPayService extends BasePayService data = new TreeMap<>(); data.put("username", payConfigStorage.getSeller()); data.put("password", payConfigStorage.getKeyPrivate()); @@ -125,7 +119,7 @@ public class WxYouDianPayService extends BasePayService params = noticeParams.getBody(); - if (!"SUCCESS".equals(params.get("return_code"))) { - LOG.debug(String.format("友店微信支付异常:return_code=%s,参数集=%s", params.get("return_code"), params)); + 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")); + 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()); - } - return false; + return signVerify(params, (String) params.get("sign")) && verifySource((String) params.get("out_trade_no")); } /** @@ -176,7 +165,7 @@ public class WxYouDianPayService extends BasePayService params, String sign) { + private boolean signVerify(Map params, String sign) { return SignUtils.valueOf(payConfigStorage.getSignType()).verify(params, sign, "&key=" + payConfigStorage.getKeyPublic(), payConfigStorage.getInputCharset()); } @@ -188,12 +177,12 @@ public class WxYouDianPayService extends BasePayService= 400) { @@ -216,13 +205,13 @@ 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); @@ -271,15 +260,10 @@ public class WxYouDianPayService extends BasePayService parameterMap = request.getParameterMap(); - 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 new NoticeParams(params); - - } - /** * 具体需要返回的数据为 * return_code 返回码只有SUCCESS和FAIL @@ -342,11 +301,11 @@ 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", 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(); } @@ -393,8 +352,7 @@ public class WxYouDianPayService extends BasePayService microPay(PayOrder order) { order.setTransactionType(YoudianTransactionType.MICROPAY); - JSONObject orderInfo = orderInfo(order); - return orderInfo; + return orderInfo(order); } /** @@ -406,7 +364,7 @@ public class WxYouDianPayService extends BasePayService query(String tradeNo, String outTradeNo) { - return query(new AssistOrder(tradeNo, outTradeNo)); + return query(new AssistOrder(tradeNo, outTradeNo)); } /** @@ -419,18 +377,17 @@ public class WxYouDianPayService extends BasePayService 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(assistOrder.getTradeNo())) { - data.put("order_sn", assistOrder.getOutTradeNo()); + data.put(ORDER_SN, assistOrder.getOutTradeNo()); } else { - data.put("order_sn", assistOrder.getTradeNo()); + data.put(ORDER_SN, assistOrder.getTradeNo()); } String sign = createSign(SignTextUtils.parameterText(data, "") + apbNonce, payConfigStorage.getInputCharset()); String queryParam = SignTextUtils.parameterText(data) + "&apb_nonce=" + apbNonce + "&sign=" + sign; - JSONObject jsonObject = execute(getReqUrl(YoudianTransactionType.NATIVE_STATUS) + "?" + queryParam, MethodType.GET, null); - return jsonObject; + return execute(getReqUrl(YoudianTransactionType.NATIVE_STATUS) + "?" + queryParam, MethodType.GET, null); } @@ -438,14 +395,15 @@ public class WxYouDianPayService extends BasePayService close(String tradeNo, String outTradeNo) { return Collections.emptyMap(); } + /** * 交易关闭接口 * - * @param assistOrder 关闭订单 + * @param assistOrder 关闭订单 * @return 返回支付方交易关闭后的结果 */ @Override - public Map close(AssistOrder assistOrder){ + public Map close(AssistOrder assistOrder) { return Collections.emptyMap(); } @@ -459,13 +417,13 @@ public class WxYouDianPayService extends BasePayService 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()); + data.put(ORDER_SN, refundOrder.getOutTradeNo()); } else { - data.put("order_sn", refundOrder.getTradeNo()); + data.put(ORDER_SN, refundOrder.getTradeNo()); } //支付类型刷卡为3扫码为4 data.put("type", "4"); @@ -476,7 +434,7 @@ public class WxYouDianPayService extends BasePayService pay-java-parent com.egzosn - 2.14.4-fix + 2.14.5 4.0.0 pay-java-wx 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 index a1dc98a..de4a6e8 100644 --- 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 @@ -81,6 +81,7 @@ public class WxCombinePayService extends WxPayService { * @param order 支付订单集 * @return 下单结果 */ + @Override public JSONObject unifiedOrder(PayOrder order) { //统一下单 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 index 0900af2..2d84635 100644 --- 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 @@ -200,6 +200,7 @@ public class WxPayService extends BasePayService { * @param noticeParams 通知参数 * @return the boolean */ + @Override public boolean verify(NoticeParams noticeParams) { //当前使用的微信平台证书序列号 @@ -238,7 +239,6 @@ public class WxPayService extends BasePayService { //统一下单 Map parameters = wxParameterStructure.initPartner(null); - ; // wxParameterStructure.getPublicParameters(parameters); // 商品描述 OrderParaStructure.loadParameters(parameters, WxConst.DESCRIPTION, order.getSubject()); @@ -573,6 +573,7 @@ public class WxPayService extends BasePayService { * @param billType 账单类型 {@link WxBillType} 与 {@link WxAccountType} * @return 返回支付方下载对账单的结果, 如果【账单类型】为gzip的话则返回值中key为data值为gzip的输入流 */ + @Override public Map downloadBill(Date billDate, BillType billType) { //获取公共参数 Map parameters = new HashMap<>(5); 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 index cc9d1a9..d030742 100644 --- 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 @@ -94,6 +94,7 @@ public class WxProfitSharingService extends WxPayService implements ProfitSharin * @param noticeParams 通知参数 * @return the boolean */ + @Override public boolean verify(NoticeParams noticeParams) { throw new PayErrorException(new WxPayError("", "分账不支持方式")); } @@ -105,6 +106,7 @@ public class WxProfitSharingService extends WxPayService implements ProfitSharin * @param order 支付订单集 * @return 下单结果 */ + @Override public JSONObject unifiedOrder(PayOrder order) { Map parameters = new MapGen(WxConst.APPID, payConfigStorage.getAppId()) @@ -336,6 +338,7 @@ public class WxProfitSharingService extends WxPayService implements ProfitSharin * @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)) diff --git a/pay-java-yiji/pom.xml b/pay-java-yiji/pom.xml index c49d41e..b3a7b48 100644 --- a/pay-java-yiji/pom.xml +++ b/pay-java-yiji/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.4-fix + 2.14.5 4.0.0 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 5f1f973..7a93c0d 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 @@ -11,7 +11,6 @@ import com.egzosn.pay.common.api.BasePayService; import com.egzosn.pay.common.bean.AssistOrder; import com.egzosn.pay.common.bean.BaseRefundResult; import com.egzosn.pay.common.bean.BillType; - import com.egzosn.pay.common.bean.CurType; import com.egzosn.pay.common.bean.DefaultCurType; import com.egzosn.pay.common.bean.MethodType; @@ -94,7 +93,7 @@ public class YiJiPayService extends BasePayService { @Override public boolean verify(Map params) { - return verify(new NoticeParams(params)); + return verify(new NoticeParams(params)); } @@ -104,10 +103,11 @@ public class YiJiPayService extends BasePayService { * @param noticeParams 回调回来的参数集 * @return 签名校验 true通过 */ - public boolean verify(NoticeParams noticeParams){ + @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; } @@ -311,14 +311,15 @@ public class YiJiPayService extends BasePayService { public Map close(String tradeNo, String outTradeNo) { return Collections.emptyMap(); } + /** * 交易关闭接口 * - * @param assistOrder 关闭订单 + * @param assistOrder 关闭订单 * @return 返回支付方交易关闭后的结果 */ @Override - public Map close(AssistOrder assistOrder){ + public Map close(AssistOrder assistOrder) { return Collections.emptyMap(); } @@ -400,7 +401,6 @@ public class YiJiPayService extends BasePayService { /** - * * @param billDate 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型:trade、signcustomer;trade指商户基于易极付交易收单的业务账单;signcustomer是指基于商户易极付余额收入及支出等资金变动的帐务账单; * @param billType 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 * @return 返回支付方下载对账单的结果 diff --git a/pom.xml b/pom.xml index 65aa5fb..00881fe 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.egzosn pay-java-parent pom - 2.14.4-fix + 2.14.5 Pay Java - Parent Pay Java Parent @@ -65,7 +65,7 @@ - 2.14.4-fix + 2.14.5 4.5.4 1.2.17 1.2.73 -- Gitee From bbc10565b89295917eb7b4e0793912fa96254502 Mon Sep 17 00:00:00 2001 From: egan Date: Sat, 2 Jul 2022 21:31:23 +0800 Subject: [PATCH 131/165] =?UTF-8?q?=E5=8F=91=E7=BA=A2=E5=8C=85=E4=BF=AE?= =?UTF-8?q?=E5=A4=8Dwxappid=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/wx/api/WxPayService.java | 142 ++++++++++-------- .../egzosn/pay/wx/api/WxRedPackService.java | 20 +++ .../com/egzosn/pay/wx/bean/RedpackOrder.java | 63 ++++++-- 3 files changed, 151 insertions(+), 74 deletions(-) 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 2f2e7f3..f5eaf7b 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 @@ -12,6 +12,7 @@ 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; @@ -44,7 +45,6 @@ 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.Order; import com.egzosn.pay.common.bean.OrderParaStructure; import com.egzosn.pay.common.bean.PayMessage; import com.egzosn.pay.common.bean.PayOrder; @@ -155,27 +155,23 @@ public class WxPayService extends BasePayService implements * @param noticeParams 回调回来的参数集 * @return 签名校验 true通过 */ + @Override public boolean verify(NoticeParams noticeParams) { final Map params = noticeParams.getBody(); //如果为退款不需要校验, 直接返回, - if (params.containsKey(REQ_INFO)){ + if (params.containsKey(REQ_INFO)) { return true; } - if (null == params.get(SIGN) || !(SUCCESS.equals(params.get(RETURN_CODE)) && SUCCESS.equals(params.get(RESULT_CODE)))) { + 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; } - try { - return signVerify(params, (String) params.get(SIGN)); - } - catch (PayErrorException e) { - LOG.error("", e); - } - return false; + return signVerify(params, (String) params.get(SIGN)); + } @@ -186,7 +182,7 @@ public class WxPayService extends BasePayService implements * @param sign 比对的签名结果 * @return 生成的签名结果 */ - public boolean signVerify(Map params, String sign) { + private boolean signVerify(Map params, String sign) { return signVerify(params, sign, payConfigStorage.isTest()); } @@ -207,7 +203,7 @@ public class WxPayService extends BasePayService implements */ private Map getPublicParameters() { - Map parameters = new TreeMap(); + Map parameters = new TreeMap<>(); parameters.put(APPID, payConfigStorage.getAppId()); parameters.put(MCH_ID, payConfigStorage.getMchId()); //判断如果是服务商模式信息则加入 @@ -298,7 +294,7 @@ public class WxPayService extends BasePayService implements return result; } - Map params = new TreeMap(); + Map params = new TreeMap<>(); if (WxTransactionType.JSAPI == order.getTransactionType()) { params.put("signType", payConfigStorage.getSignType()); @@ -653,6 +649,7 @@ public class WxPayService extends BasePayService implements * RECHARGE_REFUND,返回当日充值退款订单 * @return 返回支付方下载对账单的结果 */ + @Override public Map downloadBill(Date billDate, String billType) { return downloadBill(billDate, WxPayBillType.valueOf(billType)); } @@ -668,6 +665,7 @@ public class WxPayService extends BasePayService implements * RECHARGE_REFUND,返回当日充值退款订单 * @return 返回支付方下载对账单的结果, 如果【账单类型】为gzip的话则返回值中key为data值为gzip的输入流 */ + @Override public Map downloadBill(Date billDate, BillType billType) { //获取公共参数 Map parameters = getPublicParameters(); @@ -710,24 +708,19 @@ public class WxPayService extends BasePayService implements //设置签名 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); } + } /** @@ -739,15 +732,15 @@ public class WxPayService extends BasePayService implements */ @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; + } /** @@ -758,12 +751,11 @@ public class WxPayService extends BasePayService implements * @throws IOException IOException */ @Deprecated - private void writeToLocal(String destination, InputStream inputStream) - throws IOException { + private void writeToLocal(String destination, InputStream inputStream) throws IOException { // 判断字节大小 if (inputStream.available() != 0) { - System.out.println("结果大小:" + inputStream.available()); + LOG.debug("结果大小:{}", inputStream.available()); File file = new File(destination); if (!file.getParentFile().exists()) { boolean result = file.getParentFile().mkdirs(); @@ -771,17 +763,17 @@ public class WxPayService extends BasePayService implements LOG.warn("创建失败"); } } - OutputStream out = new FileOutputStream(file); - int size = 0; - int len = 0; - byte[] buf = new byte[1024]; - while ((size = inputStream.read(buf)) != -1) { - len += size; - out.write(buf, 0, size); + try (OutputStream out = new FileOutputStream(file)) { + int size = 0; + int len = 0; + byte[] buf = new byte[1024]; + while ((size = inputStream.read(buf)) != -1) { + len += size; + out.write(buf, 0, size); + } + LOG.debug("最终写入字节数大小:{}", len); } - LOG.info("最终写入字节数大小:" + len); - inputStream.close(); - out.close(); + } } @@ -813,7 +805,7 @@ public class WxPayService extends BasePayService implements * @param transactionType 交易类型 * @return 返回支付方对应接口的结果 */ - 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)); @@ -844,18 +836,11 @@ public class WxPayService extends BasePayService implements * * @param order 转账订单 *
-     *
-     * 注意事项:
-     * ◆ 当返回错误码为“SYSTEMERROR”时,请不要更换商户订单号,一定要使用原商户订单号重试,否则可能造成重复支付等资金风险。
-     * ◆ XML具有可扩展性,因此返回参数可能会有新增,而且顺序可能不完全遵循此文档规范,如果在解析回包的时候发生错误,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新回包字段,会更新到此API文档中。
-     * ◆ 因为错误代码字段err_code的值后续可能会增加,所以商户如果遇到回包返回新的错误码,请商户务必不要换单重试,请商户联系客服确认付款情况。如果有新的错误码,会更新到此API文档中。
-     * ◆ 错误代码描述字段err_code_des只供人工定位问题时做参考,系统实现时请不要依赖这个字段来做自动化处理。
-     * 
- * @return 对应的转账结果 + * @return 对应的转账结果 */ @Override public Map transfer(TransferOrder order) { - Map parameters = new TreeMap(); + Map parameters = new TreeMap<>(); parameters.put("partner_trade_no", order.getOutNo()); @@ -889,7 +874,7 @@ 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("openid", order.getPayeeAccount()); @@ -911,7 +896,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())); @@ -952,7 +937,7 @@ public class WxPayService extends BasePayService implements } - public String keyPublic(String content) { + private String keyPublic(String content) { try { return RSA2.encrypt(content, payConfigStorage.getKeyPublic(), CIPHER_ALGORITHM, payConfigStorage.getInputCharset()); } @@ -981,7 +966,19 @@ public class WxPayService extends BasePayService implements */ @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); final TransferType transferType = redpackOrder.getTransferType(); if (WxSendredpackType.SENDGROUPREDPACK == transferType) { @@ -998,7 +995,7 @@ public class WxPayService extends BasePayService implements if (WxSendredpackType.SENDMINIPROGRAMHB != transferType || FAIL.equals(resp.getString(RESULT_CODE))) { return resp; } - Map params = new TreeMap(); + Map params = new TreeMap<>(); params.put("appId", payConfigStorage.getAppId()); params.put("timeStamp", System.currentTimeMillis() / 1000 + ""); params.put("nonceStr", parameters.get(NONCE_STR)); @@ -1021,8 +1018,22 @@ public class WxPayService extends BasePayService implements */ @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(SignTextUtils.parameterText(parameters, "&", SIGN), payConfigStorage.getInputCharset())); return requestTemplate.postForObject(getReqUrl(WxSendredpackType.GETHBINFO), XML.getMap2Xml(parameters), JSONObject.class); @@ -1037,10 +1048,13 @@ public class WxPayService extends BasePayService implements private void redPackParam(RedpackOrder redpackOrder, Map parameters) { 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 341f3a8..a18cff5 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 8354f3c..2587431 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; + } } -- Gitee From db34cb0c7e69a258937361fc71203bfe0adb564c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=91=E7=81=B6=E7=94=9F?= Date: Sun, 8 Jan 2023 22:58:23 +0800 Subject: [PATCH 132/165] =?UTF-8?q?=E8=BD=AC=E8=B4=A6=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pay/common/api/TransferService.java | 33 +++++++ .../egzosn/pay/common/bean/AssistOrder.java | 12 +-- .../com/egzosn/pay/common/bean/Attrs.java | 91 ++++++++++++++++++- 3 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 pay-java-common/src/main/java/com/egzosn/pay/common/api/TransferService.java 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 0000000..695fe66 --- /dev/null +++ b/pay-java-common/src/main/java/com/egzosn/pay/common/api/TransferService.java @@ -0,0 +1,33 @@ +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 index 52cb462..09dbd18 100644 --- 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 @@ -18,11 +18,11 @@ public class AssistOrder implements Order { /** - * 支付平台订单号,交易号 + * 支付平台订单号,交易号, 平台批次单号 */ private String tradeNo; /** - * 商户订单号 + * 商户订单号,商家批次单号 */ private String outTradeNo; /** @@ -77,18 +77,18 @@ public class AssistOrder implements Order { } /** - * 获取商户订单号 + * 获取商户订单号,商家批次单号 * - * @return 商户订单号 + * @return 商户订单号, 商家批次单号 */ public String getOutTradeNo() { return outTradeNo; } /** - * 设置商户订单号 + * 设置商户订单号,商家批次单号 * - * @param outTradeNo 商户订单号 + * @param outTradeNo 商户订单号,商家批次单号 */ public void setOutTradeNo(String outTradeNo) { this.outTradeNo = outTradeNo; 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 index cd7d883..2713b0d 100644 --- 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 @@ -1,6 +1,8 @@ package com.egzosn.pay.common.bean; import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; import java.util.Map; /** @@ -27,5 +29,92 @@ public interface Attrs extends Serializable { * @param key 属性名 * @return 属性 */ - Object getAttr(String key); + 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 属性名 + * @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); + } } -- Gitee From 8f7ca71d9162596ff0184de623765daf5a8c20fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=91=E7=81=B6=E7=94=9F?= Date: Sun, 8 Jan 2023 22:59:16 +0800 Subject: [PATCH 133/165] =?UTF-8?q?=E9=80=9A=E7=9F=A5=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E5=A4=B4=E5=90=8D=E7=A7=B0=E4=B8=8D=E5=8C=BA?= =?UTF-8?q?=E5=88=86=E5=A4=A7=E5=B0=8F=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egzosn/pay/common/bean/NoticeParams.java | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) 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 index 54f59a6..6b19357 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2017-2021 the original Egan. + * 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. @@ -59,19 +59,34 @@ public class NoticeParams { this.headers = headers; } + 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 = this.headers.get(name); + List value = getValueMatchingKey(headers, name); return (null == value || value.isEmpty()) ? null : value.get(0); } public Enumeration getHeaders(String name) { - List value = this.headers.get(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()); + if (null == headers) { + return Collections.enumeration(Collections.emptySet()); } return Collections.enumeration(this.headers.keySet()); } -- Gitee From fdb7fcdbb908470f5a47509a8b5d7156314f2961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=91=E7=81=B6=E7=94=9F?= Date: Sun, 8 Jan 2023 23:01:14 +0800 Subject: [PATCH 134/165] =?UTF-8?q?=E5=BE=AE=E4=BF=A1V3=E5=95=86=E5=AE=B6?= =?UTF-8?q?=E8=BD=AC=E8=B4=A6=E5=88=B0=E9=9B=B6=E9=92=B1=E7=9B=B8=E5=85=B3?= =?UTF-8?q?API=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wx/v3/api/DefaultWxPayAssistService.java | 5 + .../egzosn/pay/wx/v3/api/WxPayService.java | 108 +++++++++++++-- .../pay/wx/v3/bean/WxTransactionType.java | 2 +- .../egzosn/pay/wx/v3/bean/WxTransferType.java | 89 ++++++++++++ .../wx/v3/bean/transfer/TransferDetail.java | 101 ++++++++++++++ .../wx/v3/bean/transfer/WxTransferOrder.java | 124 +++++++++++++++++ .../bean/transfer/WxTransferQueryOrder.java | 131 ++++++++++++++++++ .../com/egzosn/pay/wx/v3/utils/WxConst.java | 16 +++ 8 files changed, 566 insertions(+), 10 deletions(-) create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxTransferType.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/transfer/TransferDetail.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/transfer/WxTransferOrder.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/transfer/WxTransferQueryOrder.java 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 index e9daaf6..6e7703e 100644 --- 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 @@ -60,6 +60,7 @@ public class DefaultWxPayAssistService implements WxPayAssistService { * @param transactionType 交易类型 * @return 响应内容体 */ + @Override public JSONObject doExecute(Map parameters, TransactionType transactionType) { // String requestBody = JSON.toJSONString(parameters, SerializerFeature.WriteMapNullValue); String requestBody = JSON.toJSONString(parameters); @@ -75,6 +76,7 @@ public class DefaultWxPayAssistService implements WxPayAssistService { * @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()); @@ -95,6 +97,7 @@ public class DefaultWxPayAssistService implements WxPayAssistService { * @param uriVariables 用于匹配表达式 * @return 响应内容体 */ + @Override public JSONObject doExecute(String body, TransactionType transactionType, Object... uriVariables) { final ResponseEntity responseEntity = doExecuteEntity(body, transactionType, uriVariables); int statusCode = responseEntity.getStatusCode(); @@ -112,6 +115,7 @@ public class DefaultWxPayAssistService implements WxPayAssistService { * @param order 订单 * @return 请求响应 */ + @Override public JSONObject doExecute(Map parameters, PayOrder order) { TransactionType transactionType = order.getTransactionType(); return doExecute(parameters, transactionType); @@ -127,6 +131,7 @@ public class DefaultWxPayAssistService implements WxPayAssistService { * @param method 请求方法 * @return 请求实体 */ + @Override public HttpEntity buildHttpEntity(String url, String body, String method) { String nonceStr = SignTextUtils.randomStr(); long timestamp = DateUtils.toEpochSecond(); 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 index 2d84635..36c2a89 100644 --- 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 @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStream; import java.security.PrivateKey; import java.security.cert.Certificate; +import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.Enumeration; @@ -11,6 +12,7 @@ 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; @@ -21,9 +23,9 @@ 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; @@ -50,16 +52,17 @@ import com.egzosn.pay.common.util.sign.SignUtils; import com.egzosn.pay.common.util.sign.encrypt.RSA2; import com.egzosn.pay.common.util.str.StringUtils; import com.egzosn.pay.wx.bean.WxPayError; -import com.egzosn.pay.wx.bean.WxTransferType; 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; @@ -72,7 +75,7 @@ import com.egzosn.pay.wx.v3.utils.WxConst; * date 2021/10/6 * */ -public class WxPayService extends BasePayService { +public class WxPayService extends BasePayService implements TransferService { /** @@ -91,14 +94,14 @@ public class WxPayService extends BasePayService { */ private volatile WxParameterStructure wxParameterStructure; + /** * 创建支付服务 * * @param payConfigStorage 微信对应的支付配置 */ public WxPayService(WxPayConfigStorage payConfigStorage) { - super(payConfigStorage); - + this(payConfigStorage, null); } /** @@ -110,6 +113,7 @@ public class WxPayService extends BasePayService { public WxPayService(WxPayConfigStorage payConfigStorage, HttpConfigStorage configStorage) { super(payConfigStorage, configStorage); } + { initAfter(); } @@ -613,14 +617,100 @@ public class WxPayService extends BasePayService { /** - * 转账 + * 发起商家转账, 转账账单电子回单申请受理接口 * - * @param order 转账订单 + * @param transferOrder 转账订单 * @return 对应的转账结果 */ @Override - public Map transfer(TransferOrder order) { - throw new PayErrorException(new WxPayError("", "V3不支持转账")); + public Map transfer(TransferOrder transferOrder) { + //转账账单电子回单申请受理接口 + if (transferOrder.getTransferType() == WxTransferType.TRANSFER_BILL_RECEIPT) { + Map attr = new MapGen(WxConst.OUT_BATCH_NO, transferOrder.getBatchNo()).getAttr(); + return getAssistService().doExecute(attr, transferOrder.getTransferType()); + } + + Map parameters = new HashMap<>(12); + parameters.put(WxConst.APPID, payConfigStorage.getAppId()); + parameters.put(WxConst.OUT_BATCH_NO, transferOrder.getBatchNo()); + OrderParaStructure.loadParameters(parameters, WxConst.BATCH_NAME, transferOrder); + parameters.put(WxConst.BATCH_REMARK, transferOrder.getRemark()); + parameters.put(WxConst.TOTAL_AMOUNT, Util.conversionCentAmount(transferOrder.getAmount())); + parameters.put(WxConst.TOTAL_NUM, transferOrder.getAttr(WxConst.TOTAL_NUM)); + Object transferDetailListAttr = transferOrder.getAttr(WxConst.TRANSFER_DETAIL_LIST); + List transferDetails = initTransferDetailListAttr(transferDetailListAttr); + parameters.put(WxConst.TRANSFER_DETAIL_LIST, transferDetails); + OrderParaStructure.loadParameters(parameters, WxConst.TRANSFER_SCENE_ID, transferOrder); + return getAssistService().doExecute(parameters, transferOrder.getTransferType()); + } + + private List initTransferDetailListAttr(Object transferDetailListAttr) { + List transferDetails = null; + if (transferDetailListAttr instanceof String) { + transferDetails = JSON.parseArray((String) transferDetailListAttr, TransferDetail.class); + } + else if (null != transferDetailListAttr) { + //偷懒的做法 + transferDetails = JSON.parseArray(JSON.toJSONString(transferDetailListAttr), TransferDetail.class); + } + else { + return null; + } + + String serialNumber = payConfigStorage.getCertEnvironment().getSerialNumber(); + Certificate certificate = getAssistService().getCertificate(serialNumber); + return transferDetails.stream() + .peek(transferDetailListItem -> { + String userName = transferDetailListItem.getUserName(); + if (StringUtils.isNotEmpty(userName)) { + String encryptedUserName = AntCertificationUtil.encryptToString(userName, certificate); + transferDetailListItem.setUserName(encryptedUserName); + } + String userIdCard = transferDetailListItem.getUserIdCard(); + if (StringUtils.isNotEmpty(userIdCard)) { + String encryptedUserIdCard = AntCertificationUtil.encryptToString(userIdCard, certificate); + transferDetailListItem.setUserIdCard(encryptedUserIdCard); + } + }).collect(Collectors.toList()); + } + + /** + * 转账查询API + * 通过批次单号查询批次单 与 通过明细单号查询明细单 + * + * @param assistOrder 辅助交易订单 + * @return 对应的转账订单 + */ + @Override + public Map transferQuery(AssistOrder assistOrder) { + Map parameters = new HashMap<>(6); + TransactionType transactionType = assistOrder.getTransactionType(); + List uriVariables = new ArrayList<>(3); + + if (StringUtils.isNotEmpty(assistOrder.getTradeNo())) { + uriVariables.add(assistOrder.getTradeNo()); + String detailId = assistOrder.getAttrForString(WxConst.DETAIL_ID); + if (StringUtils.isNotEmpty(detailId)) { + uriVariables.add(detailId); + } + } + else if (StringUtils.isNotEmpty(assistOrder.getOutTradeNo())) { + uriVariables.add(assistOrder.getOutTradeNo()); + String outDetailNo = assistOrder.getAttrForString(WxConst.OUT_DETAIL_NO); + if (StringUtils.isNotEmpty(outDetailNo)) { + uriVariables.add(outDetailNo); + } + } + + if (transactionType == com.egzosn.pay.wx.v3.bean.WxTransferType.QUERY_BATCH_BY_BATCH_ID || transactionType == com.egzosn.pay.wx.v3.bean.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 = JSON.toJSONString(parameters); + return getAssistService().doExecute(requestBody, assistOrder.getTransactionType(), uriVariables.toArray()); } 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 index ea883a7..873e2ee 100644 --- 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 @@ -153,7 +153,7 @@ public enum WxTransactionType implements TransactionType { /** * 合单关闭订单 */ - COMBINE_CLOSE("/v3/combine-transactions/out-trade-no/{combine_out_trade_no}/close", MethodType.POST), + COMBINE_CLOSE("/v3/combine-transactions/out-trade-no/{combine_out_trade_no}/close", MethodType.POST) ; WxTransactionType(String type, MethodType method) { 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 0000000..1e6c55a --- /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/transfer/TransferDetail.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/transfer/TransferDetail.java new file mode 100644 index 0000000..cfe858b --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/transfer/TransferDetail.java @@ -0,0 +1,101 @@ +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 0000000..0407a0d --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/transfer/WxTransferOrder.java @@ -0,0 +1,124 @@ +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 0000000..36585a9 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/transfer/WxTransferQueryOrder.java @@ -0,0 +1,131 @@ +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/WxConst.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/utils/WxConst.java index 06d43f4..38ab1bd 100644 --- 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 @@ -69,4 +69,20 @@ public final class WxConst { 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"; + + } -- Gitee From 547cbd6f841721d4f5f6676ad1baa18f85d9d11c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=91=E7=81=B6=E7=94=9F?= Date: Sun, 8 Jan 2023 23:01:56 +0800 Subject: [PATCH 135/165] =?UTF-8?q?=E5=BE=AE=E4=BF=A1V3=E5=95=86=E5=AE=B6?= =?UTF-8?q?=E8=BD=AC=E8=B4=A6=E5=88=B0=E9=9B=B6=E9=92=B1=E7=9B=B8=E5=85=B3?= =?UTF-8?q?API=20=E6=A1=88=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../demo/controller/WxV3PayController.java | 138 +++++++++++++++++- 1 file changed, 137 insertions(+), 1 deletion(-) 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 index a3bc5da..f3d5088 100644 --- 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 @@ -5,11 +5,11 @@ 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.annotation.PostConstruct; import javax.imageio.ImageIO; import javax.servlet.http.HttpServletRequest; @@ -27,8 +27,12 @@ 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; /** @@ -233,5 +237,137 @@ public class WxV3PayController { 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); + } } -- Gitee From 73bcd4f8331ed0663a50ae98b655286fd7fca54b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=91=E7=81=B6=E7=94=9F?= Date: Sun, 8 Jan 2023 23:21:47 +0800 Subject: [PATCH 136/165] =?UTF-8?q?=E7=BB=9F=E4=B8=80=E6=94=B6=E5=8D=95?= =?UTF-8?q?=E4=BA=A4=E6=98=93=E6=9F=A5=E8=AF=A2=E8=A1=A5=E5=85=85=E3=80=90?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E9=80=89=E9=A1=B9=E3=80=91=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/egzosn/pay/ali/api/AliPayService.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) 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 f43b76f..14bc62d 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 @@ -216,7 +216,7 @@ public class AliPayService extends BasePayService implement 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.getNotifyUrl()); OrderParaStructure.loadParameters(orderInfo, NOTIFY_URL, order); } @@ -497,7 +497,16 @@ public class AliPayService extends BasePayService implement */ @Override public Map query(AssistOrder assistOrder) { - return secondaryInterface(assistOrder.getTradeNo(), assistOrder.getOutTradeNo(), 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.getOutTradeNo(), assistOrder.getTradeNo(), bizContent))); + //设置签名 + setSign(parameters); + return requestTemplate.getForObject(getReqUrl(assistOrder.getTransactionType()) + "?" + UriVariables.getMapToParameters(parameters), JSONObject.class); } -- Gitee From c8a99c00de652ff4e3578b423a87f070382579b7 Mon Sep 17 00:00:00 2001 From: egan Date: Sun, 5 Feb 2023 16:15:48 +0800 Subject: [PATCH 137/165] =?UTF-8?q?2.14.5=E5=8F=91=E5=B8=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/egzosn/pay/common/api/TransferService.java | 6 ++++-- .../src/main/java/com/egzosn/pay/common/bean/Attrs.java | 1 + .../src/main/java/com/egzosn/pay/wx/api/WxPayService.java | 3 +-- .../java/com/egzosn/pay/wx/v3/api/WxParameterStructure.java | 3 ++- .../com/egzosn/pay/wx/v3/bean/transfer/TransferDetail.java | 6 ++++-- .../com/egzosn/pay/wx/v3/bean/transfer/WxTransferOrder.java | 6 ++++-- .../pay/wx/v3/bean/transfer/WxTransferQueryOrder.java | 6 ++++-- 7 files changed, 20 insertions(+), 11 deletions(-) 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 index 695fe66..69d51e2 100644 --- 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 @@ -9,8 +9,10 @@ import com.egzosn.pay.common.bean.TransferOrder; * 转账服务 * * @author Egan - * @email egan@egzosn.com - * @date 2023/1/8 + *
+ *  email egan@egzosn.com
+ *  date 2023/1/8
+ *  
*/ public interface TransferService { 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 index 2713b0d..48dcf88 100644 --- 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 @@ -73,6 +73,7 @@ public interface Attrs extends Serializable { * 获取属性 这里可用做覆盖已设置的属性信息属性。 * * @param key 属性名 + * @param defaultValue 默认值 * @return 属性 */ default Integer getAttrForInt(String key, Integer defaultValue) { 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 f5eaf7b..09c7922 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 @@ -835,8 +835,7 @@ public class WxPayService extends BasePayService implements * 转账 * * @param order 转账订单 - *
-     *              @return 对应的转账结果
+     * @return 对应的转账结果
      */
     @Override
     public Map transfer(TransferOrder order) {
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
index 24c2553..21f2cbe 100644
--- 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
@@ -28,7 +28,7 @@ public class WxParameterStructure {
 
     /**
      * 获取公共参数
-     *
+     * @param parameters 参数
      * @return 公共参数
      */
     public Map getPublicParameters(Map parameters) {
@@ -93,6 +93,7 @@ public class WxParameterStructure {
      * 初始化商户相关信息
      *
      * @param parameters 参数信息
+     * @return 参数信息
      */
     public Map initPartner(Map parameters) {
         if (null == parameters) {
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
index cfe858b..0dd09f9 100644
--- 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
@@ -6,8 +6,10 @@ import com.alibaba.fastjson.annotation.JSONField;
  *
  * 转账明细
  * @author Egan
- * @email egan@egzosn.com
- * @date 2023/1/8
+ * 
+ * email egan@egzosn.com
+ * date 2023/1/8
+ * 
*/ public class TransferDetail { /** 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 index 0407a0d..a3008dc 100644 --- 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 @@ -10,8 +10,10 @@ import com.egzosn.pay.wx.v3.utils.WxConst; * 发起商家转账 * * @author Egan - * @email egan@egzosn.com - * @date 2023/1/8 + *
+ * email egan@egzosn.com
+ * date 2023/1/8
+ * 
*/ public class WxTransferOrder extends TransferOrder { 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 index 36585a9..34cc2df 100644 --- 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 @@ -7,8 +7,10 @@ import com.egzosn.pay.wx.v3.utils.WxConst; * 微信转账查询订单 * * @author Egan - * @email egan@egzosn.com - * @date 2023/1/8 + *
+ * email egan@egzosn.com
+ * date 2023/1/8
+ * 
*/ public class WxTransferQueryOrder extends AssistOrder { -- Gitee From c2e9b16809dba950e8fb6702b9121a49b7a1f63b Mon Sep 17 00:00:00 2001 From: egan Date: Sun, 5 Feb 2023 16:16:54 +0800 Subject: [PATCH 138/165] =?UTF-8?q?2.14.5=E5=8F=91=E5=B8=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1ac066e..e80219f 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ com.egzosn {module-name} - 2.14.4-fix + 2.14.5 ``` -- Gitee From 7656c3322ba41d9b4843fdcbdc75b9cdb56e2564 Mon Sep 17 00:00:00 2001 From: lwejn <12523192+lwejn@user.noreply.gitee.com> Date: Mon, 27 Mar 2023 08:16:07 +0000 Subject: [PATCH 139/165] =?UTF-8?q?=E5=8D=87=E7=BA=A7fastjson=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=88=B01.2.83,1.2.83=E7=89=88=E6=9C=AC=E4=B9=8B?= =?UTF-8?q?=E5=89=8D=E5=AD=98=E5=9C=A8=E4=BB=A3=E7=A0=81=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E6=BC=8F=E6=B4=9E=E9=A3=8E=E9=99=A9=EF=BC=8CCVE-2022-25845=20?= =?UTF-8?q?=E5=8D=87=E7=BA=A7fastjson=E7=89=88=E6=9C=AC=E5=88=B01.2.83,1.2?= =?UTF-8?q?.83=E7=89=88=E6=9C=AC=E4=B9=8B=E5=89=8D=E5=AD=98=E5=9C=A8?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=89=A7=E8=A1=8C=E6=BC=8F=E6=B4=9E=E9=A3=8E?= =?UTF-8?q?=E9=99=A9=EF=BC=8CCVE-2022-25845?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: lwejn <12523192+lwejn@user.noreply.gitee.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 00881fe..ab7ffe1 100644 --- a/pom.xml +++ b/pom.xml @@ -68,7 +68,7 @@ 2.14.5 4.5.4 1.2.17 - 1.2.73 + 1.2.83 3.3.1 4.0.1 1.59 -- Gitee From c102c2a1f3c8e2d96f602bd082e35126fb099de2 Mon Sep 17 00:00:00 2001 From: pengkaibing <1787009158@qq.com> Date: Fri, 2 Jun 2023 23:46:11 +0800 Subject: [PATCH 140/165] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?V3=EF=BC=9A=E5=95=86=E6=88=B7=E4=B8=8A=E9=80=81=E6=95=8F?= =?UTF-8?q?=E6=84=9F=E4=BF=A1=E6=81=AF=E6=97=B6=E5=8A=A0=E5=AF=86=E7=A7=98?= =?UTF-8?q?=E9=92=A5=E9=94=99=E8=AF=AF=E9=97=AE=E9=A2=98=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=20=E6=94=AF=E4=BB=98=E5=AE=9D=EF=BC=9A=E4=BA=A4?= =?UTF-8?q?=E6=98=93=E6=9F=A5=E8=AF=A2=E6=8E=A5=E5=8F=A3=E8=B0=83=E7=94=A8?= =?UTF-8?q?getBizContent=E6=96=B9=E6=B3=95tradeNo=E4=B8=8EoutTradeNo?= =?UTF-8?q?=E5=BC=84=E5=8F=8D=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/ali/api/AliPayService.java | 2 +- .../pay/wx/v3/api/DefaultWxPayAssistService.java | 12 ++++++++++++ .../com/egzosn/pay/wx/v3/api/WxPayService.java | 3 ++- .../egzosn/pay/wx/v3/bean/CertEnvironment.java | 15 +++++++++++++-- 4 files changed, 28 insertions(+), 4 deletions(-) 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 14bc62d..1e2e1f1 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 @@ -503,7 +503,7 @@ public class AliPayService extends BasePayService implement OrderParaStructure.loadParameters(bizContent, "query_options", assistOrder); //设置请求参数的集合 - parameters.put(BIZ_CONTENT, JSON.toJSONString(getBizContent(assistOrder.getOutTradeNo(), assistOrder.getTradeNo(), bizContent))); + 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); 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 index 6e7703e..3bf1e68 100644 --- 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 @@ -5,6 +5,7 @@ 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; @@ -105,6 +106,17 @@ public class DefaultWxPayAssistService implements WxPayAssistService { 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 ("Wechatpay-Serial".equals(header.getName())) { + // 更新平台证书的序列号,需要每次都更新,因为这个可能会改变 + payConfigStorage.getCertEnvironment().setPlatformSerialNumber(header.getValue()); + break; + } + } return responseBody; } 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 index 36c2a89..3a25ab5 100644 --- 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 @@ -657,7 +657,8 @@ public class WxPayService extends BasePayService implements return null; } - String serialNumber = payConfigStorage.getCertEnvironment().getSerialNumber(); + // 商户上送敏感信息时使用`微信支付平台公钥`加密 + String serialNumber = payConfigStorage.getCertEnvironment().getPlatformSerialNumber(); Certificate certificate = getAssistService().getCertificate(serialNumber); return transferDetails.stream() .peek(transferDetailListItem -> { 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 index eed1c5b..c1c4360 100644 --- 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 @@ -26,7 +26,10 @@ public class CertEnvironment { */ private String serialNumber; - + /** + * 微信平台证书序列号 + */ + private String platformSerialNumber; @@ -63,4 +66,12 @@ public class CertEnvironment { public void setSerialNumber(String serialNumber) { this.serialNumber = serialNumber; } -} \ No newline at end of file + + public String getPlatformSerialNumber() { + return platformSerialNumber; + } + + public void setPlatformSerialNumber(String platformSerialNumber) { + this.platformSerialNumber = platformSerialNumber; + } +} -- Gitee From 84cf034338655f3ed5b55bcad58aada4ed444dec Mon Sep 17 00:00:00 2001 From: egan Date: Tue, 12 Sep 2023 22:33:37 +0800 Subject: [PATCH 141/165] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E4=BD=93=E5=8E=9F=E5=A7=8B=E5=AD=97=E7=AC=A6=E4=B8=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/common/api/PayService.java | 16 +++---- .../egzosn/pay/common/bean/NoticeParams.java | 25 ++++++++++- .../pay/common/http/HttpRequestTemplate.java | 45 +++++++++++++------ .../pay/common/util/sign/SignUtils.java | 3 ++ 4 files changed, 67 insertions(+), 22 deletions(-) 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 cc1f708..28f4106 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 @@ -7,7 +7,6 @@ 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; @@ -223,14 +222,12 @@ public interface PayService { /** * 交易查询接口 * - * @param assistOrder 查询条件 + * @param assistOrder 查询条件 * @return 返回查询回来的结果集,支付方原值返回 */ Map query(AssistOrder assistOrder); - - /** * 交易关闭接口 * @@ -241,10 +238,11 @@ public interface PayService { */ @Deprecated Map close(String tradeNo, String outTradeNo); + /** * 交易关闭接口 * - * @param assistOrder 关闭订单 + * @param assistOrder 关闭订单 * @return 返回支付方交易关闭后的结果 */ Map close(AssistOrder assistOrder); @@ -343,7 +341,6 @@ public interface PayService { Map downloadBill(Date billDate, BillType billType); - /** * 转账 * @@ -370,7 +367,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); /** @@ -395,15 +394,16 @@ public interface PayService { */ @Deprecated PayOutMessage payBack(Map parameterMap, InputStream is); + /** - * 回调处理 + * 回调处理 + * * @param request 请求参数 * @return 获得回调响应信息 */ PayOutMessage payBack(NoticeRequest request); - /** * 设置支付消息处理器,这里用于处理具体的支付业务 * 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 index 6b19357..752e7fb 100644 --- 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 @@ -29,8 +29,12 @@ import java.util.Map; * email egzosn@gmail.com * date 2021/8/8 */ -public class NoticeParams { +public class NoticeParams implements Attrs { + /** + * body原始字符串 + */ + private String bodyStr; /** * 为了获取request里面传过来的动态参数 @@ -59,6 +63,14 @@ public class NoticeParams { 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) { @@ -115,4 +127,15 @@ public class NoticeParams { 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/http/HttpRequestTemplate.java b/pay-java-common/src/main/java/com/egzosn/pay/common/http/HttpRequestTemplate.java index d53f372..0f9564c 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 @@ -153,7 +153,7 @@ public class HttpRequestTemplate { //指定TLS版本 sslsf = new SSLConnectionSocketFactory( - sslcontext, new String[]{"TLSv1","TLSv1.2"}, null, + sslcontext, new String[]{"TLSv1", "TLSv1.2"}, null, new DefaultHostnameVerifier()); return sslsf; @@ -268,7 +268,7 @@ public class HttpRequestTemplate { * @param uriVariables 用于匹配表达式 * @param 响应类型 * @return 类型对象 - * + * * * getForObject("http://egan.in/pay/{id}/f/{type}", String.class, "1", "APP") * @@ -288,11 +288,11 @@ public class HttpRequestTemplate { * @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)
*
*/ @@ -334,11 +334,29 @@ public class HttpRequestTemplate { * getForObject("http://egan.in/pay/{id}/f/{type}", String.class, "1", "APP") * */ - public ResponseEntity getForObjectEntity(String uri, HttpHeader header, Class responseType, Object... uriVariables) { + 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 类型对象 + * + * + * getForObject("http://egan.in/pay/{id}/f/{type}", String.class, "1", "APP") + * + */ + public ResponseEntity getForObjectEntity(String uri, Class responseType, Object... uriVariables) { + + return doExecuteEntity(URI.create(UriVariables.getUri(uri, uriVariables)), null, responseType, MethodType.GET); + } + /** * get 请求 * @@ -350,17 +368,18 @@ public class HttpRequestTemplate { * @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 T getForObject(String uri, HttpHeader header, Class responseType, Map uriVariables) { return getForObjectEntity(uri, header, responseType, uriVariables).getBody(); } + /** * get 请求 * @@ -372,15 +391,15 @@ public class HttpRequestTemplate { * @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) { + public ResponseEntity getForObjectEntity(String uri, HttpHeader header, Class responseType, Map uriVariables) { return doExecuteEntity(URI.create(UriVariables.getUri(uri, uriVariables)), header, responseType, MethodType.GET); } 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 69b7090..7d6aaa3 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 @@ -156,6 +156,7 @@ public enum SignUtils implements SignType { * @param characterEncoding 编码格式 * @return 签名值 */ + @Override public String sign(Map parameters, String key, String characterEncoding) { return createSign(parameterText(parameters, "&"), key, characterEncoding); @@ -170,6 +171,7 @@ public enum SignUtils implements SignType { * @param characterEncoding 编码格式 * @return 签名值 */ + @Override public String sign(Map parameters, String key, String separator, String characterEncoding) { return createSign(parameterText(parameters, separator), key, characterEncoding); @@ -186,6 +188,7 @@ public enum SignUtils implements SignType { * @param characterEncoding 编码格式 * @return 签名结果 */ + @Override public boolean verify(Map params, String sign, String key, String characterEncoding) { //判断是否一样 return this.verify(parameterText(params), sign, key, characterEncoding); -- Gitee From c51499a6463959f82c84bdbfa0b7fd18b1de5acc Mon Sep 17 00:00:00 2001 From: egan Date: Tue, 12 Sep 2023 22:33:49 +0800 Subject: [PATCH 142/165] =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=AE=9D=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E8=87=AA=E5=AE=9A=E4=B9=89URL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/ali/api/AliPayService.java | 20 +++++++++++++++++++ .../egzosn/pay/ali/api/AliPayServiceInf.java | 9 +++++++++ .../com/egzosn/pay/ali/bean/AliPayConst.java | 2 +- 3 files changed, 30 insertions(+), 1 deletion(-) 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 1e2e1f1..8217515 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 @@ -65,6 +65,11 @@ import com.egzosn.pay.common.util.str.StringUtils; public class AliPayService extends BasePayService implements AliPayServiceInf { + /** + * api服务地址,默认为国内 + */ + private String apiServerUrl; + /** * 获取对应的请求地址 * @@ -72,6 +77,9 @@ public class AliPayService extends BasePayService implement */ @Override public String getReqUrl(TransactionType transactionType) { + if (StringUtils.isNotEmpty(apiServerUrl)) { + return apiServerUrl; + } return payConfigStorage.isTest() ? AliPayConst.DEV_REQ_URL : HTTPS_REQ_URL; } @@ -850,5 +858,17 @@ public class AliPayService extends BasePayService implement 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 index c425c45..699f2f8 100644 --- 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 @@ -19,4 +19,13 @@ public interface AliPayServiceInf { * @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/AliPayConst.java b/pay-java-ali/src/main/java/com/egzosn/pay/ali/bean/AliPayConst.java index dd09e40..7bce3cd 100644 --- 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 @@ -20,7 +20,7 @@ public final class AliPayConst { /** * 沙箱测试环境账号 */ - public static final String DEV_REQ_URL = "https://openapi.alipaydev.com/gateway.do"; + public static final String DEV_REQ_URL = "https://openapi-sandbox.dl.alipaydev.com/gateway.do"; public static final String SIGN = "sign"; -- Gitee From 89beb1d5b4a8b8bb6aabe2c32f5e82a9498ba9c4 Mon Sep 17 00:00:00 2001 From: egan Date: Tue, 12 Sep 2023 22:34:04 +0800 Subject: [PATCH 143/165] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E4=BF=AE=E5=A4=8DV3?= =?UTF-8?q?=E8=BD=AC=E8=B4=A6=E6=9F=A5=E8=AF=A2=E6=8E=A5=E5=8F=A3=EF=BC=8C?= =?UTF-8?q?=E8=A1=A5=E5=85=85=E5=88=86=E8=B4=A6=E5=8A=9F=E8=83=BD=E8=AF=81?= =?UTF-8?q?=E4=B9=A6=E5=BA=8F=E5=88=97=E5=8F=82=E6=95=B0=EF=BC=8CV2?= =?UTF-8?q?=E6=B2=99=E7=AE=B1=E5=9C=B0=E5=9D=80=E5=8F=98=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/egzosn/pay/wx/api/WxConst.java | 2 +- .../wx/v3/api/DefaultWxPayAssistService.java | 13 ++-- .../egzosn/pay/wx/v3/api/WxPayService.java | 27 ++++---- .../egzosn/pay/wx/v3/api/WxPayServiceInf.java | 67 +++++++++++++++++++ .../pay/wx/v3/api/WxProfitSharingService.java | 35 +++++----- .../pay/wx/v3/bean/CertEnvironment.java | 7 +- .../com/egzosn/pay/wx/v3/utils/WxConst.java | 1 + 7 files changed, 111 insertions(+), 41 deletions(-) create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayServiceInf.java 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 fe3d4e4..07a290b 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"; 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 index 3bf1e68..54897ff 100644 --- 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 @@ -43,10 +43,10 @@ public class DefaultWxPayAssistService implements WxPayAssistService { private HttpRequestTemplate requestTemplate; - private WxPayService wxPayService; + private WxPayServiceInf wxPayService; - public DefaultWxPayAssistService(WxPayService wxPayService) { + public DefaultWxPayAssistService(WxPayServiceInf wxPayService) { this.wxPayService = wxPayService; payConfigStorage = wxPayService.getPayConfigStorage(); requestTemplate = wxPayService.getHttpRequestTemplate(); @@ -111,7 +111,7 @@ public class DefaultWxPayAssistService implements WxPayAssistService { return responseBody; } for (Header header : headers) { - if ("Wechatpay-Serial".equals(header.getName())) { + if (WxConst.WECHATPAY_SERIAL.equals(header.getName())) { // 更新平台证书的序列号,需要每次都更新,因为这个可能会改变 payConfigStorage.getCertEnvironment().setPlatformSerialNumber(header.getValue()); break; @@ -138,7 +138,7 @@ public class DefaultWxPayAssistService implements WxPayAssistService { * 构建请求实体 * 这里也做签名处理 * - * @param url url + * @param url url * @param body 请求内容体 * @param method 请求方法 * @return 请求实体 @@ -158,7 +158,9 @@ public class DefaultWxPayAssistService implements WxPayAssistService { 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 entity; + + + return wxPayService.hookHttpEntity(entity); } @@ -210,5 +212,4 @@ public class DefaultWxPayAssistService implements WxPayAssistService { } - } 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 index 3a25ab5..3170155 100644 --- 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 @@ -75,7 +75,7 @@ import com.egzosn.pay.wx.v3.utils.WxConst; * date 2021/10/6 *

*/ -public class WxPayService extends BasePayService implements TransferService { +public class WxPayService extends BasePayService implements TransferService, WxPayServiceInf { /** @@ -123,12 +123,13 @@ public class WxPayService extends BasePayService implements * * @return 辅助api */ + @Override public WxPayAssistService getAssistService() { if (null == assistService) { assistService = new DefaultWxPayAssistService(this); - //在这预先进行初始化 - assistService.refreshCertificate(); } + //在这预先进行初始化 + assistService.refreshCertificate(); return assistService; } @@ -144,7 +145,7 @@ public class WxPayService extends BasePayService implements // new Thread(() -> { payConfigStorage.loadCertEnvironment(); wxParameterStructure = new WxParameterStructure(payConfigStorage); - getAssistService(); + setApiServerUrl(WxConst.URI); // }).start(); } @@ -155,11 +156,14 @@ public class WxPayService extends BasePayService implements * @param apiServerUrl api服务器地址 * @return 自身 */ + @Override public WxPayService setApiServerUrl(String apiServerUrl) { this.apiServerUrl = apiServerUrl; + getAssistService(); return this; } + @Override public String getApiServerUrl() { return apiServerUrl; } @@ -218,14 +222,9 @@ public class WxPayService extends BasePayService implements Certificate certificate = getAssistService().getCertificate(serial); - Map attr = noticeParams.getAttr(); - - if (Util.isEmpty(attr)) { - throw new PayErrorException(new WxPayError(FAILURE, "请勿置空NoticeParams.attr中的数据")); - } //这里为微信回调时的请求内容体,原值数据 - String body = (String) attr.get(WxConst.RESP_BODY); + String body = noticeParams.getBodyStr(); //签名信息 String signText = StringUtils.joining("\n", timestamp, nonce, body); @@ -362,6 +361,7 @@ public class WxPayService extends BasePayService implements 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(); @@ -703,14 +703,15 @@ public class WxPayService extends BasePayService implements } } - if (transactionType == com.egzosn.pay.wx.v3.bean.WxTransferType.QUERY_BATCH_BY_BATCH_ID || transactionType == com.egzosn.pay.wx.v3.bean.WxTransferType.QUERY_BATCH_BY_OUT_BATCH_NO) { + 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 = JSON.toJSONString(parameters); + + String requestBody = UriVariables.getMapToParameters(parameters); return getAssistService().doExecute(requestBody, assistOrder.getTransactionType(), uriVariables.toArray()); } @@ -728,7 +729,7 @@ public class WxPayService extends BasePayService implements */ @Override public Map transferQuery(String outNo, String wxTransferType) { - throw new PayErrorException(new WxPayError("", "V3不支持转账查询")); + throw new PayErrorException(new WxPayError("", "V3不支持此转账查询:替代方法transferQuery(AssistOrder assistOrder)")); } 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 0000000..01080b9 --- /dev/null +++ b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayServiceInf.java @@ -0,0 +1,67 @@ +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 index d030742..d4bc919 100644 --- 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 @@ -1,30 +1,29 @@ package com.egzosn.pay.wx.v3.api; import java.io.InputStream; -import java.security.PrivateKey; 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.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.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.common.util.sign.encrypt.RSA2; import com.egzosn.pay.wx.bean.WxPayError; import com.egzosn.pay.wx.bean.WxTransferType; import com.egzosn.pay.wx.v3.bean.WxProfitSharingTransactionType; @@ -70,8 +69,8 @@ public class WxProfitSharingService extends WxPayService implements ProfitSharin @Override protected void initAfter() { // new Thread(() -> { - payConfigStorage.loadCertEnvironment(); - getAssistService(); + payConfigStorage.loadCertEnvironment(); + setApiServerUrl(WxConst.URI); // }).start(); } @@ -121,6 +120,17 @@ public class WxProfitSharingService extends WxPayService implements ProfitSharin return getAssistService().doExecute(parameters, order); } + /** + * http 实体 钩子 + * + * @param entity 实体 + * @return 返回处理后的实体 + */ + @Override + public HttpStringEntity hookHttpEntity(HttpStringEntity entity) { + entity.addHeader(new BasicHeader(WxConst.WECHATPAY_SERIAL, payConfigStorage.getCertEnvironment().getPlatformSerialNumber())); + return entity; + } /** * 返回创建的订单信息 @@ -148,19 +158,6 @@ public class WxProfitSharingService extends WxPayService implements ProfitSharin } - /** - * 签名 - * - * @param content 需要签名的内容 不包含key - * @param characterEncoding 字符编码 - * @return 签名结果 - */ - @Override - public String createSign(String content, String characterEncoding) { - PrivateKey privateKey = payConfigStorage.getCertEnvironment().getPrivateKey(); - return RSA2.sign(content, privateKey, characterEncoding); - } - /** * 将请求参数或者请求流转化为 Map * 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 index c1c4360..4b226a3 100644 --- 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 @@ -3,6 +3,8 @@ package com.egzosn.pay.wx.v3.bean; import java.security.PrivateKey; import java.security.PublicKey; +import com.egzosn.pay.common.util.str.StringUtils; + /** * 证书模式运行时环境 * @@ -32,8 +34,6 @@ public class CertEnvironment { private String platformSerialNumber; - - public CertEnvironment() { } @@ -68,6 +68,9 @@ public class CertEnvironment { } public String getPlatformSerialNumber() { + if (StringUtils.isEmpty(platformSerialNumber)) { + setPlatformSerialNumber(serialNumber); + } return platformSerialNumber; } 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 index 38ab1bd..b491eb5 100644 --- 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 @@ -83,6 +83,7 @@ public final class WxConst { 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"; } -- Gitee From 17be83c20f2439d86d8adb0e85a9899b8af1f2d0 Mon Sep 17 00:00:00 2001 From: egan Date: Tue, 12 Sep 2023 22:34:15 +0800 Subject: [PATCH 144/165] =?UTF-8?q?paypal=20v2=E6=96=B0=E5=A2=9Ewebhook?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E6=A0=A1=E9=AA=8C=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../v2/api/PayPalOutMessageBuilder.java | 26 ----- .../pay/paypal/v2/api/PayPalPayService.java | 100 +++++++++++++++--- .../egzosn/pay/paypal/v2/bean/Constants.java | 59 +++++++++++ .../pay/paypal/v2/utils/PayPalUtil.java | 93 ++++++++++++++++ 4 files changed, 238 insertions(+), 40 deletions(-) delete mode 100644 pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/api/PayPalOutMessageBuilder.java create mode 100644 pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/Constants.java create mode 100644 pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/utils/PayPalUtil.java diff --git a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/api/PayPalOutMessageBuilder.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/api/PayPalOutMessageBuilder.java deleted file mode 100644 index 71e19c4..0000000 --- a/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/api/PayPalOutMessageBuilder.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.egzosn.pay.paypal.v2.api; - -import java.util.Map; - -import com.egzosn.pay.common.bean.outbuilder.TextBuilder; - -/** - * @author Egan - *
- * email egzosn@gmail.com
- * date 2021/1/17
- * 
- */ -public class PayPalOutMessageBuilder extends TextBuilder { - - - public PayPalOutMessageBuilder(Map message) { - StringBuilder out = new StringBuilder(); - for (Map.Entry entry : message.entrySet()) { - out.append(entry.getKey()).append('=').append(entry.getValue()).append("
"); - } - super.content(out.toString()); - } - - -} 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 index 5eb631d..0dae83f 100644 --- 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 @@ -1,13 +1,16 @@ 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.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.UUID; @@ -23,11 +26,11 @@ 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; @@ -38,10 +41,13 @@ 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; @@ -49,6 +55,7 @@ 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; /** @@ -61,6 +68,7 @@ import com.egzosn.pay.paypal.v2.bean.order.ShippingDetail; */ public class PayPalPayService extends BasePayService implements PayPalPayServiceInf { + /** * 沙箱环境 */ @@ -161,12 +169,18 @@ public class PayPalPayService extends BasePayService implem @Deprecated @Override public boolean verify(Map params) { - return verify(new NoticeParams(params)); + + throw new PayErrorException(new PayException("failure", "payPal V2版本不支持此校验方式")); } - @Override - public boolean verify(NoticeParams noticeParams) { + + /** + * 保留IPN的校验方式 + * @param noticeParams 参数 + * @return 结果 + */ + public boolean verifyIpn(NoticeParams noticeParams) { final Map params = noticeParams.getBody(); Object paymentStatus = params.get("payment_status"); if (!"Completed".equals(paymentStatus)) { @@ -177,6 +191,64 @@ public class PayPalPayService extends BasePayService implem 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); + Map body = noticeParams.getBody(); + String webHookId = (String) body.get(Constants.ID); + 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; + } + /** * 获取授权请求头 * @@ -305,14 +377,14 @@ public class PayPalPayService extends BasePayService implem @Override public PayOutMessage getPayOutMessage(String code, String message) { - String out = "The response from IPN was: " + code + ""; - return PayOutMessage.TEXT().content(out).build(); + + return PayOutMessage.TEXT().content(code).build(); } @Override public PayOutMessage successPayOutMessage(PayMessage payMessage) { - Map message = payMessage.getPayMessage(); - return new PayPalOutMessageBuilder(message).build(); + + return PayOutMessage.TEXT().content("200").build(); } @Override @@ -367,16 +439,18 @@ public class PayPalPayService extends BasePayService implem public Map close(String tradeNo, String outTradeNo) { return null; } + /** * 交易关闭接口 * - * @param assistOrder 关闭订单 + * @param assistOrder 关闭订单 * @return 返回支付方交易关闭后的结果 */ @Override - public Map close(AssistOrder assistOrder){ + public Map close(AssistOrder assistOrder) { throw new UnsupportedOperationException("不支持该操作"); } + /** * 注意:最好在付款成功之后回调时进行调用 * 确认订单并返回确认后订单信息 @@ -518,13 +592,11 @@ public class PayPalPayService extends BasePayService implem 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/bean/Constants.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/bean/Constants.java new file mode 100644 index 0000000..f0819fa --- /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/utils/PayPalUtil.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/utils/PayPalUtil.java new file mode 100644 index 0000000..aaec461 --- /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; + } + + } +} -- Gitee From 15ac60e29e87ee3170fdb133a5a57ead8e1b9ee1 Mon Sep 17 00:00:00 2001 From: egan Date: Tue, 12 Sep 2023 22:34:28 +0800 Subject: [PATCH 145/165] =?UTF-8?q?fastjson.version=E7=89=88=E6=9C=AC1.2.8?= =?UTF-8?q?3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ab7ffe1..aae5e54 100644 --- a/pom.xml +++ b/pom.xml @@ -99,7 +99,7 @@ com.alibaba fastjson - 1.2.73 + ${fastjson.version} org.bouncycastle -- Gitee From 0ebe2878a4c6750d304ef9c0e80548463ee0e8b0 Mon Sep 17 00:00:00 2001 From: egan Date: Tue, 12 Sep 2023 22:35:01 +0800 Subject: [PATCH 146/165] =?UTF-8?q?paypal=20v2=E6=96=B0=E5=A2=9Ewebhook?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E6=A0=A1=E9=AA=8C=E6=96=B9=E5=BC=8Fdemo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PayPalV2PayController.java | 60 +++++-------------- 1 file changed, 14 insertions(+), 46 deletions(-) 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 index 13f5eeb..1393847 100644 --- 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 @@ -1,15 +1,14 @@ package com.egzosn.pay.demo.controller; -import java.io.IOException; -import java.io.InputStream; 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.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -117,49 +116,15 @@ public class PayPalV2PayController { order.setRefundNo("退款成功之后返回的退款单号"); return service.refundquery(order); } - /** - * 注意:这里不是异步回调的通知 IPN 地址设置的路径:https://developer.paypal.com/developer/ipnSimulator/ - * PayPal确认付款调用的接口 - * 用户确认付款后,paypal调用的这个方法执行付款 - * - * @param request 请求 - * @return 付款成功信息 - * @throws IOException IOException - */ - @GetMapping(value = "payBackBefore.json") - public String payBackBefore(HttpServletRequest request) throws IOException { - try (InputStream is = request.getInputStream()) { - // 参数解析与校验 https://developer.paypal.com/docs/api-basics/notifications/ipn/IPNIntro/#id08CKFJ00JYK - if (service.verify(service.getParameter2Map(request.getParameterMap(), is))) { - // TODO 这里进行成功后的订单业务处理 - // TODO 返回成功付款页面,这个到时候再做一个漂亮的页面显示,并使用前后端分离的模式 - return service.successPayOutMessage(null).toMessage(); - } - } - return "failure"; - } + /** * 支付回调地址 + * 请求方式必须为post, 回调详情文档:https://developer.paypal.com/api/rest/webhooks + * 回调成功之后必须返回http状态码 200 才能行,不然一直会重复回调,注意:如果您的应用程序以任何其他状态码响应,贝宝会在三天内重试25次通知消息 * - * @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 = "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 请求 + * @param request 请求 + * @param response 响应 * @return 是否成功 *

* 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link com.egzosn.pay.common.api.PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} @@ -171,12 +136,15 @@ public class PayPalV2PayController { * 注意:此方法一个订单只能调用一次, 建议在支付回调时进行调用 * 这里主要用来获取captureId使用,后续退款,查订单等等使用,用来替换下单返回的id * 详情: https://developer.paypal.com/docs/api/orders/v2/#orders_capture - * */ - @RequestMapping(value = "payBack.json") - public String payBack(HttpServletRequest request) { + @PostMapping(value = "payBack.json") + public String payBack(HttpServletRequest request, HttpServletResponse response) { //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() - return service.payBack(new HttpRequestNoticeParams(request)).toMessage(); + final String message = service.payBack(new HttpRequestNoticeParams(request)).toMessage(); + if (!"200".equals(message)) { + response.setStatus(400); + } + return message; } -- Gitee From bf3670665d700a3794d3b58e373926d5a66a0597 Mon Sep 17 00:00:00 2001 From: egan Date: Wed, 13 Sep 2023 21:23:24 +0800 Subject: [PATCH 147/165] =?UTF-8?q?2.14.6=E5=8F=91=E5=B8=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- pay-java-ali/pom.xml | 2 +- pay-java-baidu/pom.xml | 2 +- pay-java-common/pom.xml | 2 +- .../pay/common/http/HttpRequestTemplate.java | 18 +++++++++--------- pay-java-demo/pom.xml | 2 +- pay-java-fuiou/pom.xml | 2 +- pay-java-payoneer/pom.xml | 2 +- pay-java-paypal/pom.xml | 2 +- .../pay/paypal/v2/api/PayPalPayService.java | 2 +- .../egzosn/pay/paypal/v2/bean/Constants.java | 4 ++-- .../egzosn/pay/paypal/v2/utils/PayPalUtil.java | 4 ++-- pay-java-union/pom.xml | 2 +- pay-java-web-support/pom.xml | 2 +- pay-java-wx-youdian/pom.xml | 2 +- pay-java-wx/pom.xml | 2 +- .../egzosn/pay/wx/v3/api/WxPayServiceInf.java | 5 +++-- pay-java-yiji/pom.xml | 2 +- pom.xml | 4 ++-- 19 files changed, 32 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index e80219f..7c75183 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ com.egzosn {module-name} - 2.14.5 + 2.14.6 ``` diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index feabf14..08e8e88 100644 --- a/pay-java-ali/pom.xml +++ b/pay-java-ali/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.5 + 2.14.6 4.0.0 pay-java-ali diff --git a/pay-java-baidu/pom.xml b/pay-java-baidu/pom.xml index e2723cd..d89dd0f 100644 --- a/pay-java-baidu/pom.xml +++ b/pay-java-baidu/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.5 + 2.14.6 4.0.0 pay-java-baidu diff --git a/pay-java-common/pom.xml b/pay-java-common/pom.xml index c7d3e72..c62b177 100644 --- a/pay-java-common/pom.xml +++ b/pay-java-common/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.5 + 2.14.6 4.0.0 jar 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 0f9564c..ae0b000 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 @@ -288,11 +288,11 @@ public class HttpRequestTemplate { * @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)
*
*/ @@ -368,11 +368,11 @@ public class HttpRequestTemplate { * @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)
*
*/ @@ -391,11 +391,11 @@ public class HttpRequestTemplate { * @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)
*
*/ diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index ddc9ab0..fd3776c 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.5 + 2.14.6 4.0.0 war diff --git a/pay-java-fuiou/pom.xml b/pay-java-fuiou/pom.xml index 514bcb9..2fbaac4 100644 --- a/pay-java-fuiou/pom.xml +++ b/pay-java-fuiou/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.5 + 2.14.6 4.0.0 pay-java-fuiou diff --git a/pay-java-payoneer/pom.xml b/pay-java-payoneer/pom.xml index 415950d..bf98485 100644 --- a/pay-java-payoneer/pom.xml +++ b/pay-java-payoneer/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.5 + 2.14.6 4.0.0 pay-java-payoneer diff --git a/pay-java-paypal/pom.xml b/pay-java-paypal/pom.xml index efcb5e2..b6e88f7 100644 --- a/pay-java-paypal/pom.xml +++ b/pay-java-paypal/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.5 + 2.14.6 4.0.0 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 index 0dae83f..a5c2eb1 100644 --- 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 @@ -62,7 +62,7 @@ import com.egzosn.pay.paypal.v2.utils.PayPalUtil; * 贝宝支付配置存储 * * @author egan - *

+ * * email egzosn@gmail.com * date 2021-1-16 ‏‎22:15:09 */ 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 index f0819fa..3f8fc20 100644 --- 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 @@ -2,8 +2,8 @@ package com.egzosn.pay.paypal.v2.bean; /** * @author Egan - * @email egan@egzosn.com - * @date 2023/9/12 + * email egan@egzosn.com + * date 2023/9/12 */ public final class Constants { private Constants() { 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 index aaec461..cc8c0d9 100644 --- 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 @@ -20,8 +20,8 @@ import com.egzosn.pay.common.exception.PayErrorException; /** * @author Egan - * @email egan@egzosn.com - * @date 2023/9/12 + * email egan@egzosn.com + * date 2023/9/12 */ public final class PayPalUtil { private static final Logger LOG = LoggerFactory.getLogger(PayPalUtil.class); diff --git a/pay-java-union/pom.xml b/pay-java-union/pom.xml index 7058352..9a1a2ca 100644 --- a/pay-java-union/pom.xml +++ b/pay-java-union/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.5 + 2.14.6 4.0.0 diff --git a/pay-java-web-support/pom.xml b/pay-java-web-support/pom.xml index 8ddb16d..a5132df 100644 --- a/pay-java-web-support/pom.xml +++ b/pay-java-web-support/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.5 + 2.14.6 4.0.0 jar diff --git a/pay-java-wx-youdian/pom.xml b/pay-java-wx-youdian/pom.xml index 6d1afc9..519d7a0 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.14.5 + 2.14.6 4.0.0 pay-java-wx-youdian diff --git a/pay-java-wx/pom.xml b/pay-java-wx/pom.xml index b12b890..93bae15 100644 --- a/pay-java-wx/pom.xml +++ b/pay-java-wx/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.5 + 2.14.6 4.0.0 pay-java-wx 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 index 01080b9..dbf5436 100644 --- 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 @@ -6,9 +6,10 @@ import com.egzosn.pay.common.bean.TransactionType; import com.egzosn.pay.common.http.HttpStringEntity; /** + * 微信支付接口 * @author Egan - * @email egan@egzosn.com - * @date 2023/9/11 + * email egan@egzosn.com + * date 2023/9/11 */ public interface WxPayServiceInf extends PayService { /** diff --git a/pay-java-yiji/pom.xml b/pay-java-yiji/pom.xml index b3a7b48..eb9fd92 100644 --- a/pay-java-yiji/pom.xml +++ b/pay-java-yiji/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.5 + 2.14.6 4.0.0 diff --git a/pom.xml b/pom.xml index aae5e54..42783ae 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.egzosn pay-java-parent pom - 2.14.5 + 2.14.6 Pay Java - Parent Pay Java Parent @@ -65,7 +65,7 @@ - 2.14.5 + 2.14.6 4.5.4 1.2.17 1.2.83 -- Gitee From ac256c3ef54d760d003a5ba9ea4d49272951fdb2 Mon Sep 17 00:00:00 2001 From: egan Date: Wed, 13 Sep 2023 22:17:39 +0800 Subject: [PATCH 148/165] =?UTF-8?q?2.14.7=E5=BC=80=E5=A7=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pay-java-ali/pom.xml | 2 +- pay-java-baidu/pom.xml | 2 +- pay-java-common/pom.xml | 2 +- pay-java-demo/pom.xml | 2 +- pay-java-fuiou/pom.xml | 2 +- pay-java-payoneer/pom.xml | 2 +- pay-java-paypal/pom.xml | 2 +- pay-java-union/pom.xml | 2 +- pay-java-web-support/pom.xml | 2 +- pay-java-wx-youdian/pom.xml | 2 +- pay-java-wx/pom.xml | 2 +- pay-java-yiji/pom.xml | 2 +- pom.xml | 4 ++-- 13 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pay-java-ali/pom.xml b/pay-java-ali/pom.xml index 08e8e88..2425630 100644 --- a/pay-java-ali/pom.xml +++ b/pay-java-ali/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.6 + 2.14.7 4.0.0 pay-java-ali diff --git a/pay-java-baidu/pom.xml b/pay-java-baidu/pom.xml index d89dd0f..4d2ed8f 100644 --- a/pay-java-baidu/pom.xml +++ b/pay-java-baidu/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.6 + 2.14.7 4.0.0 pay-java-baidu diff --git a/pay-java-common/pom.xml b/pay-java-common/pom.xml index c62b177..217b192 100644 --- a/pay-java-common/pom.xml +++ b/pay-java-common/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.6 + 2.14.7 4.0.0 jar diff --git a/pay-java-demo/pom.xml b/pay-java-demo/pom.xml index fd3776c..2e0b44b 100644 --- a/pay-java-demo/pom.xml +++ b/pay-java-demo/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.6 + 2.14.7 4.0.0 war diff --git a/pay-java-fuiou/pom.xml b/pay-java-fuiou/pom.xml index 2fbaac4..59192fe 100644 --- a/pay-java-fuiou/pom.xml +++ b/pay-java-fuiou/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.6 + 2.14.7 4.0.0 pay-java-fuiou diff --git a/pay-java-payoneer/pom.xml b/pay-java-payoneer/pom.xml index bf98485..683ac49 100644 --- a/pay-java-payoneer/pom.xml +++ b/pay-java-payoneer/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.6 + 2.14.7 4.0.0 pay-java-payoneer diff --git a/pay-java-paypal/pom.xml b/pay-java-paypal/pom.xml index b6e88f7..d818075 100644 --- a/pay-java-paypal/pom.xml +++ b/pay-java-paypal/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.6 + 2.14.7 4.0.0 diff --git a/pay-java-union/pom.xml b/pay-java-union/pom.xml index 9a1a2ca..a49a64b 100644 --- a/pay-java-union/pom.xml +++ b/pay-java-union/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.6 + 2.14.7 4.0.0 diff --git a/pay-java-web-support/pom.xml b/pay-java-web-support/pom.xml index a5132df..a69e2c7 100644 --- a/pay-java-web-support/pom.xml +++ b/pay-java-web-support/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.6 + 2.14.7 4.0.0 jar diff --git a/pay-java-wx-youdian/pom.xml b/pay-java-wx-youdian/pom.xml index 519d7a0..01f935b 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.14.6 + 2.14.7 4.0.0 pay-java-wx-youdian diff --git a/pay-java-wx/pom.xml b/pay-java-wx/pom.xml index 93bae15..7a41e74 100644 --- a/pay-java-wx/pom.xml +++ b/pay-java-wx/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.6 + 2.14.7 4.0.0 pay-java-wx diff --git a/pay-java-yiji/pom.xml b/pay-java-yiji/pom.xml index eb9fd92..8cb8f7f 100644 --- a/pay-java-yiji/pom.xml +++ b/pay-java-yiji/pom.xml @@ -5,7 +5,7 @@ pay-java-parent com.egzosn - 2.14.6 + 2.14.7 4.0.0 diff --git a/pom.xml b/pom.xml index 42783ae..cf2cacc 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.egzosn pay-java-parent pom - 2.14.6 + 2.14.7 Pay Java - Parent Pay Java Parent @@ -65,7 +65,7 @@ - 2.14.6 + 2.14.7 4.5.4 1.2.17 1.2.83 -- Gitee From b95efc4f3ac9eca7a4e49502d27967d3ece19d7d Mon Sep 17 00:00:00 2001 From: egan Date: Wed, 13 Sep 2023 22:18:30 +0800 Subject: [PATCH 149/165] =?UTF-8?q?paypal=20v2=20=E7=A1=AE=E8=AE=A4?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E6=8E=A5=E5=8F=A3=E8=A1=A5=E5=85=85=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E5=A4=B4Content-Type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/egzosn/pay/paypal/v2/api/PayPalPayService.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 index a5c2eb1..0679e6a 100644 --- 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 @@ -367,6 +367,7 @@ public class PayPalPayService extends BasePayService implem 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"))) { @@ -464,7 +465,9 @@ public class PayPalPayService extends BasePayService implem */ @Override public Map ordersCapture(String tradeNo) { - JSONObject ordersCaptureInfo = getHttpRequestTemplate().postForObject(getReqUrl(PayPalTransactionType.ORDERS_CAPTURE), authHeader(), JSONObject.class, 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; } -- Gitee From 7ff87a2aed5ff535023261c5e82f03280b16ee07 Mon Sep 17 00:00:00 2001 From: zhaoyz Date: Tue, 26 Sep 2023 15:32:53 +0800 Subject: [PATCH 150/165] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=88=86=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/WxV3PayScoreController.java | 142 ++++++++++++++ .../pay/wx/v3/api/WxPayConfigStorage.java | 14 +- .../pay/wx/v3/api/WxPayScoreService.java | 179 ++++++++++++++++++ .../wx/v3/bean/WxPayScoreTransactionType.java | 54 ++++++ .../pay/wx/v3/bean/payscore/CancelOrder.java | 16 ++ .../wx/v3/bean/payscore/CompleteOrder.java | 39 ++++ .../pay/wx/v3/bean/payscore/CreateOrder.java | 95 ++++++++++ .../pay/wx/v3/bean/payscore/ModifyOrder.java | 42 ++++ .../pay/wx/v3/bean/payscore/PostPayment.java | 59 ++++++ .../pay/wx/v3/bean/payscore/RiskFund.java | 48 +++++ .../pay/wx/v3/bean/payscore/SyncOrder.java | 18 ++ .../pay/wx/v3/bean/payscore/TimeRange.java | 30 +++ .../com/egzosn/pay/wx/v3/utils/WxConst.java | 3 +- 13 files changed, 735 insertions(+), 4 deletions(-) create mode 100644 pay-java-demo/src/main/java/com/egzosn/pay/demo/controller/WxV3PayScoreController.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/api/WxPayScoreService.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxPayScoreTransactionType.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/CancelOrder.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/CompleteOrder.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/CreateOrder.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/ModifyOrder.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/PostPayment.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/RiskFund.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/SyncOrder.java create mode 100644 pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/TimeRange.java 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 0000000..695e4ff --- /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-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 index 02e0747..1457d1b 100644 --- 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 @@ -78,8 +78,10 @@ public class WxPayConfigStorage extends BasePayConfigStorage { * 是否为服务商模式, 默认为false */ private boolean partner = false; - - + /** + * 微信支付分服务服务ID + */ + private String serviceId; @Deprecated @Override @@ -263,4 +265,12 @@ public class WxPayConfigStorage extends BasePayConfigStorage { 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 0000000..7a857be --- /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/bean/WxPayScoreTransactionType.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/WxPayScoreTransactionType.java new file mode 100644 index 0000000..e7b37fc --- /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/payscore/CancelOrder.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/bean/payscore/CancelOrder.java new file mode 100644 index 0000000..bac21da --- /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 0000000..3f70512 --- /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 0000000..68dd755 --- /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 0000000..3edc086 --- /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 0000000..288266c --- /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 0000000..d432948 --- /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 0000000..333fd3c --- /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 0000000..02d466a --- /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/utils/WxConst.java b/pay-java-wx/src/main/java/com/egzosn/pay/wx/v3/utils/WxConst.java index b491eb5..bbd311d 100644 --- 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 @@ -84,6 +84,5 @@ public final class WxConst { 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"; } -- Gitee From 8cdef6f818b59682d325c16209f4cbc9ea439316 Mon Sep 17 00:00:00 2001 From: egan Date: Mon, 9 Oct 2023 21:45:29 +0800 Subject: [PATCH 151/165] =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=AE=9D=E8=A1=A5?= =?UTF-8?q?=E5=85=85=E8=AE=A2=E5=8D=95=E6=9F=A5=E8=AF=A2=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=80=BC=EF=BC=8C=E8=BD=AC=E8=B4=A6=E8=AE=A2=E5=8D=95=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E6=96=B0=E6=8E=A5=E5=8F=A3=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egzosn/pay/ali/api/AliPayService.java | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) 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 8217515..f55316a 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 @@ -31,6 +31,7 @@ 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.api.TransferService; import com.egzosn.pay.common.bean.AssistOrder; import com.egzosn.pay.common.bean.BillType; import com.egzosn.pay.common.bean.MethodType; @@ -62,7 +63,7 @@ import com.egzosn.pay.common.util.str.StringUtils; * email egzosn@gmail.com * date 2017-2-22 20:09 */ -public class AliPayService extends BasePayService implements AliPayServiceInf { +public class AliPayService extends BasePayService implements TransferService, AliPayServiceInf { /** @@ -505,6 +506,9 @@ public class AliPayService extends BasePayService implement */ @Override public Map query(AssistOrder assistOrder) { + if (null == assistOrder.getTransactionType()) { + assistOrder.setTransactionType(AliTransactionType.QUERY); + } //获取公共参数 Map parameters = getPublicParameters(assistOrder.getTransactionType()); Map bizContent = new TreeMap<>(); @@ -739,6 +743,32 @@ public class AliPayService extends BasePayService implement return getHttpRequestTemplate().postForObject(getReqUrl() + "?" + UriVariables.getMapToParameters(parameters), null, JSONObject.class); } + /** + * 转账查询 + * + * @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); @@ -765,21 +795,8 @@ public class AliPayService extends BasePayService implement */ @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)); } -- Gitee From e7892bc27f4b3507935bd5ae7e1e3c6f8808969c Mon Sep 17 00:00:00 2001 From: egan Date: Mon, 9 Oct 2023 21:59:30 +0800 Subject: [PATCH 152/165] =?UTF-8?q?=E8=BD=AC=E8=B4=A6=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E6=96=B0=E6=8E=A5=E5=8F=A3=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pay/payoneer/api/PayoneerPayService.java | 23 ++++++++++--- .../com/egzosn/pay/wx/api/WxPayService.java | 32 +++++++++++++++---- 2 files changed, 45 insertions(+), 10 deletions(-) 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 965b67e..fbc74a9 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 @@ -15,10 +15,10 @@ 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.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; @@ -52,7 +52,7 @@ import com.egzosn.pay.payoneer.bean.PayoneerTransactionType; * create 2018-01-19 * */ -public class PayoneerPayService extends BasePayService implements AdvancedPayService { +public class PayoneerPayService extends BasePayService implements AdvancedPayService, TransferService { /** * 测试地址 */ @@ -150,6 +150,7 @@ public class PayoneerPayService extends BasePayService im return verify(new NoticeParams(params)); } + /** * 回调校验 * @@ -318,16 +319,18 @@ public class PayoneerPayService extends BasePayService im public Map close(String tradeNo, String outTradeNo) { return secondaryInterface(tradeNo, outTradeNo, PayoneerTransactionType.CHARGE_CANCEL); } + /** * 交易关闭接口 * - * @param assistOrder 关闭订单 + * @param assistOrder 关闭订单 * @return 返回支付方交易关闭后的结果 */ @Override - public Map close(AssistOrder assistOrder){ + public Map close(AssistOrder assistOrder) { return secondaryInterface(assistOrder.getTradeNo(), assistOrder.getOutTradeNo(), PayoneerTransactionType.CHARGE_CANCEL); } + /** * 交易交易撤销 * @@ -465,6 +468,17 @@ 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); + } + /** * 转账 * @@ -482,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-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 09c7922..fad36ae 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 @@ -40,6 +40,7 @@ import static com.egzosn.pay.wx.bean.WxTransferType.TRANSFERS; import com.alibaba.fastjson.JSONObject; import com.egzosn.pay.common.api.BasePayService; +import com.egzosn.pay.common.api.TransferService; import com.egzosn.pay.common.bean.AssistOrder; import com.egzosn.pay.common.bean.BillType; import com.egzosn.pay.common.bean.MethodType; @@ -86,7 +87,7 @@ import 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 { /** @@ -862,6 +863,7 @@ public class WxPayService extends BasePayService implements return getHttpRequestTemplate().postForObject(getReqUrl(order.getTransferType()), XML.getMap2Xml(parameters), JSONObject.class); } + /** * 转账到余额所需要参数 * @@ -914,18 +916,37 @@ 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("partner_trade_no", assistOrder.getOutTradeNo()); parameters.put(NONCE_STR, SignTextUtils.randomStr()); - if (StringUtils.isEmpty(wxTransferType)) { - throw new PayErrorException(new WxPayError(FAILURE, "微信转账类型 #transferQuery(String outNo, String wxTransferType) 必填,详情com.egzosn.pay.wx.bean.WxTransferType")); + 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); @@ -935,7 +956,6 @@ public class WxPayService extends BasePayService implements return getHttpRequestTemplate().postForObject(getReqUrl(QUERY_BANK), XML.getMap2Xml(parameters), JSONObject.class); } - private String keyPublic(String content) { try { return RSA2.encrypt(content, payConfigStorage.getKeyPublic(), CIPHER_ALGORITHM, payConfigStorage.getInputCharset()); -- Gitee From 7c76dd206a66c2c6c123ce3dbbdbe0a193a8b5cc Mon Sep 17 00:00:00 2001 From: egan Date: Mon, 9 Oct 2023 22:22:46 +0800 Subject: [PATCH 153/165] =?UTF-8?q?=E8=BD=AC=E8=B4=A6=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E6=96=B0=E6=8E=A5=E5=8F=A3=E6=A1=88=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/egzosn/pay/demo/controller/AliPayController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1ca3cc2..4be22b6 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 @@ -397,6 +397,6 @@ public class AliPayController { */ @RequestMapping("transferQuery") public Map transferQuery(String outNo, String tradeNo) { - return service.transferQuery(outNo, tradeNo); + return service.transferQuery(new AssistOrder(tradeNo, outNo)); } } -- Gitee From 2b47bf95cb1161f338a6db50e74f5aaf4deb66da Mon Sep 17 00:00:00 2001 From: egan Date: Sat, 13 Jan 2024 19:45:08 +0800 Subject: [PATCH 154/165] =?UTF-8?q?#I8VQM7=20paypal=20webhook=E5=9B=9E?= =?UTF-8?q?=E8=B0=83=E9=AA=8C=E7=AD=BE=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pay/demo/controller/PayPalPayController.java | 4 +++- .../pay/paypal/api/PayPalConfigStorage.java | 15 ++++++++++++++- .../pay/paypal/v2/api/PayPalPayService.java | 9 +++++---- 3 files changed, 22 insertions(+), 6 deletions(-) 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 e40d037..01af6e9 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 @@ -41,8 +41,10 @@ public class PayPalPayController { @PostConstruct public void init() { PayPalConfigStorage storage = new PayPalConfigStorage(); - storage.setClientID("AZDS0IhUZvJTO99unlvSDMfbZIP-p-UecYXZdJoweha9LFuqKXKcQIGZgfVaX6oGiAOJAUuJD7JwyTl1"); + 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"); 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 7c267dc..69c5a89 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 @@ -16,6 +16,12 @@ public class PayPalConfigStorage extends BasePayConfigStorage { private String clientId; + /** + * 回调验签使用 + */ + private String webHookId; + + @Override @Deprecated public String getAppid() { @@ -86,7 +92,6 @@ public class PayPalConfigStorage extends BasePayConfigStorage { * 获取取消页面的url *
      * 注意:这里不是异步回调的通知
-     * IPN 地址设置的路径:https://developer.paypal.com/developer/ipnSimulator/
      * 
* * @return 取消页面的url @@ -98,4 +103,12 @@ public class PayPalConfigStorage extends BasePayConfigStorage { 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/v2/api/PayPalPayService.java b/pay-java-paypal/src/main/java/com/egzosn/pay/paypal/v2/api/PayPalPayService.java index 0679e6a..e6cce20 100644 --- 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 @@ -62,7 +62,7 @@ import com.egzosn.pay.paypal.v2.utils.PayPalUtil; * 贝宝支付配置存储 * * @author egan - * + *

* email egzosn@gmail.com * date 2021-1-16 ‏‎22:15:09 */ @@ -177,6 +177,7 @@ public class PayPalPayService extends BasePayService implem /** * 保留IPN的校验方式 + * * @param noticeParams 参数 * @return 结果 */ @@ -191,6 +192,7 @@ public class PayPalPayService extends BasePayService implem return "VERIFIED".equals(resp); } + @Override public boolean verify(NoticeParams noticeParams) { @@ -208,8 +210,7 @@ public class PayPalPayService extends BasePayService implem } InputStream inputStream = clientCertificateResponseEntity.getBody(); Collection clientCerts = PayPalUtil.getCertificateFromStream(inputStream); - Map body = noticeParams.getBody(); - String webHookId = (String) body.get(Constants.ID); + 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); @@ -466,7 +467,7 @@ public class PayPalPayService extends BasePayService implem @Override public Map ordersCapture(String tradeNo) { final HttpHeader header = authHeader(); - header.addHeader(new BasicHeader("Content-Type","application/json")); + 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; -- Gitee From cd8f37c930a5119209b1fa7630119064a8d9b9e1 Mon Sep 17 00:00:00 2001 From: egan Date: Sat, 13 Jan 2024 20:05:49 +0800 Subject: [PATCH 155/165] =?UTF-8?q?2.14.7=E7=89=88=E6=9C=AC=E5=8F=91?= =?UTF-8?q?=E5=B8=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- pom.xml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 7c75183..228b7c3 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ com.egzosn {module-name} - 2.14.6 + 2.14.7 ``` diff --git a/pom.xml b/pom.xml index cf2cacc..1220e08 100644 --- a/pom.xml +++ b/pom.xml @@ -60,7 +60,6 @@ pay-java-yiji pay-java-baidu pay-java-demo - -- Gitee From 0552106211c8bf7badac8567c21f9a407a87a0fc Mon Sep 17 00:00:00 2001 From: egan Date: Thu, 29 Feb 2024 21:42:01 +0800 Subject: [PATCH 156/165] =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +-- pay-java-demo/src/main/webapp/gzh.png | Bin 37332 -> 4268298 bytes 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 228b7c3..ea7479f 100644 --- a/README.md +++ b/README.md @@ -68,14 +68,13 @@ android 例子 [pay-java-android](https://gitee.com/egzosn/pay-java-android) 非常欢迎和感谢对本项目发起Pull Request的同学,不过本项目基于git flow开发流程,因此在发起Pull Request的时候请选择develop分支。 -作者公众号(每周输出) +作者公众号(未来输出) ![公众号](https://egzosn.gitee.io/pay-java-parent/gzh.png "gzh.png") E-Mail:egan@egzosn.com **QQ群:** - 1. pay-java(1群): 542193977(已满) 2. pay-java(2群):766275051 diff --git a/pay-java-demo/src/main/webapp/gzh.png b/pay-java-demo/src/main/webapp/gzh.png index e607cb9ba904d1ac409680f6d8e7c282a0607ff8..313e80165c1536b9dfa12b815ad71095d2d29424 100644 GIT binary patch literal 4268298 zcmeFa3EUi2fxV3*i=rsbH#n%{CkoEEq9XeuK}8UhT~HPQ5d>L8lu;0r$gqtd$QB|2 z0oeuFkv(i7Bq1oW$|{7gqXv?&s8jzsMNV_yN>z1rzt!E<-B0~exwYQq+^71jx~J}~ z&whE)_ph~prvHuS{o?tz3oo$1p8vAI0&iGgsRiB`uOG0~Yy0EHV2T!4;I)6+7JYV| zO}=-)jdMP<-4*};5=$NQ>>I{>`qTg40XyyVpJR5~Y2j1GkAL&(i{AUjWwt(YxgdfC z77SOqw7lI7i@YnIw~FU$;yGeQ0~mk-7=Qs7fB_hQ0T_S*7=Qs7fB_hQ0T_S*7=Qs7 zfB_ij8wTX>$mdzIOV(25$;5eIY+e~2i$lCBG=KpZfB_hQ0T_S*7=Qs7fB_hQ0T_S* z7=Qs7fB_hQ0T_UR{%2suJpX60%FU$?L>TAwJ@I@bUKL&)48Q;kzyJ)u01UtY48Q;k zzyJ)u01UtY48Q;kzyJ)u01ON?1M-93(*e6_)KSLy`nPyK4*mw3KYZ~1_ra+{>JSEC z00v+H24DaNU;qYS00v+H24DaNU;qYS00v;7Y6gy#Pc&+?se%aNe7rH9cgC;6uYv&> zfB_hQ0T_S*7=Qs7fB_hQ0T_S*7=Qs7fB_hQ0T_URp=LmS(HkqUpW2EdPJ5Yn=KEFn zvQ_)C#0OI_00S@p126ysFaQHE00S@p126ysFaQHE00S@p12E8^4Cwn;A8gBb8pTO* znp?#)%%A}bzyJ)u01UtY48Q;kzyJ)u01UtY48Q;kzyJ)u01UtY48TB|fvrYiLzO-? z_No?)XN^;sPM>u&fdLqR0T_S*7=Qs7fB_hQ0T_S*7=Qs7fB_hQ0T_S*7=Qs7Faz?F z7A$Q-wRm70_oDH9Bc2tW9Spz#48Q;kzyJ)u01UtY48Q;kzyJ)u01UtY48Q;kzyJ)u zKnVtJijP^eMN6vb;c=Xc#q&cY(zl03h@T1rFaQHE00S@p126ysFaQHE00S@p126ys zFaQHE00S@p12FJ_e5R&mRLKM4xYW-|al#fFzyJ)u01UtY48Q;kzyJ)u01UtY48Q;k zzyJ)u01UtY48Q;kw90_`Mvc1PRUC)DXQe(|t)|cMXa)l?00S@p126ysFaQHE00S@p z126ysFaQHE00S@p126ysFklAsEviv{?z`$d1oNsKHQw0n0aXWV)eZ0`Y^Fpap z>J$cG00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24J9F26is6tCC%@U-fTzR`{kc z00S@p126ysFaQHE00S@p126ysFaQHE00S@p126ysFaQHxVBp`1ysJ3^zlFk#{;D< zsY@7u0T_S*7=Qs7fB_hQ0T_S*7=Qs7fB_hQ0T_S*7=VG%GVm^WSFvZs7=f@fT9!B; zhF|~&U;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00u_GKr&Wf>|0$!e=GX@!2k@v z01UtY48Q;kzyJ)u01UtY48Q;kzyJ)u01UtY48Q;k^pFAhRSWho{n2=CoQr1*126ys zFaQHE00S@p126ysFaQHE00S@p126ysFaQHE00V=?z24DaNU;qYS00v+H z24DaNU;qYS00v+H24DaNU;qYS00wHnz?d530WWm0yin?s`h)=(fB_hQ0T_S*7=Qs7 zfB_hQ0T_S*7=Qs7fB_hQ0T^h9f!*+}@K0d?24DaNU;qYS00v+H24DaNU;qYS00v+H z24DaNU;qYS00wHn!1x;Ep&h@CbMR?l00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H z24DaNU|_HqxDnq9{}cvb00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNV4wyJ zJXV7|;DrvB7fO9npD+LeFaQHE00S@p126ysFaQHE00S@p126ysFaQHE00ZqXFc;qn z{}cvb00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS z00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H z24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaN zU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS z00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H z24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaN zU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS z00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H z24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaN zU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS z00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H z24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaN zU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS z00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H z24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaN zU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS z00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H z24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaN zU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS z00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H z24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaN zU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS z00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H z24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaN zU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS z00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H24DaNU;qYS00v+H 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

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