From 34dbc7d0368841c1b627d980e3645e208dac8407 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Mon, 2 Jun 2025 09:43:01 +0800 Subject: [PATCH] =?UTF-8?q?=E8=81=AA=E6=98=8E=E4=BB=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/mobile/AppWxPayController.java | 16 +- .../java/com/ycwl/basic/pay/PayFactory.java | 10 +- .../basic/pay/adapter/CongMingPayAdapter.java | 239 ++++++++++++++++++ .../basic/pay/entity/CancelOrderRequest.java | 1 + .../basic/pay/entity/CongMingPayConfig.java | 11 + .../basic/pay/entity/CreateOrderResponse.java | 3 + .../ycwl/basic/pay/entity/PayResponse.java | 15 ++ .../basic/pay/entity/RefundOrderRequest.java | 11 + .../ycwl/basic/pay/entity/RefundResponse.java | 10 + .../ycwl/basic/pay/enums/PayAdapterType.java | 1 + .../pay/adapter/CongMingPayAdapterTest.java | 64 +++++ 11 files changed, 374 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/ycwl/basic/pay/adapter/CongMingPayAdapter.java create mode 100644 src/main/java/com/ycwl/basic/pay/entity/CongMingPayConfig.java create mode 100644 src/test/java/com/ycwl/basic/pay/adapter/CongMingPayAdapterTest.java diff --git a/src/main/java/com/ycwl/basic/controller/mobile/AppWxPayController.java b/src/main/java/com/ycwl/basic/controller/mobile/AppWxPayController.java index b3f1573..5d1d5ec 100644 --- a/src/main/java/com/ycwl/basic/controller/mobile/AppWxPayController.java +++ b/src/main/java/com/ycwl/basic/controller/mobile/AppWxPayController.java @@ -5,10 +5,12 @@ import com.ycwl.basic.annotation.IgnoreToken; import com.ycwl.basic.enums.BizCodeEnum; import com.ycwl.basic.model.wx.WXPayOrderReqVO; import com.ycwl.basic.model.wx.WxPayRespVO; +import com.ycwl.basic.pay.entity.PayResponse; import com.ycwl.basic.service.mobile.WxPayService; import com.ycwl.basic.utils.ApiResponse; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -17,6 +19,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import jakarta.servlet.http.HttpServletRequest; + import java.io.IOException; import java.security.GeneralSecurityException; @@ -41,10 +44,17 @@ public class AppWxPayController { wxPayService.payNotify(request); return ApiResponse.success(BizCodeEnum.REQUEST_OK); } - @PostMapping("/{scenicId}/payNotify") + + @RequestMapping("/{scenicId}/payNotify") @IgnoreToken - public ApiResponse payNotifyByScenicId(@PathVariable Long scenicId, HttpServletRequest request) { - wxPayService.payNotify(scenicId, request); + public Object payNotifyByScenicId(@PathVariable Long scenicId, HttpServletRequest request) { + PayResponse payResponse = wxPayService.payNotify(scenicId, request); + if (payResponse == null) { + return ApiResponse.buildResult(BizCodeEnum.ADVANCE_PAYMENT_CALLBACK_FAILED); + } + if (StringUtils.isNotBlank(payResponse.getCustomResponse())) { + return payResponse.getCustomResponse(); + } return ApiResponse.success(BizCodeEnum.REQUEST_OK); } diff --git a/src/main/java/com/ycwl/basic/pay/PayFactory.java b/src/main/java/com/ycwl/basic/pay/PayFactory.java index f48f5fb..1b4d317 100644 --- a/src/main/java/com/ycwl/basic/pay/PayFactory.java +++ b/src/main/java/com/ycwl/basic/pay/PayFactory.java @@ -1,5 +1,6 @@ package com.ycwl.basic.pay; +import com.ycwl.basic.pay.adapter.CongMingPayAdapter; import com.ycwl.basic.pay.adapter.IPayAdapter; import com.ycwl.basic.pay.adapter.WxMpPayAdapter; import com.ycwl.basic.pay.enums.PayAdapterType; @@ -23,10 +24,11 @@ public class PayFactory { } public static IPayAdapter getAdapter(PayAdapterType type) { - if (Objects.requireNonNull(type) == PayAdapterType.WX_MP_PAY) { - return new WxMpPayAdapter(); - } - throw new PayUnsupportedException("不支持的Adapter类型"); + return switch (type) { + case WX_MP_PAY -> new WxMpPayAdapter(); + case CONG_MING_PAY -> new CongMingPayAdapter(); + default -> throw new PayUnsupportedException("不支持的Adapter类型"); + }; } protected static Map namedAdapter = new HashMap<>(); diff --git a/src/main/java/com/ycwl/basic/pay/adapter/CongMingPayAdapter.java b/src/main/java/com/ycwl/basic/pay/adapter/CongMingPayAdapter.java new file mode 100644 index 0000000..e78825d --- /dev/null +++ b/src/main/java/com/ycwl/basic/pay/adapter/CongMingPayAdapter.java @@ -0,0 +1,239 @@ +package com.ycwl.basic.pay.adapter; + +import cn.hutool.crypto.SecureUtil; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.ycwl.basic.pay.entity.CancelOrderRequest; +import com.ycwl.basic.pay.entity.CongMingPayConfig; +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.RefundOrderRequest; +import com.ycwl.basic.pay.entity.RefundOrderResponse; +import com.ycwl.basic.pay.entity.RefundResponse; +import com.ycwl.basic.pay.exceptions.PayException; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class CongMingPayAdapter implements IPayAdapter { + private CongMingPayConfig config; + private static final String MP_PAY_URI = "/api/hFivePay.do"; + private static final String QUERY_ORDER_URI = "/api/query.do"; + private static final String REFUND_ORDER_URI = "/api/refund.do"; + private static final String CANCEL_ORDER_URI = "/api/userCancelOrder.do"; + + public CongMingPayAdapter() { + + } + + public CongMingPayAdapter(CongMingPayConfig config) { + this.config = config; + } + + @Override + public void loadConfig(Map config) { + this.config = new CongMingPayConfig(); + this.config.setProgramId(config.get("programId")); + this.config.setShopId(config.get("shopId")); + this.config.setApiKey(config.get("apiKey")); + this.config.setApiHost(config.get("apiHost")); + } + + @Override + public CreateOrderResponse createOrder(CreateOrderRequest request) { + CreateOrderResponse resp = new CreateOrderResponse(); + if (request.getPrice() <= 0) { + resp.setSkipPay(true); + return resp; + } + String url = config.getApiHost() + MP_PAY_URI; + Map params = new HashMap<>(); + params.put("program_id", config.getProgramId()); + params.put("shop_id", config.getShopId()); + params.put("money", request.getPriceInYuan().toString()); + params.put("order_id", request.getOrderNo()); + if (StringUtils.isNotBlank(request.getGoodsName())) { + params.put("goods_msg", request.getGoodsName()); + } + params.put("notify_url", request.getNotifyUrl()); + params.put("s_source", "mini_app"); + params.put("sign", parseSign(params, config.getApiKey())); + System.out.println(url); + System.out.println(JSON.toJSONString(params)); + String response = HttpUtil.post(url, JSON.toJSONString(params)); + JSONObject json = JSONObject.parseObject(response); + System.out.println(json); + if (StringUtils.equals(json.getString("result_code"), "fail")) { + throw new PayException("查询异常!异常提示:" + json.getString("error_msg")); + } + resp.setSkipPay(false); + resp.setSuccess(true); + resp.setOrderNo(json.getString("order_id")); + resp.setExtData(json.toJavaObject(Map.class)); + return resp; + } + + @Override + public Map getPaymentParams(CreateOrderResponse response) { + Map result = new HashMap<>(); + if (response.isSkipPay()) { + result.put("skipPay", true); + return result; + } + if (response.getExtData() == null) { + throw new PayException("查询异常!异常提示:" + "订单信息异常"); + } + result.put("payWay", "embeddedApp"); + result.put("appId", response.getExtData().get("appid")); + result.put("path", response.getExtData().get("path")); + return result; + } + + @Override + public PayResponse handleCallback(HttpServletRequest request) throws IOException { + String moneyStr = request.getParameter("money"); + String orderId = request.getParameter("orderId"); + String resultCode = request.getParameter("result_code"); + PayResponse resp = new PayResponse(); + resp.setPayPriceInYuan(new BigDecimal(moneyStr)); + resp.setOrderNo(orderId); + if (StringUtils.equalsIgnoreCase("success", resultCode)) { + resp.setState(PayResponse.PAY_STATE.SUCCESS); + } else { + resp.setState(PayResponse.PAY_STATE.FAIL); + } + resp.setCustomResponse("success"); + return resp; + } + + @Override + public PayResponse queryOrder(String orderNo) { + PayResponse resp = new PayResponse(); + String url = config.getApiHost() + QUERY_ORDER_URI; + Map params = new HashMap<>(); + params.put("program_id", config.getProgramId()); + params.put("shop_id", config.getShopId()); + params.put("order_id", orderNo); + params.put("sign", parseSign(params, config.getApiKey())); + String response = HttpUtil.post(url, JSON.toJSONString(params)); + JSONObject json = JSONObject.parseObject(response); + System.out.println(json); + if (StringUtils.equals(json.getString("result_code"), "fail")) { + throw new PayException("查询异常!异常提示:" + json.getString("error_msg")); + } + resp.setOriginalResponse(json); + resp.setValid(true); + resp.setOrderPriceInYuan(json.getBigDecimal("pay_ble")); + resp.setPayPriceInYuan(json.getBigDecimal("paid_out")); + resp.setOrderNo(json.getString("order_id")); + switch (json.getString("order_status")) { + case "2": + resp.setState(PayResponse.PAY_STATE.NOT_PAY); + break; + case "1": + resp.setState(PayResponse.PAY_STATE.SUCCESS); + resp.setPayTime(json.getString("time_end")); + break; + case "0": + resp.setState(PayResponse.PAY_STATE.FAIL); + break; + default: + resp.setState(PayResponse.PAY_STATE.CANCEL); + break; + } + return resp; + } + + @Override + public RefundOrderResponse refund(RefundOrderRequest request) { + RefundOrderResponse resp = new RefundOrderResponse(); + if (request.getPrice() <= 0) { + resp.setSuccess(true); + return resp; + } + String url = config.getApiHost() + REFUND_ORDER_URI; + Map params = new HashMap<>(); + params.put("program_id", config.getProgramId()); + params.put("shop_id", config.getShopId()); + params.put("shop_order_id", request.getOrderNo()); + params.put("money", request.getRefundPriceInYuan().toString()); + params.put("refund_order_id", request.getRefundNo()); + params.put("sign", parseSign(params, config.getApiKey())); + String response = HttpUtil.post(url, JSON.toJSONString(params)); + JSONObject json = JSONObject.parseObject(response); + System.out.println(json); + if (StringUtils.equalsIgnoreCase(json.getString("result_code"), "fail")) { + throw new PayException("退款异常!异常提示:" + json.getString("error_msg")); + } + return resp; + } + + @Override + public RefundResponse handleRefundCallback(HttpServletRequest request) throws IOException { + String moneyStr = request.getParameter("money"); + String orderId = request.getParameter("orderId"); + String resultCode = request.getParameter("result_code"); + RefundResponse resp = new RefundResponse(); + resp.setValid(true); + resp.setRefundPriceInYuan(new BigDecimal(moneyStr)); + resp.setOrderNo(orderId); + if (StringUtils.equals("success", resultCode)) { + resp.setSuccess(); + } else { + resp.setFail(); + } + resp.setCustomResponse("success"); + return resp; + } + + @Override + public RefundResponse checkRefundStatus(String refundNo) { + return null; + } + + @Override + public void cancelOrder(CancelOrderRequest request) { + String url = config.getApiHost() + CANCEL_ORDER_URI; + Map params = new HashMap<>(); + params.put("program_id", config.getProgramId()); + params.put("shop_id", config.getShopId()); + params.put("order_id", request.getOrderNo()); + params.put("error_msg", request.getReason()); + params.put("sign", parseSign(params, config.getApiKey())); + String response = HttpUtil.post(url, JSON.toJSONString(params)); + JSONObject json = JSONObject.parseObject(response); + if (StringUtils.equals(json.getString("result_code"), "fail")) { + throw new PayException("取消订单异常!异常提示:" + json.getString("error_msg")); + } + } + + public static String parseSign(Map map, String key) { + Collection keyset = map.keySet(); + List list = new ArrayList<>(keyset); + Collections.sort(list); + StringBuilder signStr = new StringBuilder(); + for (String key1 : list) { + signStr.append(key1).append("=").append(map.get(key1)).append("&"); + } + signStr.append("key=").append(key); + return SecureUtil.md5(signStr.toString()).toUpperCase(); + } +} diff --git a/src/main/java/com/ycwl/basic/pay/entity/CancelOrderRequest.java b/src/main/java/com/ycwl/basic/pay/entity/CancelOrderRequest.java index 63594ec..be97205 100644 --- a/src/main/java/com/ycwl/basic/pay/entity/CancelOrderRequest.java +++ b/src/main/java/com/ycwl/basic/pay/entity/CancelOrderRequest.java @@ -7,4 +7,5 @@ import lombok.experimental.Accessors; @Accessors(chain = true) public class CancelOrderRequest { private String orderNo; + private String reason = "取消订单"; } diff --git a/src/main/java/com/ycwl/basic/pay/entity/CongMingPayConfig.java b/src/main/java/com/ycwl/basic/pay/entity/CongMingPayConfig.java new file mode 100644 index 0000000..917fd84 --- /dev/null +++ b/src/main/java/com/ycwl/basic/pay/entity/CongMingPayConfig.java @@ -0,0 +1,11 @@ +package com.ycwl.basic.pay.entity; + +import lombok.Data; + +@Data +public class CongMingPayConfig { + private String programId; + private String shopId; + private String apiKey; + private String apiHost = "https://api.congmingpay.com"; +} diff --git a/src/main/java/com/ycwl/basic/pay/entity/CreateOrderResponse.java b/src/main/java/com/ycwl/basic/pay/entity/CreateOrderResponse.java index 8decdb9..4223641 100644 --- a/src/main/java/com/ycwl/basic/pay/entity/CreateOrderResponse.java +++ b/src/main/java/com/ycwl/basic/pay/entity/CreateOrderResponse.java @@ -2,9 +2,12 @@ package com.ycwl.basic.pay.entity; import lombok.Data; +import java.util.Map; + @Data public class CreateOrderResponse { private boolean success; private boolean skipPay; private String orderNo; + private Map extData; } diff --git a/src/main/java/com/ycwl/basic/pay/entity/PayResponse.java b/src/main/java/com/ycwl/basic/pay/entity/PayResponse.java index 307454a..67fb445 100644 --- a/src/main/java/com/ycwl/basic/pay/entity/PayResponse.java +++ b/src/main/java/com/ycwl/basic/pay/entity/PayResponse.java @@ -1,16 +1,22 @@ package com.ycwl.basic.pay.entity; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; +import java.math.BigDecimal; + @Data public class PayResponse { private boolean valid; private String orderNo; + @JsonIgnore private Object originalResponse; private Integer orderPrice; private Integer payPrice; private PAY_STATE state; private String payTime; + @JsonIgnore + private String customResponse; public boolean isPay() { return state == PAY_STATE.SUCCESS; @@ -24,8 +30,17 @@ public class PayResponse { return state == PAY_STATE.REFUND; } + public void setPayPriceInYuan(BigDecimal money) { + payPrice = money.multiply(BigDecimal.valueOf(100)).intValue(); + } + + public void setOrderPriceInYuan(BigDecimal money) { + orderPrice = money.multiply(BigDecimal.valueOf(100)).intValue(); + } + public enum PAY_STATE { SUCCESS, + NOT_PAY, CANCEL, REFUND, FAIL, diff --git a/src/main/java/com/ycwl/basic/pay/entity/RefundOrderRequest.java b/src/main/java/com/ycwl/basic/pay/entity/RefundOrderRequest.java index c7ddd5f..76fcb62 100644 --- a/src/main/java/com/ycwl/basic/pay/entity/RefundOrderRequest.java +++ b/src/main/java/com/ycwl/basic/pay/entity/RefundOrderRequest.java @@ -3,6 +3,9 @@ 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 RefundOrderRequest { @@ -11,4 +14,12 @@ public class RefundOrderRequest { private String orderNo; private String refundNo; private String notifyUrl; + + public BigDecimal getRefundPriceInYuan() { + return new BigDecimal(BigInteger.valueOf(refundPrice), 2); + } + + public void setRefundPriceInYuan(BigDecimal refundPriceInYuan) { + this.refundPrice = refundPriceInYuan.multiply(BigDecimal.valueOf(100)).intValue(); + } } diff --git a/src/main/java/com/ycwl/basic/pay/entity/RefundResponse.java b/src/main/java/com/ycwl/basic/pay/entity/RefundResponse.java index 15aac74..dda2cde 100644 --- a/src/main/java/com/ycwl/basic/pay/entity/RefundResponse.java +++ b/src/main/java/com/ycwl/basic/pay/entity/RefundResponse.java @@ -1,17 +1,23 @@ package com.ycwl.basic.pay.entity; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; +import java.math.BigDecimal; + @Data public class RefundResponse { private boolean valid; private String orderNo; private String refundNo; + @JsonIgnore private Object originalResponse; private Integer orderPrice; private Integer refundPrice; private PAY_STATE state; private String refundTime; + @JsonIgnore + private String customResponse; public void setSuccess() { state = PAY_STATE.SUCCESS; @@ -21,6 +27,10 @@ public class RefundResponse { state = PAY_STATE.FAIL; } + public void setRefundPriceInYuan(BigDecimal bigDecimal) { + refundPrice = bigDecimal.multiply(BigDecimal.valueOf(100)).intValue(); + } + public enum PAY_STATE { SUCCESS, FAIL diff --git a/src/main/java/com/ycwl/basic/pay/enums/PayAdapterType.java b/src/main/java/com/ycwl/basic/pay/enums/PayAdapterType.java index 932988d..4f0a1d7 100644 --- a/src/main/java/com/ycwl/basic/pay/enums/PayAdapterType.java +++ b/src/main/java/com/ycwl/basic/pay/enums/PayAdapterType.java @@ -5,6 +5,7 @@ import lombok.Getter; @Getter public enum PayAdapterType { WX_MP_PAY("WX_MP_PAY"), + CONG_MING_PAY("CONG_MING_PAY"), ; private final String type; diff --git a/src/test/java/com/ycwl/basic/pay/adapter/CongMingPayAdapterTest.java b/src/test/java/com/ycwl/basic/pay/adapter/CongMingPayAdapterTest.java new file mode 100644 index 0000000..d599e0d --- /dev/null +++ b/src/test/java/com/ycwl/basic/pay/adapter/CongMingPayAdapterTest.java @@ -0,0 +1,64 @@ +package com.ycwl.basic.pay.adapter; + +import com.ycwl.basic.pay.entity.CancelOrderRequest; +import com.ycwl.basic.pay.entity.CongMingPayConfig; +import com.ycwl.basic.pay.entity.CreateOrderRequest; +import com.ycwl.basic.pay.entity.CreateOrderResponse; +import com.ycwl.basic.pay.entity.RefundOrderRequest; +import com.ycwl.basic.pay.enums.PayAdapterType; +import org.junit.Test; + +public class CongMingPayAdapterTest { + private CongMingPayAdapter getAdapter() { + CongMingPayConfig config = new CongMingPayConfig(); + config.setProgramId("202311792173507165"); + config.setShopId("7898ffe6550cc695ce6d31c248ced1a6"); + config.setApiKey("2803188405D8E09930CC47918399D5D9"); + return new CongMingPayAdapter(config); + } + + @Test + public void testCreateOrder() { + CongMingPayAdapter adapter = getAdapter(); + CreateOrderRequest request = new CreateOrderRequest(); + request.setPrice(100); + request.setOrderNo("1234567890"); + request.setGoodsName("测试订单"); + request.setDescription("测试订单"); + request.setUserIdentify("123456789"); + request.setNotifyUrl("https://www.baidu.com"); + CreateOrderResponse response = adapter.createOrder(request); + System.out.println(response); + } + + @Test + public void testQueryOrder() { + CongMingPayAdapter adapter = getAdapter(); + adapter.queryOrder("123456789"); + } + + @Test + public void testRefundOrder() { + CongMingPayAdapter adapter = getAdapter(); + RefundOrderRequest request = new RefundOrderRequest(); + request.setOrderNo("3993158860935401472"); + request.setRefundNo("3993158860935401472"); + request.setPrice(1); + request.setRefundPrice(1); + request.setNotifyUrl("https://www.zhentuai.com/api/mobile/wx/pay/v1/3955650120997015552/payNotify"); + adapter.refund(request); + } + + @Test + public void testCancelOrder() { + CongMingPayAdapter adapter = getAdapter(); + CancelOrderRequest request = new CancelOrderRequest(); + request.setOrderNo("123456789"); + adapter.cancelOrder(request); + } + + @Test + public void testA() { + PayAdapterType.valueOf("CONG_MING_PAY"); + } +} \ No newline at end of file