| | |
| | | /** Cancels every unpaid order that has reached its payment deadline. */ |
| | | @Transactional |
| | | public int expireDueOrders() { |
| | | return jdbcTemplate.update(""" |
| | | return expireDueOrders(null); |
| | | } |
| | | |
| | | /** |
| | | * Cancels expired unpaid orders for one talent user. Besides the scheduled |
| | | * cleanup this is used on the purchase path, so an expired order can never |
| | | * continue to occupy that user's coupon while the scheduler is waiting for |
| | | * its next run. |
| | | */ |
| | | @Transactional |
| | | public int expireDueOrders(Long talentId) { |
| | | String sql = """ |
| | | update benefit_order |
| | | set order_status = 2 |
| | | where deleted = 0 and order_status = 0 |
| | | and expires_at is not null and expires_at <= current_timestamp |
| | | """); |
| | | """; |
| | | if (talentId == null) return jdbcTemplate.update(sql); |
| | | return jdbcTemplate.update(sql + " and talent_id = ?", talentId); |
| | | } |
| | | |
| | | @Transactional |
| | |
| | | select o.order_no, o.talent_id, o.merchant_id, o.order_type, o.product_id, o.order_amount, o.subsidy_amount, o.base_pay_amount, |
| | | o.pay_amount, o.coupon_amount, o.coupon_id, o.order_status, o.verify_status, o.expires_at, o.price_version, |
| | | o.merchant_name_snapshot, o.merchant_address_snapshot, o.merchant_mobile_snapshot, |
| | | o.merchant_longitude_snapshot, o.merchant_latitude_snapshot, o.product_name_snapshot, |
| | | c.coupon_name |
| | | o.merchant_longitude_snapshot, o.merchant_latitude_snapshot, o.product_name_snapshot, |
| | | c.coupon_name, c.coupon_amount as configured_coupon_amount, p.original_price as product_original_price |
| | | from benefit_order o left join merchant_coupon c on c.id = o.coupon_id |
| | | left join merchant_product p on p.id = o.product_id and p.deleted = 0 |
| | | where o.deleted = 0 and o.order_no = ? and o.talent_id = ? |
| | | """, orderNo, talentId); |
| | | if (rows.isEmpty()) throw new BusinessException(ErrorCode.NOT_FOUND, "订单不存在或已删除"); |
| | |
| | | && !"GROUP_BUY".equals(String.valueOf(order.get("orderType"))) |
| | | && orderStatus == 0 && !expired; |
| | | result.put("couponSelectable", couponSelectable); |
| | | result.put("couponLabel", order.get("couponName") == null ? "无" : "减" + yuan(order.get("couponAmount")).stripTrailingZeros().toPlainString() + "元"); |
| | | long couponAmount = longNumber(order.get("couponAmount")); |
| | | if (couponAmount <= 0 && order.get("couponId") != null) { |
| | | couponAmount = Math.min( |
| | | MoneyUtils.yuanToCent(decimal(order.get("configuredCouponAmount"))), |
| | | longNumber(order.get("basePayAmount"))); |
| | | } |
| | | result.put("couponLabel", order.get("couponName") == null ? "无" : "减" + yuan(couponAmount).stripTrailingZeros().toPlainString() + "元"); |
| | | result.put("talentDiscountText", talentDiscountText(order)); |
| | | result.put("originalAmount", yuan(order.get("orderAmount"))); |
| | | long originalAmount = longNumber(order.get("orderAmount")); |
| | | if (originalAmount <= 0) { |
| | | BigDecimal productOriginalPrice = decimal(order.get("productOriginalPrice")); |
| | | originalAmount = productOriginalPrice.signum() > 0 |
| | | ? MoneyUtils.yuanToCent(productOriginalPrice) |
| | | : longNumber(order.get("basePayAmount")); |
| | | } |
| | | if (originalAmount <= 0) { |
| | | // Historical orders may have missed both price snapshots. Their selected |
| | | // coupon and payable amount still represent the amount before coupon use. |
| | | originalAmount = Math.max(0, longNumber(order.get("payAmount")) + couponAmount); |
| | | } |
| | | result.put("originalAmount", yuan(originalAmount)); |
| | | // “优惠金额”在待支付页仅表示优惠券抵扣,不应把人才折扣或补贴混入其中。 |
| | | result.put("discountAmount", yuan(order.get("couponAmount"))); |
| | | result.put("discountAmount", yuan(couponAmount)); |
| | | result.put("payAmount", yuan(order.get("payAmount"))); |
| | | // 待支付页的补贴金额展示人才折扣与优惠券抵扣合计,即原价减去用户实付。 |
| | | result.put("subsidyAmount", yuan(Math.max(0, longNumber(order.get("orderAmount")) - longNumber(order.get("payAmount"))))); |
| | | result.put("subsidyAmount", yuan(Math.max(0, originalAmount - longNumber(order.get("payAmount"))))); |
| | | return result; |
| | | } |
| | | |
| | | private List<Map<String, Object>> query(String sql, Object... args) { return jdbcTemplate.queryForList(sql, args).stream().map(DatabaseRows::normalize).toList(); } |
| | | private BigDecimal decimal(Object value) { |
| | | if (value == null) return BigDecimal.ZERO; |
| | | if (value instanceof BigDecimal decimal) return decimal; |
| | | try { return new BigDecimal(String.valueOf(value)); } |
| | | catch (Exception ignored) { return BigDecimal.ZERO; } |
| | | } |
| | | private String activeCardLevel(Long talentId) { |
| | | List<Map<String, Object>> cards = query("select card_level from talent_card where talent_id=? and deleted=0 and status=1 and card_type='PERSONAL' and (valid_end is null or valid_end >= current_date) order by id desc limit 1", talentId); |
| | | if (cards.isEmpty()) throw new BusinessException(ErrorCode.CONFLICT, "当前暂无有效人才卡"); |