From 7b7f998645cb7325f03c0cb37878b415578886ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=9D=99?= Date: Tue, 22 Feb 2022 13:59:32 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=B9=E9=9B=B6=E5=BE=AE=E4=BF=A1=E6=94=AF?= =?UTF-8?q?=E4=BB=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Dealer/OrderController.php | 61 +++++----- .../Controllers/YzkWeChatPayController.php | 73 ++++++++++++ app/Endpoint/Callback/routes.php | 3 + app/Models/DealerOrder.php | 11 ++ app/Services/Dealer/OrderService.php | 106 +++++++++++++++++- app/Services/PayService.php | 28 +++++ config/wechat.php | 21 ++++ ...2419_add_pay_sn_to_dealer_orders_table.php | 32 ++++++ ...dd_out_trade_no_to_dealer_orders_table.php | 32 ++++++ 9 files changed, 339 insertions(+), 28 deletions(-) create mode 100644 app/Endpoint/Callback/Http/Controllers/YzkWeChatPayController.php create mode 100644 database/migrations/2022_02_22_112419_add_pay_sn_to_dealer_orders_table.php create mode 100644 database/migrations/2022_02_22_112425_add_out_trade_no_to_dealer_orders_table.php diff --git a/app/Endpoint/Api/Http/Controllers/Dealer/OrderController.php b/app/Endpoint/Api/Http/Controllers/Dealer/OrderController.php index 89deefdd..824c0607 100644 --- a/app/Endpoint/Api/Http/Controllers/Dealer/OrderController.php +++ b/app/Endpoint/Api/Http/Controllers/Dealer/OrderController.php @@ -212,43 +212,50 @@ class OrderController extends Controller */ public function payOrder($id, Request $request, OrderService $orderService) { - $order = DealerOrder::findOrFail($id); - $userId = $request->user()->id; - //不是采购人 - if (!$order->isUser($userId)) { - throw new BizException('订单未找到'); - } - $input = $request->validate([ 'pay_image' => ['bail', 'string'], 'pay_way' => ['bail', 'string'], - 'pay_password' => ['bail', 'string'], - ], [], [ + 'pay_password' => ['bail', 'required_if:pay_way,wallet'], + ], [ + 'pay_password.required_if' => '支付密码 不能为空。', + ], [ 'pay_image' => '打款凭证', 'pay_way' => '支付方式', 'pay_password' => '支付密码', ]); + + $user = $request->user(); + + $order = $user->dealerOrders()->findOrFail($id); + $payWay = $input['pay_way'] ?? 'offline'; - if ($payWay == DealerOrder::PAY_WAY_WALLET) { - //验证支付密码 - if (! $request->user()->wallet?->verifyPassword($input['pay_password'] ?? '')) { - throw new PayPasswordIncorrectException(); - } - } - try { - DB::beginTransaction(); - $orderService->payOrder($order, $input['pay_way'] ?? 'offline', $input['pay_image'] ?? null); - DB::commit(); - } catch (BizException $th) { - DB::rollBack(); - throw $th; - } catch (Throwable $th) { - DB::rollBack(); - report($th); - throw new BizException('操作失败,请刷新后再试'); + + if ( + $payWay === DealerOrder::PAY_WAY_WALLET && + !$user->wallet?->verifyPassword($input['pay_password']) + ) { + throw new PayPasswordIncorrectException(); } - return response()->noContent(); + try { + DB::beginTransaction(); + + $data = $orderService->pay($order, $payWay, $input['pay_image'] ?? null); + + DB::commit(); + } catch (Throwable $e) { + DB::rollBack(); + + report($e); + + if (! $e instanceof BizException) { + $e = new BizException('操作失败,请刷新后再试'); + } + + throw $e; + } + + return response()->json($data); } /** diff --git a/app/Endpoint/Callback/Http/Controllers/YzkWeChatPayController.php b/app/Endpoint/Callback/Http/Controllers/YzkWeChatPayController.php new file mode 100644 index 00000000..ca5387d4 --- /dev/null +++ b/app/Endpoint/Callback/Http/Controllers/YzkWeChatPayController.php @@ -0,0 +1,73 @@ +handlePaidNotify(function ($message, $fail) { + $this->log('paid notify', $message); + + // 通信失败 + if (data_get($message, 'return_code') !== 'SUCCESS') { + return $fail('通信失败'); + } + + try { + DB::transaction(function () use ($message) { + $payService = new PayService(); + + if (data_get($message, 'result_code') === 'SUCCESS') { + return $payService->handleSuccessByPaySerialNumber($message['out_trade_no'], [ + 'out_trade_no' => $message['transaction_id'], + 'pay_at' => Carbon::parse($message['time_end']), + ]); + } elseif (data_get($message, 'result_code') === 'FAIL') { + return $payService->handleFailedByPaySerialNumber($message['out_trade_no'], [ + 'out_trade_no' => $message['transaction_id'] ?? null, + 'failed_reason' => '['.$message['err_code'].']'.$message['err_code_des'], + ]); + } + }); + } catch (ModelNotFoundException | BizException $e) { + } catch (Throwable $e) { + throw $e; + } + + 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/yzk-wxpay-notify.log'), + ])->info($message, $context); + } +} diff --git a/app/Endpoint/Callback/routes.php b/app/Endpoint/Callback/routes.php index cf361084..871c05c5 100644 --- a/app/Endpoint/Callback/routes.php +++ b/app/Endpoint/Callback/routes.php @@ -3,6 +3,7 @@ use App\Endpoint\Callback\Http\Controllers\AlipayController; use App\Endpoint\Callback\Http\Controllers\Kuaidi100Controller; use App\Endpoint\Callback\Http\Controllers\WeChatPayController; +use App\Endpoint\Callback\Http\Controllers\YzkWeChatPayController; use Illuminate\Support\Facades\Route; //快递100物流推送 @@ -12,3 +13,5 @@ Route::post('wxpay/paid-notify', [WeChatPayController::class, 'paidNotify'])->na Route::post('wxpay/order-refund-notify', [WeChatPayController::class, 'orderRefundedNotify'])->name('wxpay.order_refund_notify'); Route::post('alipay', AlipayController::class)->name('alipay.notify'); + +Route::post('yzk-wxpay/paid-notify', [YzkWeChatPayController::class, 'paidNotify'])->name('yzk_wxpay.paid_notify'); diff --git a/app/Models/DealerOrder.php b/app/Models/DealerOrder.php index 7bd77755..fc5067e6 100644 --- a/app/Models/DealerOrder.php +++ b/app/Models/DealerOrder.php @@ -16,6 +16,7 @@ class DealerOrder extends Model public const PAY_WAY_WALLET = 'wallet'; // 余额 public const PAY_WAY_OFFLINE = 'offline'; // 线下支付 + public const PAY_WAY_WXPAY = 'wxpay'; // 线下支付 protected $attributes = [ 'status' => DealerOrderStatus::Pending, @@ -50,6 +51,8 @@ class DealerOrder extends Model 'shippinged_time', 'allocated_at', //分配时间 'remark', + 'pay_sn', + 'out_trade_no', ]; public static $payWayText = [ @@ -290,4 +293,12 @@ class DealerOrder extends Model } return $payInfo ?: null; } + + /** + * 属于此订单的支付记录 + */ + public function payLogs() + { + return $this->morphMany(PayLog::class, 'payable'); + } } diff --git a/app/Services/Dealer/OrderService.php b/app/Services/Dealer/OrderService.php index 06009c18..247d44ae 100644 --- a/app/Services/Dealer/OrderService.php +++ b/app/Services/Dealer/OrderService.php @@ -11,9 +11,12 @@ use App\Models\DealerOrderAllocateLog; use App\Models\DealerOrderProduct; use App\Models\DealerProduct; use App\Models\DealerUserProductLog; +use App\Models\PayLog; use App\Models\ShippingAddress; use App\Models\User; -use App\Models\UserInfo; +use App\Services\PayService; +use App\Services\WeChatPayService; +use EasyWeChat\Factory as EasyWeChatFactory; use Illuminate\Database\QueryException; class OrderService @@ -264,6 +267,107 @@ class OrderService return $order; } + /** + * 支付订单 + * + * @param DealerOrder $order + * @param string $payWay + * @param string|null $payImage + * @return array + * + * @throws \App\Exceptions\BizException + */ + public function pay(DealerOrder $order, string $payWay, ?string $payImage = null): array + { + if (! $order->isPendinged()) { + throw new BizException('订单状态不是待付款'); + } + + do { + $payLog = null; + + try { + $payLog = $order->payLogs()->create([ + 'pay_sn' => serial_number(), + 'pay_way' => $payWay, + ]); + } catch (QueryException $e) { + if (strpos($e->getMessage(), 'Duplicate entry') === false) { + throw $e; + } + } + } while ($payLog === null); + + $data = [ + 'pay_sn' => $payLog->pay_sn, + ]; + + switch ($payLog->pay_way) { + case PayLog::PAY_WAY_OFFLINE: + (new PayService())->handleSuccess($payLog, [ + 'pay_image' => $payImage, + 'pay_info' => $order->getConsignorPayInfo() ?? null, + 'pay_at' => now(), + ]); + + $data = null; + + break; + + case PayLog::PAY_WAY_WALLET: + $walletService = new WalletService(); + + // 扣除打款人余额 + $walletService->changeBalance( + $order->user, + bcmul($order->total_amount, '-1', 2), + DealerWalletAction::OrderPaid, + "订单:{$order->sn}", + $order + ); + + // 增加收款人余额 + if ($order->consignor) { + $walletService->changeBalance( + $order->consignor, + $order->total_amount, + DealerWalletAction::OrderIncome, + "订单:{$order->sn}", + $order + ); + } + + (new PayService())->handleSuccess($payLog, [ + 'pay_at' => now(), + ]); + + $data = null; + + break; + + case PayLog::PAY_WAY_WXPAY_H5: + $app = EasyWeChatFactory::payment(config('wechat.payment.yzk')); + + $data = (new WeChatPayService($app))->pay([ + 'body' => app_settings('app.app_name').'-批零订单', + 'out_trade_no' => $payLog->pay_sn, + 'total_fee' => bcmul($order->total_amount, '100'), + 'trade_type' => WeChatPayService::$tradeTypes[$payLog->pay_way], + 'notify_url' => url(route('yzk_wxpay.paid_notify', [], false), [], true), + ]); + break; + + default: + throw new BizException('支付方式不支持'); + break; + } + + return [ + 'pay_way' => $payLog->pay_way, + 'data' => $data, + ]; + } + /** * 确认收款 * diff --git a/app/Services/PayService.php b/app/Services/PayService.php index 3ea446f7..7bb17327 100644 --- a/app/Services/PayService.php +++ b/app/Services/PayService.php @@ -2,8 +2,10 @@ namespace App\Services; +use App\Enums\DealerOrderStatus; use App\Exceptions\BizException; use App\Exceptions\InvalidPaySerialNumberException; +use App\Models\DealerOrder; use App\Models\DistributionPreIncomeJob; use App\Models\Order; use App\Models\PayLog; @@ -97,6 +99,32 @@ class PayService 'jobable_type' => $payable->getMorphClass(), 'remarks' => '支付订单', ]); + } elseif ($payable instanceof DealerOrder) { + if (! $payable->isPendinged()) { + throw new BizException('订单不是待打款'); + } + + $payable->pay_sn = $payLog->pay_sn; + $payable->pay_time = $payLog->pay_at; + $payable->out_trade_no = $payLog->out_trade_no; + + if ($payLog->isOffline()) { + $payable->pay_image = $params['pay_image'] ?? null; + $payable->pay_info = $params['pay_info'] ?? null; + $payable->pay_way = DealerOrder::PAY_WAY_OFFLINE; + $payable->status = DealerOrderStatus::Confirming; + } else { + if ($payLog->isWallet()) { + $payable->pay_way = DealerOrder::PAY_WAY_WALLET; + } elseif ($payLog->isWxpay()) { + $payable->pay_way = DealerOrder::PAY_WAY_WXPAY; + } + + $payable->paied_time = $payLog->pay_at; + $payable->status = DealerOrderStatus::Paid; + } + + $payable->save(); } return $payLog; diff --git a/config/wechat.php b/config/wechat.php index ea02f988..6cb7b26d 100644 --- a/config/wechat.php +++ b/config/wechat.php @@ -23,5 +23,26 @@ return [ ], ], ], + 'yzk' => [ + 'sandbox' => env('WECHAT_PAYMENT_SANDBOX_YZK', false), + 'app_id' => env('WECHAT_PAYMENT_APPID_YZK'), + 'mch_id' => env('WECHAT_PAYMENT_MCH_ID_YZK'), + 'key' => env('WECHAT_PAYMENT_KEY_YZK'), + 'cert_path' => env('WECHAT_PAYMENT_CERT_PATH_YZK'), // 绝对地址 + 'key_path' => env('WECHAT_PAYMENT_KEY_PATH_YZK'), // 绝对地址 + 'notify_url' => env('WECHAT_PAYMENT_NOTIFY_URL_YZK'), + + // 日志 + 'log' => [ + 'default' => 'daily', + 'channels' => [ + 'daily' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/yzk-wxpay.log'), + 'level' => 'info', + ], + ], + ], + ], ], ]; diff --git a/database/migrations/2022_02_22_112419_add_pay_sn_to_dealer_orders_table.php b/database/migrations/2022_02_22_112419_add_pay_sn_to_dealer_orders_table.php new file mode 100644 index 00000000..84631036 --- /dev/null +++ b/database/migrations/2022_02_22_112419_add_pay_sn_to_dealer_orders_table.php @@ -0,0 +1,32 @@ +string('pay_sn')->nullable()->comment('支付流水号'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('dealer_orders', function (Blueprint $table) { + $table->dropColumn(['pay_sn']); + }); + } +} diff --git a/database/migrations/2022_02_22_112425_add_out_trade_no_to_dealer_orders_table.php b/database/migrations/2022_02_22_112425_add_out_trade_no_to_dealer_orders_table.php new file mode 100644 index 00000000..624ba5b7 --- /dev/null +++ b/database/migrations/2022_02_22_112425_add_out_trade_no_to_dealer_orders_table.php @@ -0,0 +1,32 @@ +string('out_trade_no')->nullable()->comment('外部交易单号'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('dealer_orders', function (Blueprint $table) { + $table->dropColumn(['out_trade_no']); + }); + } +}