diff --git a/app/Endpoint/Api/Http/Controllers/Account/WalletController.php b/app/Endpoint/Api/Http/Controllers/Account/WalletController.php index 15c6c576..9dc11e31 100644 --- a/app/Endpoint/Api/Http/Controllers/Account/WalletController.php +++ b/app/Endpoint/Api/Http/Controllers/Account/WalletController.php @@ -5,8 +5,16 @@ namespace App\Endpoint\Api\Http\Controllers\Account; use App\Endpoint\Api\Http\Controllers\Controller; use App\Endpoint\Api\Http\Resources\DistributionPreIncomeResource; use App\Endpoint\Api\Http\Resources\WalletLogResource; +use App\Exceptions\BizException; use App\Helpers\Paginator as PaginatorHelper; +use App\Models\Order; +use App\Models\PayLog; +use App\Models\WalletLog; +use App\Services\PayService; +use App\Services\WalletService; use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; +use Throwable; class WalletController extends Controller { @@ -51,14 +59,10 @@ class WalletController extends Controller public function walletLogs(Request $request) { $perPage = PaginatorHelper::resolvePerPage('per_page', 20, 50); - $walletLogs = $request->user()->wallet?->logs() + + $walletLogs = $request->user()->walletLogs() ->latest('id') ->simplePaginate($perPage); - if (is_null($walletLogs)) { - return response()->json([ - 'data'=>[], - ]); - } return WalletLogResource::collection($walletLogs); } @@ -71,4 +75,53 @@ class WalletController extends Controller public function balanceLogs(Request $request) { } + + /** + * 钱包付款 + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + * + * @throws \App\Exceptions\BizException + */ + public function pay(Request $request) + { + $validated = $request->validate([ + 'pay_sn' => ['bail', 'required'], + ]); + + $user = $request->user(); + + try { + DB::transaction(function () use ($user, $validated) { + $payLog = PayLog::where('pay_sn', $validated['pay_sn'])->lockForUpdate()->firstOrFail(); + + $payable = $payLog->payable; + + switch (get_class($payable)) { + case Order::class: + (new WalletService())->changeBalance($user, -$payable->total_amount, WalletLog::ACTION_ORDER_PAY, '订单支付扣款', $payable); + break; + + default: + throw new BizException('非法操作'); + break; + } + + (new PayService())->handleSuccess($payLog); + }); + } catch (Throwable $e) { + try { + (new PayService())->handleFailedByPaySerialNumber($validated['pay_sn'], [ + 'failed_reason' => $e->getMessage(), + ]); + } catch (Throwable $e) { + report($e); + } + + throw $e; + } + + return response()->noContent(); + } } diff --git a/app/Endpoint/Api/Http/Resources/WalletLogResource.php b/app/Endpoint/Api/Http/Resources/WalletLogResource.php index f05340c4..055a4296 100644 --- a/app/Endpoint/Api/Http/Resources/WalletLogResource.php +++ b/app/Endpoint/Api/Http/Resources/WalletLogResource.php @@ -15,9 +15,9 @@ class WalletLogResource extends JsonResource public function toArray($request) { return [ - 'remarks' => $this->remarks, - 'created_at' => $this->created_at->toDateTimeString(), - 'fee' => $this->fee_format, + 'remarks' => $this->remarks, + 'created_at' => $this->created_at->toDateTimeString(), + 'change_balance' => $this->change_balance, ]; } } diff --git a/app/Endpoint/Api/routes.php b/app/Endpoint/Api/routes.php index 888960e9..40e7c0b0 100644 --- a/app/Endpoint/Api/routes.php +++ b/app/Endpoint/Api/routes.php @@ -91,6 +91,7 @@ Route::group([ Route::get('wallet', [WalletController::class, 'index']); Route::get('wallet/distribution-logs', [WalletController::class, 'distributionLogs']); Route::get('wallet/wallet-logs', [WalletController::class, 'walletLogs']); + Route::post('wallet/pay', [WalletController::class, 'pay']); //银行卡 Route::get('user-bank', [UserBankController::class, 'show']); diff --git a/app/Models/PayLog.php b/app/Models/PayLog.php index 21b2833b..98c471eb 100644 --- a/app/Models/PayLog.php +++ b/app/Models/PayLog.php @@ -17,6 +17,7 @@ class PayLog extends Model public const PAY_WAY_WXPAY_JSAPI = 'wxpay_jsapi'; // 微信支付 - JSAPI 支付 public const PAY_WAY_WXPAY_MINI = 'wxpay_mini'; // 微信支付 - 小程序支付 public const PAY_WAY_WXPAY_H5 = 'wxpay_h5'; // 微信支付 - H5 支付 + public const PAY_WAY_WALLET = 'wallet'; // 钱包付款(可提现) public const PAY_WAY_BALANCE = 'balance'; // 余额支付 public const PAY_WAY_OFFLINE = 'offline'; // 线下支付 @@ -86,4 +87,14 @@ class PayLog extends Model { return $this->pay_way === static::PAY_WAY_OFFLINE; } + + /** + * 确认支付方式是否是钱包付款 + * + * @return bool + */ + public function isWallet(): bool + { + return $this->pay_way === static::PAY_WAY_WALLET; + } } diff --git a/app/Models/User.php b/app/Models/User.php index cdf69e88..2fdc153c 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -188,6 +188,14 @@ class User extends Model implements AuthorizableContract, AuthenticatableContrac return $this->hasOne(Wallet::class, 'user_id'); } + /** + * 用户的钱包日志 + */ + public function walletLogs() + { + return $this->hasMany(WalletLog::class, 'user_id'); + } + /** * 用户的预收益 */ @@ -270,6 +278,22 @@ class User extends Model implements AuthorizableContract, AuthenticatableContrac ]; } + /** + * 获取此用户的钱包并加行锁 + * + * @return \App\Models\Wallet + */ + public function lockWallet(): Wallet + { + if ($wallet = $this->wallet()->lockForUpdate()->first()) { + return $wallet; + } + + $this->wallet()->create(); + + return $this->wallet()->lockForUpdate()->first(); + } + /** * 创建用户 * @@ -290,8 +314,8 @@ class User extends Model implements AuthorizableContract, AuthenticatableContrac 'inviter_id' => null, 'depth' => 1, 'path' => '/', - ]) - ; + ] + ); return $user; } diff --git a/app/Models/Wallet.php b/app/Models/Wallet.php index e0dda954..f022367e 100644 --- a/app/Models/Wallet.php +++ b/app/Models/Wallet.php @@ -20,6 +20,9 @@ class Wallet extends Model * @var array */ protected $attributes = [ + 'balance' => 0, + 'total_revenue' => 0, + 'total_expenses' => 0, 'withdrawable' => true, ]; @@ -42,15 +45,6 @@ class Wallet extends Model 'withdrawable' => 'bool', ]; - /** - * 资产日志 - * - */ - public function logs() - { - return $this->hasMany(WalletLog::class, 'wallet_id'); - } - /** * 设置此用户的安全密码 * diff --git a/app/Models/WalletLog.php b/app/Models/WalletLog.php index f515e87f..a32b8736 100644 --- a/app/Models/WalletLog.php +++ b/app/Models/WalletLog.php @@ -7,13 +7,28 @@ use Illuminate\Database\Eloquent\Model; class WalletLog extends Model { + public const ACTION_ORDER_PAY = 1; + + /** + * @var array + */ + protected $fillable = [ + 'user_id', + 'loggable_id', + 'loggable_type', + 'action', + 'before_balance', + 'change_balance', + 'remarks', + ]; + /** * 获取变动金额 * * @return string */ - public function getFeeFormatAttribute() + public function getChangeBalanceFormatAttribute() { - return Numeric::trimTrailingZero(bcdiv($this->attributes['fee'], 100, 2)); + return Numeric::trimTrailingZero(bcdiv($this->attributes['change_balance'], 100, 2)); } } diff --git a/app/Services/OrderService.php b/app/Services/OrderService.php index 36d1cac5..db83d620 100644 --- a/app/Services/OrderService.php +++ b/app/Services/OrderService.php @@ -185,8 +185,6 @@ class OrderService $totalAmount += $shippingFee; - $totalAmount=0; - do { // 如果订单号重复,则直接重试 try { @@ -783,6 +781,11 @@ class OrderService 'trade_type' => WeChatPayService::$tradeTypes[$payLog->pay_way], ]); } + + return [ + 'pay_sn' => $payLog->pay_sn, + 'pay_way' => $payLog->pay_way, + ]; } /** diff --git a/app/Services/PayService.php b/app/Services/PayService.php index 49bd45f6..7e22ec91 100644 --- a/app/Services/PayService.php +++ b/app/Services/PayService.php @@ -37,7 +37,7 @@ class PayService public function handleSuccess(PayLog $payLog, array $params = []): PayLog { if (! $payLog->isPending()) { - throw new BizException('支付记录状态异常'); + throw new BizException('支付异常'); } $payLog->update([ @@ -109,7 +109,7 @@ class PayService public function handleFailed(PayLog $payLog, array $params = []): PayLog { if (! $payLog->isPending()) { - throw new BizException('支付记录状态异常'); + throw new BizException('支付异常'); } $payLog->update([ diff --git a/app/Services/WalletService.php b/app/Services/WalletService.php new file mode 100644 index 00000000..7f8d45a8 --- /dev/null +++ b/app/Services/WalletService.php @@ -0,0 +1,57 @@ +lockWallet(); + + // 变更前余额 + $beforeBalance = $wallet->balance; + $_balance = abs($changeBalance); + + if ($changeBalance > 0) { + // 收入 + $user->wallet()->update([ + 'balance' => DB::raw("balance + {$_balance}"), + 'total_revenue' => DB::raw("total_revenue + {$_balance}"), + ]); + } else { + // 支出 + + if ($wallet->balance < $_balance) { + throw new BizException('可提现金额不足'); + } + + $user->wallet()->update([ + 'balance' => DB::raw("balance - {$_balance}"), + 'total_expenses' => DB::raw("total_expenses + {$_balance}"), + ]); + } + + $user->walletLogs()->create([ + 'loggable_id' => $loggable?->id, + 'loggable_type' => $loggable?->getMorphClass(), + 'before_balance' => $beforeBalance, + 'change_balance' => $changeBalance, + 'action' => $action, + 'remarks' => $remarks, + ]); + } +} diff --git a/database/migrations/2021_12_22_190137_create_order_refund_logs_table.php b/database/migrations/2021_12_22_190137_create_order_refund_logs_table.php index 0365a3a3..b2b7676f 100644 --- a/database/migrations/2021_12_22_190137_create_order_refund_logs_table.php +++ b/database/migrations/2021_12_22_190137_create_order_refund_logs_table.php @@ -21,7 +21,7 @@ class CreateOrderRefundLogsTable extends Migration $table->unsignedBigInteger('amount')->comment('退款金额'); $table->tinyInteger('status')->default(0)->comment('状态'); $table->string('reason')->nullable()->comment('退款原因'); - $table->string('failed_reason')->nullable()->comment('失败原因'); + $table->text('failed_reason')->nullable()->comment('失败原因'); $table->timestamps(); $table->unique(['order_id', 'after_sale_id']); diff --git a/database/migrations/2021_12_22_190846_create_distribution_pre_income_jobs_table.php b/database/migrations/2021_12_22_190846_create_distribution_pre_income_jobs_table.php index 7f637e4b..473ec6eb 100644 --- a/database/migrations/2021_12_22_190846_create_distribution_pre_income_jobs_table.php +++ b/database/migrations/2021_12_22_190846_create_distribution_pre_income_jobs_table.php @@ -18,7 +18,7 @@ class CreateDistributionPreIncomeJobsTable extends Migration $table->morphs('jobable'); $table->tinyInteger('status')->default(0)->comment('状态'); $table->string('remarks')->nullable()->comment('备注'); - $table->string('failed_reason')->nullable()->comment('失败原因'); + $table->text('failed_reason')->nullable()->comment('失败原因'); $table->timestamps(); }); } diff --git a/database/migrations/2021_12_23_171714_create_wallet_logs_table.php b/database/migrations/2021_12_23_171714_create_wallet_logs_table.php index 7bd4bd42..ddd355f8 100644 --- a/database/migrations/2021_12_23_171714_create_wallet_logs_table.php +++ b/database/migrations/2021_12_23_171714_create_wallet_logs_table.php @@ -19,7 +19,7 @@ class CreateWalletLogsTable extends Migration $table->nullableMorphs('loggable'); $table->tinyInteger('action')->comment('操作类型'); $table->unsignedBigInteger('before_balance')->default(0)->comment('变更前的余额'); - $table->bigInteger('fee')->default(0)->comment('变动金额(分)'); + $table->bigInteger('change_balance')->default(0)->comment('变动余额(分)'); $table->string('remarks')->nullable()->comment('备注'); $table->timestamps(); diff --git a/database/migrations/2021_12_25_171325_create_pay_logs_table.php b/database/migrations/2021_12_25_171325_create_pay_logs_table.php index d4acadd2..0a297052 100644 --- a/database/migrations/2021_12_25_171325_create_pay_logs_table.php +++ b/database/migrations/2021_12_25_171325_create_pay_logs_table.php @@ -21,7 +21,7 @@ class CreatePayLogsTable extends Migration $table->string('out_trade_no')->nullable()->comment('外部交易单号'); $table->timestamp('pay_at')->nullable()->comment('支付时间'); $table->tinyInteger('status')->comment('状态: 0 待付款, 1 支付成功, 2 支付失败'); - $table->string('failed_reason')->nullable()->comment('失败原因'); + $table->text('failed_reason')->nullable()->comment('失败原因'); $table->timestamps(); }); }