- 新增地址选择插件,支持地图位置选择并返回地址和经纬度 - 新增微信订阅消息发送功能,支持审核结果、提现通知等场景 - 新增订单支付状态查询、提现收款人姓名保存等接口 - 新增服务区域管理,支持按省市区划分并同步到信息发布 - 新增资金流水和营收报表导出功能 - 新增可用优惠券查询、订单退款拆分处理等功能 - 优化后台角标显示、首页数据统计、UI配置下发等 - 修复订单支付并发问题、地区管理逻辑等
196 行
6.8 KiB
PHP
196 行
6.8 KiB
PHP
<?php
|
||
|
||
namespace app\api\controller;
|
||
|
||
use think\Db;
|
||
|
||
/**
|
||
* 用户端 · 优惠券(Module-COUPON)
|
||
* 路由:
|
||
* GET /api/v1/coupon/list 可领取优惠券分页列表(带 received 已领取标记)
|
||
* POST /api/v1/coupon/receive 领取优惠券
|
||
*
|
||
* 数据表:fa_fc_coupon(券模板)/ fa_fc_user_coupon(领取记录)。
|
||
* 说明:本轮仅做「领取 + 我的资产展示」,**不含下单抵扣**(下单抵扣在后续版本接入)。
|
||
*/
|
||
class Coupon extends V1
|
||
{
|
||
/**
|
||
* 优惠券功能总开关(后台「系统设置」写入 fc_config 的 coupon.enabled,默认开启)
|
||
* 关闭后用户端列表/领取一律拒绝,避免「后台关了前端还能领」。
|
||
*/
|
||
protected function assertEnabled()
|
||
{
|
||
if (!(int)$this->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']),
|
||
];
|
||
}
|
||
}
|