diff --git a/app/Admin/Controllers/UserVipController.php b/app/Admin/Controllers/UserVipController.php index e8598d8e..33e3b1fb 100644 --- a/app/Admin/Controllers/UserVipController.php +++ b/app/Admin/Controllers/UserVipController.php @@ -18,6 +18,9 @@ class UserVipController extends AdminController protected function grid() { return Grid::make(UserVip::with(['user', 'vip']), function (Grid $grid) { + + $grid->model()->where('status', 1); + $grid->column('user.phone'); $grid->column('vip.name'); $grid->column('times')->display(function ($v) { diff --git a/app/Admin/Controllers/VipController.php b/app/Admin/Controllers/VipController.php index c45148dc..8c2ee299 100644 --- a/app/Admin/Controllers/VipController.php +++ b/app/Admin/Controllers/VipController.php @@ -30,6 +30,7 @@ class VipController extends AdminController $grid->column('sort')->editable(); $grid->column('status')->switch(); + $grid->disableViewButton(false); //新增 if (Admin::user()->can('dcat.admin.vips.create')) { $grid->disableCreateButton(false); @@ -67,6 +68,7 @@ class VipController extends AdminController $show->filed('price'); $show->filed('sort'); $show->field('status'); + $show->field('description'); $show->field('created_at'); $show->field('updated_at'); }); @@ -90,6 +92,7 @@ class VipController extends AdminController $form->number('price')->min(0); $form->number('sort')->min(0); $form->switch('status')->default(1); + $form->textarea('description'); }); } diff --git a/app/Endpoint/Api/Http/Controllers/VipController.php b/app/Endpoint/Api/Http/Controllers/VipController.php new file mode 100644 index 00000000..1bf179cd --- /dev/null +++ b/app/Endpoint/Api/Http/Controllers/VipController.php @@ -0,0 +1,52 @@ +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); + } +} diff --git a/app/Endpoint/Api/Http/Resources/UserVipResource.php b/app/Endpoint/Api/Http/Resources/UserVipResource.php new file mode 100644 index 00000000..68a0c426 --- /dev/null +++ b/app/Endpoint/Api/Http/Resources/UserVipResource.php @@ -0,0 +1,22 @@ + $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, + ]; + } +} diff --git a/app/Endpoint/Api/Http/Resources/VipResource.php b/app/Endpoint/Api/Http/Resources/VipResource.php new file mode 100644 index 00000000..89d6babd --- /dev/null +++ b/app/Endpoint/Api/Http/Resources/VipResource.php @@ -0,0 +1,20 @@ + $this->id, + 'name' => $this->name, + 'times' => $this->times, + 'price' => (int)$this->price, + 'gift' => $this->gift, + 'description' => $this->description, + ]; + } +} diff --git a/app/Endpoint/Api/routes.php b/app/Endpoint/Api/routes.php index 2a17b27b..98a68940 100644 --- a/app/Endpoint/Api/routes.php +++ b/app/Endpoint/Api/routes.php @@ -209,6 +209,11 @@ Route::group([ 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/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']); }); // 微信小程序 diff --git a/app/Models/PayLog.php b/app/Models/PayLog.php index 0658395a..8554e40a 100644 --- a/app/Models/PayLog.php +++ b/app/Models/PayLog.php @@ -39,6 +39,8 @@ class PayLog extends Model 'out_trade_no', 'status', 'failed_reason', + 'payable_type', + 'payable_id', ]; /** diff --git a/app/Models/UserVip.php b/app/Models/UserVip.php index a66599df..f2b883cf 100644 --- a/app/Models/UserVip.php +++ b/app/Models/UserVip.php @@ -11,6 +11,11 @@ class UserVip extends Model protected $fillable = ['user_id', 'vip_id', 'name', 'price', 'times', 'gift', 'status', 'success_time']; + protected $casts = [ + 'times' => 'json', + 'gift' => 'json' + ]; + public function user() { return $this->belongsTo(User::class, 'user_id'); @@ -20,9 +25,4 @@ class UserVip extends Model { return $this->belongsTo(Vip::class, 'vip_id'); } - - public function pay() - { - return $this->morphOne(PayLog::class, 'payable'); - } } diff --git a/app/Models/Vip.php b/app/Models/Vip.php index f021002b..5eb6a4dd 100644 --- a/app/Models/Vip.php +++ b/app/Models/Vip.php @@ -16,7 +16,7 @@ class Vip extends Model const TIME_MONTH = 'month'; const TIME_DAY = 'day'; - protected $fillable = ['name', 'times', 'sort', 'status', 'gift', 'price']; + protected $fillable = ['name', 'times', 'sort', 'status', 'gift', 'price', 'description']; protected $casts = [ 'times' => 'json', diff --git a/app/Services/PayService.php b/app/Services/PayService.php index a60cfb0d..7e17949b 100644 --- a/app/Services/PayService.php +++ b/app/Services/PayService.php @@ -70,6 +70,8 @@ class PayService 'out_trade_no' => $payLog->out_trade_no, 'status' => Order::STATUS_PAID, ]); + } else if ($payable instanceof \App\Models\UserVip) { + (new App\Services\VipService())->success($payable, ['pay_at' => $payLog->pay_at]); } return $payLog; diff --git a/app/Services/VipService.php b/app/Services/VipService.php index a007563c..4d5e0434 100644 --- a/app/Services/VipService.php +++ b/app/Services/VipService.php @@ -2,10 +2,11 @@ namespace App\Services; -use App\Models\{User, Vip, UserVip}; +use App\Models\{User, Vip, UserVip, PayLog}; use App\Exceptions\BizException; -use App\Enums\{PayWay, SocialiteType}; +use App\Enums\{PayWay, SocialiteType, WxpayTradeType}; use App\Services\Payment\WxpayService; +use Carbon\Carbon; class VipService { @@ -25,32 +26,89 @@ class VipService 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) { throw new BizException('支付处理中,请勿重复操作'); } $userVip->update([ '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_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'); + if (!$openid && $debug) { + $openid = 'oU7xS5UspzVvpPEBqKZuW6N9WXDg'; + } if (!$openid) { throw new BizException('未找到用户的微信授权信息, 无法使用微信支付'); } $params = [ 'body' => app_settings('app.app_name').'-会员-' . $userVip->name, 'out_trade_no' => $pay_log->pay_sn, - 'total_fee' => $userVip->price, - 'trade_type' => 'JSAPI', + 'total_fee' => $money * 100, + 'trade_type' => WxpayTradeType::JSAPI->value, 'openid' => $openid, ]; + return (new WxpayService())->pay($params); } 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; + } } diff --git a/database/migrations/2021_12_03_135905_create_vips_table.php b/database/migrations/2021_12_03_135905_create_vips_table.php index e79d689c..76012ae2 100644 --- a/database/migrations/2021_12_03_135905_create_vips_table.php +++ b/database/migrations/2021_12_03_135905_create_vips_table.php @@ -21,6 +21,7 @@ class CreateVipsTable extends Migration $table->unsignedInteger('sort')->default(1)->comment('排序(asc)'); $table->tinyInteger('status')->default(1)->comment('状态'); $table->text('gift')->nullable()->comment('赠品'); + $table->text('description')->nullable()->comment('描述'); $table->timestamps(); }); Schema::create('user_vips', function (Blueprint $table) { diff --git a/database/seeders/AppSettingSeeder.php b/database/seeders/AppSettingSeeder.php index 1e8ff1c9..33604c6c 100644 --- a/database/seeders/AppSettingSeeder.php +++ b/database/seeders/AppSettingSeeder.php @@ -99,6 +99,12 @@ class AppSettingSeeder extends Seeder ], '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) { Setting::firstOrCreate(['key' => $key], $values); } diff --git a/database/seeders/VipsTableSeeder.php b/database/seeders/VipsTableSeeder.php index baf21167..7cc53b84 100644 --- a/database/seeders/VipsTableSeeder.php +++ b/database/seeders/VipsTableSeeder.php @@ -18,9 +18,9 @@ class VipsTableSeeder extends Seeder UserVip::truncate(); $time = now(); $list = [ - ['name' => '年卡', 'times' => json_encode(['text' => '1年', 'num' => 1, 'unit' => 'year']), 'price' => 100, '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' => '1月', 'num' => 1, 'unit' => 'month']), 'price' => 10, '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, '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, 'description' => '瞰澜书店月卡'.PHP_EOL.'咖啡饮品85折,图书85折,免费咖啡饮品40杯;'.PHP_EOL.'生日礼物惊喜图书一本;'.PHP_EOL.'通用现金券500元(有限期365天)', 'created_at' => $time, 'updated_at' => $time], ]; Vip::insert($list); } diff --git a/resources/lang/zh_CN/vip.php b/resources/lang/zh_CN/vip.php index 5a4cc56a..5e934978 100644 --- a/resources/lang/zh_CN/vip.php +++ b/resources/lang/zh_CN/vip.php @@ -12,7 +12,8 @@ return [ 'times' => '时效', 'gift' => '赠品', 'price' => '价格', - 'status' => '状态' + 'status' => '状态', + 'description' => '描述', ], 'options' => [ 'deny' => '删除失败',