6
0
Fork 0

会员卡

release
panliang 2022-05-18 11:01:01 +08:00
parent 0e395eb986
commit 16b04e024b
15 changed files with 192 additions and 17 deletions

View File

@ -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) {

View File

@ -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');
}); });
} }

View File

@ -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);
}
}

View File

@ -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,
];
}
}

View File

@ -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,
];
}
}

View File

@ -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']);
}); });
// 微信小程序 // 微信小程序

View File

@ -39,6 +39,8 @@ class PayLog extends Model
'out_trade_no', 'out_trade_no',
'status', 'status',
'failed_reason', 'failed_reason',
'payable_type',
'payable_id',
]; ];
/** /**

View File

@ -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');
}
} }

View File

@ -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',

View File

@ -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;

View File

@ -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;
}
} }

View File

@ -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) {

View File

@ -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);
} }

View File

@ -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);
} }

View File

@ -12,7 +12,8 @@ return [
'times' => '时效', 'times' => '时效',
'gift' => '赠品', 'gift' => '赠品',
'price' => '价格', 'price' => '价格',
'status' => '状态' 'status' => '状态',
'description' => '描述',
], ],
'options' => [ 'options' => [
'deny' => '删除失败', 'deny' => '删除失败',