会员卡
parent
0e395eb986
commit
16b04e024b
|
|
@ -18,6 +18,9 @@ class UserVipController extends AdminController
|
||||||
protected function grid()
|
protected function grid()
|
||||||
{
|
{
|
||||||
return Grid::make(UserVip::with(['user', 'vip']), function (Grid $grid) {
|
return Grid::make(UserVip::with(['user', 'vip']), function (Grid $grid) {
|
||||||
|
|
||||||
|
$grid->model()->where('status', 1);
|
||||||
|
|
||||||
$grid->column('user.phone');
|
$grid->column('user.phone');
|
||||||
$grid->column('vip.name');
|
$grid->column('vip.name');
|
||||||
$grid->column('times')->display(function ($v) {
|
$grid->column('times')->display(function ($v) {
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ class VipController extends AdminController
|
||||||
$grid->column('sort')->editable();
|
$grid->column('sort')->editable();
|
||||||
$grid->column('status')->switch();
|
$grid->column('status')->switch();
|
||||||
|
|
||||||
|
$grid->disableViewButton(false);
|
||||||
//新增
|
//新增
|
||||||
if (Admin::user()->can('dcat.admin.vips.create')) {
|
if (Admin::user()->can('dcat.admin.vips.create')) {
|
||||||
$grid->disableCreateButton(false);
|
$grid->disableCreateButton(false);
|
||||||
|
|
@ -67,6 +68,7 @@ class VipController extends AdminController
|
||||||
$show->filed('price');
|
$show->filed('price');
|
||||||
$show->filed('sort');
|
$show->filed('sort');
|
||||||
$show->field('status');
|
$show->field('status');
|
||||||
|
$show->field('description');
|
||||||
$show->field('created_at');
|
$show->field('created_at');
|
||||||
$show->field('updated_at');
|
$show->field('updated_at');
|
||||||
});
|
});
|
||||||
|
|
@ -90,6 +92,7 @@ class VipController extends AdminController
|
||||||
$form->number('price')->min(0);
|
$form->number('price')->min(0);
|
||||||
$form->number('sort')->min(0);
|
$form->number('sort')->min(0);
|
||||||
$form->switch('status')->default(1);
|
$form->switch('status')->default(1);
|
||||||
|
$form->textarea('description');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Endpoint\Api\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Endpoint\Api\Http\Resources\{VipResource, UserVipResource};
|
||||||
|
use App\Models\Vip;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use App\Exceptions\BizException;
|
||||||
|
use App\Services\VipService;
|
||||||
|
|
||||||
|
class VipController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$list = Vip::where('status', 1)->get();
|
||||||
|
|
||||||
|
return VipResource::collection($list);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buy(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'vip_id' => 'required'
|
||||||
|
]);
|
||||||
|
$vip = Vip::findOrFail($request->input('vip_id'));
|
||||||
|
$service = new VipService();
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
$user_vip = $service->buy($request->user(), $vip);
|
||||||
|
|
||||||
|
$result = $service->pay($user_vip, $request->input('pay_way'));
|
||||||
|
DB::commit();
|
||||||
|
return $result;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
report($e);
|
||||||
|
throw new BizException($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function list(Request $request)
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
$query = $user->vips();
|
||||||
|
|
||||||
|
$list = $query->paginate($request->input('per_page'));
|
||||||
|
|
||||||
|
return UserVipResource::collection($list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Endpoint\Api\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class UserVipResource extends JsonResource
|
||||||
|
{
|
||||||
|
public function toArray($request)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'vip_id' => $this->vip_id,
|
||||||
|
'name' => $this->name,
|
||||||
|
'times' => $this->times,
|
||||||
|
'price' => (int)$this->price,
|
||||||
|
'gift' => $this->gift,
|
||||||
|
'status' => $this->status,
|
||||||
|
'success_time' => $this->success_time,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Endpoint\Api\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class VipResource extends JsonResource
|
||||||
|
{
|
||||||
|
public function toArray($request)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'name' => $this->name,
|
||||||
|
'times' => $this->times,
|
||||||
|
'price' => (int)$this->price,
|
||||||
|
'gift' => $this->gift,
|
||||||
|
'description' => $this->description,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -209,6 +209,11 @@ Route::group([
|
||||||
Route::post('bargains/create-order/{sku}', [\App\Endpoint\Api\Http\Controllers\BargainController::class, 'createBargainOrder']);
|
Route::post('bargains/create-order/{sku}', [\App\Endpoint\Api\Http\Controllers\BargainController::class, 'createBargainOrder']);
|
||||||
Route::post('bargains/bargain/{order}', [\App\Endpoint\Api\Http\Controllers\BargainController::class, 'bargain']);
|
Route::post('bargains/bargain/{order}', [\App\Endpoint\Api\Http\Controllers\BargainController::class, 'bargain']);
|
||||||
Route::post('bargains/create-mall-order/{bargainOrder}', [\App\Endpoint\Api\Http\Controllers\BargainController::class, 'createMallOrderByBargainOrder']);
|
Route::post('bargains/create-mall-order/{bargainOrder}', [\App\Endpoint\Api\Http\Controllers\BargainController::class, 'createMallOrderByBargainOrder']);
|
||||||
|
|
||||||
|
// 会员卡
|
||||||
|
Route::get('vip', [\App\Endpoint\Api\Http\Controllers\VipController::class, 'index']);
|
||||||
|
Route::get('vip/buy', [\App\Endpoint\Api\Http\Controllers\VipController::class, 'list']);
|
||||||
|
Route::post('vip/buy', [\App\Endpoint\Api\Http\Controllers\VipController::class, 'buy']);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 微信小程序
|
// 微信小程序
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,8 @@ class PayLog extends Model
|
||||||
'out_trade_no',
|
'out_trade_no',
|
||||||
'status',
|
'status',
|
||||||
'failed_reason',
|
'failed_reason',
|
||||||
|
'payable_type',
|
||||||
|
'payable_id',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,11 @@ class UserVip extends Model
|
||||||
|
|
||||||
protected $fillable = ['user_id', 'vip_id', 'name', 'price', 'times', 'gift', 'status', 'success_time'];
|
protected $fillable = ['user_id', 'vip_id', 'name', 'price', 'times', 'gift', 'status', 'success_time'];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'times' => 'json',
|
||||||
|
'gift' => 'json'
|
||||||
|
];
|
||||||
|
|
||||||
public function user()
|
public function user()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class, 'user_id');
|
return $this->belongsTo(User::class, 'user_id');
|
||||||
|
|
@ -20,9 +25,4 @@ class UserVip extends Model
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Vip::class, 'vip_id');
|
return $this->belongsTo(Vip::class, 'vip_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function pay()
|
|
||||||
{
|
|
||||||
return $this->morphOne(PayLog::class, 'payable');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ class Vip extends Model
|
||||||
const TIME_MONTH = 'month';
|
const TIME_MONTH = 'month';
|
||||||
const TIME_DAY = 'day';
|
const TIME_DAY = 'day';
|
||||||
|
|
||||||
protected $fillable = ['name', 'times', 'sort', 'status', 'gift', 'price'];
|
protected $fillable = ['name', 'times', 'sort', 'status', 'gift', 'price', 'description'];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'times' => 'json',
|
'times' => 'json',
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,8 @@ class PayService
|
||||||
'out_trade_no' => $payLog->out_trade_no,
|
'out_trade_no' => $payLog->out_trade_no,
|
||||||
'status' => Order::STATUS_PAID,
|
'status' => Order::STATUS_PAID,
|
||||||
]);
|
]);
|
||||||
|
} else if ($payable instanceof \App\Models\UserVip) {
|
||||||
|
(new App\Services\VipService())->success($payable, ['pay_at' => $payLog->pay_at]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $payLog;
|
return $payLog;
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@
|
||||||
|
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
use App\Models\{User, Vip, UserVip};
|
use App\Models\{User, Vip, UserVip, PayLog};
|
||||||
use App\Exceptions\BizException;
|
use App\Exceptions\BizException;
|
||||||
use App\Enums\{PayWay, SocialiteType};
|
use App\Enums\{PayWay, SocialiteType, WxpayTradeType};
|
||||||
use App\Services\Payment\WxpayService;
|
use App\Services\Payment\WxpayService;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
class VipService
|
class VipService
|
||||||
{
|
{
|
||||||
|
|
@ -25,32 +26,89 @@ class VipService
|
||||||
return $user_vip;
|
return $user_vip;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function pay(UserVip $userVip, $pay_way)
|
public function pay(UserVip $userVip, $pay_way = '')
|
||||||
{
|
{
|
||||||
|
if (!$pay_way) {
|
||||||
|
$pay_way = PayWay::WxpayMiniProgram->value;
|
||||||
|
}
|
||||||
if ($userVip->status == 2) {
|
if ($userVip->status == 2) {
|
||||||
throw new BizException('支付处理中,请勿重复操作');
|
throw new BizException('支付处理中,请勿重复操作');
|
||||||
}
|
}
|
||||||
$userVip->update([
|
$userVip->update([
|
||||||
'status' => 2
|
'status' => 2
|
||||||
]);
|
]);
|
||||||
$pay_log = $user_vip->pay()->create([
|
$pay_log = PayLog::create([
|
||||||
|
'payable_type' => UserVip::class,
|
||||||
|
'payable_id' => $userVip->id,
|
||||||
'pay_sn' => serial_number(),
|
'pay_sn' => serial_number(),
|
||||||
'pay_way' => $pay_way
|
'pay_way' => $pay_way
|
||||||
]);
|
]);
|
||||||
|
$money = $userVip->price;
|
||||||
|
$debug = config('app.debug');
|
||||||
|
if ($debug) {
|
||||||
|
$money = 0.01;
|
||||||
|
}
|
||||||
|
$user = $userVip->user;
|
||||||
// 微信小程序支付
|
// 微信小程序支付
|
||||||
if ($pay_log->pay_way === PayWay::WxpayMiniProgram->value) {
|
if ($pay_log->pay_way === PayWay::WxpayMiniProgram) {
|
||||||
$openid = $user->socialites()->where('socialite_type', SocialiteType::WechatMiniProgram)->value('socialite_id');
|
$openid = $user->socialites()->where('socialite_type', SocialiteType::WechatMiniProgram)->value('socialite_id');
|
||||||
|
if (!$openid && $debug) {
|
||||||
|
$openid = 'oU7xS5UspzVvpPEBqKZuW6N9WXDg';
|
||||||
|
}
|
||||||
if (!$openid) {
|
if (!$openid) {
|
||||||
throw new BizException('未找到用户的微信授权信息, 无法使用微信支付');
|
throw new BizException('未找到用户的微信授权信息, 无法使用微信支付');
|
||||||
}
|
}
|
||||||
$params = [
|
$params = [
|
||||||
'body' => app_settings('app.app_name').'-会员-' . $userVip->name,
|
'body' => app_settings('app.app_name').'-会员-' . $userVip->name,
|
||||||
'out_trade_no' => $pay_log->pay_sn,
|
'out_trade_no' => $pay_log->pay_sn,
|
||||||
'total_fee' => $userVip->price,
|
'total_fee' => $money * 100,
|
||||||
'trade_type' => 'JSAPI',
|
'trade_type' => WxpayTradeType::JSAPI->value,
|
||||||
'openid' => $openid,
|
'openid' => $openid,
|
||||||
];
|
];
|
||||||
|
return (new WxpayService())->pay($params);
|
||||||
}
|
}
|
||||||
throw new BizException('未知的支付方式');
|
throw new BizException('未知的支付方式');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function success(UserVip $userVip, array $params = null)
|
||||||
|
{
|
||||||
|
$userVip->update([
|
||||||
|
'status' => 1,
|
||||||
|
'success_time' => data_get($params, 'pay_at', now())
|
||||||
|
]);
|
||||||
|
$user = $userVip->user;
|
||||||
|
$vip_expired = $this->convertTimes(data_get($userVip->times, 'num'), data_get($userVip->times, 'unit'), $user->vip_expired);
|
||||||
|
$userVip->user->update([
|
||||||
|
'vip_expired' => $vip_expired
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时间转换
|
||||||
|
*
|
||||||
|
* @param int $num 数量
|
||||||
|
* @param string $unit 单位
|
||||||
|
* @return Carbon
|
||||||
|
*/
|
||||||
|
public function convertTimes(int $num, string $unit, Carbon $start = null)
|
||||||
|
{
|
||||||
|
if (!$start) {
|
||||||
|
$start = now();
|
||||||
|
}
|
||||||
|
switch ($unit) {
|
||||||
|
case Vip::TIME_YEAR:
|
||||||
|
$start->addYears($num);
|
||||||
|
break;
|
||||||
|
case Vip::TIME_MONTH:
|
||||||
|
$start->addYears($num);
|
||||||
|
break;
|
||||||
|
case TIME_DAY:
|
||||||
|
$start->addDays($num);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new BizException('未知的时间单位');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $start;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ class CreateVipsTable extends Migration
|
||||||
$table->unsignedInteger('sort')->default(1)->comment('排序(asc)');
|
$table->unsignedInteger('sort')->default(1)->comment('排序(asc)');
|
||||||
$table->tinyInteger('status')->default(1)->comment('状态');
|
$table->tinyInteger('status')->default(1)->comment('状态');
|
||||||
$table->text('gift')->nullable()->comment('赠品');
|
$table->text('gift')->nullable()->comment('赠品');
|
||||||
|
$table->text('description')->nullable()->comment('描述');
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
Schema::create('user_vips', function (Blueprint $table) {
|
Schema::create('user_vips', function (Blueprint $table) {
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,12 @@ class AppSettingSeeder extends Seeder
|
||||||
],
|
],
|
||||||
'remarks' => '个推配置',
|
'remarks' => '个推配置',
|
||||||
],
|
],
|
||||||
|
'custom' => [
|
||||||
|
'key_value' => [
|
||||||
|
'wechat_mini_show_vip_banner' => 'https://zcs-test.oss-cn-chengdu.aliyuncs.com/ac/is_vip.png',
|
||||||
|
'wechat_mini_become_vip_banner' => 'https://zcs-test.oss-cn-chengdu.aliyuncs.com/ac/not_vip.png',
|
||||||
|
]
|
||||||
|
]
|
||||||
] as $key => $values) {
|
] as $key => $values) {
|
||||||
Setting::firstOrCreate(['key' => $key], $values);
|
Setting::firstOrCreate(['key' => $key], $values);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@ class VipsTableSeeder extends Seeder
|
||||||
UserVip::truncate();
|
UserVip::truncate();
|
||||||
$time = now();
|
$time = now();
|
||||||
$list = [
|
$list = [
|
||||||
['name' => '年卡', 'times' => json_encode(['text' => '1年', 'num' => 1, 'unit' => 'year']), 'price' => 100, 'created_at' => $time, 'updated_at' => $time],
|
['name' => '年卡', 'times' => json_encode(['text' => '1年', 'num' => 1, 'unit' => 'year']), 'price' => 100, 'description' => '瞰澜书店年卡'.PHP_EOL.'咖啡饮品85折,图书85折,免费咖啡饮品40杯;'.PHP_EOL.'生日礼物惊喜图书一本;'.PHP_EOL.'通用现金券500元(有限期365天)', 'created_at' => $time, 'updated_at' => $time],
|
||||||
['name' => '季卡', 'times' => json_encode(['text' => '3月', 'num' => 3, 'unit' => 'month']), 'price' => 25, 'created_at' => $time, 'updated_at' => $time],
|
['name' => '季卡', 'times' => json_encode(['text' => '3月', 'num' => 3, 'unit' => 'month']), 'price' => 25, 'description' => '瞰澜书店季卡'.PHP_EOL.'咖啡饮品85折,图书85折,免费咖啡饮品40杯;'.PHP_EOL.'生日礼物惊喜图书一本;'.PHP_EOL.'通用现金券500元(有限期365天)', 'created_at' => $time, 'updated_at' => $time],
|
||||||
['name' => '月卡', 'times' => json_encode(['text' => '1月', 'num' => 1, 'unit' => 'month']), 'price' => 10, 'created_at' => $time, 'updated_at' => $time],
|
['name' => '月卡', 'times' => json_encode(['text' => '1月', 'num' => 1, 'unit' => 'month']), 'price' => 10, 'description' => '瞰澜书店月卡'.PHP_EOL.'咖啡饮品85折,图书85折,免费咖啡饮品40杯;'.PHP_EOL.'生日礼物惊喜图书一本;'.PHP_EOL.'通用现金券500元(有限期365天)', 'created_at' => $time, 'updated_at' => $time],
|
||||||
];
|
];
|
||||||
Vip::insert($list);
|
Vip::insert($list);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ return [
|
||||||
'times' => '时效',
|
'times' => '时效',
|
||||||
'gift' => '赠品',
|
'gift' => '赠品',
|
||||||
'price' => '价格',
|
'price' => '价格',
|
||||||
'status' => '状态'
|
'status' => '状态',
|
||||||
|
'description' => '描述',
|
||||||
],
|
],
|
||||||
'options' => [
|
'options' => [
|
||||||
'deny' => '删除失败',
|
'deny' => '删除失败',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue