480 lines
14 KiB
PHP
Executable File
480 lines
14 KiB
PHP
Executable File
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Utils\ClassHelper;
|
|
use Psr\Http\Message\ResponseInterface;
|
|
use voku\helper\AntiXSS;
|
|
use App\Models\Invoice;
|
|
use App\Models\Order;
|
|
use App\Models\PlanPending;
|
|
use App\Models\Product;
|
|
use App\Models\UserMoneyLog;
|
|
use App\Models\UserCoupon;
|
|
use App\Services\Auth;
|
|
|
|
final class Payment
|
|
{
|
|
public static function getAllPaymentMap(): array
|
|
{
|
|
$payments = [];
|
|
|
|
$helper = new ClassHelper();
|
|
$class_list = $helper->getClassesByNamespace('\\App\\Services\\Gateway\\');
|
|
|
|
foreach ($class_list as $class) {
|
|
if (get_parent_class($class) === 'App\\Services\\Gateway\\Base') {
|
|
$payments[] = $class;
|
|
}
|
|
}
|
|
|
|
return $payments;
|
|
}
|
|
|
|
public static function getPaymentsEnabled(): array
|
|
{
|
|
return array_values(array_filter(Payment::getAllPaymentMap(), static function ($payment) {
|
|
return $payment::_enable();
|
|
}));
|
|
}
|
|
|
|
public static function getPaymentMap(): array
|
|
{
|
|
$result = [];
|
|
|
|
foreach (self::getPaymentsEnabled() as $payment) {
|
|
$result[$payment::_name()] = $payment;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
public static function getPaymentByName($name)
|
|
{
|
|
$all = self::getPaymentMap();
|
|
|
|
return $all[$name];
|
|
}
|
|
|
|
public static function notify($request, $response, $args)
|
|
{
|
|
$payment = self::getPaymentByName($args['type']);
|
|
|
|
if ($payment !== null) {
|
|
$instance = new $payment();
|
|
return $instance->notify($request, $response, $args);
|
|
}
|
|
|
|
return $response->withStatus(404);
|
|
}
|
|
|
|
public static function returnHTML($request, $response, $args): string|ResponseInterface
|
|
{
|
|
$payment = self::getPaymentByName($args['type']);
|
|
|
|
if ($payment !== null) {
|
|
$instance = new $payment();
|
|
return $instance->getReturnHTML($request, $response, $args);
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
public static function getStatus($request, $response, $args)
|
|
{
|
|
$payment = self::getPaymentByName($args['type']);
|
|
|
|
if ($payment !== null) {
|
|
$instance = new $payment();
|
|
return $instance->getStatus($request, $response, $args);
|
|
}
|
|
|
|
return $response->withStatus(404);
|
|
}
|
|
|
|
public static function payBalance($oThisUser, $oThisInvoice)
|
|
{
|
|
|
|
if ($oThisInvoice === null) {
|
|
return [
|
|
'ret' => 0,
|
|
'msg' => '账单不存在',
|
|
];
|
|
}
|
|
|
|
if ($oThisUser->is_shadow_banned) {
|
|
return [
|
|
'ret' => 0,
|
|
'msg' => '支付失败,请稍后再试',
|
|
];
|
|
}
|
|
|
|
if ($oThisUser->money < $oThisInvoice->price) {
|
|
return [
|
|
'ret' => 0,
|
|
'msg' => '余额不足',
|
|
];
|
|
}
|
|
|
|
$money_before = $oThisUser->money;
|
|
$oThisUser->money -= $oThisInvoice->price;
|
|
$oThisUser->save();
|
|
|
|
(new UserMoneyLog())->add(
|
|
$oThisUser->id,
|
|
$money_before,
|
|
(float) $oThisUser->money,
|
|
-$oThisInvoice->price,
|
|
'购买套餐 #' . $oThisInvoice->id
|
|
);
|
|
|
|
$oThisInvoice->status = 'paid_balance';
|
|
$oThisInvoice->update_time = time();
|
|
$oThisInvoice->pay_time = time();
|
|
$oThisInvoice->money_use = $oThisInvoice->price;
|
|
$oThisInvoice->save();
|
|
|
|
return [
|
|
'ret' => 1,
|
|
'msg' => '支付成功',
|
|
];
|
|
}
|
|
|
|
public static function code($request, $response, $args)
|
|
{
|
|
$oAntiXss = new AntiXSS();
|
|
|
|
$type_pay = "epay"; // epay, balance=本地钱包
|
|
$redir = $oAntiXss->xss_clean($request->getParam('redir'));
|
|
$type_onlinePay = $oAntiXss->xss_clean($request->getParam('payType'));
|
|
$iAmount = $oAntiXss->xss_clean($request->getParam('amount') ?? 0);
|
|
$iAmount = (int) $iAmount;
|
|
|
|
if ($iAmount < 1) {
|
|
return $response->withJson([
|
|
'ret' => 0,
|
|
'msg' => '金额不能小于1',
|
|
]);
|
|
}
|
|
|
|
$user = Auth::getUser();
|
|
|
|
$order = new Order();
|
|
$order->user_id = $user->id;
|
|
$order->product_code = "code";
|
|
$order->user_product_name = "钱包充值";
|
|
$order->user_price = $iAmount;
|
|
// $order->user_coupon = "";
|
|
$order->status = 'payment_pending';
|
|
$order->create_time = time();
|
|
$order->update_time = time();
|
|
$order->save();
|
|
|
|
|
|
$invoice = new Invoice();
|
|
$invoice->user_id = $user->id;
|
|
$invoice->order_id = $order->id;
|
|
// $invoice->content = json_encode($invoice_content);
|
|
$invoice->price = $iAmount;
|
|
$invoice->status = 'unpaid';
|
|
$invoice->create_time = time();
|
|
$invoice->update_time = time();
|
|
$invoice->pay_time = 0;
|
|
// $invoice->set = $iSet; // 新增
|
|
// $invoice->money_use = $iYuezhifu;
|
|
$invoice->save();
|
|
|
|
|
|
|
|
$args["true_price"] = $iAmount;
|
|
$args["true_invoice_id"] = $invoice->id;
|
|
$args["type"] = $type_onlinePay;
|
|
$args["redir"] = $redir;
|
|
|
|
$payment = self::getPaymentByName($type_pay);
|
|
|
|
$instance = new $payment();
|
|
return $instance->purchase($request, $response, $args);
|
|
|
|
// return $response->withJson([
|
|
// 'ret' => 1,
|
|
// 'msg' => $iAmount,
|
|
// ]);
|
|
}
|
|
|
|
public static function purchase($request, $response, $args)
|
|
{
|
|
|
|
$antiXss = new AntiXSS();
|
|
|
|
$type_pay = $args["type"]; // epay, balance=本地钱包
|
|
|
|
$coupon_raw = $antiXss->xss_clean($request->getParam('coupon'));
|
|
$product_id = $antiXss->xss_clean($request->getParam('shopid'));
|
|
|
|
// 暂时EPay用
|
|
$type_onlinePay = $antiXss->xss_clean($request->getParam('type'));
|
|
$redir = $antiXss->xss_clean($request->getParam('redir'));
|
|
|
|
$user = Auth::getUser();
|
|
|
|
//test
|
|
// tomd("type_pay:".$type_pay);
|
|
// tomd("coupon_raw:".$coupon_raw);
|
|
// tomd("product_id:".$product_id);
|
|
// tomd("autorenew:".$autorenew);
|
|
// tomd("type_onlinePay:".$type_onlinePay);
|
|
// tomd("redir:".$redir);
|
|
// exit;
|
|
|
|
|
|
|
|
////// 先创建订单
|
|
|
|
$product = (new Product())->find($product_id);
|
|
|
|
if ($product === null || $product->stock === 0) {
|
|
return $response->withJson([
|
|
'ret' => 0,
|
|
'msg' => '商品不存在或库存不足',
|
|
]);
|
|
}
|
|
|
|
if ($product->end_time != 0 && $product->end_time < time()) {
|
|
return $response->withJson([
|
|
'ret' => 0,
|
|
'msg' => '商品已过期',
|
|
]);
|
|
}
|
|
|
|
// 此时,商品原价
|
|
$true_price = $product->price;
|
|
|
|
// 用户等级折扣最先算
|
|
$iUserVipRate = $user->getVipRate();
|
|
|
|
$true_price = $true_price * ((100 - $iUserVipRate) / 100);
|
|
|
|
if ($user->is_shadow_banned) {
|
|
return $response->withJson([
|
|
'ret' => 0,
|
|
'msg' => '商品不存在或库存不足',
|
|
]);
|
|
}
|
|
|
|
if ($coupon_raw !== '') {
|
|
|
|
$aPayOrderStatus = ["activated", "pending", "expired"];
|
|
|
|
$coupon = (new UserCoupon())->where('code', $coupon_raw)->first();
|
|
|
|
if ($coupon === null || ($coupon->expire_time !== 0 && $coupon->expire_time < time())) {
|
|
return $response->withJson([
|
|
'ret' => 0,
|
|
'msg' => '优惠码不存在或已过期',
|
|
]);
|
|
}
|
|
|
|
$coupon_limit = json_decode($coupon->limit);
|
|
|
|
if ($coupon_limit->disabled) {
|
|
return $response->withJson([
|
|
'ret' => 0,
|
|
'msg' => '优惠码已被禁用',
|
|
]);
|
|
}
|
|
|
|
if ($product->coupon_disable == 1) {
|
|
return $response->withJson([
|
|
'ret' => 0,
|
|
'msg' => '优惠码不适用于此商品',
|
|
]);
|
|
}
|
|
|
|
|
|
if ($coupon_limit->product_id !== '' && ! in_array($product_id, explode(',', $coupon_limit->product_id))) {
|
|
return $response->withJson([
|
|
'ret' => 0,
|
|
'msg' => '优惠码不适用于此商品',
|
|
]);
|
|
}
|
|
|
|
$coupon_use_limit = $coupon_limit->use_time;
|
|
|
|
if ($coupon_use_limit > 0) {
|
|
$user_use_count = (new Order())->where('user_id', $user->id)->where('user_coupon', $coupon->code)->whereIn("status", $aPayOrderStatus)->count();
|
|
if ($user_use_count >= $coupon_use_limit) {
|
|
return $response->withJson([
|
|
'ret' => 0,
|
|
'msg' => '优惠码使用次数已达上限',
|
|
]);
|
|
}
|
|
}
|
|
|
|
if (property_exists($coupon_limit, 'total_use_time')) {
|
|
$coupon_total_use_limit = $coupon_limit->total_use_time;
|
|
} else {
|
|
$coupon_total_use_limit = -1;
|
|
}
|
|
|
|
if ($coupon_total_use_limit > 0 && $coupon->use_count >= $coupon_total_use_limit) {
|
|
return $response->withJson([
|
|
'ret' => 0,
|
|
'msg' => '优惠码使用次数已达上限',
|
|
]);
|
|
}
|
|
|
|
$content = json_decode($coupon->content);
|
|
|
|
if ($content->type === 'percentage') {
|
|
$discount = $true_price * $content->value / 100;
|
|
} else {
|
|
$discount = $content->value;
|
|
}
|
|
|
|
$true_price = $true_price - $discount;
|
|
}
|
|
|
|
$product_limit = json_decode($product->limit);
|
|
|
|
if ($product_limit->class_required !== '' && $user->class < (int) $product_limit->class_required) {
|
|
return $response->withJson([
|
|
'ret' => 0,
|
|
'msg' => '您的账户等级不足,无法购买此商品',
|
|
]);
|
|
}
|
|
|
|
if ($product_limit->node_group_required !== ''
|
|
&& $user->node_group !== (int) $product_limit->node_group_required) {
|
|
return $response->withJson([
|
|
'ret' => 0,
|
|
'msg' => '您所在的用户组无法购买此商品',
|
|
]);
|
|
}
|
|
|
|
if ($product_limit->new_user_required !== 0) {
|
|
$order_count = (new Order())->where('user_id', $user->id)->whereIn("status", $aPayOrderStatus)->count();
|
|
if ($order_count > 0) {
|
|
return $response->withJson([
|
|
'ret' => 0,
|
|
'msg' => '此商品仅限新用户购买',
|
|
]);
|
|
}
|
|
}
|
|
|
|
// 最后余额抵扣
|
|
$iYuezhifu = 0;
|
|
if ($user->money > 0) { // 如果有余额
|
|
|
|
$iRengxuzhifu = $true_price - $user->money; // 仍需支付
|
|
|
|
if ($iRengxuzhifu <= 0) { // 余额能覆盖支付额,强制走钱包
|
|
$type_pay = "balance";
|
|
} else { // 余额抵付
|
|
$iYuezhifu = $user->money;
|
|
}
|
|
|
|
}
|
|
|
|
$iSet = $user->havePayClass() ? 2 : 1;
|
|
|
|
if ($product->name_sub) {
|
|
$sProductName = $product->name." - ".$product->name_sub;
|
|
} else {
|
|
$sProductName = $product->name;
|
|
}
|
|
|
|
$order = new Order();
|
|
$order->user_id = $user->id;
|
|
$order->product_code = $product->code;
|
|
$order->user_product_name = $sProductName;
|
|
$order->user_price = $true_price;
|
|
$order->user_coupon = $coupon_raw;
|
|
$order->status = 'payment_pending';
|
|
$order->create_time = time();
|
|
$order->update_time = time();
|
|
$order->save();
|
|
|
|
// $invoice_content = [];
|
|
|
|
// $invoice_content[] = [
|
|
// 'content_id' => 0,
|
|
// 'name' => $product->name,
|
|
// 'price' => $product->price,
|
|
// ];
|
|
//
|
|
// if ($coupon_raw !== '') {
|
|
// $invoice_content[] = [
|
|
// 'content_id' => 1,
|
|
// 'name' => '优惠码 ' . $coupon_raw,
|
|
// 'price' => '-' . $discount,
|
|
// ];
|
|
// }
|
|
|
|
$invoice = new Invoice();
|
|
$invoice->user_id = $user->id;
|
|
$invoice->order_id = $order->id;
|
|
// $invoice->content = json_encode($invoice_content);
|
|
$invoice->price = $true_price;
|
|
$invoice->status = 'unpaid';
|
|
$invoice->create_time = time();
|
|
$invoice->update_time = time();
|
|
$invoice->pay_time = 0;
|
|
// $invoice->set = $iSet; // 新增
|
|
$invoice->money_use = $iYuezhifu;
|
|
$invoice->save();
|
|
|
|
if ($type_pay !== "balance" && $iYuezhifu > 0) {
|
|
$user->money = 0;
|
|
$user->save();
|
|
(new UserMoneyLog())->add(
|
|
$user->id,
|
|
$iYuezhifu,
|
|
(float) $user->money,
|
|
-$iYuezhifu,
|
|
'余额抵付 #账单号:'.$invoice->id
|
|
);
|
|
}
|
|
|
|
|
|
// if ($product->stock > 0) {
|
|
// $product->stock -= 1;
|
|
// }
|
|
// $product->sale_count += 1;
|
|
// $product->save();
|
|
|
|
if ($coupon_raw !== '') {
|
|
$coupon->use_count += 1;
|
|
$coupon->save();
|
|
}
|
|
|
|
////// end //////////
|
|
|
|
// 如果是钱包支付,拦截
|
|
if ($type_pay == "balance") {
|
|
$result = self::payBalance($user, $invoice);
|
|
|
|
return $response->withJson($result);
|
|
}
|
|
|
|
$payment = self::getPaymentByName($type_pay);
|
|
|
|
if ($payment !== null) {
|
|
|
|
// if ($type_pay == "epay") {
|
|
$args["true_price"] = $true_price;
|
|
$args["true_invoice_id"] = $invoice->id;
|
|
$args["type"] = $type_onlinePay;
|
|
$args["redir"] = $redir;
|
|
// }
|
|
|
|
$instance = new $payment();
|
|
return $instance->purchase($request, $response, $args);
|
|
}
|
|
|
|
return $response->withStatus(404);
|
|
}
|
|
}
|