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);}
|
}
|