cfg('coupon.enabled', 1)) { $this->fail('优惠券功能暂未开放'); } } /** * GET /api/v1/coupon/list {page,size} * → 分页 {list,page,size,total,hasMore} * 列表项:{id,couponId,name,type,amount,minAmount,total,receivedCount,left,days,received,createdAt} * 规则:仅 enabled=1(停用券不下发);received 表示「当前用户是否已领取」; * left = 剩余可领数量(-1 表示不限量);total=0 表示不限量。 */ public function list() { $this->assertEnabled(); $user = $this->auth(); list($rows, $total, $page, $size) = $this->pageQuery(function ($forCount) { $q = Db::name('fc_coupon')->where('enabled', 1); if ($forCount) { return $q; } return $q->order('sort desc, id desc'); }); $list = []; foreach ($rows as $row) { $received = (bool)Db::name('fc_user_coupon') ->where('user_id', $user['id']) ->where('coupon_id', $row['id']) ->find(); $list[] = $this->couponItem($row, $received); } $this->ok($this->paginate($list, $total, $page, $size)); } /** * POST /api/v1/coupon/receive {couponId} * → {userCouponId} * 规则:券必须存在且 enabled=1;领完 → fail('优惠券已领完'); * 同一用户同一张券只能领一次 → fail('您已领取过该优惠券'); * days>0 时按「领取时间 + days 天」写过期时间,days=0 表示永久有效。 */ public function receive() { $this->assertEnabled(); $user = $this->auth(); $couponId = (int)$this->input('couponId', 0); $coupon = $couponId > 0 ? Db::name('fc_coupon')->where('id', $couponId)->find() : null; if (!$coupon || (int)$coupon['enabled'] !== 1) { $this->fail('优惠券不存在'); } $total = (int)$coupon['total']; $receivedCount = (int)$coupon['received']; if ($total > 0 && $receivedCount >= $total) { $this->fail('优惠券已领完'); } $exist = Db::name('fc_user_coupon') ->where('user_id', $user['id']) ->where('coupon_id', $couponId) ->find(); if ($exist) { $this->fail('您已领取过该优惠券'); } $now = time(); $days = max(0, (int)$coupon['days']); try { $userCouponId = Db::name('fc_user_coupon')->insertGetId([ 'user_id' => $user['id'], 'coupon_id' => $couponId, 'status' => 'unused', 'order_no' => '', 'usetime' => null, 'expiretime' => $days > 0 ? $now + $days * 86400 : null, 'createtime' => $now, ]); } catch (\Exception $e) { // 并发重复领取兜底 $this->fail('您已领取过该优惠券'); } // 已领取数 +1(setInc 自增,避免并发覆盖) Db::name('fc_coupon')->where('id', $couponId)->setInc('received', 1); $this->ok(['userCouponId' => $userCouponId]); } /** * GET /api/v1/coupon/usable?amount=xx * 下单可用券列表(按金额计算可抵扣金额) */ public function usable() { $this->assertEnabled(); $user = $this->auth(); $amount = round((float)$this->input('amount', 0), 2); if ($amount <= 0) { $this->ok([]); } $now = time(); $rows = Db::name('fc_user_coupon')->alias('uc') ->join('fc_coupon c', 'c.id = uc.coupon_id') ->where('uc.user_id', $user['id']) ->where('uc.status', 'unused') ->where('c.enabled', 1) ->where('c.min_amount', '<=', $amount) ->where(function ($q) use ($now) { $q->whereNull('uc.expiretime')->whereOr('uc.expiretime', '>', $now); }) ->field('uc.id,uc.coupon_id,uc.expiretime,c.name,c.type,c.amount,c.min_amount') ->order('c.sort desc, c.id desc') ->select(); $list = []; foreach ($rows as $row) { try { $p = \app\common\library\CouponService::preview($user['id'], (int)$row['id'], $amount); } catch (\Throwable $e) { continue; } $list[] = [ 'userCouponId' => (int)$row['id'], 'couponId' => (int)$row['coupon_id'], 'name' => (string)$row['name'], 'type' => (string)$row['type'], 'amount' => (float)$row['amount'], 'minAmount' => (float)$row['min_amount'], 'discount' => $p['discount'], 'payable' => $p['payable'], 'expireAt' => !empty($row['expiretime']) ? $this->iso($row['expiretime']) : null, ]; } $this->ok($list); } /** * 券模板 → 列表项 * @param array $row fc_coupon 行 * @param bool $received 当前用户是否已领取 * @return array */ protected function couponItem($row, $received) { $total = (int)$row['total']; $receivedCount = (int)$row['received']; return [ 'id' => (int)$row['id'], 'couponId' => (int)$row['id'], 'name' => (string)$row['name'], 'type' => (string)$row['type'], 'amount' => (float)$row['amount'], 'minAmount' => (float)$row['min_amount'], 'total' => $total, 'receivedCount' => $receivedCount, // -1 表示不限量(total=0) 'left' => $total > 0 ? max(0, $total - $receivedCount) : -1, 'days' => (int)$row['days'], 'received' => (bool)$received, 'createdAt' => $this->iso($row['createtime']), ]; } }