From f0c94a635682106e1dfc5c47526447ae28eab8fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=9D=99?= Date: Mon, 27 Dec 2021 13:33:55 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Http/Controllers/WeChatPayController.php | 51 ++++------- app/Endpoint/Callback/routes.php | 2 +- app/Helpers/Order.php | 16 ---- app/Listeners/SendCoupons.php | 63 ++++++++++++++ app/Models/Order.php | 10 ++- app/Models/PayLog.php | 63 ++++++++++++++ app/Providers/EventServiceProvider.php | 1 + app/Services/OrderService.php | 82 +++++------------- app/Services/PayService.php | 86 +++++++++++++++++++ config/wechat.php | 2 +- ...021_12_25_171325_create_pay_logs_table.php | 38 ++++++++ ...11510_add_trade_out_no_to_orders_table.php | 32 +++++++ 12 files changed, 335 insertions(+), 111 deletions(-) delete mode 100644 app/Helpers/Order.php create mode 100644 app/Listeners/SendCoupons.php create mode 100644 app/Models/PayLog.php create mode 100644 app/Services/PayService.php create mode 100644 database/migrations/2021_12_25_171325_create_pay_logs_table.php create mode 100644 database/migrations/2021_12_27_111510_add_trade_out_no_to_orders_table.php diff --git a/app/Endpoint/Callback/Http/Controllers/WeChatPayController.php b/app/Endpoint/Callback/Http/Controllers/WeChatPayController.php index 998583ea..a78793d0 100644 --- a/app/Endpoint/Callback/Http/Controllers/WeChatPayController.php +++ b/app/Endpoint/Callback/Http/Controllers/WeChatPayController.php @@ -6,47 +6,49 @@ use App\Events\OrderPaid; use App\Exceptions\BizException; use App\Models\Order; use App\Models\OrderRefundLog; -use App\Services\OrderService; +use App\Services\PayService; use App\Services\WeChatPayService; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Log; use Throwable; class WeChatPayController extends Controller { /** - * 订单支付结果通知 + * 支付结果通知 * * @param \App\Services\WeChatPayService $weChatPayService * @return \Illuminate\Http\Response */ - public function orderPaidNotify(WeChatPayService $weChatPayService) + public function paidNotify(WeChatPayService $weChatPayService) { return $weChatPayService->handlePaidNotify(function ($message, $fail) { - $this->log('order paid notify', $message); - // 通信失败 if (data_get($message, 'return_code') !== 'SUCCESS') { return $fail('通信失败'); } - // 支付失败 - if (data_get($message, 'result_code') !== 'SUCCESS') { - return true; - } - try { - $order = DB::transaction(function () use ($message) { - return (new OrderService())->paySuccess($message['out_trade_no'], [ - 'pay_sn' => $message['transaction_id'], - 'pay_way' => Order::PAY_WAY_WXPAY, + $payLog = DB::transaction(function () use ($message) { + $payService = new PayService(); + + if (data_get($message, 'result_code') !== 'SUCCESS') { + return $payService->payFailed($message['out_trade_no'], [ + 'pay_sn' => $message['transaction_id'] ?? null, + 'failed_reason' => '['.$message['err_code'].']'.$message['err_code_des'], + ]); + } + + return $payService->paySuccess($message['out_trade_no'], [ + 'out_trade_no' => $message['transaction_id'], 'pay_at' => Carbon::parse($message['time_end']), ]); }); - OrderPaid::dispatchIf($order->isPaid(), $order); + if ($payLog->payable instanceof Order) { + OrderPaid::dispatchIf($payLog->payable->isPaid(), $payLog->payable); + } } catch (ModelNotFoundException | BizException $e) { } catch (Throwable $e) { throw $e; @@ -65,8 +67,6 @@ class WeChatPayController extends Controller public function orderRefundedNotify(WeChatPayService $weChatPayService) { return $weChatPayService->handleRefundedNotify(function ($message, $reqInfo, $fail) { - $this->log('order refunded notify', $reqInfo); - // 通信失败 if (data_get($message, 'return_code') !== 'SUCCESS') { return $fail('通信失败'); @@ -85,19 +85,4 @@ class WeChatPayController extends Controller return true; }); } - - /** - * 微信回调日志 - * - * @param string $message - * @param array $context - * @return void - */ - protected function log(string $message, array $context = []) - { - return Log::build([ - 'driver' => 'daily', - 'path' => storage_path('logs/wxpay.log'), - ])->info($message, $context); - } } diff --git a/app/Endpoint/Callback/routes.php b/app/Endpoint/Callback/routes.php index d9207f14..d4091b17 100644 --- a/app/Endpoint/Callback/routes.php +++ b/app/Endpoint/Callback/routes.php @@ -7,5 +7,5 @@ use Illuminate\Support\Facades\Route; //快递100物流推送 Route::post('kuaidi100', [Kuaidi100Controller::class, 'notify']); // 微信支付通知 -Route::post('wxpay/order-paid-notify', [WeChatPayController::class, 'orderPaidNotify'])->name('wxpay.order_paid_notify'); +Route::post('wxpay/paid-notify', [WeChatPayController::class, 'paidNotify'])->name('wxpay.paid_notify'); Route::post('wxpay/order-refund-notify', [WeChatPayController::class, 'orderRefundedNotify'])->name('wxpay.order_refund_notify'); diff --git a/app/Helpers/Order.php b/app/Helpers/Order.php deleted file mode 100644 index f2ced01d..00000000 --- a/app/Helpers/Order.php +++ /dev/null @@ -1,16 +0,0 @@ -order; + + if (is_null($order)) { + return; + } + + // 处理购买分区商品送券 + try { + DB::beginTransaction(); + + $products = $order->products()->with('sku.parts')->get(); + + // 整理订单商品的分区 + $inValidParts = []; + + foreach ($products->pluck('sku.parts') as $parts) { + foreach ($parts as $part) { + if ($part->is_show) { + // 分区去重 + $inValidParts[$part->id] = $part; + } + } + } + + foreach ($inValidParts as $inValidPart) { + $this->couponService->receivePartCoupon($inValidPart, $order->user); + } + + DB::commit(); + } catch (Throwable $th) { + DB::rollBack(); + + report($th); + } + } +} diff --git a/app/Models/Order.php b/app/Models/Order.php index 7fd2b682..c972cb8e 100644 --- a/app/Models/Order.php +++ b/app/Models/Order.php @@ -36,7 +36,6 @@ class Order extends Model public const PAY_WAY_WALLET = 'wallet'; // 钱包 public const PAY_WAY_BALANCE = 'balance'; // 余额 public const PAY_WAY_OFFLINE = 'offline'; // 现金支付 - public const PAY_WAY_NONE = 'none'; // 无 /** * @var array @@ -74,6 +73,7 @@ class Order extends Model 'pay_sn', 'pay_way', 'pay_at', + 'out_trade_no', 'consignee_name', 'consignee_telephone', 'consignee_zone', @@ -154,6 +154,14 @@ class Order extends Model return $this->belongsToMany(Tag::class, 'taggables', 'taggable_id', 'tag_id')->wherePivot('taggable_type', self::class)->withTimestamps(); } + /** + * 属于此订单的支付记录 + */ + public function payLogs() + { + return $this->morphMany(PayLog::class, 'payable'); + } + /** * 此订单是否待付款 * diff --git a/app/Models/PayLog.php b/app/Models/PayLog.php new file mode 100644 index 00000000..658dbf25 --- /dev/null +++ b/app/Models/PayLog.php @@ -0,0 +1,63 @@ + self::STATUS_PENDING, + ]; + + /** + * @var array + */ + protected $casts = [ + 'pay_at' => 'datetime', + ]; + + /** + * @var array + */ + protected $fillable = [ + 'pay_sn', + 'pay_way', + 'pay_at', + 'out_trade_no', + 'status', + 'failed_reason', + ]; + + /** + * 获取支付记录所属的模型 + */ + public function payable() + { + return $this->morphTo(); + } + + /** + * 获取支付记录是否是待付款 + */ + public function isPending() + { + return $this->status === static::STATUS_PENDING; + } +} diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index e924056c..9dc06795 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -18,6 +18,7 @@ class EventServiceProvider extends ServiceProvider ], \App\Events\OrderPaid::class => [ \App\Listeners\OrderPaidNotify::class, + \App\Listeners\SendCoupons::class, ], ]; diff --git a/app/Services/OrderService.php b/app/Services/OrderService.php index 773b9514..667ce550 100644 --- a/app/Services/OrderService.php +++ b/app/Services/OrderService.php @@ -9,7 +9,6 @@ use App\Endpoint\Api\Http\Resources\UserCouponResource; use App\Exceptions\BizException; use App\Exceptions\ShippingNotSupportedException; use App\Helpers\Numeric; -use App\Helpers\Order as OrderHelper; use App\Models\DistributionPreIncomeJob; use App\Models\Order; use App\Models\OrderProduct; @@ -20,7 +19,6 @@ use App\Models\User; use App\Models\UserCoupon; use Illuminate\Database\QueryException; use Illuminate\Support\Facades\DB; -use Throwable; class OrderService { @@ -144,7 +142,7 @@ class OrderService $totalAmount += $shippingFee; $orderAttrs = [ - 'sn' => OrderHelper::serialNumber(), + 'sn' => serial_number(), 'user_coupon_id' => $coupon?->id, 'coupon_discount_amount' => $couponDiscountAmount, 'vip_discount_amount' => $vipDiscountAmount, @@ -162,8 +160,8 @@ class OrderService if ($totalAmount === 0) { $orderAttrs = array_merge([ 'status' => Order::STATUS_PAID, - 'pay_sn' => OrderHelper::serialNumber(), - 'pay_way' => Order::PAY_WAY_NONE, + 'pay_sn' => serial_number(), + 'pay_way' => Order::PAY_WAY_BALANCE, 'pay_at' => now(), ]); } @@ -708,20 +706,24 @@ class OrderService throw new BizException('订单状态不是待付款'); } - if (in_array($payWay, $this->wxpayWays)) { - return (new WeChatPayService())->pay([ - 'attach' => json_encode([ - 'pay_way' => $payWay, - ]), - 'body' => app_settings('app.app_name').'-商城订单', - 'out_trade_no' => $order->sn, - 'total_fee' => $order->total_amount, - 'notify_url' => url(route('wxpay.order_paid_notify', [], false), [], true), - 'trade_type' => PayWay::$wxpayTradeTypes[$payWay], - ]); - } + $payLog = $order->payLogs()->create([ + 'pay_sn' => serial_number(), + 'pay_way' => $payWay, + ]); - throw new BizException('支付方式不支持'); + switch ($payWay) { + case PayWay::WXPAY_APP: + case PayWay::WXPAY_H5: + case PayWay::WXPAY_JSAPI: + case PayWay::WXPAY_MINI: + return (new WeChatPayService())->pay([ + 'body' => app_settings('app_name').'-商城订单', + 'out_trade_no' => $payLog->pay_sn, + 'total_fee' => $order->total_amount, + 'trade_type' => PayWay::$wxpayTradeTypes[$payWay], + ]); + break; + } } /** @@ -731,10 +733,8 @@ class OrderService * @param array $params * @return \App\Models\Order */ - public function paySuccess(string $sn, array $params = []): Order + public function paySuccess(Order $order, array $params = []): Order { - $order = Order::where('sn', $sn)->firstOrFail(); - if (! $order->isPending()) { throw new BizException('订单状态不是待支付'); } @@ -743,19 +743,10 @@ class OrderService 'pay_sn' => $params['pay_sn'], 'pay_way' => $params['pay_way'], 'pay_at' => $params['pay_at'], + 'out_trade_no' => $params['out_trade_no'], 'status' => Order::STATUS_PAID, ]); - // 处理购买分区商品送券 - try { - DB::beginTransaction(); - $this->sendPartCoupon($order); - DB::commit(); - } catch (Throwable $th) { - DB::rollBack(); - report($th); - } - DistributionPreIncomeJob::create([ 'jobable_id' => $order->id, 'jobable_type' => $order->getMorphClass(), @@ -803,7 +794,7 @@ class OrderService if ($order->isWaitShipping()) { $refundLog = $order->refundLogs()->create([ - 'sn' => OrderHelper::serialNumber(), + 'sn' => serial_number(), 'amount' => $order->total_amount, 'reason' => '取消订单', ]); @@ -857,31 +848,4 @@ class OrderService 'status' => Order::STATUS_CANCELLED, ]); } - - /** - * 发送分区优惠券 - * - * @param Order $order - * @return void - */ - protected function sendPartCoupon(Order $order) - { - $products = $order->products()->with('sku.parts')->get(); - // 整理订单商品的分区 - $inValidParts = []; - foreach ($products->pluck('sku.parts') as $parts) { - foreach ($parts as $part) { - if ($part->is_show) { - // 分区去重 - $inValidParts[$part->id] = $part; - } - } - } - - // 将有效的分区丢进去领券 - $couponService = new CouponService(); - foreach ($inValidParts as $inValidPart) { - $couponService->receivePartCoupon($inValidPart, $order->user); - } - } } diff --git a/app/Services/PayService.php b/app/Services/PayService.php new file mode 100644 index 00000000..a67efbd3 --- /dev/null +++ b/app/Services/PayService.php @@ -0,0 +1,86 @@ +firstOrFail(); + + if (! $payLog->isPending()) { + throw new BizException('支付记录状态异常'); + } + + $payLog->update([ + 'pay_at' => $params['pay_at'] ?? now(), + 'out_trade_no' => $params['out_trade_no'] ?? null, + 'status' => PayLog::STATUS_SUCCESS, + ]); + + if ($payLog->payable instanceof Order) { + switch ($payLog->pay_way) { + case PayWay::WXPAY_APP: + case PayWay::WXPAY_H5: + case PayWay::WXPAY_JSAPI: + case PayWay::WXPAY_MINI: + case PayWay::WXPAY_NATIVE: + $payWay = Order::PAY_WAY_WXPAY; + break; + + default: + $payWay = $payLog->pay_way; + break; + } + + (new OrderService())->paySuccess($payLog->payable, [ + 'pay_sn' => $payLog->pay_sn, + 'pay_way' => $payWay, + 'pay_at' => $payLog->pay_at, + 'out_trade_no' => $payLog->out_trade_no, + ]); + } + + return $payLog; + } + + /** + * 支付失败 + * + * @param string $sn + * @param array $params + * @return \App\Models\PayLog + * + * @throws \App\Exceptions\BizException + */ + public function payFailed(string $sn, array $params = []): PayLog + { + $payLog = PayLog::where('pay_sn', $sn)->firstOrFail(); + + if (! $payLog->isPending()) { + throw new BizException('支付记录状态异常'); + } + + $payLog->update([ + 'out_trade_no' => $params['out_trade_no'] ?? null, + 'status' => PayLog::STATUS_FAILED, + 'failed_reason' => $params['failed_reason'] ?? null, + ]); + + return $payLog; + } +} diff --git a/config/wechat.php b/config/wechat.php index d3303768..f1206e16 100644 --- a/config/wechat.php +++ b/config/wechat.php @@ -9,7 +9,7 @@ return [ 'key' => env('WECHAT_PAYMENT_KEY'), 'cert_path' => env('WECHAT_PAYMENT_CERT_PATH'), // 绝对地址 'key_path' => env('WECHAT_PAYMENT_KEY_PATH'), // 绝对地址 - 'notify_url' => env('WECHAT_PAYMENT_NOTIFY_URL'), + 'notify_url' => url(route('wxpay.paid_notify', [], false), [], true), ], ], ]; 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 new file mode 100644 index 00000000..d4acadd2 --- /dev/null +++ b/database/migrations/2021_12_25_171325_create_pay_logs_table.php @@ -0,0 +1,38 @@ +id(); + $table->morphs('payable'); + $table->string('pay_sn')->unique()->comment('支付流水号'); + $table->string('pay_way')->comment('支付方式'); + $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->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('pay_logs'); + } +} diff --git a/database/migrations/2021_12_27_111510_add_trade_out_no_to_orders_table.php b/database/migrations/2021_12_27_111510_add_trade_out_no_to_orders_table.php new file mode 100644 index 00000000..a820daa7 --- /dev/null +++ b/database/migrations/2021_12_27_111510_add_trade_out_no_to_orders_table.php @@ -0,0 +1,32 @@ +string('out_trade_no')->nullable()->comment('外部交易单号'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('orders', function (Blueprint $table) { + $table->dropColumn(['out_trade_no']); + }); + } +}