6
0
Fork 0

优化微信支付

release
李静 2022-02-25 15:05:31 +08:00
parent f5cbf6a638
commit 50233736c6
13 changed files with 148 additions and 423 deletions

View File

@ -2,14 +2,15 @@
namespace App\Console\Commands;
use App\Enums\PayWay;
use App\Exceptions\BizException;
use App\Models\BalanceLog;
use App\Models\OrderRefundLog;
use App\Models\WalletLog;
use App\Services\AlipayService;
use App\Services\BalanceService;
use App\Services\Payment\WxpayService;
use App\Services\WalletService;
use App\Services\WeChatPayService;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
use Throwable;
@ -43,7 +44,11 @@ class OrderRefundCommand extends Command
OrderRefundLog::pending()->chunkById(200, function ($logs) use (&$page) {
foreach ($logs as $log) {
try {
$method = 'refundBy'.Str::studly($log->order->pay_way->value);
$method = match ($log->order->pay_way) {
PayWay::WxpayApp, PayWay::WxpayH5, PayWay::WxpayJsApi, PayWay::WxpayMiniProgram => 'refundByWxpay',
PayWay::AlipayApp => 'refundByAlipay',
default => 'refundBy'.Str::studly($log->order->pay_way->value),
};
if (! method_exists($this, $method)) {
throw new BizException('退款方式暂不支持');
@ -79,20 +84,22 @@ class OrderRefundCommand extends Command
* @param \App\Models\OrderRefundLog $log
* @return void
*/
protected function refundByWxpayApp(OrderRefundLog $log)
protected function refundByWxpay(OrderRefundLog $log)
{
$order = $log->order;
(new WeChatPayService())->refundByOutTradeNumber(
$order->pay_sn,
$log->sn,
$order->total_amount,
$log->amount,
[
(new WxpayService())->refundByOutTradeNumber([
'number' => $order->pay_sn,
'refund_number' => $log->sn,
'total_fee' => $order->total_amount,
'refund_fee' => $log->amount,
'optional' => [
'refund_desc' => $log->reason,
'notify_url' => url(route('wxpay.order_refund_notify', [], false), [], true),
]
);
],
], match ($order->pay_way) {
PayWay::WxpayMiniProgram => 'mini_program',
default => 'default',
});
$log->update([
'status' => OrderRefundLog::STATUS_SUCCESS,
@ -106,7 +113,7 @@ class OrderRefundCommand extends Command
* @param \App\Models\OrderRefundLog $log
* @return void
*/
protected function refundByAlipayApp(OrderRefundLog $log)
protected function refundByAlipay(OrderRefundLog $log)
{
$order = $log->order;

View File

@ -4,6 +4,7 @@ namespace App\Endpoint\Api\Http\Controllers\Auth;
use App\Constants\Device;
use App\Endpoint\Api\Http\Controllers\Controller;
use App\Enums\SocialiteType;
use App\Exceptions\BizException;
use App\Helpers\PhoneNumber;
use App\Models\SmsCode;
@ -135,7 +136,7 @@ class SocialiteAuthController extends Controller
$config = config('socialite', []);
$socialite = new SocialiteManager($config);
switch ($provider) {
case 'wechat-mini'://微信小程序
case SocialiteType::WechatMiniProgram://微信小程序
$user = $socialite->create('wehcat-mini')->userFromCode($code);
break;
default:

View File

@ -1,110 +0,0 @@
<?php
namespace App\Endpoint\Callback\Http\Controllers;
use App\Events\OrderPaid;
use App\Exceptions\BizException;
use App\Models\Order;
use App\Models\OrderRefundLog;
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 paidNotify(WeChatPayService $weChatPayService)
{
return $weChatPayService->handlePaidNotify(function ($message, $fail) {
$this->log('paid notify', $message);
// 通信失败
if (data_get($message, 'return_code') !== 'SUCCESS') {
return $fail('通信失败');
}
try {
$payLog = 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'],
]);
}
});
$payable = $payLog?->payable;
if ($payable instanceof Order) {
OrderPaid::dispatchIf($payable->isPaid(), $payable);
}
} catch (ModelNotFoundException | BizException $e) {
} catch (Throwable $e) {
throw $e;
}
return true;
});
}
/**
* 订单退款结果通知
*
* @param \App\Services\WeChatPayService $weChatPayService
* @return \Illuminate\Http\Response
*/
public function orderRefundedNotify(WeChatPayService $weChatPayService)
{
return $weChatPayService->handleRefundedNotify(function ($message, $reqInfo, $fail) {
$this->log('refund notify', $reqInfo);
// 通信失败
if (data_get($message, 'return_code') !== 'SUCCESS') {
return $fail('通信失败');
}
// 退款失败
if ($reqInfo['refund_status'] !== 'SUCCESS') {
$log = OrderRefundLog::where('sn', $reqInfo['out_refund_no'])->first();
$log?->update([
'status' => OrderRefundLog::STATUS_FAILED,
'failed_reason' => $reqInfo['refund_status'] === 'CHANGE' ? '退款异常' : '退款关闭',
]);
}
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-notify.log'),
])->info($message, $context);
}
}

View File

@ -9,10 +9,8 @@ use App\Models\OrderRefundLog;
use App\Services\Payment\WxpayService;
use App\Services\PayService;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Throwable;
class WxpayController extends Controller
@ -20,13 +18,14 @@ class WxpayController extends Controller
/**
* 支付结果通知
*
* @param string $payment
* @param \App\Services\Payment\WxpayService $wxpayService
* @return \Illuminate\Http\Response
*/
public function paidNotify(WxpayService $wxpayService, Request $request)
public function paidNotify($payment, WxpayService $wxpayService)
{
return $wxpayService->payment()->handlePaidNotify(function ($message, $fail) use ($request) {
$this->log('paid notify--------'.$request->url(), $message);
return $wxpayService->payment($payment)->handlePaidNotify(function ($message, $fail) {
$this->log('paid notify', $message);
// 通信失败
if (data_get($message, 'return_code') !== 'SUCCESS') {
@ -67,12 +66,13 @@ class WxpayController extends Controller
/**
* 订单退款结果通知
*
* @param string $payment
* @param \App\Services\Payment\WxpayService $wxpayService
* @return \Illuminate\Http\Response
*/
public function orderRefundedNotify(WxpayService $wxpayService)
public function orderRefundedNotify($payment, WxpayService $wxpayService)
{
return $wxpayService->payment()->handleRefundedNotify(function ($message, $reqInfo, $fail) {
return $wxpayService->payment($payment)->handleRefundedNotify(function ($message, $reqInfo, $fail) {
$this->log('refund notify', $reqInfo);
// 通信失败
@ -93,19 +93,4 @@ class WxpayController 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-notify.log'),
])->info($message, $context);
}
}

View File

@ -1,73 +0,0 @@
<?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

@ -2,17 +2,14 @@
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\WxpayController;
use App\Endpoint\Callback\Http\Controllers\YzkWeChatPayController;
use Illuminate\Support\Facades\Route;
//快递100物流推送
Route::post('kuaidi100', [Kuaidi100Controller::class, 'notify']);
// 微信支付通知
Route::post('wxpay/paid-notify', [WxpayController::class, 'paidNotify'])->name('wxpay.paid_notify');
Route::post('wxpay/order-refund-notify', [WeChatPayController::class, 'orderRefundedNotify'])->name('wxpay.order_refund_notify');
Route::post('wxpay/{payment}/paid-notify', [WxpayController::class, 'paidNotify'])->name('wxpay.paid_notify');
Route::post('wxpay/order-refund-notify', [WxpayController::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

@ -0,0 +1,7 @@
<?php
namespace App\Enums;
enum SocialiteType: string {
case WechatMiniProgram = 'wechat-mini';
}

View File

@ -224,6 +224,11 @@ class User extends Model implements AuthorizableContract, AuthenticatableContrac
return $this->hasMany(DealerUserProductLog::class, 'user_id');
}
public function socialites()
{
return $this->hasMany(SocialiteUser::class, 'user_id');
}
/**
* 确认此用户是否是 VIP
*

View File

@ -15,9 +15,8 @@ use App\Models\DealerProduct;
use App\Models\DealerUserProductLog;
use App\Models\ShippingAddress;
use App\Models\User;
use App\Services\Payment\WxpayService;
use App\Services\PayService;
use App\Services\WeChatPayService;
use EasyWeChat\Factory as EasyWeChatFactory;
use Illuminate\Database\QueryException;
class OrderService
@ -351,15 +350,15 @@ class OrderService
throw new BizException('支付方式 非法');
}
$app = EasyWeChatFactory::payment(config('wechat.payment.yzk'));
$data = (new WeChatPayService($app))->pay([
$params = [
'body' => app_settings('app.app_name').'-批零订单',
'out_trade_no' => $payLog->pay_sn,
'total_fee' => bcmul($order->total_amount, '100'),
'trade_type' => $tradeType->value,
'notify_url' => url(route('yzk_wxpay.paid_notify', [], false), [], true),
]);
];
$data = (new WxpayService())->pay($params, 'yzk_h5');
break;
default:

View File

@ -6,6 +6,7 @@ use App\Endpoint\Api\Http\Resources\ProductSkuSimpleResource;
use App\Endpoint\Api\Http\Resources\ShippingAddressResource;
use App\Endpoint\Api\Http\Resources\UserCouponResource;
use App\Enums\PayWay;
use App\Enums\SocialiteType;
use App\Enums\WxpayTradeType;
use App\Exceptions\BizException;
use App\Exceptions\ShippingNotSupportedException;
@ -15,6 +16,7 @@ use App\Models\OrderProduct;
use App\Models\ProductGift;
use App\Models\ProductSku;
use App\Models\ShippingAddress;
use App\Models\SocialiteUser;
use App\Models\User;
use App\Models\UserCoupon;
use App\Services\Payment\WxpayService;
@ -781,16 +783,17 @@ class OrderService
}
} while ($payLog === null);
$data = [
'pay_sn' => $payLog->pay_sn,
];
// 如果支付方式为线下支付,或支付金额为 0则按支付完成处理
if ($order->total_amount === 0 || $payLog->isOffline()) {
(new PayService())->handleSuccess($payLog, [
'pay_at' => now(),
]);
return [
'pay_sn' => $payLog->pay_sn,
'data' => null,
];
$data = null;
} elseif ($payLog->isWxpay()) {
if (is_null($tradeType = WxpayTradeType::tryFromPayWay($payLog->pay_way))) {
throw new BizException('支付方式 非法');
@ -804,13 +807,24 @@ class OrderService
];
if ($tradeType === WxpayTradeType::JSAPI) {
$params['openid'] = 'o-BdO46p2hRnk-SQIXa1TEdBFtfs';
$socialite = match ($payLog->pay_way) {
PayWay::WxpayMiniProgram => SocialiteUser::where([
'user_id' => $order->user_id,
'socialite_type' => SocialiteType::WechatMiniProgram,
])->first(),
};
if ($socialite === null) {
throw new BizException('未绑定微信小程序');
}
$params['openid'] = $socialite->socialite_id;
}
return [
'pay_sn' => $payLog->pay_sn,
'data' => (new WxpayService())->pay($params),
];
$data = (new WxpayService())->pay($params, match ($payLog->pay_way) {
PayWay::WxpayMiniProgram => 'mini_program',
default => 'default',
});
} elseif ($payLog->isAlipay()) {
$params = [
'subject' => app_settings('app.app_name').'-商城订单',
@ -818,13 +832,13 @@ class OrderService
'total_amount' => bcdiv($order->total_amount, 100, 2),
];
return [
'pay_sn' => $payLog->pay_sn,
'data' => app(AlipayService::class)->pay($params),
];
$data = app(AlipayService::class)->pay($params);
}
throw new BizException('【-1】支付方式 非法');
return [
'pay_way' => $payLog->pay_way,
'data' => $data,
];
}
/**

View File

@ -19,19 +19,22 @@ class WxpayService
* 支付
*
* @param array $params
* @param string|null $payment
* @return array
*/
public function pay(array $params, string $paymentName = null): array
public function pay(array $params, ?string $payment = null): array
{
if (! isset($params['notify_url'])) {
$params['notify_url'] = url(route('wxpay.paid_notify', ['payment' => $paymentName], false), [], true);
$path = route('wxpay.paid_notify', ['payment' => $payment ?: 'default'], false);
$params['notify_url'] = url($path, [], true);
}
if (! isset($params['trade_type'])) {
$params['trade_type'] = WxpayTradeType::App->value;
}
$app = $this->payment($paymentName);
$app = $this->payment($payment);
$result = $app->order->unify($params);
@ -44,6 +47,45 @@ class WxpayService
};
}
/**
* 根据商户订单号退款
*
* @param string $number
* @param string $refundNumber
* @param int $totalFee
* @param int $refundFee
* @param array $optional
* @return array
*/
public function refundByOutTradeNumber(array $params, ?string $payment = null)
{
$optional = Arr::get($params, 'optional', []);
if (! is_array($optional)) {
$optional = [];
}
if (! isset($optional['notify_url'])) {
$path = route('wxpay.order_refund_notify', [
'payment' => $payment ?: 'default',
], false);
$optional['notify_url'] = url($path, [], true);
}
$result = $this->payment($payment)->refund->byOutTradeNumber(
$params['number'],
$params['refund_number'],
$params['total_fee'],
$params['refund_fee'],
$optional,
);
$this->validateResult($result, $params);
return $result;
}
/**
* @param string|null $name
* @return \EasyWeChat\Payment\Application
@ -100,7 +142,7 @@ class WxpayService
throw new WeChatPayException(sprintf(
'[%s] %s',
Arr::get($result, 'err_code', '-1'),
Arr::get($result, 'err_code_des', '结果异常')
Arr::get($result, 'err_code_des', '交易失败')
), $raw);
}
}

View File

@ -1,172 +0,0 @@
<?php
namespace App\Services;
use App\Exceptions\WeChatPayException;
use Closure;
use EasyWeChat\Factory;
use EasyWeChat\Payment\Application;
use Illuminate\Support\Arr;
class WeChatPayService
{
/**
* 小程序交易类型
* https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_2
*/
public const TRADE_TYPE_JSAPI = 'JSAPI'; // JSAPI支付
public const TRADE_TYPE_APP = 'APP'; // App支付
public const TRADE_TYPE_NATIVE = 'NATIVE'; // Native支付
public const TRADE_TYPE_H5 = 'MWEB'; // H5支付
/**
* @var \EasyWeChat\Payment\Application
*/
protected $app;
/**
* @param \EasyWeChat\Payment\Application|null $app
*/
public function __construct(?Application $app = null)
{
if ($app === null) {
$app = Factory::payment(config('wechat.payment.default'));
}
$this->app = $app;
}
/**
* 支付
*
* @param array $params
* @return array
*
* @throws \App\Exceptions\WeChatPayException
*/
public function pay(array $params)
{
if (! isset($params['notify_url'])) {
$params['notify_url'] = url(route('wxpay.paid_notify', [], false), [], true);
}
// 如果交易类型不存在,则使用 App 支付
if (! isset($params['trade_type'])) {
$params['trade_type'] = static::TRADE_TYPE_APP;
}
$result = $this->app->order->unify($params);
if (data_get($result, 'return_code') !== 'SUCCESS') {
throw new WeChatPayException(
data_get($result, 'return_msg', '请求失败'),
$params
);
}
if (data_get($result, 'result_code') !== 'SUCCESS') {
throw new WeChatPayException(
sprintf(
'[%s] %s',
data_get($result, 'err_code', '-1'),
data_get($result, 'err_code_des', '交易失败')
),
$params
);
}
return match ($params['trade_type']) {
static::TRADE_TYPE_APP => $this->app->jssdk->appConfig($result['prepay_id']),
static::TRADE_TYPE_H5 => Arr::only($result, ['mweb_url']),
default => $this->app->jssdk->bridgeConfig($result['prepay_id'], false),
};
}
/**
* 支付结果通知
*
* @param \Closure $closure
* @return \Symfony\Component\HttpFoundation\Response
*/
public function handlePaidNotify(Closure $closure)
{
return $this->app->handlePaidNotify($closure);
}
/**
* 根据微信订单号退款
*
* @param string $transactionId
* @param string $refundNumber
* @param int $totalFee
* @param int $refundFee
* @param array $optional
* @return array
*/
public function refundByTransactionId(string $transactionId, string $refundNumber, int $totalFee, int $refundFee, array $optional = [])
{
$result = $this->app->refund->byTransactionId($transactionId, $refundNumber, $totalFee, $refundFee, $optional);
if (data_get($result, 'return_code') !== 'SUCCESS') {
throw new WeChatPayException(
data_get($result, 'return_msg', '请求失败')
);
}
if (data_get($result, 'result_code') !== 'SUCCESS') {
throw new WeChatPayException(
sprintf(
'[%s] %s',
data_get($result, 'err_code', '-1'),
data_get($result, 'err_code_des', '退款失败')
)
);
}
return $result;
}
/**
* 根据商户订单号退款
*
* @param string $number
* @param string $refundNumber
* @param int $totalFee
* @param int $refundFee
* @param array $optional
* @return array
*/
public function refundByOutTradeNumber(string $number, string $refundNumber, int $totalFee, int $refundFee, array $optional = [])
{
$result = $this->app->refund->byOutTradeNumber($number, $refundNumber, $totalFee, $refundFee, $optional);
if (data_get($result, 'return_code') !== 'SUCCESS') {
throw new WeChatPayException(
data_get($result, 'return_msg', '请求失败')
);
}
if (data_get($result, 'result_code') !== 'SUCCESS') {
throw new WeChatPayException(
sprintf(
'[%s] %s',
data_get($result, 'err_code', '-1'),
data_get($result, 'err_code_des', '退款失败')
)
);
}
return $result;
}
/**
* 退款结果通知
*
* @param \Closure $closure
* @return \Symfony\Component\HttpFoundation\Response
*/
public function handleRefundedNotify(Closure $closure)
{
return $this->app->handleRefundedNotify($closure);
}
}

View File

@ -50,6 +50,7 @@ return [
],
],
'payment' => [
// 商城 - 微信 App支付
'default' => [
'sandbox' => env('WECHAT_PAYMENT_SANDBOX', false),
'app_id' => env('WECHAT_PAYMENT_APPID'),
@ -71,7 +72,29 @@ return [
],
],
],
'yzk' => [
// 商城 - 微信小程序支付
'mini_program' => [
'sandbox' => env('WECHAT_PAYMENT_SANDBOX', false),
'app_id' => env('WECHAT_MINI_PROGRAM_APPID'),
'mch_id' => env('WECHAT_PAYMENT_MCH_ID'),
'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'),
// 日志
'log' => [
'default' => 'daily',
'channels' => [
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/wxpay-mini-program.log'),
'level' => 'info',
],
],
],
],
'yzk_h5' => [
'sandbox' => env('WECHAT_PAYMENT_SANDBOX_YZK', false),
'app_id' => env('WECHAT_PAYMENT_APPID_YZK'),
'mch_id' => env('WECHAT_PAYMENT_MCH_ID_YZK'),