| | |
| | | package com.ruoyi.payment.controller; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.ruoyi.common.core.domain.R; |
| | | import com.ruoyi.order.api.feignClient.ChargingOrderClient; |
| | | import com.ruoyi.payment.api.vo.IcbcPaymentReq; |
| | |
| | | import com.ruoyi.payment.api.vo.IcbcRefundResp; |
| | | import com.ruoyi.payment.api.vo.IcbcSubOrderQueryReq; |
| | | import com.ruoyi.payment.api.vo.IcbcSubOrderQueryResp; |
| | | import com.ruoyi.payment.api.vo.IcbcSubOrderRefundReq; |
| | | import com.ruoyi.payment.api.vo.IcbcSubOrderRefundResp; |
| | | import com.ruoyi.payment.api.vo.IcbcSubOrderSplitReq; |
| | | import com.ruoyi.payment.api.vo.IcbcSubOrderSplitResp; |
| | | import com.ruoyi.payment.icbc.config.IcbcPayProperties; |
| | | import com.ruoyi.payment.icbc.service.IcbcPayService; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.io.BufferedReader; |
| | | import java.math.BigDecimal; |
| | | import java.io.PrintWriter; |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.Enumeration; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * 工行统一支付入口。 |
| | | */ |
| | | @Slf4j |
| | | @RestController |
| | | @RequestMapping("/icbc") |
| | | public class IcbcPayController { |
| | | private static final String TEST_ORDER_PREFIX = "ICBCTEST"; |
| | | private static final String TEST_SUB_MER_ID = "231011990055"; |
| | | private static final String TEST_SUB_MER_PRTCL_NO = "2310119900550201"; |
| | | |
| | | @Resource |
| | | private IcbcPayService icbcPayService; |
| | | |
| | | @Resource |
| | | private IcbcPayProperties icbcPayProperties; |
| | | |
| | | @Resource |
| | | private ChargingOrderClient chargingOrderClient; |
| | | |
| | | @PostMapping("/payment") |
| | | public R<IcbcPaymentResp> payment(@RequestBody IcbcPaymentReq req) { |
| | | return R.ok(icbcPayService.payment(req)); |
| | | log.info("工行支付请求入参:{}", JSON.toJSONString(req)); |
| | | IcbcPaymentResp resp = icbcPayService.payment(req); |
| | | log.info("工行支付返回前端:{}", JSON.toJSONString(resp)); |
| | | return R.ok(resp); |
| | | } |
| | | |
| | | @PostMapping("/test/payment") |
| | | public R<IcbcPaymentResp> testPayment(@RequestBody(required = false) IcbcPaymentReq req) { |
| | | log.info("工行测试支付请求入参:{}", JSON.toJSONString(req)); |
| | | IcbcPaymentReq paymentReq = req == null ? new IcbcPaymentReq() : req; |
| | | paymentReq.setOutTradeNo(TEST_ORDER_PREFIX + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"))); |
| | | if (paymentReq.getAmount() == null || paymentReq.getAmount().compareTo(BigDecimal.ZERO) <= 0) { |
| | | paymentReq.setAmount(new BigDecimal("0.01")); |
| | | } |
| | | if (paymentReq.getSubject() == null || paymentReq.getSubject().trim().isEmpty()) { |
| | | paymentReq.setSubject("工行测试支付"); |
| | | } |
| | | if (paymentReq.getBody() == null || paymentReq.getBody().trim().isEmpty()) { |
| | | paymentReq.setBody("工行测试支付"); |
| | | } |
| | | if (paymentReq.getPaymentType() == null) { |
| | | paymentReq.setPaymentType(1); |
| | | } |
| | | log.info("工行测试支付实际下单参数:{}", JSON.toJSONString(paymentReq)); |
| | | IcbcPaymentResp resp = icbcPayService.payment(paymentReq); |
| | | log.info("工行测试支付返回前端:{}", JSON.toJSONString(resp)); |
| | | return R.ok(resp); |
| | | } |
| | | |
| | | @PostMapping("/test/query") |
| | | public R<IcbcPaymentResp> testQuery(@RequestBody Map<String, Object> req) { |
| | | String outTradeNo = getString(req, "outTradeNo"); |
| | | log.info("工行测试查询请求入参:{}", JSON.toJSONString(req)); |
| | | IcbcPaymentResp resp = icbcPayService.query(outTradeNo); |
| | | log.info("工行测试查询返回前端:{}", JSON.toJSONString(resp)); |
| | | return R.ok(resp); |
| | | } |
| | | |
| | | @PostMapping("/test/refund") |
| | | public R<IcbcRefundResp> testRefund(@RequestBody(required = false) IcbcRefundReq req) { |
| | | req = req == null ? new IcbcRefundReq() : req; |
| | | log.info("工行测试退款请求入参:{}", JSON.toJSONString(req)); |
| | | if (req.getRefundAmount() == null || req.getRefundAmount().compareTo(BigDecimal.ZERO) <= 0) { |
| | | req.setRefundAmount(new BigDecimal("0.01")); |
| | | } |
| | | if (req.getTotalAmount() == null || req.getTotalAmount().compareTo(BigDecimal.ZERO) <= 0) { |
| | | req.setTotalAmount(req.getRefundAmount()); |
| | | } |
| | | if (req.getOutRefundNo() == null || req.getOutRefundNo().trim().isEmpty()) { |
| | | req.setOutRefundNo("ICBCREFUND" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"))); |
| | | } |
| | | if (req.getReason() == null || req.getReason().trim().isEmpty()) { |
| | | req.setReason("工行测试退款"); |
| | | } |
| | | log.info("工行测试退款实际参数:{}", JSON.toJSONString(req)); |
| | | IcbcRefundResp resp = icbcPayService.refund(req); |
| | | log.info("工行测试退款返回前端:{}", JSON.toJSONString(resp)); |
| | | return R.ok(resp); |
| | | } |
| | | |
| | | @PostMapping("/test/sub-order/split") |
| | | public R<IcbcSubOrderSplitResp> testSplitSubOrder(@RequestBody(required = false) IcbcSubOrderSplitReq req) { |
| | | req = req == null ? new IcbcSubOrderSplitReq() : req; |
| | | log.info("工行测试分账请求入参:{}", JSON.toJSONString(req)); |
| | | if (req.getSubOrderId() == null || req.getSubOrderId().trim().isEmpty()) { |
| | | req.setSubOrderId("ICBCSUB" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"))); |
| | | } |
| | | if (req.getSeqNo() == null || req.getSeqNo().trim().isEmpty()) { |
| | | req.setSeqNo(req.getChargingOrderCode()); |
| | | } |
| | | if (req.getBusiType() == null || req.getBusiType().trim().isEmpty()) { |
| | | req.setBusiType("1"); |
| | | } |
| | | if (req.getOriTrxDate() == null || req.getOriTrxDate().trim().isEmpty()) { |
| | | req.setOriTrxDate(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); |
| | | } |
| | | if (req.getClassifyAmt() == null || req.getClassifyAmt().compareTo(BigDecimal.ZERO) <= 0) { |
| | | req.setClassifyAmt(new BigDecimal("0.01")); |
| | | } |
| | | if (req.getSubMerId() == null || req.getSubMerId().trim().isEmpty()) { |
| | | req.setSubMerId(TEST_SUB_MER_ID); |
| | | } |
| | | if (req.getSubMerPrtclNo() == null || req.getSubMerPrtclNo().trim().isEmpty()) { |
| | | req.setSubMerPrtclNo(TEST_SUB_MER_PRTCL_NO); |
| | | } |
| | | if (req.getOriMerId() == null || req.getOriMerId().trim().isEmpty()) { |
| | | req.setOriMerId(icbcPayProperties.getMerId()); |
| | | } |
| | | log.info("工行测试分账实际参数:{}", JSON.toJSONString(req)); |
| | | IcbcSubOrderSplitResp resp = icbcPayService.splitSubOrder(req); |
| | | log.info("工行测试分账返回前端:{}", JSON.toJSONString(resp)); |
| | | return R.ok(resp); |
| | | } |
| | | |
| | | @PostMapping("/test/sub-order/refund") |
| | | public R<IcbcSubOrderRefundResp> testRefundSubOrder(@RequestBody(required = false) IcbcSubOrderRefundReq req) { |
| | | req = req == null ? new IcbcSubOrderRefundReq() : req; |
| | | log.info("工行测试分账退款请求入参:{}", JSON.toJSONString(req)); |
| | | if (req.getSeqNo() == null || req.getSeqNo().trim().isEmpty()) { |
| | | req.setSeqNo(req.getChargingOrderCode()); |
| | | } |
| | | if (req.getBusiType() == null || req.getBusiType().trim().isEmpty()) { |
| | | req.setBusiType("1"); |
| | | } |
| | | if (req.getOriTrxDate() == null || req.getOriTrxDate().trim().isEmpty()) { |
| | | req.setOriTrxDate(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); |
| | | } |
| | | if (req.getRefundAmount() == null || req.getRefundAmount().compareTo(BigDecimal.ZERO) <= 0) { |
| | | req.setRefundAmount(new BigDecimal("0.01")); |
| | | } |
| | | if (req.getRetSubOrderId() == null || req.getRetSubOrderId().trim().isEmpty()) { |
| | | req.setRetSubOrderId("ICBCSUBREFUND" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"))); |
| | | } |
| | | if (req.getOuttrxSerialNo() == null || req.getOuttrxSerialNo().trim().isEmpty()) { |
| | | req.setOuttrxSerialNo("ICBCSUBREFUNDNO" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"))); |
| | | } |
| | | if (req.getSubMerId() == null || req.getSubMerId().trim().isEmpty()) { |
| | | req.setSubMerId(TEST_SUB_MER_ID); |
| | | } |
| | | if (req.getSubMerPrtclNo() == null || req.getSubMerPrtclNo().trim().isEmpty()) { |
| | | req.setSubMerPrtclNo(TEST_SUB_MER_PRTCL_NO); |
| | | } |
| | | if (req.getOriMerId() == null || req.getOriMerId().trim().isEmpty()) { |
| | | req.setOriMerId(icbcPayProperties.getMerId()); |
| | | } |
| | | log.info("工行测试分账退款实际参数:{}", JSON.toJSONString(req)); |
| | | IcbcSubOrderRefundResp resp = icbcPayService.refundSubOrder(req); |
| | | log.info("工行测试分账退款返回前端:{}", JSON.toJSONString(resp)); |
| | | return R.ok(resp); |
| | | } |
| | | |
| | | @PostMapping("/query") |
| | |
| | | return R.ok(icbcPayService.splitSubOrder(req)); |
| | | } |
| | | |
| | | @PostMapping("/sub-order/refund") |
| | | public R<IcbcSubOrderRefundResp> refundSubOrder(@RequestBody IcbcSubOrderRefundReq req) { |
| | | return R.ok(icbcPayService.refundSubOrder(req)); |
| | | } |
| | | |
| | | @PostMapping("/sub-order/query") |
| | | public R<IcbcSubOrderQueryResp> querySubOrder(@RequestBody IcbcSubOrderQueryReq req) { |
| | | return R.ok(icbcPayService.querySubOrder(req)); |
| | | } |
| | | |
| | | @PostMapping("/callBack") |
| | | @RequestMapping(value = "/callBack", method = {RequestMethod.GET, RequestMethod.POST}) |
| | | public void callBack(HttpServletRequest request, HttpServletResponse response) throws Exception { |
| | | Map<String, String> callback = icbcPayService.parseNotify(request.getParameterMap()); |
| | | dispatchChargingOrderCallback(callback); |
| | | PrintWriter writer = response.getWriter(); |
| | | writer.println("success"); |
| | | writer.flush(); |
| | | writer.close(); |
| | | Map<String, String[]> requestParams = new LinkedHashMap<>(request.getParameterMap()); |
| | | String rawBody = requestParams.isEmpty() ? readRequestBody(request) : ""; |
| | | mergeJsonBody(requestParams, rawBody); |
| | | log.info("工行支付回调原始请求,method:{},uri:{},query:{},contentType:{},params:{},body:{},headers:{}", |
| | | request.getMethod(), |
| | | request.getRequestURI(), |
| | | request.getQueryString(), |
| | | request.getContentType(), |
| | | JSON.toJSONString(requestParams), |
| | | rawBody, |
| | | summarizeHeaders(request)); |
| | | try { |
| | | Map<String, String> callback = icbcPayService.parseNotify(requestParams); |
| | | log.info("工行支付回调解析结果:{}", JSON.toJSONString(callback)); |
| | | dispatchChargingOrderCallback(callback); |
| | | } catch (Exception e) { |
| | | log.error("工行支付回调处理失败,已记录原始请求并返回success避免重复通知", e); |
| | | } finally { |
| | | writeCallbackResponse(response); |
| | | } |
| | | } |
| | | |
| | | private void dispatchChargingOrderCallback(Map<String, String> callback) { |
| | | String outTradeNo = callback.get("out_trade_no"); |
| | | String transactionId = callback.get("transaction_id"); |
| | | String attach = callback.get("attach"); |
| | | if (outTradeNo != null && outTradeNo.startsWith(TEST_ORDER_PREFIX)) { |
| | | log.info("工行测试支付回调成功,订单号:{},流水号:{},支付类型:{}", outTradeNo, transactionId, attach); |
| | | return; |
| | | } |
| | | Integer paymentType = Integer.valueOf(attach); |
| | | if (Integer.valueOf(1).equals(paymentType)) { |
| | | chargingOrderClient.chargingOrderWXCallback(outTradeNo, transactionId, attach); |
| | |
| | | } |
| | | throw new IllegalArgumentException("工行回调支付类型不支持:" + attach); |
| | | } |
| | | |
| | | private String getString(Map<String, Object> req, String key) { |
| | | if (req == null || req.get(key) == null) { |
| | | throw new IllegalArgumentException("缺少参数:" + key); |
| | | } |
| | | return String.valueOf(req.get(key)); |
| | | } |
| | | |
| | | private String readRequestBody(HttpServletRequest request) { |
| | | StringBuilder body = new StringBuilder(); |
| | | try (BufferedReader reader = request.getReader()) { |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | body.append(line); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("读取工行支付回调body失败", e); |
| | | } |
| | | return body.toString(); |
| | | } |
| | | |
| | | private void mergeJsonBody(Map<String, String[]> requestParams, String rawBody) { |
| | | if (rawBody == null || rawBody.trim().isEmpty()) { |
| | | return; |
| | | } |
| | | try { |
| | | JSONObject jsonObject = JSON.parseObject(rawBody); |
| | | if (jsonObject == null) { |
| | | return; |
| | | } |
| | | for (Map.Entry<String, Object> entry : jsonObject.entrySet()) { |
| | | if (entry.getValue() != null && !requestParams.containsKey(entry.getKey())) { |
| | | requestParams.put(entry.getKey(), new String[]{String.valueOf(entry.getValue())}); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("工行支付回调body不是JSON格式,body:{}", rawBody); |
| | | } |
| | | } |
| | | |
| | | private String summarizeHeaders(HttpServletRequest request) { |
| | | Map<String, String> headers = new LinkedHashMap<>(); |
| | | Enumeration<String> names = request.getHeaderNames(); |
| | | while (names != null && names.hasMoreElements()) { |
| | | String name = names.nextElement(); |
| | | if ("user-agent".equalsIgnoreCase(name) |
| | | || "host".equalsIgnoreCase(name) |
| | | || "x-forwarded-for".equalsIgnoreCase(name) |
| | | || "x-real-ip".equalsIgnoreCase(name)) { |
| | | headers.put(name, request.getHeader(name)); |
| | | } |
| | | } |
| | | return JSON.toJSONString(headers); |
| | | } |
| | | |
| | | private void writeCallbackResponse(HttpServletResponse response) throws Exception { |
| | | response.setCharacterEncoding("UTF-8"); |
| | | response.setContentType("text/plain;charset=UTF-8"); |
| | | PrintWriter writer = response.getWriter(); |
| | | writer.println("success"); |
| | | writer.flush(); |
| | | writer.close(); |
| | | } |
| | | } |