package com.example.recharge;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import tech.wantu.pay.WantuPay;
@RestController
public class RechargeController {
// 套餐表:金额永远查服务端的表,前端只传套餐名
private static final Map<String, String[]> PLANS = new HashMap<>();
static {
PLANS.put("small", new String[] {"0.01", "积分充值·体验"});
PLANS.put("big", new String[] {"100.00", "积分充值100元"});
}
// 支付渠道:填顽兔开户时给你开的那条。"alipay" 支付宝;"chinaums" 银联商务聚合码
//(一张码微信/支付宝/云闪付都能扫)。两条都开的可由前端传上来;传了没开通的渠道会报 42201
private static final String CHANNEL = "alipay";
private final WantuPay.Client pay = new WantuPay.Client(new WantuPay.Config()
.baseUrl(System.getenv("WANTU_PAY_BASE_URL"))
.merchantId(System.getenv("WANTU_MERCHANT_ID"))
.apiSecret(System.getenv("WANTU_API_SECRET")));
@PostMapping("/recharge")
public ResponseEntity<Map<String, String>> recharge(@RequestBody Map<String, Object> body) {
String[] plan = PLANS.get(String.valueOf(body.get("plan")));
if (plan == null) {
return ResponseEntity.badRequest().build();
}
String outTradeNo = "R" + System.currentTimeMillis(); // 服务端生成,保证唯一;建议换成库表序列
Map<String, Object> order = pay.createOrder(new WantuPay.CreateOrderParams()
.outTradeNo(outTradeNo)
.amount(plan[0])
.subject(plan[1])
.channel(CHANNEL)
.payMode("qrcode") // 充值页内嵌二维码;整页跳转用 "redirect"
.attach("uid=" + body.get("uid")));
// 此时应把 outTradeNo、金额、用户、状态=待支付 落到你的订单表
Map<String, String> result = new HashMap<>();
result.put("out_trade_no", outTradeNo);
result.put("channel", (String) order.get("channel"));
result.put("pay_url", (String) order.get("pay_url"));
return ResponseEntity.ok(result);
}
@PostMapping("/wantu/webhook")
public ResponseEntity<String> webhook(@RequestHeader Map<String, String> headers,
@RequestBody byte[] rawBody) { // 必须是请求体原文字节
Map<String, Object> event;
try {
event = pay.verifyWebhook(headers, rawBody);
} catch (WantuPay.WebhookVerifyException e) {
return ResponseEntity.badRequest().build(); // 验签失败:不处理业务,网关会重试
}
if ("order.paid".equals(event.get("event_type"))) {
// 入账三件套:查到本地订单 → 金额一致 → 事务内幂等入账
// UPDATE orders SET status='paid' WHERE out_trade_no=? AND status='created'
// 更新行数为 1 才执行加积分;为 0 说明是重复回调,直接返回 200
}
return ResponseEntity.ok("ok"); // 不认识的事件类型也回 2xx,不要报错
}
@GetMapping("/orders/{outTradeNo}")
public Map<String, Object> queryOrder(@PathVariable String outTradeNo) {
Map<String, Object> order = pay.queryOrder(WantuPay.OrderRef.outTradeNo(outTradeNo));
return Collections.singletonMap("status", order.get("status"));
}
}