微信支付
parent
959f8d3503
commit
f0c94a6356
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
class Order
|
||||
{
|
||||
/**
|
||||
* 生成订单流水号
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function serialNumber(): string
|
||||
{
|
||||
return date('YmdHis').sprintf('%06d', mt_rand(1, 999999));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\OrderPaid;
|
||||
use App\Services\CouponService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Throwable;
|
||||
|
||||
class SendCoupons
|
||||
{
|
||||
/**
|
||||
* @param \App\Services\CouponService $couponService
|
||||
*/
|
||||
public function __construct(
|
||||
public CouponService $couponService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param \App\Events\OrderPaid $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle(OrderPaid $event)
|
||||
{
|
||||
$order = $event->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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
* 此订单是否待付款
|
||||
*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PayLog extends Model
|
||||
{
|
||||
/**
|
||||
* 支付状态
|
||||
*/
|
||||
public const STATUS_PENDING = 0; // 待付款
|
||||
public const STATUS_SUCCESS = 1; // 成功
|
||||
public const STATUS_FAILED = 2; // 失败
|
||||
|
||||
/**
|
||||
* 支付方式
|
||||
*/
|
||||
public const PAY_WAY_WXPAY = 'wxpay'; // 微信支付
|
||||
public const PAY_WAY_ALIPAY = 'alipay'; // 支付宝
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $attributes = [
|
||||
'status' => 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ class EventServiceProvider extends ServiceProvider
|
|||
],
|
||||
\App\Events\OrderPaid::class => [
|
||||
\App\Listeners\OrderPaidNotify::class,
|
||||
\App\Listeners\SendCoupons::class,
|
||||
],
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Constants\PayWay;
|
||||
use App\Exceptions\BizException;
|
||||
use App\Models\Order;
|
||||
use App\Models\PayLog;
|
||||
|
||||
class PayService
|
||||
{
|
||||
/**
|
||||
* 支付成功
|
||||
*
|
||||
* @param string $sn
|
||||
* @param array $params
|
||||
* @return \App\Models\PayLog
|
||||
*
|
||||
* @throws \App\Exceptions\BizException
|
||||
*/
|
||||
public function paySuccess(string $sn, array $params = []): PayLog
|
||||
{
|
||||
$payLog = PayLog::where('pay_sn', $sn)->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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
],
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreatePayLogsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('pay_logs', function (Blueprint $table) {
|
||||
$table->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');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddTradeOutNoToOrdersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('orders', function (Blueprint $table) {
|
||||
$table->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']);
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue