聪明付

This commit is contained in:
2025-06-02 09:43:01 +08:00
parent 89e112e13a
commit 34dbc7d036
11 changed files with 374 additions and 7 deletions

View File

@ -5,10 +5,12 @@ import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.enums.BizCodeEnum; import com.ycwl.basic.enums.BizCodeEnum;
import com.ycwl.basic.model.wx.WXPayOrderReqVO; import com.ycwl.basic.model.wx.WXPayOrderReqVO;
import com.ycwl.basic.model.wx.WxPayRespVO; 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.service.mobile.WxPayService;
import com.ycwl.basic.utils.ApiResponse; import com.ycwl.basic.utils.ApiResponse;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; 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 org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException; import java.io.IOException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
@ -41,10 +44,17 @@ public class AppWxPayController {
wxPayService.payNotify(request); wxPayService.payNotify(request);
return ApiResponse.success(BizCodeEnum.REQUEST_OK); return ApiResponse.success(BizCodeEnum.REQUEST_OK);
} }
@PostMapping("/{scenicId}/payNotify")
@RequestMapping("/{scenicId}/payNotify")
@IgnoreToken @IgnoreToken
public ApiResponse<?> payNotifyByScenicId(@PathVariable Long scenicId, HttpServletRequest request) { public Object payNotifyByScenicId(@PathVariable Long scenicId, HttpServletRequest request) {
wxPayService.payNotify(scenicId, 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); return ApiResponse.success(BizCodeEnum.REQUEST_OK);
} }

View File

@ -1,5 +1,6 @@
package com.ycwl.basic.pay; 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.IPayAdapter;
import com.ycwl.basic.pay.adapter.WxMpPayAdapter; import com.ycwl.basic.pay.adapter.WxMpPayAdapter;
import com.ycwl.basic.pay.enums.PayAdapterType; import com.ycwl.basic.pay.enums.PayAdapterType;
@ -23,10 +24,11 @@ public class PayFactory {
} }
public static IPayAdapter getAdapter(PayAdapterType type) { public static IPayAdapter getAdapter(PayAdapterType type) {
if (Objects.requireNonNull(type) == PayAdapterType.WX_MP_PAY) { return switch (type) {
return new WxMpPayAdapter(); case WX_MP_PAY -> new WxMpPayAdapter();
} case CONG_MING_PAY -> new CongMingPayAdapter();
throw new PayUnsupportedException("不支持的Adapter类型"); default -> throw new PayUnsupportedException("不支持的Adapter类型");
};
} }
protected static Map<String, IPayAdapter> namedAdapter = new HashMap<>(); protected static Map<String, IPayAdapter> namedAdapter = new HashMap<>();

View File

@ -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<String, String> 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<String, Object> 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<String, Object> getPaymentParams(CreateOrderResponse response) {
Map<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> map, String key) {
Collection<String> keyset = map.keySet();
List<String> 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();
}
}

View File

@ -7,4 +7,5 @@ import lombok.experimental.Accessors;
@Accessors(chain = true) @Accessors(chain = true)
public class CancelOrderRequest { public class CancelOrderRequest {
private String orderNo; private String orderNo;
private String reason = "取消订单";
} }

View File

@ -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";
}

View File

@ -2,9 +2,12 @@ package com.ycwl.basic.pay.entity;
import lombok.Data; import lombok.Data;
import java.util.Map;
@Data @Data
public class CreateOrderResponse { public class CreateOrderResponse {
private boolean success; private boolean success;
private boolean skipPay; private boolean skipPay;
private String orderNo; private String orderNo;
private Map<String, Object> extData;
} }

View File

@ -1,16 +1,22 @@
package com.ycwl.basic.pay.entity; package com.ycwl.basic.pay.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data; import lombok.Data;
import java.math.BigDecimal;
@Data @Data
public class PayResponse { public class PayResponse {
private boolean valid; private boolean valid;
private String orderNo; private String orderNo;
@JsonIgnore
private Object originalResponse; private Object originalResponse;
private Integer orderPrice; private Integer orderPrice;
private Integer payPrice; private Integer payPrice;
private PAY_STATE state; private PAY_STATE state;
private String payTime; private String payTime;
@JsonIgnore
private String customResponse;
public boolean isPay() { public boolean isPay() {
return state == PAY_STATE.SUCCESS; return state == PAY_STATE.SUCCESS;
@ -24,8 +30,17 @@ public class PayResponse {
return state == PAY_STATE.REFUND; 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 { public enum PAY_STATE {
SUCCESS, SUCCESS,
NOT_PAY,
CANCEL, CANCEL,
REFUND, REFUND,
FAIL, FAIL,

View File

@ -3,6 +3,9 @@ package com.ycwl.basic.pay.entity;
import lombok.Data; import lombok.Data;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import java.math.BigDecimal;
import java.math.BigInteger;
@Data @Data
@Accessors(chain = true) @Accessors(chain = true)
public class RefundOrderRequest { public class RefundOrderRequest {
@ -11,4 +14,12 @@ public class RefundOrderRequest {
private String orderNo; private String orderNo;
private String refundNo; private String refundNo;
private String notifyUrl; 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();
}
} }

View File

@ -1,17 +1,23 @@
package com.ycwl.basic.pay.entity; package com.ycwl.basic.pay.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data; import lombok.Data;
import java.math.BigDecimal;
@Data @Data
public class RefundResponse { public class RefundResponse {
private boolean valid; private boolean valid;
private String orderNo; private String orderNo;
private String refundNo; private String refundNo;
@JsonIgnore
private Object originalResponse; private Object originalResponse;
private Integer orderPrice; private Integer orderPrice;
private Integer refundPrice; private Integer refundPrice;
private PAY_STATE state; private PAY_STATE state;
private String refundTime; private String refundTime;
@JsonIgnore
private String customResponse;
public void setSuccess() { public void setSuccess() {
state = PAY_STATE.SUCCESS; state = PAY_STATE.SUCCESS;
@ -21,6 +27,10 @@ public class RefundResponse {
state = PAY_STATE.FAIL; state = PAY_STATE.FAIL;
} }
public void setRefundPriceInYuan(BigDecimal bigDecimal) {
refundPrice = bigDecimal.multiply(BigDecimal.valueOf(100)).intValue();
}
public enum PAY_STATE { public enum PAY_STATE {
SUCCESS, SUCCESS,
FAIL FAIL

View File

@ -5,6 +5,7 @@ import lombok.Getter;
@Getter @Getter
public enum PayAdapterType { public enum PayAdapterType {
WX_MP_PAY("WX_MP_PAY"), WX_MP_PAY("WX_MP_PAY"),
CONG_MING_PAY("CONG_MING_PAY"),
; ;
private final String type; private final String type;

View File

@ -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");
}
}