|
|
@@ -0,0 +1,118 @@
|
|
|
+package com.etotem.cfc.controller;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.etotem.cfc.service.VirtualPayService;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.web.bind.annotation.PostMapping;
|
|
|
+import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
+import org.springframework.web.bind.annotation.RestController;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
+import java.io.BufferedReader;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 小程序虚拟支付消息推送接收端点。
|
|
|
+ * 微信服务器推送 URL 配置指向本控制器;/api/internal/ 已在 JwtInterceptor.PUBLIC_PATHS 前缀公开。
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@RestController
|
|
|
+@RequestMapping("/api/internal/virtual-pay")
|
|
|
+public class VirtualPayController {
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private VirtualPayService virtualPayService;
|
|
|
+
|
|
|
+ /** 微信虚拟支付发货/退款等消息推送(xpay_goods_deliver_notify / xpay_refund_notify / 其他) */
|
|
|
+ @PostMapping("/notify")
|
|
|
+ public Map<String, Object> notify(HttpServletRequest request) {
|
|
|
+ try {
|
|
|
+ // 读原始 body(getReader(),参照 ProductOrderController.notify() L120-155 范式)
|
|
|
+ BufferedReader reader = request.getReader();
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ String line;
|
|
|
+ while ((line = reader.readLine()) != null) {
|
|
|
+ sb.append(line);
|
|
|
+ }
|
|
|
+ String requestBody = sb.toString();
|
|
|
+ log.info("虚拟支付消息推送: body={}", requestBody);
|
|
|
+
|
|
|
+ JSONObject body = JSON.parseObject(requestBody);
|
|
|
+ if (body == null) {
|
|
|
+ log.error("虚拟支付消息推送body解析失败: {}", requestBody);
|
|
|
+ return successResult();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按 Event 类型分发(设计文档 §6.2)
|
|
|
+ // 微信虚拟支付推送字段: Event(如 xpay_goods_deliver_notify / xpay_refund_notify)、OutTradeNo、TransactionId、其他业务字段
|
|
|
+ // 若官方文档使用其他字段名,做多 key 兜底
|
|
|
+ String event = body.getString("Event");
|
|
|
+ if (event == null || event.isEmpty()) {
|
|
|
+ event = body.getString("EventType");
|
|
|
+ }
|
|
|
+ Map<String, Object> payload = new HashMap<>(body.getInnerMap());
|
|
|
+
|
|
|
+ if ("xpay_goods_deliver_notify".equals(event)) {
|
|
|
+ return virtualPayService.handleDeliverNotify(payload);
|
|
|
+ } else if ("xpay_refund_notify".equals(event)) {
|
|
|
+ return virtualPayService.handleRefundNotify(payload);
|
|
|
+ } else {
|
|
|
+ // 未知事件(含 xpay_coin_pay_notify):记日志忽略,道具直购不涉及
|
|
|
+ log.info("虚拟支付推送未知事件,忽略: event={}", event);
|
|
|
+ return successResult();
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ // 解析失败返回成功 ack,避免微信重试死循环(最多15次)
|
|
|
+ log.error("处理虚拟支付推送异常,返回成功ack", e);
|
|
|
+ return successResult();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** iOS App Store 退款问询应答(3秒内应答;3次未应答默认"不确定"交Apple裁决) */
|
|
|
+ @PostMapping("/ios-refund-query")
|
|
|
+ public Map<String, Object> iosRefundQuery(HttpServletRequest request) {
|
|
|
+ try {
|
|
|
+ BufferedReader reader = request.getReader();
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ String line;
|
|
|
+ while ((line = reader.readLine()) != null) {
|
|
|
+ sb.append(line);
|
|
|
+ }
|
|
|
+ String requestBody = sb.toString();
|
|
|
+ log.info("iOS退款问询: body={}", requestBody);
|
|
|
+
|
|
|
+ JSONObject body = JSON.parseObject(requestBody);
|
|
|
+ // 校验 payload 基本格式(如失败记日志返回最低响应)
|
|
|
+ if (body == null) {
|
|
|
+ log.error("iOS退款问询body解析失败: {}", requestBody);
|
|
|
+ return iosRefundBaseResponse();
|
|
|
+ }
|
|
|
+
|
|
|
+ // TODO Task 8/9: 按 OutTradeNo 查订阅状态后返回 {"IsAgree": true/false, "Reason": "..."}
|
|
|
+ // 未接入 Apple 服务端通知前返回协议规定的最低响应(默认不同意退款,交 Apple 裁决)
|
|
|
+ return iosRefundBaseResponse();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处理iOS退款问询异常", e);
|
|
|
+ return iosRefundBaseResponse();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 微信协议成功应答 */
|
|
|
+ private Map<String, Object> successResult() {
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
+ result.put("ErrCode", 0);
|
|
|
+ result.put("ErrMsg", "success");
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** iOS 退款问询最低响应(协议规定应答字段,未接入时默认不同意由 Apple 裁决) */
|
|
|
+ private Map<String, Object> iosRefundBaseResponse() {
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
+ result.put("IsAgree", false);
|
|
|
+ result.put("Reason", "SERVER_NOT_IMPLEMENTED");
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+}
|