shinnosuke
2026-07-31 82141e1b9e46a154418295aa95b34cb842c001c3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package com.talentcard.server.business.portal;
 
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.*;
import javax.imageio.ImageIO;
 
import com.google.zxing.BarcodeFormat;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.talentcard.server.business.DatabaseRows;
import com.talentcard.server.business.order.OrderRefundRequest;
import com.talentcard.server.business.order.OrderService;
import com.talentcard.server.common.BusinessException;
import com.talentcard.server.common.ErrorCode;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
@Service
public class PendingUsePortalService {
    private final JdbcTemplate jdbc; private final OrderService orderService;
    public PendingUsePortalService(JdbcTemplate jdbc, OrderService orderService) { this.jdbc = jdbc; this.orderService = orderService; }
    @Transactional(readOnly = true) public Map<String,Object> detail(String orderNo, Long talentId) { return view(find(orderNo, talentId)); }
    @Transactional(readOnly = true) public Map<String,Object> completedDetail(String orderNo, Long talentId) {
        Map<String,Object> result = detail(orderNo, talentId);
        if (!Boolean.TRUE.equals(result.get("verified"))) throw new BusinessException(ErrorCode.CONFLICT, "订单尚未完成核销");
        return result;
    }
    @Transactional(readOnly = true) public Map<String,Object> cancelledDetail(String orderNo, Long talentId) {
        Map<String,Object> result = detail(orderNo, talentId);
        if (number(result.get("orderStatus")) != 2) throw new BusinessException(ErrorCode.CONFLICT, "订单当前未取消");
        long refundStatus = number(result.get("refundStatus"));
        result.put("cancelTime", result.get("refundTime") == null ? result.get("updatedAt") : result.get("refundTime"));
        result.put("cancelSource", refundStatus == 0 ? "订单取消" : "退款处理");
        result.put("cancelReason", result.get("refundReason") == null ? "订单已取消" : result.get("refundReason"));
        result.put("refundStatusText", switch ((int) refundStatus) { case 1 -> "退款中"; case 2 -> "退款成功"; case 3 -> "退款失败"; default -> "无需退款"; });
        result.put("actualPaidAmount", refundStatus == 1 || refundStatus == 3 ? result.get("payAmount") : BigDecimal.ZERO);
        result.put("refundAmount", money(result.get("refundAmount")));
        return result;
    }
    @Transactional public Map<String,Object> issueCode(String orderNo, Long talentId) {
        Map<String,Object> order = find(orderNo, talentId); if (number(order.get("orderStatus")) != 1 || number(order.get("verifyStatus")) == 1 || "STORE_CONSUME".equals(String.valueOf(order.get("orderType")))) throw new BusinessException(ErrorCode.CONFLICT, "订单当前不可核销");
        jdbc.update("update order_dynamic_verification_code set status = 2 where order_id = ? and status = 0", order.get("id"));
        String token = UUID.randomUUID().toString().replace("-", ""); String display = String.format("%06d", new Random().nextInt(1_000_000)); Instant expires = Instant.now().plusSeconds(60);
        jdbc.update("insert into order_dynamic_verification_code(code_token, display_code, order_id, talent_id, expires_at, status) values (?, ?, ?, ?, ?, 0)", token, display, order.get("id"), talentId, Timestamp.from(expires));
        return Map.of("token", token, "displayCode", display, "imagePath", "/api/app/pending-use-orders/verification-codes/" + token + "/image", "expiresAt", expires.toEpochMilli(), "refreshAfterSeconds", 60);
    }
    @Transactional(readOnly = true) public byte[] codeImage(String token) {
        Integer count = jdbc.queryForObject("select count(*) from order_dynamic_verification_code where code_token = ? and status = 0 and expires_at > now()", Integer.class, token);
        if (count == null || count == 0) throw new BusinessException(ErrorCode.NOT_FOUND, "核销码已失效");
        try { BitMatrix matrix = new QRCodeWriter().encode(token, BarcodeFormat.QR_CODE, 360, 360); BufferedImage image = MatrixToImageWriter.toBufferedImage(matrix); ByteArrayOutputStream output = new ByteArrayOutputStream(); ImageIO.write(image, "PNG", output); return output.toByteArray(); } catch (Exception e) { throw new BusinessException(ErrorCode.INTERNAL_ERROR, "核销码生成失败"); }
    }
    @Transactional public Map<String,Object> refund(String orderNo, PendingUseRefundRequest request) {
        Map<String,Object> order = find(orderNo, request.talentId());
        if (number(order.get("orderStatus")) != 1 || (number(order.get("verifyStatus")) == 1 && !"STORE_CONSUME".equals(String.valueOf(order.get("orderType"))))) throw new BusinessException(ErrorCode.CONFLICT, "该订单不满足退款条件");
        Integer existing = jdbc.queryForObject("select count(*) from order_refund where deleted = 0 and order_id = ? and refund_status in (1,2)", Integer.class, order.get("id"));
        if (existing != null && existing > 0) throw new BusinessException(ErrorCode.CONFLICT, "订单已提交退款申请");
        return orderService.refund(orderNo, new OrderRefundRequest(BigDecimal.ZERO, request.reason() == null ? "用户申请退款" : request.reason(), "人才端用户"));
    }
    private Map<String,Object> find(String orderNo, Long talentId) {
        List<Map<String,Object>> rows = jdbc.queryForList("""
            select o.id,o.order_no,o.talent_id,o.order_type,o.order_amount,o.subsidy_amount,o.pay_amount,o.coupon_amount,o.order_status,o.verify_status,o.created_at,o.updated_at,
             o.merchant_name_snapshot,o.merchant_address_snapshot,o.merchant_mobile_snapshot,o.merchant_longitude_snapshot,o.merchant_latitude_snapshot,o.product_name_snapshot,o.product_cover_url_snapshot,
             case when o.verify_status = 1 then coalesce((select max(v.verify_time) from verify_record v where v.order_id=o.id), o.updated_at) else null end as verify_time,
             (select r.refund_status from order_refund r where r.order_id=o.id and r.deleted=0 order by r.id desc limit 1) as refund_status,
             (select r.reason from order_refund r where r.order_id=o.id and r.deleted=0 order by r.id desc limit 1) as refund_reason,
             (select r.refund_time from order_refund r where r.order_id=o.id and r.deleted=0 order by r.id desc limit 1) as refund_time,
             (select r.refund_amount from order_refund r where r.order_id=o.id and r.deleted=0 order by r.id desc limit 1) as refund_amount
            from benefit_order o where o.deleted=0 and o.order_no=? and o.talent_id=?
            """, orderNo,talentId).stream().map(DatabaseRows::normalize).toList();
        if(rows.isEmpty()) throw new BusinessException(ErrorCode.NOT_FOUND,"订单不存在或已删除"); return rows.get(0);
    }
    private Map<String,Object> view(Map<String,Object> o) { Map<String,Object> r=new LinkedHashMap<>(o); boolean verified=number(o.get("verifyStatus"))==1; boolean storeConsume="STORE_CONSUME".equals(String.valueOf(o.get("orderType"))); boolean refunded=o.get("refundStatus")!=null; r.put("verified",verified); r.put("canRefund",number(o.get("orderStatus"))==1&&(!verified||storeConsume)&&!refunded); r.put("originalAmount",money(o.get("orderAmount"))); r.put("payAmount",money(o.get("payAmount"))); r.put("discountAmount",money(Math.max(0,number(o.get("orderAmount"))-number(o.get("payAmount"))))); r.put("subsidyAmount",money(o.get("subsidyAmount"))); r.put("couponAmount",money(o.get("couponAmount"))); return r; }
    private long number(Object v){return v instanceof Number n?n.longValue():0;} private BigDecimal money(Object cents){return BigDecimal.valueOf(number(cents),2);}
}