feat: 新增地址选择插件及订阅消息等多项功能优化
- 新增地址选择插件,支持地图位置选择并返回地址和经纬度 - 新增微信订阅消息发送功能,支持审核结果、提现通知等场景 - 新增订单支付状态查询、提现收款人姓名保存等接口 - 新增服务区域管理,支持按省市区划分并同步到信息发布 - 新增资金流水和营收报表导出功能 - 新增可用优惠券查询、订单退款拆分处理等功能 - 优化后台角标显示、首页数据统计、UI配置下发等 - 修复订单支付并发问题、地区管理逻辑等
这个提交包含在:
@@ -4,6 +4,7 @@ namespace app\api\controller;
|
||||
|
||||
use think\Db;
|
||||
use app\common\library\DistributionService;
|
||||
use app\common\library\CouponService;
|
||||
|
||||
/**
|
||||
* 用户端 · 信息发布与详情(Module-POST)
|
||||
@@ -113,11 +114,13 @@ class Post extends V1
|
||||
/**
|
||||
* P1 发布信息
|
||||
* POST /api/v1/post/create
|
||||
* 入参 {categoryId,title,content,media[],contact,phone,address,lat?,lng?,plan,payType}
|
||||
* 入参 {categoryId,title,content,media[],contact,phone,address,areaId?,districtId?,area?,lat?,lng?,plan,payType}
|
||||
* → {postId,auditStatus,order,plan,amount,discountRate,quotaLeft,freeQuotaLeft}
|
||||
* 规则 BR-03/BR-04/BR-02/BR-13(计费顺序:套餐额度 → 免费发布条数 → 单条付费)
|
||||
* 说明:
|
||||
* - `media` 接收**上传后的 URL 数组**(先调 POST /post/upload 拿 url,再随发布提交);
|
||||
* - 服务区域为**区县**:优先取 `areaId`(=fc_area.id),兼容 `districtId` / `area` 名称;
|
||||
* 都没传但带坐标时,按最近服务区域自动归属;均无则允许为空(兼容旧客户端);
|
||||
* - `lat`/`lng` 可选(gcj02),由前端 chooseLocation 采集,用于首页「附近」排序;
|
||||
* - 单条付费金额 = round(单条价格 × 会员折扣率, 2),折扣率取自 member.benefit.postDiscount
|
||||
* (会员权益,非会员/解析失败视作 1);金额一律服务端计算,忽略客户端传值。
|
||||
@@ -186,10 +189,6 @@ class Post extends V1
|
||||
if ($amount > 0 && !in_array($payType, ['wxpay', 'balance'], true)) {
|
||||
$this->fail('支付方式不支持');
|
||||
}
|
||||
// 余额校验须在落库之前:否则余额不足时会残留一条 is_paid=1 的无效发布
|
||||
if ($amount > 0 && $payType === 'balance' && (float)$user['money'] < $amount) {
|
||||
$this->fail('余额不足');
|
||||
}
|
||||
} elseif ($quotaLeft > 0) {
|
||||
$useQuota = true; // ① 套餐额度(开通月/年卡赠送,随会员有效期)
|
||||
} elseif ($free['unlimited'] || $free['left'] > 0) {
|
||||
@@ -204,87 +203,160 @@ class Post extends V1
|
||||
}
|
||||
$paid = $amount > 0;
|
||||
|
||||
$now = time();
|
||||
$postId = Db::name('fc_post')->insertGetId([
|
||||
'user_id' => $user['id'],
|
||||
'category_id' => $categoryId,
|
||||
'title' => $title,
|
||||
'content' => $content,
|
||||
'cover' => '',
|
||||
'media' => json_encode($this->mediaArray(), JSON_UNESCAPED_UNICODE),
|
||||
'price' => round((float)$this->input('price', 0), 2),
|
||||
'price_unit' => (string)$this->input('priceUnit', ''),
|
||||
'area' => (string)$this->input('area', ''),
|
||||
'address' => (string)$this->input('address', ''),
|
||||
'contact' => (string)$this->input('contact', ''),
|
||||
'phone' => $phone,
|
||||
'wechat' => '',
|
||||
'tags' => $this->tagsString(),
|
||||
// LBS 坐标(gcj02;未定位/为 0 时存 NULL,首页「附近」不参与距离排序)
|
||||
'lat' => $this->coord('lat', 90),
|
||||
'lng' => $this->coord('lng', 180),
|
||||
'is_paid' => $paid ? 1 : 0,
|
||||
'amount' => $paid ? $amount : 0,
|
||||
'pay_type' => $paid ? $payType : '',
|
||||
'audit_status' => 0,
|
||||
'views' => 0,
|
||||
'likes' => 0,
|
||||
'comment_count' => 0,
|
||||
'status' => 1,
|
||||
'createtime' => $now,
|
||||
'updatetime' => $now,
|
||||
]);
|
||||
|
||||
$order = null;
|
||||
if ($paid) {
|
||||
// 余额支付为「即时支付」:下单即扣款、订单直接置为已支付;
|
||||
// 微信支付需要前端调起收银台,保持 pending 由支付回调置为 paid。
|
||||
$paidNow = $payType === 'balance';
|
||||
$orderNo = $this->orderNo('PO');
|
||||
Db::name('fc_order')->insert([
|
||||
'order_no' => $orderNo,
|
||||
'user_id' => $user['id'],
|
||||
'type' => 'post',
|
||||
'title' => $title,
|
||||
'amount' => $amount,
|
||||
'pay_type' => $payType,
|
||||
'status' => $paidNow ? 'paid' : 'pending',
|
||||
'paytime' => $paidNow ? $now : null,
|
||||
'related_id' => $postId,
|
||||
'createtime' => $now,
|
||||
'updatetime' => $now,
|
||||
]);
|
||||
|
||||
if ($paidNow) {
|
||||
$balance = round((float)$user['money'] - $amount, 2);
|
||||
Db::name('user')->where('id', $user['id'])->update([
|
||||
'money' => $balance,
|
||||
'updatetime' => $now,
|
||||
]);
|
||||
$this->flow('income', '发布付费', $amount, $balance, $postId);
|
||||
|
||||
// 余额支付发布付费:触发分销结算
|
||||
$orderRow = Db::name('fc_order')->where('order_no', $orderNo)->find();
|
||||
if ($orderRow) {
|
||||
DistributionService::settleOrder($orderRow);
|
||||
// 优惠券(仅单条付费发布可用)
|
||||
$originAmount = $amount;
|
||||
$couponAmount = 0;
|
||||
$userCouponId = 0;
|
||||
if ($paid && $plan === 'single') {
|
||||
$userCouponId = (int)$this->input('userCouponId', 0);
|
||||
if ($userCouponId > 0) {
|
||||
try {
|
||||
$couponInfo = CouponService::preview($user['id'], $userCouponId, $originAmount);
|
||||
} catch (\Throwable $e) {
|
||||
$this->fail($e->getMessage());
|
||||
}
|
||||
$couponAmount = $couponInfo['discount'];
|
||||
$amount = round($originAmount - $couponAmount, 2);
|
||||
}
|
||||
|
||||
$order = [
|
||||
'orderNo' => $orderNo,
|
||||
'amount' => $amount,
|
||||
'payType' => $payType,
|
||||
'status' => $paidNow ? 'paid' : 'pending',
|
||||
];
|
||||
}
|
||||
|
||||
// 套餐额度内免费发布:落库成功后扣减 1 条额度(先发布后扣,发布失败不白扣)
|
||||
if ($useQuota) {
|
||||
$quotaLeft = max(0, $quotaLeft - 1);
|
||||
Db::name('fc_user_ext')->where('user_id', $user['id'])->update([
|
||||
'post_quota_left' => $quotaLeft,
|
||||
'updatetime' => $now,
|
||||
]);
|
||||
// 余额校验须在落库之前(按抵扣后的实付金额判断)
|
||||
if ($paid && $payType === 'balance' && (float)$user['money'] < $amount) {
|
||||
$this->fail('余额不足');
|
||||
}
|
||||
|
||||
// 区域:优先 districtId,其次 area 名称,最后按坐标匹配最近区县
|
||||
$region = $this->resolveRegion();
|
||||
|
||||
// 封面:显式传 cover 优先;否则取媒体里的第一张图片
|
||||
$cover = trim((string)$this->input('cover', ''));
|
||||
if ($cover === '') {
|
||||
foreach ($this->mediaArray() as $m) {
|
||||
if (!preg_match('/\.(mp4|mov|m4v|avi|wmv|flv|m3u8)(\?|$)/i', $m)) {
|
||||
$cover = $m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($cover !== '' && !preg_match('#^(/uploads/|https?://)#i', $cover)) {
|
||||
$this->fail('封面地址不合法');
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$postId = 0;
|
||||
$order = null;
|
||||
$balanceAfter = 0;
|
||||
|
||||
try {
|
||||
// 发布落库 + 扣款 + 订单 + 额度扣减 同一事务:任一步失败整体回滚
|
||||
Db::transaction(function () use (
|
||||
&$postId, &$order, &$balanceAfter, &$quotaLeft,
|
||||
$paid, $user, $amount, $originAmount, $couponAmount, $userCouponId, $payType,
|
||||
$now, $categoryId, $title, $content, $phone, $useQuota, $region, $cover
|
||||
) {
|
||||
$paidNow = $paid && $payType === 'balance';
|
||||
// 订单号提前生成:优惠券锁定需要绑定订单号
|
||||
$orderNo = $paid ? $this->orderNo('PO') : '';
|
||||
|
||||
if ($userCouponId > 0) {
|
||||
CouponService::lock($user['id'], $userCouponId, $originAmount, $orderNo);
|
||||
}
|
||||
|
||||
// 余额扣减:条件更新,并发下不会扣成负数
|
||||
if ($paidNow) {
|
||||
$affected = Db::name('user')
|
||||
->where('id', (int)$user['id'])
|
||||
->where('money', '>=', $amount)
|
||||
->setDec('money', $amount);
|
||||
if (!$affected) {
|
||||
throw new \RuntimeException('余额不足');
|
||||
}
|
||||
$balanceAfter = (float)Db::name('user')->where('id', (int)$user['id'])->value('money');
|
||||
}
|
||||
|
||||
$postId = Db::name('fc_post')->insertGetId([
|
||||
'user_id' => $user['id'],
|
||||
'category_id' => $categoryId,
|
||||
'title' => $title,
|
||||
'content' => $content,
|
||||
'cover' => $cover,
|
||||
'media' => json_encode($this->mediaArray(), JSON_UNESCAPED_UNICODE),
|
||||
'price' => round((float)$this->input('price', 0), 2),
|
||||
'price_unit' => (string)$this->input('priceUnit', ''),
|
||||
'area' => $region['area'],
|
||||
'district_id' => $region['district_id'],
|
||||
'address' => (string)$this->input('address', ''),
|
||||
'contact' => (string)$this->input('contact', ''),
|
||||
'phone' => $phone,
|
||||
'wechat' => '',
|
||||
'tags' => $this->tagsString(),
|
||||
// LBS 坐标(gcj02;未定位/为 0 时存 NULL,首页「附近」不参与距离排序)
|
||||
'lat' => $this->coord('lat', 90),
|
||||
'lng' => $this->coord('lng', 180),
|
||||
'is_paid' => $paid ? 1 : 0,
|
||||
'amount' => $paid ? $amount : 0,
|
||||
'pay_type' => $paid ? $payType : '',
|
||||
'audit_status' => 0,
|
||||
'views' => 0,
|
||||
'likes' => 0,
|
||||
'comment_count' => 0,
|
||||
'status' => 1,
|
||||
'createtime' => $now,
|
||||
'updatetime' => $now,
|
||||
]);
|
||||
|
||||
if ($paid) {
|
||||
// 余额支付为「即时支付」:扣款 + 订单直接 paid;
|
||||
// 微信支付保持 pending,由支付回调置为 paid 并发放权益。
|
||||
Db::name('fc_order')->insert([
|
||||
'order_no' => $orderNo,
|
||||
'user_id' => $user['id'],
|
||||
'type' => 'post',
|
||||
'title' => $title,
|
||||
'amount' => $amount,
|
||||
'origin_amount' => $originAmount,
|
||||
'coupon_amount' => $couponAmount,
|
||||
'coupon_id' => $userCouponId > 0 ? $userCouponId : 0,
|
||||
'pay_type' => $payType,
|
||||
'status' => $paidNow ? 'paid' : 'pending',
|
||||
'paytime' => $paidNow ? $now : null,
|
||||
'related_id' => $postId,
|
||||
'createtime' => $now,
|
||||
'updatetime' => $now,
|
||||
]);
|
||||
|
||||
if ($paidNow) {
|
||||
$this->flow('income', '发布付费', $amount, $balanceAfter, $postId);
|
||||
|
||||
// 余额支付:核销优惠券 + 触发分销结算
|
||||
CouponService::markUsed($orderNo);
|
||||
$orderRow = Db::name('fc_order')->where('order_no', $orderNo)->find();
|
||||
if ($orderRow) {
|
||||
DistributionService::settleOrder($orderRow);
|
||||
}
|
||||
}
|
||||
|
||||
$order = [
|
||||
'orderNo' => $orderNo,
|
||||
'amount' => $amount,
|
||||
'originAmount' => $originAmount,
|
||||
'couponAmount' => $couponAmount,
|
||||
'payType' => $payType,
|
||||
'status' => $paidNow ? 'paid' : 'pending',
|
||||
];
|
||||
}
|
||||
|
||||
// 套餐额度内免费发布:落库成功后扣减 1 条额度(先发布后扣,发布失败不白扣)
|
||||
if ($useQuota) {
|
||||
$quotaLeft = max(0, $quotaLeft - 1);
|
||||
Db::name('fc_user_ext')->where('user_id', $user['id'])->update([
|
||||
'post_quota_left' => $quotaLeft,
|
||||
'updatetime' => $now,
|
||||
]);
|
||||
}
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
$this->fail($e->getMessage() ?: '发布失败,请重试');
|
||||
}
|
||||
|
||||
$this->ok([
|
||||
@@ -293,6 +365,8 @@ class Post extends V1
|
||||
'order' => $order,
|
||||
'plan' => $useQuota ? 'quota' : ($useFreeQuota ? 'free' : ($paid ? 'single' : 'free')),
|
||||
'amount' => $amount,
|
||||
'originAmount' => $originAmount,
|
||||
'couponAmount' => $couponAmount,
|
||||
// 本次生效的会员发布折扣率(非会员/未配置为 1)
|
||||
'discountRate' => $discountRate,
|
||||
'quotaLeft' => $quotaLeft,
|
||||
@@ -349,6 +423,7 @@ class Post extends V1
|
||||
'price' => (float)$row['price'],
|
||||
'priceUnit' => (string)$row['price_unit'],
|
||||
'area' => (string)$row['area'],
|
||||
'areaId' => (int)$row['district_id'],
|
||||
'address' => (string)$row['address'],
|
||||
'auditStatus' => (int)$row['audit_status'],
|
||||
'auditReason' => (string)$row['audit_reason'],
|
||||
@@ -562,6 +637,8 @@ class Post extends V1
|
||||
'contact' => (string)$row['contact'],
|
||||
'phone' => (string)$row['phone'],
|
||||
'address' => (string)$row['address'],
|
||||
'area' => (string)$row['area'],
|
||||
'areaId' => (int)$row['district_id'],
|
||||
'savedAt' => $this->iso($row['savedat']),
|
||||
];
|
||||
}
|
||||
@@ -611,6 +688,8 @@ class Post extends V1
|
||||
'contact' => (string)$this->input('contact', ''),
|
||||
'phone' => (string)$this->input('phone', ''),
|
||||
'address' => (string)$this->input('address', ''),
|
||||
'area' => (string)$this->input('area', ''),
|
||||
'district_id' => (int)$this->input('areaId', (int)$this->input('districtId', 0)),
|
||||
'savedat' => $now,
|
||||
'updatetime' => $now,
|
||||
];
|
||||
@@ -683,10 +762,112 @@ class Post extends V1
|
||||
$this->fail('信息不存在', 404);
|
||||
}
|
||||
|
||||
Db::name('fc_post')->where('id', $id)->setInc('views');
|
||||
$post['views'] = (int)$post['views'] + 1;
|
||||
// accessOnly=1:仅回读解锁态(如从会员中心返回时刷新),不计浏览量
|
||||
if (!$this->input('accessOnly', false)) {
|
||||
Db::name('fc_post')->where('id', $id)->setInc('views');
|
||||
$post['views'] = (int)$post['views'] + 1;
|
||||
}
|
||||
|
||||
$this->ok($this->buildPost($post));
|
||||
$this->ok($this->contactAccess($this->buildPost($post), $post));
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情页联系方式可见性(避免「已开通会员仍显示已受保护」)
|
||||
*
|
||||
* 可见规则(与 payView 计费口径一致):
|
||||
* ① 会员有效期内 → 免费看
|
||||
* ② 已付费解锁 fc_pay_view → 看(余额 / 微信回调写入)
|
||||
* ③ 本人发布的信息 → 看
|
||||
* 其它(游客 / 非会员未付费)→ 仍返回脱敏串
|
||||
*
|
||||
* 出参新增:unlocked(bool)、wechat(明文,未解锁为空串)。
|
||||
* 注意:只加在详情接口,列表(首页/收藏等)仍保持脱敏,避免手机号被批量抓取。
|
||||
*
|
||||
* @param array $data buildPost() 结果
|
||||
* @param array $post fc_post 行
|
||||
* @return array
|
||||
*/
|
||||
protected function contactAccess($data, $post)
|
||||
{
|
||||
$data['unlocked'] = false;
|
||||
$data['wechat'] = '';
|
||||
|
||||
$me = $this->user();
|
||||
if (!$me) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$ext = $this->ext($me['id']);
|
||||
$isMember = (int)$ext['is_member'] === 1
|
||||
&& (empty($ext['member_expiretime']) || (int)$ext['member_expiretime'] > $now);
|
||||
$isOwner = (int)$post['user_id'] === (int)$me['id'];
|
||||
$paid = (bool)Db::name('fc_pay_view')
|
||||
->where('user_id', $me['id'])
|
||||
->where('post_id', $post['id'])
|
||||
->find();
|
||||
|
||||
// 会员每日免费查看剩余次数(只做展示;解锁统一走 payView,按额度/付费计费)
|
||||
$quota = $this->memberFreeViewQuota();
|
||||
$today = date('Y-m-d');
|
||||
$used = ((string)$ext['free_view_date'] === $today) ? (int)$ext['free_view_used'] : 0;
|
||||
$data['freeViewLeft'] = $isMember ? max(0, $quota - $used) : 0;
|
||||
|
||||
if ($isOwner || $paid) {
|
||||
$data['unlocked'] = true;
|
||||
$data['contact'] = (string)$post['phone'];
|
||||
$data['wechat'] = $post['wechat'] !== '' ? (string)$post['wechat'] : ('tongcheng_' . $post['id']);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 会员每日免费查看条数(member.benefit.freeView,默认 20)
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function memberFreeViewQuota()
|
||||
{
|
||||
$cfg = $this->cfg('member.benefit', []);
|
||||
if (is_array($cfg) && isset($cfg['freeView'])) {
|
||||
return max(0, (int)$cfg['freeView']);
|
||||
}
|
||||
|
||||
return 20;
|
||||
}
|
||||
|
||||
/**
|
||||
* 消耗一次会员免费查看额度(跨天自动重置;需在事务内调用)
|
||||
*
|
||||
* @param int $userId
|
||||
* @param int $quota
|
||||
* @return bool 是否成功占用
|
||||
*/
|
||||
protected function consumeFreeView($userId, $quota)
|
||||
{
|
||||
$today = date('Y-m-d');
|
||||
$ext = Db::name('fc_user_ext')->where('user_id', (int)$userId)->lock(true)->find();
|
||||
if (!$ext) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((string)$ext['free_view_date'] !== $today) {
|
||||
Db::name('fc_user_ext')->where('user_id', (int)$userId)->update([
|
||||
'free_view_date' => $today,
|
||||
'free_view_used' => 1,
|
||||
'updatetime' => time(),
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((int)$ext['free_view_used'] >= $quota) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Db::name('fc_user_ext')->where('user_id', (int)$userId)->setInc('free_view_used', 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -733,59 +914,72 @@ class Post extends V1
|
||||
$ext = $this->ext($user['id']);
|
||||
$isMember = (int)$ext['is_member'] === 1
|
||||
&& (!$ext['member_expiretime'] || $ext['member_expiretime'] > $now);
|
||||
$quota = $this->memberFreeViewQuota();
|
||||
|
||||
$orderId = 0;
|
||||
if ($isMember) {
|
||||
// 会员免费解锁
|
||||
$amount = 0;
|
||||
$payType = 'free';
|
||||
} elseif ($payType === 'balance') {
|
||||
$balance = (float)$user['money'];
|
||||
if ($balance < $amount) {
|
||||
$this->fail('余额不足');
|
||||
}
|
||||
$balance = round($balance - $amount, 2);
|
||||
Db::name('user')->where('id', $user['id'])->update([
|
||||
'money' => $balance,
|
||||
'updatetime' => $now,
|
||||
]);
|
||||
$this->flow('expense', '付费查看', $amount, $balance, $id);
|
||||
} else {
|
||||
// 微信支付:只落 pending 订单(不预写解锁记录),
|
||||
// 支付成功后由 /pay/notify 按 type=payview + related_id 解锁(回调可独立完成)
|
||||
$orderNo = $this->orderNo('VO');
|
||||
Db::name('fc_order')->insert([
|
||||
'order_no' => $orderNo,
|
||||
'user_id' => $user['id'],
|
||||
'type' => 'payview',
|
||||
'title' => '付费查看 #' . $id,
|
||||
'amount' => $amount,
|
||||
'pay_type' => 'wxpay',
|
||||
'status' => 'pending',
|
||||
'paytime' => null,
|
||||
'related_id' => $id,
|
||||
'createtime' => $now,
|
||||
'updatetime' => $now,
|
||||
]);
|
||||
$pending = null;
|
||||
|
||||
$this->ok([
|
||||
'order' => [
|
||||
'orderNo' => $orderNo,
|
||||
'amount' => $amount,
|
||||
'payType' => 'wxpay',
|
||||
'status' => 'pending',
|
||||
],
|
||||
]);
|
||||
try {
|
||||
Db::transaction(function () use (&$orderId, &$pending, $isMember, $quota, $payType, $amount, $user, $id, $now) {
|
||||
// 会员每日免费查看额度(当天用完后按正常付费)
|
||||
$usedFree = ($isMember && $quota > 0) ? $this->consumeFreeView((int)$user['id'], $quota) : false;
|
||||
|
||||
if ($usedFree) {
|
||||
$amount = 0;
|
||||
$payType = 'free';
|
||||
} elseif ($payType === 'balance') {
|
||||
// 条件扣款:并发下不会扣成负数
|
||||
$affected = Db::name('user')
|
||||
->where('id', (int)$user['id'])
|
||||
->where('money', '>=', $amount)
|
||||
->setDec('money', $amount);
|
||||
if (!$affected) {
|
||||
throw new \RuntimeException('余额不足');
|
||||
}
|
||||
$balance = (float)Db::name('user')->where('id', (int)$user['id'])->value('money');
|
||||
$this->flow('expense', '付费查看', $amount, $balance, $id);
|
||||
} else {
|
||||
// 微信支付:只落 pending 订单(不预写解锁记录),
|
||||
// 支付成功后由 /pay/notify 按 type=payview + related_id 解锁(回调可独立完成)
|
||||
$orderNo = $this->orderNo('VO');
|
||||
Db::name('fc_order')->insert([
|
||||
'order_no' => $orderNo,
|
||||
'user_id' => $user['id'],
|
||||
'type' => 'payview',
|
||||
'title' => '付费查看 #' . $id,
|
||||
'amount' => $amount,
|
||||
'pay_type' => 'wxpay',
|
||||
'status' => 'pending',
|
||||
'paytime' => null,
|
||||
'related_id' => $id,
|
||||
'createtime' => $now,
|
||||
'updatetime' => $now,
|
||||
]);
|
||||
$pending = [
|
||||
'orderNo' => $orderNo,
|
||||
'amount' => $amount,
|
||||
'payType' => 'wxpay',
|
||||
'status' => 'pending',
|
||||
];
|
||||
return;
|
||||
}
|
||||
|
||||
Db::name('fc_pay_view')->insert([
|
||||
'user_id' => $user['id'],
|
||||
'post_id' => $id,
|
||||
'pay_type' => $payType,
|
||||
'amount' => $amount,
|
||||
'order_id' => $orderId,
|
||||
'createtime' => $now,
|
||||
]);
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
$this->fail($e->getMessage() ?: '解锁失败,请重试');
|
||||
}
|
||||
|
||||
Db::name('fc_pay_view')->insert([
|
||||
'user_id' => $user['id'],
|
||||
'post_id' => $id,
|
||||
'pay_type' => $payType,
|
||||
'amount' => $amount,
|
||||
'order_id' => $orderId,
|
||||
'createtime' => $now,
|
||||
]);
|
||||
if ($pending !== null) {
|
||||
$this->ok(['order' => $pending]);
|
||||
}
|
||||
|
||||
$this->ok(['unlocked' => true, 'contact' => $contact]);
|
||||
}
|
||||
@@ -936,6 +1130,66 @@ class Post extends V1
|
||||
return $val;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析发布信息的服务区域(区县)
|
||||
* 优先级:areaId(兼容 districtId)> area 名称 > 经纬度最近区域(300km 内)。
|
||||
* 未提供任何区域时返回空区域,保持与旧客户端兼容。
|
||||
* @return array{area:string,district_id:int}
|
||||
*/
|
||||
protected function resolveRegion()
|
||||
{
|
||||
$areaId = (int)$this->input('areaId', 0);
|
||||
if (!$areaId) {
|
||||
$areaId = (int)$this->input('districtId', 0);
|
||||
}
|
||||
$areaName = trim((string)$this->input('area', ''));
|
||||
$area = null;
|
||||
|
||||
if ($areaId > 0) {
|
||||
$area = Db::name('fc_area')
|
||||
->where('id', $areaId)
|
||||
->where('enabled', 1)
|
||||
->find();
|
||||
if (!$area) {
|
||||
$this->fail('所选服务区域不可用,请重新选择');
|
||||
}
|
||||
} elseif ($areaName !== '') {
|
||||
$area = Db::name('fc_area')
|
||||
->where('district', $areaName)
|
||||
->where('enabled', 1)
|
||||
->find();
|
||||
} else {
|
||||
$lat = $this->coord('lat', 90);
|
||||
$lng = $this->coord('lng', 180);
|
||||
if ($lat !== null && $lng !== null) {
|
||||
$rows = Db::name('fc_area')
|
||||
->where('enabled', 1)
|
||||
->whereNotNull('lat')
|
||||
->whereNotNull('lng')
|
||||
->select();
|
||||
$best = null;
|
||||
foreach ($rows as $row) {
|
||||
$distance = $this->haversine($lat, $lng, (float)$row['lat'], (float)$row['lng']);
|
||||
if ($best === null || $distance < $best['distance']) {
|
||||
$best = ['row' => $row, 'distance' => $distance];
|
||||
}
|
||||
}
|
||||
if ($best && $best['distance'] <= 300) {
|
||||
$area = $best['row'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$area) {
|
||||
return ['area' => $areaName, 'district_id' => 0];
|
||||
}
|
||||
|
||||
return [
|
||||
'area' => (string)$area['district'],
|
||||
'district_id' => (int)$area['id'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 会员发布折扣率
|
||||
* 取自 member.benefit.postDiscount(如 '8 折'→0.8、'9.5折'→0.95、'八折' 视为解析失败→1);
|
||||
@@ -1123,6 +1377,7 @@ class Post extends V1
|
||||
'price' => (float)$row['price'],
|
||||
'priceUnit' => (string)$row['price_unit'],
|
||||
'area' => (string)$row['area'],
|
||||
'areaId' => (int)$row['district_id'],
|
||||
'address' => (string)$row['address'],
|
||||
'contact' => $this->maskPhone($row['phone']),
|
||||
'views' => (int)$row['views'],
|
||||
|
||||
在新工单中引用
屏蔽一个用户