diff --git a/src/main/java/com/ycwl/basic/pay/PayFactory.java b/src/main/java/com/ycwl/basic/pay/PayFactory.java new file mode 100644 index 0000000..404af4e --- /dev/null +++ b/src/main/java/com/ycwl/basic/pay/PayFactory.java @@ -0,0 +1,58 @@ +package com.ycwl.basic.pay; + +import com.ycwl.basic.pay.adapter.IPayAdapter; +import com.ycwl.basic.pay.adapter.WxMpPayAdapter; +import com.ycwl.basic.pay.enums.PayAdapterType; +import com.ycwl.basic.pay.exceptions.PayUndefinedException; +import com.ycwl.basic.pay.exceptions.PayUnsupportedException; + +import java.util.HashMap; +import java.util.Map; + +public class PayFactory { + + public static IPayAdapter getAdapter(String typeName) { + PayAdapterType adapterEnum; + try { + adapterEnum = PayAdapterType.valueOf(typeName); + } catch (IllegalArgumentException e) { + throw new PayUnsupportedException("不支持的Adapter类型"); + } + return getAdapter(adapterEnum); + } + + public static IPayAdapter getAdapter(PayAdapterType type) { + switch (type) { + case WX_MP_PAY: + return new WxMpPayAdapter(); + default: + throw new PayUnsupportedException("不支持的Adapter类型"); + } + } + + protected static Map namedAdapter = new HashMap<>(); + protected static IPayAdapter defaultAdapter = null; + + public static void register(String name, IPayAdapter adapter) { + namedAdapter.put(name, adapter); + } + + public static IPayAdapter use(String name) { + IPayAdapter adapter = namedAdapter.get(name); + if (adapter == null) { + throw new PayUndefinedException("未定义的支付方式:"+name); + } + return adapter; + } + + public static IPayAdapter use() { + if (defaultAdapter == null) { + throw new PayUndefinedException("未定义默认的支付方式"); + } + return defaultAdapter; + } + + public static void setDefault(String defaultName) { + PayFactory.defaultAdapter = use(defaultName); + } +} diff --git a/src/main/java/com/ycwl/basic/pay/adapter/IPayAdapter.java b/src/main/java/com/ycwl/basic/pay/adapter/IPayAdapter.java new file mode 100644 index 0000000..ce6758c --- /dev/null +++ b/src/main/java/com/ycwl/basic/pay/adapter/IPayAdapter.java @@ -0,0 +1,42 @@ +package com.ycwl.basic.pay.adapter; + +// 假设request对象所在的包,可根据实际情况修改 +import com.ycwl.basic.pay.entity.CancelOrderRequest; +import com.ycwl.basic.pay.entity.CreateOrderRequest; +import com.ycwl.basic.pay.entity.CreateOrderResponse; +import com.ycwl.basic.pay.entity.PayResponse; +import com.ycwl.basic.pay.entity.RefundResponse; +import com.ycwl.basic.pay.entity.RefundOrderRequest; +import com.ycwl.basic.pay.entity.RefundOrderResponse; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.Map; + +// 将接口改为抽象类 +public interface IPayAdapter { + // 下单方法 + CreateOrderResponse createOrder(CreateOrderRequest request); + + // 获取支付参数方法 + Map getPaymentParams(CreateOrderResponse response); + + // 处理回调信息方法 + PayResponse handleCallback(HttpServletRequest request) throws IOException; + + // 查询订单状态方法 + PayResponse queryOrder(String orderNo); + + // 退款方法 + RefundOrderResponse refund(RefundOrderRequest request); + + // 处理退款回调方法 + RefundResponse handleRefundCallback(HttpServletRequest request) throws IOException; + + // 查询退款订单状态方法 + RefundResponse checkRefundStatus(String refundNo); + + void loadConfig(Map config); + + void cancelOrder(CancelOrderRequest request); +} diff --git a/src/main/java/com/ycwl/basic/pay/adapter/WxMpPayAdapter.java b/src/main/java/com/ycwl/basic/pay/adapter/WxMpPayAdapter.java new file mode 100644 index 0000000..ca57307 --- /dev/null +++ b/src/main/java/com/ycwl/basic/pay/adapter/WxMpPayAdapter.java @@ -0,0 +1,353 @@ +package com.ycwl.basic.pay.adapter; + +import com.wechat.pay.java.core.Config; +import com.wechat.pay.java.core.RSAAutoCertificateConfig; +import com.wechat.pay.java.core.notification.NotificationConfig; +import com.wechat.pay.java.core.notification.NotificationParser; +import com.wechat.pay.java.core.notification.RequestParam; +import com.wechat.pay.java.core.util.PemUtil; +import com.wechat.pay.java.service.payments.jsapi.JsapiService; +import com.wechat.pay.java.service.payments.jsapi.model.Amount; +import com.wechat.pay.java.service.payments.jsapi.model.CloseOrderRequest; +import com.wechat.pay.java.service.payments.jsapi.model.Payer; +import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest; +import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse; +import com.wechat.pay.java.service.payments.jsapi.model.QueryOrderByOutTradeNoRequest; +import com.wechat.pay.java.service.payments.model.Transaction; +import com.wechat.pay.java.service.refund.RefundService; +import com.wechat.pay.java.service.refund.model.AmountReq; +import com.wechat.pay.java.service.refund.model.CreateRequest; +import com.wechat.pay.java.service.refund.model.QueryByOutRefundNoRequest; +import com.wechat.pay.java.service.refund.model.Refund; +import com.wechat.pay.java.service.refund.model.RefundNotification; +import com.ycwl.basic.constant.NumberConstant; +import com.ycwl.basic.pay.entity.CancelOrderRequest; +import com.ycwl.basic.pay.entity.CreateOrderRequest; +import com.ycwl.basic.pay.entity.CreateOrderResponse; +import com.ycwl.basic.pay.entity.PayResponse; +import com.ycwl.basic.pay.entity.RefundResponse; +import com.ycwl.basic.pay.entity.RefundOrderRequest; +import com.ycwl.basic.pay.entity.RefundOrderResponse; +import com.ycwl.basic.pay.entity.WxMpPayConfig; +import com.ycwl.basic.pay.exceptions.PayWrongConfigException; +import org.springframework.util.Base64Utils; + +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Signature; +import java.security.SignatureException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.wechat.pay.java.core.http.Constant.WECHAT_PAY_NONCE; +import static com.wechat.pay.java.core.http.Constant.WECHAT_PAY_SERIAL; +import static com.wechat.pay.java.core.http.Constant.WECHAT_PAY_SIGNATURE; +import static com.wechat.pay.java.core.http.Constant.WECHAT_PAY_TIMESTAMP; +import static com.wechat.pay.java.service.refund.model.Status.SUCCESS; + +public class WxMpPayAdapter implements IPayAdapter { + private WxMpPayConfig config; + public static final String WECHAT_PAY_SIGNATURE_TYPE = "Wechatpay-Signature-Type"; + public WxMpPayAdapter() { + + } + public WxMpPayAdapter(WxMpPayConfig config) { + this.config = config; + } + @Override + public void loadConfig(Map _config) { + this.config = new WxMpPayConfig(); + if (_config != null) { + this.config.setMerchantId(_config.get("merchantId")); + this.config.setAppId(_config.get("appId")); + this.config.setPrivateKey(_config.get("privateKey")); + this.config.setSerialNumber(_config.get("serialNumber")); + this.config.setApiV3Key(_config.get("apiV3Key")); + } + } + + private Config clientConfig; + + private Config getConfig() { + if (clientConfig == null) { + clientConfig = new RSAAutoCertificateConfig.Builder() + .merchantId(config.getMerchantId()) + .privateKey(config.getPrivateKey()) + .merchantSerialNumber(config.getSerialNumber()) + .apiV3Key(config.getApiV3Key()) + .build(); + } + return clientConfig; + } + + @Override + public CreateOrderResponse createOrder(CreateOrderRequest request) { + CreateOrderResponse resp = new CreateOrderResponse(); + if (request.getPrice() <= 0) { + resp.setSkipPay(true); + return resp; + } + Config wxConfig = getConfig(); + JsapiService service = new JsapiService.Builder().config(wxConfig).build(); + PrepayRequest prepayRequest = new PrepayRequest(); + Amount amount = new Amount(); + amount.setTotal(request.getPrice()); + prepayRequest.setAmount(amount); + prepayRequest.setAppid(config.getAppId()); + prepayRequest.setMchid(config.getMerchantId()); + prepayRequest.setDescription(request.getDescription()); + prepayRequest.setNotifyUrl(request.getNotifyUrl()); + prepayRequest.setOutTradeNo(request.getOrderNo()); + Payer payer = new Payer(); + payer.setOpenid(request.getUserIdentify()); + prepayRequest.setPayer(payer); + PrepayResponse response = service.prepay(prepayRequest); + resp.setSuccess(true); + resp.setSkipPay(false); + resp.setOrderNo(response.getPrepayId()); + return resp; + } + + @Override + public Map getPaymentParams(CreateOrderResponse response) { + Map params = new HashMap<>(); + if (response.isSkipPay()) { + return params; + } + Long timeStamp = System.currentTimeMillis() / NumberConstant.THOUSAND; + params.put("appId", config.getAppId()); + params.put("timeStamp", timeStamp); + String nonce = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32); + params.put("nonceStr", nonce); + String signStr = Stream.of(config.getAppId(), String.valueOf(timeStamp), nonce, "prepay_id=" + response.getOrderNo()) + .collect(Collectors.joining("\n", "", "\n")); + String sign; + try { + sign = getSign(signStr, config.getPrivateKey()); + } catch (InvalidKeyException | SignatureException | NoSuchAlgorithmException e) { + throw new PayWrongConfigException("配置错误"); + } + params.put("paySign", sign); + params.put("signType", "RSA-SHA256"); + params.put("prepayId", "prepay_id=" + response.getOrderNo()); + return params; + } + + @Override + public PayResponse handleCallback(HttpServletRequest request) throws IOException { + ServletInputStream inputStream = request.getInputStream(); + StringBuffer body = new StringBuffer(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + String s; + // 读取回调请求体 + while ((s = bufferedReader.readLine()) != null) { + body.append(s); + } + PayResponse resp = new PayResponse(); + + String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP); + String nonce = request.getHeader(WECHAT_PAY_NONCE); + String signType = request.getHeader(WECHAT_PAY_SIGNATURE_TYPE); + String serialNo = request.getHeader(WECHAT_PAY_SERIAL); + String signature = request.getHeader(WECHAT_PAY_SIGNATURE); + NotificationConfig config = (NotificationConfig) getConfig(); + NotificationParser parser = new NotificationParser(config); + RequestParam requestParam = new RequestParam.Builder() + .serialNumber(serialNo) + .nonce(nonce) + .signature(signature) + .timestamp(timestamp) + // 若未设置signType,默认值为 WECHATPAY2-SHA256-RSA2048 + .signType(signType) + .body(body.toString()) + .build(); + Transaction parse = parser.parse(requestParam, Transaction.class); + resp.setValid(true); + resp.setOrderNo(parse.getOutTradeNo()); + if (parse.getAmount() != null) { + resp.setOrderPrice(parse.getAmount().getTotal()); + resp.setPayPrice(parse.getAmount().getPayerTotal()); + } + switch (parse.getTradeState()) { + case SUCCESS: + resp.setState(PayResponse.PAY_STATE.SUCCESS); + break; + case NOTPAY: + case CLOSED: + case REVOKED: + resp.setState(PayResponse.PAY_STATE.FAIL); + break; + case REFUND: + resp.setState(PayResponse.PAY_STATE.REFUND); + break; + default: + resp.setState(PayResponse.PAY_STATE.CANCEL); + break; + } + resp.setPayTime(parse.getSuccessTime()); + resp.setOriginalResponse(parse); + return resp; + } + + @Override + public PayResponse queryOrder(String orderNo) { + Config wxConfig = getConfig(); + JsapiService service = new JsapiService.Builder().config(wxConfig).build(); + QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest(); + queryRequest.setMchid(config.getMerchantId()); + queryRequest.setOutTradeNo(orderNo); + PayResponse resp = new PayResponse(); + Transaction result = service.queryOrderByOutTradeNo(queryRequest); + resp.setValid(true); + resp.setOrderNo(result.getOutTradeNo()); + if (result.getAmount() != null) { + resp.setOrderPrice(result.getAmount().getTotal()); + resp.setPayPrice(result.getAmount().getPayerTotal()); + } + switch (result.getTradeState()) { + case SUCCESS: + resp.setState(PayResponse.PAY_STATE.SUCCESS); + break; + case NOTPAY: + case CLOSED: + case REVOKED: + resp.setState(PayResponse.PAY_STATE.FAIL); + break; + case REFUND: + resp.setState(PayResponse.PAY_STATE.REFUND); + break; + default: + resp.setState(PayResponse.PAY_STATE.CANCEL); + break; + } + resp.setPayTime(result.getSuccessTime()); + resp.setOriginalResponse(result); + return resp; + } + + @Override + public RefundOrderResponse refund(RefundOrderRequest request) { + RefundOrderResponse resp = new RefundOrderResponse(); + Config wxConfig = getConfig(); + RefundService service = new RefundService.Builder().config(wxConfig).build(); + CreateRequest createRequest = new CreateRequest(); + createRequest.setOutTradeNo(request.getOrderNo()); + createRequest.setOutRefundNo(request.getRefundNo()); + AmountReq amountReq = new AmountReq(); + amountReq.setTotal(Long.valueOf(request.getPrice())); + amountReq.setRefund(Long.valueOf(request.getRefundPrice())); + amountReq.setCurrency("CNY"); + createRequest.setAmount(amountReq); + createRequest.setNotifyUrl(request.getNotifyUrl()); + Refund refund = service.create(createRequest); + if (refund.getStatus() == SUCCESS) { + resp.setSuccess(true); + resp.setRefundNo(refund.getOutRefundNo()); + } else { + resp.setSuccess(false); + } + return resp; + } + + @Override + public RefundResponse handleRefundCallback(HttpServletRequest request) throws IOException { + ServletInputStream inputStream = request.getInputStream(); + StringBuffer body = new StringBuffer(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + String s; + // 读取回调请求体 + while ((s = bufferedReader.readLine()) != null) { + body.append(s); + } + RefundResponse resp = new RefundResponse(); + String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP); + String nonce = request.getHeader(WECHAT_PAY_NONCE); + String signType = request.getHeader(WECHAT_PAY_SIGNATURE_TYPE); + String serialNo = request.getHeader(WECHAT_PAY_SERIAL); + String signature = request.getHeader(WECHAT_PAY_SIGNATURE); + NotificationConfig config = (NotificationConfig) getConfig(); + NotificationParser parser = new NotificationParser(config);RequestParam requestParam = new RequestParam.Builder() + .serialNumber(serialNo) + .nonce(nonce) + .signature(signature) + .timestamp(timestamp) + // 若未设置signType,默认值为 WECHATPAY2-SHA256-RSA2048 + .signType(signType) + .body(body.toString()) + .build(); + RefundNotification parse = parser.parse(requestParam, RefundNotification.class); + resp.setValid(true); + resp.setOriginalResponse(parse); + if (parse.getRefundStatus() == SUCCESS) { + //退款成功 + resp.setSuccess(); + resp.setRefundTime(parse.getSuccessTime()); + resp.setOrderNo(parse.getOutTradeNo()); + resp.setRefundNo(parse.getRefundId()); + if (parse.getAmount() != null) { + resp.setRefundPrice(Math.toIntExact(parse.getAmount().getPayerRefund())); + resp.setOrderPrice(Math.toIntExact(parse.getAmount().getTotal())); + } + } else { + //退款失败 + resp.setFail(); + } + return resp; + } + + @Override + public RefundResponse checkRefundStatus(String refundNo) { + Config wxConfig = getConfig(); + RefundService service = new RefundService.Builder().config(wxConfig).build(); + QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest(); + request.setOutRefundNo(refundNo); + RefundResponse resp = new RefundResponse(); + Refund result = service.queryByOutRefundNo(request); + resp.setValid(true); + resp.setOriginalResponse(result); + if (result.getStatus() == SUCCESS) { + //退款成功 + resp.setSuccess(); + resp.setRefundTime(result.getSuccessTime()); + resp.setOrderNo(result.getOutTradeNo()); + resp.setRefundNo(result.getRefundId()); + if (result.getAmount() != null) { + resp.setRefundPrice(Math.toIntExact(result.getAmount().getPayerRefund())); + resp.setOrderPrice(Math.toIntExact(result.getAmount().getTotal())); + } + } else { + //退款失败 + resp.setFail(); + } + return resp; + } + + @Override + public void cancelOrder(CancelOrderRequest request) { + CloseOrderRequest closeOrderRequest = new CloseOrderRequest(); + closeOrderRequest.setOutTradeNo(request.getOrderNo()); + closeOrderRequest.setMchid(config.getMerchantId()); + Config config = getConfig(); + JsapiService service = new JsapiService.Builder().config(config).build(); + service.closeOrder(closeOrderRequest); + } + + public static String getSign(String signatureStr,String privateKey) throws InvalidKeyException, SignatureException, NoSuchAlgorithmException { + String replace = privateKey.replace("\\n", "\n"); + PrivateKey merchantPrivateKey = PemUtil.loadPrivateKeyFromString(replace); + Signature sign = Signature.getInstance("SHA256withRSA"); + sign.initSign(merchantPrivateKey); + sign.update(signatureStr.getBytes(StandardCharsets.UTF_8)); + return Base64Utils.encodeToString(sign.sign()); + } +} diff --git a/src/main/java/com/ycwl/basic/pay/entity/CancelOrderRequest.java b/src/main/java/com/ycwl/basic/pay/entity/CancelOrderRequest.java new file mode 100644 index 0000000..63594ec --- /dev/null +++ b/src/main/java/com/ycwl/basic/pay/entity/CancelOrderRequest.java @@ -0,0 +1,10 @@ +package com.ycwl.basic.pay.entity; + +import lombok.Data; +import lombok.experimental.Accessors; + +@Data +@Accessors(chain = true) +public class CancelOrderRequest { + private String orderNo; +} diff --git a/src/main/java/com/ycwl/basic/pay/entity/CreateOrderRequest.java b/src/main/java/com/ycwl/basic/pay/entity/CreateOrderRequest.java new file mode 100644 index 0000000..b286147 --- /dev/null +++ b/src/main/java/com/ycwl/basic/pay/entity/CreateOrderRequest.java @@ -0,0 +1,35 @@ +package com.ycwl.basic.pay.entity; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.math.BigDecimal; +import java.math.BigInteger; + +@Data +@Accessors(chain = true) +public class CreateOrderRequest { + /** + * 价格,单位为分 + */ + private Integer price; + private String goodsName; + private String orderNo; + private String description; + private String userIdentify; + private String notifyUrl; + + + public BigDecimal getPriceInYuan() { + return new BigDecimal(BigInteger.valueOf(price), 2); + } + public CreateOrderRequest setPriceInCents(Integer priceInCents) { + this.price = priceInCents; + return this; + } + + public CreateOrderRequest setPriceInYuan(BigDecimal priceInYuan) { + this.price = priceInYuan.multiply(new BigDecimal(100)).intValue(); + return this; + } +} diff --git a/src/main/java/com/ycwl/basic/pay/entity/CreateOrderResponse.java b/src/main/java/com/ycwl/basic/pay/entity/CreateOrderResponse.java new file mode 100644 index 0000000..8decdb9 --- /dev/null +++ b/src/main/java/com/ycwl/basic/pay/entity/CreateOrderResponse.java @@ -0,0 +1,10 @@ +package com.ycwl.basic.pay.entity; + +import lombok.Data; + +@Data +public class CreateOrderResponse { + private boolean success; + private boolean skipPay; + private String orderNo; +} diff --git a/src/main/java/com/ycwl/basic/pay/entity/PayResponse.java b/src/main/java/com/ycwl/basic/pay/entity/PayResponse.java new file mode 100644 index 0000000..307454a --- /dev/null +++ b/src/main/java/com/ycwl/basic/pay/entity/PayResponse.java @@ -0,0 +1,34 @@ +package com.ycwl.basic.pay.entity; + +import lombok.Data; + +@Data +public class PayResponse { + private boolean valid; + private String orderNo; + private Object originalResponse; + private Integer orderPrice; + private Integer payPrice; + private PAY_STATE state; + private String payTime; + + public boolean isPay() { + return state == PAY_STATE.SUCCESS; + } + + public boolean isCancel() { + return state == PAY_STATE.CANCEL; + } + + public boolean isRefund() { + return state == PAY_STATE.REFUND; + } + + public enum PAY_STATE { + SUCCESS, + CANCEL, + REFUND, + FAIL, + UNKNOWN; + } +} diff --git a/src/main/java/com/ycwl/basic/pay/entity/RefundOrderRequest.java b/src/main/java/com/ycwl/basic/pay/entity/RefundOrderRequest.java new file mode 100644 index 0000000..c7ddd5f --- /dev/null +++ b/src/main/java/com/ycwl/basic/pay/entity/RefundOrderRequest.java @@ -0,0 +1,14 @@ +package com.ycwl.basic.pay.entity; + +import lombok.Data; +import lombok.experimental.Accessors; + +@Data +@Accessors(chain = true) +public class RefundOrderRequest { + private Integer price; + private Integer refundPrice; + private String orderNo; + private String refundNo; + private String notifyUrl; +} diff --git a/src/main/java/com/ycwl/basic/pay/entity/RefundOrderResponse.java b/src/main/java/com/ycwl/basic/pay/entity/RefundOrderResponse.java new file mode 100644 index 0000000..0424fe3 --- /dev/null +++ b/src/main/java/com/ycwl/basic/pay/entity/RefundOrderResponse.java @@ -0,0 +1,9 @@ +package com.ycwl.basic.pay.entity; + +import lombok.Data; + +@Data +public class RefundOrderResponse { + private boolean success; + private String refundNo; +} diff --git a/src/main/java/com/ycwl/basic/pay/entity/RefundResponse.java b/src/main/java/com/ycwl/basic/pay/entity/RefundResponse.java new file mode 100644 index 0000000..15aac74 --- /dev/null +++ b/src/main/java/com/ycwl/basic/pay/entity/RefundResponse.java @@ -0,0 +1,28 @@ +package com.ycwl.basic.pay.entity; + +import lombok.Data; + +@Data +public class RefundResponse { + private boolean valid; + private String orderNo; + private String refundNo; + private Object originalResponse; + private Integer orderPrice; + private Integer refundPrice; + private PAY_STATE state; + private String refundTime; + + public void setSuccess() { + state = PAY_STATE.SUCCESS; + } + + public void setFail() { + state = PAY_STATE.FAIL; + } + + public enum PAY_STATE { + SUCCESS, + FAIL + } +} diff --git a/src/main/java/com/ycwl/basic/pay/entity/WxMpPayConfig.java b/src/main/java/com/ycwl/basic/pay/entity/WxMpPayConfig.java new file mode 100644 index 0000000..6d2ade4 --- /dev/null +++ b/src/main/java/com/ycwl/basic/pay/entity/WxMpPayConfig.java @@ -0,0 +1,12 @@ +package com.ycwl.basic.pay.entity; + +import lombok.Data; + +@Data +public class WxMpPayConfig { + private String merchantId; + private String appId; + private String privateKey; + private String serialNumber; + private String apiV3Key; +} diff --git a/src/main/java/com/ycwl/basic/pay/enums/PayAdapterType.java b/src/main/java/com/ycwl/basic/pay/enums/PayAdapterType.java new file mode 100644 index 0000000..dacbb8f --- /dev/null +++ b/src/main/java/com/ycwl/basic/pay/enums/PayAdapterType.java @@ -0,0 +1,20 @@ +package com.ycwl.basic.pay.enums; + +public enum PayAdapterType { + WX_MP_PAY("WX_MP_PAY"), + ; + + private String type; + + PayAdapterType(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/src/main/java/com/ycwl/basic/pay/exceptions/PayException.java b/src/main/java/com/ycwl/basic/pay/exceptions/PayException.java new file mode 100644 index 0000000..9f8cf16 --- /dev/null +++ b/src/main/java/com/ycwl/basic/pay/exceptions/PayException.java @@ -0,0 +1,7 @@ +package com.ycwl.basic.pay.exceptions; + +public class PayException extends RuntimeException { + public PayException(String message) { + super(message); + } +} diff --git a/src/main/java/com/ycwl/basic/pay/exceptions/PayUndefinedException.java b/src/main/java/com/ycwl/basic/pay/exceptions/PayUndefinedException.java new file mode 100644 index 0000000..7342b5d --- /dev/null +++ b/src/main/java/com/ycwl/basic/pay/exceptions/PayUndefinedException.java @@ -0,0 +1,7 @@ +package com.ycwl.basic.pay.exceptions; + +public class PayUndefinedException extends RuntimeException { + public PayUndefinedException(String message) { + super(message); + } +} diff --git a/src/main/java/com/ycwl/basic/pay/exceptions/PayUnsupportedException.java b/src/main/java/com/ycwl/basic/pay/exceptions/PayUnsupportedException.java new file mode 100644 index 0000000..058fdbc --- /dev/null +++ b/src/main/java/com/ycwl/basic/pay/exceptions/PayUnsupportedException.java @@ -0,0 +1,7 @@ +package com.ycwl.basic.pay.exceptions; + +public class PayUnsupportedException extends PayException { + public PayUnsupportedException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/pay/exceptions/PayWrongConfigException.java b/src/main/java/com/ycwl/basic/pay/exceptions/PayWrongConfigException.java new file mode 100644 index 0000000..5eb7622 --- /dev/null +++ b/src/main/java/com/ycwl/basic/pay/exceptions/PayWrongConfigException.java @@ -0,0 +1,7 @@ +package com.ycwl.basic.pay.exceptions; + +public class PayWrongConfigException extends PayException { + public PayWrongConfigException(String message) { + super(message); + } +} diff --git a/src/main/java/com/ycwl/basic/pay/starter/PayAutoConfiguration.java b/src/main/java/com/ycwl/basic/pay/starter/PayAutoConfiguration.java new file mode 100644 index 0000000..b5d82c5 --- /dev/null +++ b/src/main/java/com/ycwl/basic/pay/starter/PayAutoConfiguration.java @@ -0,0 +1,32 @@ +package com.ycwl.basic.pay.starter; + +import com.ycwl.basic.pay.PayFactory; +import com.ycwl.basic.pay.adapter.IPayAdapter; +import com.ycwl.basic.pay.starter.config.OverallPayConfig; +import com.ycwl.basic.pay.starter.config.PayConfigItem; +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class PayAutoConfiguration { + private final OverallPayConfig config; + public PayAutoConfiguration(OverallPayConfig config) { + this.config = config; + if (config != null) { + if (config.getConfigs() != null) { + loadConfig(); + } + if (StringUtils.isNotBlank(config.getDefaultUse())) { + PayFactory.setDefault(config.getDefaultUse()); + } + } + } + + private void loadConfig() { + for (PayConfigItem item : config.getConfigs()) { + IPayAdapter adapter = PayFactory.getAdapter(item.getType()); + adapter.loadConfig(item.getConfig()); + PayFactory.register(item.getName(), adapter); + } + } +} diff --git a/src/main/java/com/ycwl/basic/pay/starter/config/OverallPayConfig.java b/src/main/java/com/ycwl/basic/pay/starter/config/OverallPayConfig.java new file mode 100644 index 0000000..bf260e8 --- /dev/null +++ b/src/main/java/com/ycwl/basic/pay/starter/config/OverallPayConfig.java @@ -0,0 +1,15 @@ +package com.ycwl.basic.pay.starter.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@ConfigurationProperties(prefix = "pay") +@Data +public class OverallPayConfig { + private String defaultUse; + private List configs; +} diff --git a/src/main/java/com/ycwl/basic/pay/starter/config/PayConfigItem.java b/src/main/java/com/ycwl/basic/pay/starter/config/PayConfigItem.java new file mode 100644 index 0000000..2f1fb56 --- /dev/null +++ b/src/main/java/com/ycwl/basic/pay/starter/config/PayConfigItem.java @@ -0,0 +1,13 @@ +package com.ycwl.basic.pay.starter.config; + +import com.ycwl.basic.pay.enums.PayAdapterType; +import lombok.Data; + +import java.util.Map; + +@Data +public class PayConfigItem { + private String name; + private PayAdapterType type; + private Map config; +} diff --git a/src/main/java/com/ycwl/basic/utils/WXPayUtil.java b/src/main/java/com/ycwl/basic/utils/WXPayUtil.java deleted file mode 100644 index 56bdfe2..0000000 --- a/src/main/java/com/ycwl/basic/utils/WXPayUtil.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.ycwl.basic.utils; - - -import cn.hutool.core.util.XmlUtil; -import com.wechat.pay.java.core.util.PemUtil; -import org.springframework.util.Base64Utils; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.security.*; -import java.util.Map; -import java.util.Random; - -/** - * @Author: songmingsong - * @CreateTime: 2024-12-05 - * @Description: 微信支付 - * @Version: 1.0 - */ -public class WXPayUtil { - - private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - - private static final Random RANDOM = new SecureRandom(); - - - public static String getSign(String signatureStr,String privateKey) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException, IOException, URISyntaxException { - //replace 根据实际情况,不一定都需要 - String replace = privateKey.replace("\\n", "\n"); - PrivateKey merchantPrivateKey = PemUtil.loadPrivateKeyFromString(replace); - Signature sign = Signature.getInstance("SHA256withRSA"); - sign.initSign(merchantPrivateKey); - sign.update(signatureStr.getBytes(StandardCharsets.UTF_8)); - return Base64Utils.encodeToString(sign.sign()); - } - - /** - * 获取随机字符串 Nonce Str - * - * @return String 随机字符串 - */ - public static String generateNonceStr() { - char[] nonceChars = new char[32]; - for (int index = 0; index < nonceChars.length; ++index) { - nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length())); - } - return new String(nonceChars); - } - - public static Map xmlToMap(String xmlData) { - return XmlUtil.xmlToMap(xmlData); - } -}