6
0
Fork 0

批零微信支付

release
李静 2022-02-22 13:59:32 +08:00
parent ce2a438f69
commit 7b7f998645
9 changed files with 339 additions and 28 deletions

View File

@ -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'] ?? '')) {
if (
$payWay === DealerOrder::PAY_WAY_WALLET &&
!$user->wallet?->verifyPassword($input['pay_password'])
) {
throw new PayPasswordIncorrectException();
}
}
try {
DB::beginTransaction();
$orderService->payOrder($order, $input['pay_way'] ?? 'offline', $input['pay_image'] ?? null);
$data = $orderService->pay($order, $payWay, $input['pay_image'] ?? null);
DB::commit();
} catch (BizException $th) {
} catch (Throwable $e) {
DB::rollBack();
throw $th;
} catch (Throwable $th) {
DB::rollBack();
report($th);
throw new BizException('操作失败,请刷新后再试');
report($e);
if (! $e instanceof BizException) {
$e = new BizException('操作失败,请刷新后再试');
}
return response()->noContent();
throw $e;
}
return response()->json($data);
}
/**

View File

@ -0,0 +1,73 @@
<?php
namespace App\Endpoint\Callback\Http\Controllers;
use App\Exceptions\BizException;
use App\Services\PayService;
use App\Services\WeChatPayService;
use EasyWeChat\Factory;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Throwable;
class YzkWeChatPayController extends Controller
{
/**
* 支付结果通知
*
* @return \Illuminate\Http\Response
*/
public function paidNotify()
{
$weChatPayService = new WeChatPayService(Factory::payment(config('wechat.payment.yzk')));
return $weChatPayService->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);
}
}

View File

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

View File

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

View File

@ -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,
];
}
/**
* 确认收款
*

View File

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

View File

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

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddPaySnToDealerOrdersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('dealer_orders', function (Blueprint $table) {
$table->string('pay_sn')->nullable()->comment('支付流水号');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('dealer_orders', function (Blueprint $table) {
$table->dropColumn(['pay_sn']);
});
}
}

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddOutTradeNoToDealerOrdersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('dealer_orders', function (Blueprint $table) {
$table->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']);
});
}
}