|
|
@@ -1,344 +0,0 @@
|
|
|
-package com.etotem.cfc.service;
|
|
|
-
|
|
|
-import com.etotem.cfc.service.api.PackagePaymentServiceInterface;
|
|
|
-import javax.annotation.Resource;
|
|
|
-
|
|
|
-import com.alibaba.fastjson.JSON;
|
|
|
-import com.alibaba.fastjson.JSONObject;
|
|
|
-import com.etotem.cfc.common.OrderNoGenerator;
|
|
|
-import com.etotem.cfc.entity.PackageOrder;
|
|
|
-import com.etotem.cfc.entity.TaskTemplatePackage;
|
|
|
-import com.etotem.cfc.entity.UserIntent;
|
|
|
-import com.etotem.cfc.mapper.PackageOrderMapper;
|
|
|
-import com.etotem.cfc.mapper.UserIntentMapper;
|
|
|
-import com.etotem.cfc.service.CommissionService;
|
|
|
-import org.slf4j.Logger;
|
|
|
-import org.slf4j.LoggerFactory;
|
|
|
-import org.springframework.beans.factory.annotation.Value;
|
|
|
-import org.springframework.http.ResponseEntity;
|
|
|
-import org.springframework.stereotype.Service;
|
|
|
-import org.springframework.web.client.RestTemplate;
|
|
|
-
|
|
|
-import java.util.Date;
|
|
|
-import java.util.Map;
|
|
|
-import java.util.UUID;
|
|
|
-
|
|
|
-@Service
|
|
|
-public class PackagePaymentService implements PackagePaymentServiceInterface {
|
|
|
-
|
|
|
- private static final Logger log = LoggerFactory.getLogger(PackagePaymentService.class);
|
|
|
-
|
|
|
- @Resource
|
|
|
- private PackageOrderMapper orderMapper;
|
|
|
-
|
|
|
- @Resource
|
|
|
- private TaskTemplatePackageService packageService;
|
|
|
-
|
|
|
- @Resource
|
|
|
- private CommissionService commissionService;
|
|
|
-
|
|
|
- @Resource
|
|
|
- private CfCommissionService cfCommissionService;
|
|
|
-
|
|
|
- @Resource
|
|
|
- private CouponService couponService;
|
|
|
-
|
|
|
- @Resource
|
|
|
- private UserIntentMapper userIntentMapper;
|
|
|
-
|
|
|
- @Value("${wechat.appid}")
|
|
|
- private String appid;
|
|
|
-
|
|
|
- @Value("${wechat.mch-id}")
|
|
|
- private String mchId;
|
|
|
-
|
|
|
- @Value("${wechat.mch-key}")
|
|
|
- private String mchKey;
|
|
|
-
|
|
|
- @Value("${wechat.notify-url}")
|
|
|
- private String notifyUrl;
|
|
|
-
|
|
|
- private final RestTemplate restTemplate = new RestTemplate();
|
|
|
-
|
|
|
- /**
|
|
|
- * 创建支付订单
|
|
|
- */
|
|
|
- public PackageOrder createOrder(Long userId, Long familyId, Long packageId, String payMethod, Long userCouponId) {
|
|
|
- // 获取任务模板信息
|
|
|
- TaskTemplatePackage pkg = packageService.getPackageById(packageId);
|
|
|
- if (pkg == null) {
|
|
|
- throw new RuntimeException("任务模板不存在");
|
|
|
- }
|
|
|
-
|
|
|
- // 如果任务模板免费,直接创建并返回
|
|
|
- if (pkg.getPrice() == null || pkg.getPrice() <= 0) {
|
|
|
- PackageOrder order = new PackageOrder();
|
|
|
- order.setOrderNo(generateOrderNo());
|
|
|
- order.setUserId(userId);
|
|
|
- order.setFamilyId(familyId);
|
|
|
- order.setPackageId(packageId);
|
|
|
- order.setPackageName(pkg.getName());
|
|
|
- order.setPrice(0);
|
|
|
- order.setPlatformFee(0);
|
|
|
- order.setGuideId(pkg.getCreatorId());
|
|
|
- order.setStatus("paid");
|
|
|
- order.setPayMethod("free");
|
|
|
- order.setPaidAt(new Date());
|
|
|
- order.setCreatedAt(new Date());
|
|
|
- order.setUpdatedAt(new Date());
|
|
|
- orderMapper.insert(order);
|
|
|
- // 购买完成 → 推进旅程 stage=3(幂等)
|
|
|
- advanceStage(userId, 3);
|
|
|
- return order;
|
|
|
- }
|
|
|
-
|
|
|
- int originalPrice = pkg.getPrice();
|
|
|
- int finalPrice = originalPrice;
|
|
|
- Long appliedCouponId = null;
|
|
|
- if (userCouponId != null) {
|
|
|
- Integer discount = couponService.apply(userId, userCouponId, "PRODUCT", originalPrice);
|
|
|
- if (discount != null && discount > 0) {
|
|
|
- finalPrice = Math.max(0, originalPrice - discount);
|
|
|
- appliedCouponId = userCouponId;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 创建付费订单
|
|
|
- PackageOrder order = new PackageOrder();
|
|
|
- order.setOrderNo(generateOrderNo());
|
|
|
- order.setUserId(userId);
|
|
|
- order.setFamilyId(familyId);
|
|
|
- order.setPackageId(packageId);
|
|
|
- order.setPackageName(pkg.getName());
|
|
|
- order.setPrice(finalPrice);
|
|
|
- order.setPlatformFee(pkg.getPlatformFee());
|
|
|
- order.setUserCouponId(appliedCouponId);
|
|
|
- order.setGuideId(pkg.getCreatorId());
|
|
|
- order.setStatus("pending");
|
|
|
- order.setPayMethod(payMethod);
|
|
|
- order.setCreatedAt(new Date());
|
|
|
- order.setUpdatedAt(new Date());
|
|
|
-
|
|
|
- orderMapper.insert(order);
|
|
|
-
|
|
|
- return order;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 发起微信支付 (JSAPI)
|
|
|
- */
|
|
|
- public Map<String, Object> createWechatPayOrder(Long orderId, String openid) {
|
|
|
- PackageOrder order = orderMapper.selectById(orderId);
|
|
|
- if (order == null) {
|
|
|
- throw new RuntimeException("订单不存在");
|
|
|
- }
|
|
|
-
|
|
|
- // 调用微信统一下单接口
|
|
|
- String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
|
|
|
-
|
|
|
- JSONObject params = new JSONObject();
|
|
|
- params.put("appid", appid);
|
|
|
- params.put("mch_id", mchId);
|
|
|
- params.put("nonce_str", UUID.randomUUID().toString().replace("-", "").substring(0, 16));
|
|
|
- params.put("body", "浠艾福-" + order.getPackageName());
|
|
|
- params.put("out_trade_no", order.getOrderNo());
|
|
|
- params.put("total_fee", order.getPrice() * 100);
|
|
|
- params.put("spbill_create_ip", "127.0.0.1");
|
|
|
- params.put("notify_url", notifyUrl);
|
|
|
- params.put("trade_type", "JSAPI");
|
|
|
- params.put("openid", openid);
|
|
|
-
|
|
|
- // 生成签名
|
|
|
- String sign = generateSign(params, mchKey);
|
|
|
- params.put("sign", sign);
|
|
|
-
|
|
|
- try {
|
|
|
- ResponseEntity<String> response = restTemplate.postForEntity(url, params.toJSONString(), String.class);
|
|
|
- JSONObject result = JSON.parseObject(response.getBody());
|
|
|
-
|
|
|
- if ("SUCCESS".equals(result.getString("return_code"))) {
|
|
|
- // 返回预支付订单信息给前端
|
|
|
- JSONObject payParams = new JSONObject();
|
|
|
- payParams.put("orderId", orderId);
|
|
|
- payParams.put("prepay_id", result.getString("prepay_id"));
|
|
|
- payParams.put("nonce_str", params.getString("nonce_str"));
|
|
|
-
|
|
|
- // 生成前端签名
|
|
|
- String paySign = generatePaySign(payParams, mchKey);
|
|
|
- payParams.put("sign", paySign);
|
|
|
-
|
|
|
- return payParams;
|
|
|
- } else {
|
|
|
- log.error("微信下单失败: {}", result.toJSONString());
|
|
|
- throw new RuntimeException("微信下单失败");
|
|
|
- }
|
|
|
- } catch (Exception e) {
|
|
|
- log.error("微信支付异常", e);
|
|
|
- throw new RuntimeException("支付异常: " + e.getMessage());
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 处理微信支付回调
|
|
|
- */
|
|
|
- public boolean handlePayNotify(String xmlData) {
|
|
|
- try {
|
|
|
- // 解析XML
|
|
|
- JSONObject params = parseXmlToJson(xmlData);
|
|
|
-
|
|
|
- if (!"SUCCESS".equals(params.getString("return_code"))) {
|
|
|
- log.error("支付回调失败: {}", params.getString("return_msg"));
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- // 验签
|
|
|
- String sign = params.getString("sign");
|
|
|
- params.remove("sign");
|
|
|
- String calculatedSign = generateSign(params, mchKey);
|
|
|
-
|
|
|
- if (!sign.equals(calculatedSign)) {
|
|
|
- log.error("签名验证失败");
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- // 更新订单状态
|
|
|
- String orderNo = params.getString("out_trade_no");
|
|
|
- PackageOrder order = orderMapper.selectOne(
|
|
|
- new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<PackageOrder>()
|
|
|
- .eq("order_no", orderNo)
|
|
|
- );
|
|
|
-
|
|
|
- if (order != null) {
|
|
|
- order.setStatus("paid");
|
|
|
- order.setTransactionId(params.getString("transaction_id"));
|
|
|
- order.setPaidAt(new Date());
|
|
|
- order.setNotifyData(xmlData);
|
|
|
- order.setUpdatedAt(new Date());
|
|
|
- orderMapper.updateById(order);
|
|
|
-
|
|
|
- if (order.getUserCouponId() != null) {
|
|
|
- couponService.markUsed(order.getUserCouponId(), order.getId());
|
|
|
- }
|
|
|
-
|
|
|
- cfCommissionService.settle(order.getId(), "package", order.getUserId(),
|
|
|
- order.getFamilyId(), order.getPrice(), null);
|
|
|
-
|
|
|
- // 购买完成 → 推进旅程 stage=3(幂等)
|
|
|
- advanceStage(order.getUserId(), 3);
|
|
|
-
|
|
|
- log.info("订单支付成功: {}", orderNo);
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- return false;
|
|
|
- } catch (Exception e) {
|
|
|
- log.error("处理支付回调异常", e);
|
|
|
- return false;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 查询订单状态
|
|
|
- */
|
|
|
- public PackageOrder getOrderById(Long orderId) {
|
|
|
- return orderMapper.selectById(orderId);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 取消订单
|
|
|
- */
|
|
|
- public boolean cancelOrder(Long orderId) {
|
|
|
- PackageOrder order = new PackageOrder();
|
|
|
- order.setId(orderId);
|
|
|
- order.setStatus("cancelled");
|
|
|
- order.setUpdatedAt(new Date());
|
|
|
- return orderMapper.updateById(order) > 0;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 生成订单号
|
|
|
- */
|
|
|
- private String generateOrderNo() {
|
|
|
- return OrderNoGenerator.generate("PKG");
|
|
|
- }
|
|
|
-
|
|
|
- /** 推进用户旅程至指定阶段(幂等:只向前推进) */
|
|
|
- private void advanceStage(Long userId, int stage) {
|
|
|
- try {
|
|
|
- UserIntent intent = userIntentMapper.selectOne(
|
|
|
- new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<UserIntent>()
|
|
|
- .eq(UserIntent::getUserId, userId)
|
|
|
- .last("LIMIT 1"));
|
|
|
- if (intent != null && (intent.getJourneyStage() == null || intent.getJourneyStage() < stage)) {
|
|
|
- intent.setJourneyStage(stage);
|
|
|
- intent.setStageUpdatedAt(new Date());
|
|
|
- intent.setUpdatedAt(new Date());
|
|
|
- userIntentMapper.updateById(intent);
|
|
|
- }
|
|
|
- } catch (Exception e) {
|
|
|
- log.warn("推进旅程阶段失败 userId={}, stage={}: {}", userId, stage, e.getMessage());
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 生成签名
|
|
|
- */
|
|
|
- private String generateSign(JSONObject params, String mchKey) {
|
|
|
- StringBuilder sb = new StringBuilder();
|
|
|
- params.keySet().stream().sorted().forEach(key -> {
|
|
|
- if (params.getString(key) != null && !params.getString(key).isEmpty()) {
|
|
|
- sb.append(key).append("=").append(params.getString(key)).append("&");
|
|
|
- }
|
|
|
- });
|
|
|
- sb.append("key=").append(mchKey);
|
|
|
-
|
|
|
- // MD5加密并转大写
|
|
|
- try {
|
|
|
- java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
|
|
|
- byte[] digest = md.digest(sb.toString().getBytes("UTF-8"));
|
|
|
- StringBuilder hex = new StringBuilder();
|
|
|
- for (byte b : digest) {
|
|
|
- hex.append(String.format("%02x", b));
|
|
|
- }
|
|
|
- return hex.toString().toUpperCase();
|
|
|
- } catch (Exception e) {
|
|
|
- throw new RuntimeException("签名失败", e);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 生成前端支付签名
|
|
|
- */
|
|
|
- private String generatePaySign(JSONObject params, String mchKey) {
|
|
|
- StringBuilder sb = new StringBuilder();
|
|
|
- sb.append("appId=").append(appid).append("&");
|
|
|
- sb.append("nonceStr=").append(params.getString("nonce_str")).append("&");
|
|
|
- sb.append("package=prepay_id=").append(params.getString("prepay_id")).append("&");
|
|
|
- sb.append("signType=MD5&");
|
|
|
- sb.append("timeStamp=").append(System.currentTimeMillis() / 1000).append("&");
|
|
|
- sb.append("key=").append(mchKey);
|
|
|
-
|
|
|
- try {
|
|
|
- java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
|
|
|
- byte[] digest = md.digest(sb.toString().getBytes("UTF-8"));
|
|
|
- StringBuilder hex = new StringBuilder();
|
|
|
- for (byte b : digest) {
|
|
|
- hex.append(String.format("%02x", b));
|
|
|
- }
|
|
|
- return hex.toString().toUpperCase();
|
|
|
- } catch (Exception e) {
|
|
|
- throw new RuntimeException("签名失败", e);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 解析XML为JSON
|
|
|
- */
|
|
|
- private JSONObject parseXmlToJson(String xml) {
|
|
|
- JSONObject json = new JSONObject();
|
|
|
- // 简单XML解析
|
|
|
- xml = xml.replace("<", "{").replace(">", ":").replace("</", "}").replace("><", ",");
|
|
|
- // 这里简化处理,实际应该使用XML解析器
|
|
|
- return json;
|
|
|
- }
|
|
|
-}
|