where('id', $userCouponId)->where('user_id', (int)$userId)->find(); if (!$uc) { throw new \RuntimeException('优惠券不存在'); } if ((string)$uc['status'] !== 'unused') { throw new \RuntimeException('优惠券已使用或已失效'); } if (!empty($uc['expiretime']) && (int)$uc['expiretime'] < time()) { throw new \RuntimeException('优惠券已过期'); } $coupon = Db::name('fc_coupon')->where('id', (int)$uc['coupon_id'])->find(); if (!$coupon || (int)$coupon['enabled'] !== 1) { throw new \RuntimeException('优惠券已停用'); } $min = round((float)$coupon['min_amount'], 2); if ($min > 0 && $amount < $min) { throw new \RuntimeException('订单金额未满 ' . $min . ' 元,无法使用该券'); } $discount = self::calcDiscount($coupon, $amount); if ($discount <= 0) { throw new \RuntimeException('该优惠券不可用'); } return [ 'discount' => $discount, 'payable' => round($amount - $discount, 2), 'userCouponId' => $userCouponId, 'couponId' => (int)$coupon['id'], 'name' => (string)$coupon['name'], ]; } /** * 下单锁券(必须在创建订单的同一事务内调用) * * @param int $userId * @param int $userCouponId * @param float $amount * @param string $orderNo * @return array 同 preview() * @throws \RuntimeException */ public static function lock($userId, $userCouponId, $amount, $orderNo) { $info = self::preview($userId, $userCouponId, $amount); $affected = Db::name('fc_user_coupon') ->where('id', (int)$userCouponId) ->where('user_id', (int)$userId) ->where('status', 'unused') ->update([ 'status' => 'locked', 'order_no' => (string)$orderNo, ]); if (!$affected) { throw new \RuntimeException('优惠券已被使用,请重新选择'); } return $info; } /** * 支付成功核销(幂等:只处理 locked) * * @param string $orderNo * @return void */ public static function markUsed($orderNo) { if ((string)$orderNo === '') { return; } Db::name('fc_user_coupon') ->where('order_no', (string)$orderNo) ->where('status', 'locked') ->update([ 'status' => 'used', 'usetime' => time(), ]); } /** * 退回优惠券(订单关闭 / 退款) * * @param string $orderNo * @return void */ public static function release($orderNo) { if ((string)$orderNo === '') { return; } Db::name('fc_user_coupon') ->where('order_no', (string)$orderNo) ->where('status', 'in', ['locked', 'used']) ->update([ 'status' => 'unused', 'order_no' => '', 'usetime' => null, ]); } /** * 抵扣金额计算 * * @param array $coupon * @param float $amount * @return float */ protected static function calcDiscount(array $coupon, $amount) { $type = (string)$coupon['type']; $value = round((float)$coupon['amount'], 2); if ($type === 'discount') { // amount = 折扣数(8.5 表示 8.5 折),折扣率 = amount / 10 $rate = $value / 10; if ($rate <= 0 || $rate >= 1) { return 0; } $discount = round($amount * (1 - $rate), 2); } else { // 满减 $discount = $value; } // 抵扣后至少支付 0.01 元 $maxDiscount = round($amount - 0.01, 2); if ($discount > $maxDiscount) { $discount = max(0, $maxDiscount); } return round($discount, 2); } }