538 lines
21 KiB
PHP
538 lines
21 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
if (defined('GSP_WEBSITE_BILLING_LOADED')) {
|
|
return;
|
|
}
|
|
|
|
define('GSP_WEBSITE_BILLING_LOADED', true);
|
|
|
|
function website_table(string $baseName): string
|
|
{
|
|
return website_table_prefix() . $baseName;
|
|
}
|
|
|
|
function website_client_ip(): string
|
|
{
|
|
return substr((string)($_SERVER['REMOTE_ADDR'] ?? ''), 0, 64);
|
|
}
|
|
|
|
function website_csrf_token(): string
|
|
{
|
|
website_start_session();
|
|
if (empty($_SESSION['website_csrf_token'])) {
|
|
$_SESSION['website_csrf_token'] = bin2hex(random_bytes(32));
|
|
}
|
|
return (string)$_SESSION['website_csrf_token'];
|
|
}
|
|
|
|
function website_csrf_field(): string
|
|
{
|
|
return '<input type="hidden" name="csrf_token" value="' . website_escape(website_csrf_token()) . '">';
|
|
}
|
|
|
|
function website_verify_csrf(): bool
|
|
{
|
|
website_start_session();
|
|
$token = (string)($_POST['csrf_token'] ?? '');
|
|
return $token !== '' && hash_equals((string)($_SESSION['website_csrf_token'] ?? ''), $token);
|
|
}
|
|
|
|
function website_require_login(string $returnPath = 'account.php'): ?array
|
|
{
|
|
$user = website_current_user();
|
|
if ($user === null) {
|
|
$_SESSION['website_login_return'] = website_safe_return_path($returnPath, 'account.php');
|
|
header('Location: ' . website_login_url($returnPath), true, 302);
|
|
exit;
|
|
}
|
|
return $user;
|
|
}
|
|
|
|
function website_require_staff(): array
|
|
{
|
|
$user = website_require_login('staff.php');
|
|
if ($user === null || !website_current_user_is_staff()) {
|
|
http_response_code(403);
|
|
website_render('message.php', [
|
|
'activePage' => 'account',
|
|
'pageTitle' => 'Access Denied - Gameservers.World',
|
|
'heading' => 'Access Denied',
|
|
'message' => 'This page is available only to authorized website staff.',
|
|
]);
|
|
exit;
|
|
}
|
|
return $user;
|
|
}
|
|
|
|
function website_column_exists(string $tableName, string $columnName): bool
|
|
{
|
|
return isset(website_table_columns($tableName)[$columnName]);
|
|
}
|
|
|
|
function website_billing_migrations(): array
|
|
{
|
|
$prefix = website_table_prefix();
|
|
return [
|
|
"CREATE TABLE IF NOT EXISTS `{$prefix}billing_invoices` (
|
|
`invoice_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
`user_id` INT NOT NULL,
|
|
`status` VARCHAR(24) NOT NULL DEFAULT 'due',
|
|
`amount` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
|
`currency` CHAR(3) NOT NULL DEFAULT 'USD',
|
|
`description` VARCHAR(255) DEFAULT NULL,
|
|
`invoice_date` DATETIME NOT NULL,
|
|
`due_date` DATETIME DEFAULT NULL,
|
|
`paid_date` DATETIME DEFAULT NULL,
|
|
`payment_method` VARCHAR(40) DEFAULT NULL,
|
|
`payment_txid` VARCHAR(128) DEFAULT NULL,
|
|
`paypal_order_id` VARCHAR(128) DEFAULT NULL,
|
|
`paypal_capture_id` VARCHAR(128) DEFAULT NULL,
|
|
`payer_email` VARCHAR(255) DEFAULT NULL,
|
|
`coupon_id` INT DEFAULT NULL,
|
|
`discount_amount` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
|
`order_id` INT DEFAULT NULL,
|
|
`metadata_json` TEXT DEFAULT NULL,
|
|
PRIMARY KEY (`invoice_id`),
|
|
KEY `idx_invoice_user_status` (`user_id`, `status`),
|
|
KEY `idx_invoice_payment` (`paypal_order_id`, `paypal_capture_id`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
|
|
"CREATE TABLE IF NOT EXISTS `{$prefix}billing_orders` (
|
|
`order_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
`invoice_id` INT UNSIGNED DEFAULT NULL,
|
|
`user_id` INT NOT NULL,
|
|
`service_id` INT NOT NULL,
|
|
`home_id` INT DEFAULT NULL,
|
|
`home_name` VARCHAR(120) NOT NULL,
|
|
`remote_server_id` INT DEFAULT NULL,
|
|
`max_players` INT NOT NULL DEFAULT 0,
|
|
`qty` INT NOT NULL DEFAULT 1,
|
|
`invoice_duration` VARCHAR(24) NOT NULL DEFAULT 'month',
|
|
`price` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
|
`status` VARCHAR(32) NOT NULL DEFAULT 'paid',
|
|
`order_date` DATETIME NOT NULL,
|
|
`end_date` DATETIME DEFAULT NULL,
|
|
`payment_txid` VARCHAR(128) DEFAULT NULL,
|
|
`paid_ts` DATETIME DEFAULT NULL,
|
|
`provisioning_claim` VARCHAR(64) DEFAULT NULL,
|
|
`provisioning_error` TEXT DEFAULT NULL,
|
|
`metadata_json` TEXT DEFAULT NULL,
|
|
PRIMARY KEY (`order_id`),
|
|
UNIQUE KEY `uniq_invoice_service` (`invoice_id`, `service_id`, `home_name`),
|
|
KEY `idx_order_user_status` (`user_id`, `status`),
|
|
KEY `idx_order_home` (`home_id`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
|
|
"CREATE TABLE IF NOT EXISTS `{$prefix}billing_payments` (
|
|
`payment_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
`invoice_id` INT UNSIGNED DEFAULT NULL,
|
|
`user_id` INT DEFAULT NULL,
|
|
`provider` VARCHAR(40) NOT NULL,
|
|
`provider_order_id` VARCHAR(128) DEFAULT NULL,
|
|
`provider_capture_id` VARCHAR(128) DEFAULT NULL,
|
|
`amount` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
|
`currency` CHAR(3) NOT NULL DEFAULT 'USD',
|
|
`status` VARCHAR(40) NOT NULL,
|
|
`payer_email` VARCHAR(255) DEFAULT NULL,
|
|
`created_at` DATETIME NOT NULL,
|
|
`metadata_json` TEXT DEFAULT NULL,
|
|
PRIMARY KEY (`payment_id`),
|
|
UNIQUE KEY `uniq_provider_capture` (`provider`, `provider_capture_id`),
|
|
KEY `idx_payment_invoice` (`invoice_id`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
|
|
"CREATE TABLE IF NOT EXISTS `{$prefix}billing_coupons` (
|
|
`coupon_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
`code` VARCHAR(64) NOT NULL,
|
|
`name` VARCHAR(120) NOT NULL,
|
|
`description` TEXT DEFAULT NULL,
|
|
`discount_type` VARCHAR(16) NOT NULL DEFAULT 'percent',
|
|
`discount_value` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
|
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
|
|
`starts_at` DATETIME DEFAULT NULL,
|
|
`expires` DATETIME DEFAULT NULL,
|
|
`max_uses` INT DEFAULT NULL,
|
|
`current_uses` INT NOT NULL DEFAULT 0,
|
|
`minimum_amount` DECIMAL(10,2) DEFAULT NULL,
|
|
`service_filter_json` TEXT DEFAULT NULL,
|
|
`created_at` DATETIME NOT NULL,
|
|
PRIMARY KEY (`coupon_id`),
|
|
UNIQUE KEY `uniq_coupon_code` (`code`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
|
|
"CREATE TABLE IF NOT EXISTS `{$prefix}website_settings` (
|
|
`setting_key` VARCHAR(120) NOT NULL,
|
|
`setting_value` TEXT DEFAULT NULL,
|
|
`is_secret` TINYINT(1) NOT NULL DEFAULT 0,
|
|
`updated_at` DATETIME NOT NULL,
|
|
PRIMARY KEY (`setting_key`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
|
|
"CREATE TABLE IF NOT EXISTS `{$prefix}password_reset_tokens` (
|
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
`user_id` INT NOT NULL,
|
|
`token_hash` CHAR(64) NOT NULL,
|
|
`expires_at` DATETIME NOT NULL,
|
|
`used_at` DATETIME DEFAULT NULL,
|
|
`created_at` DATETIME NOT NULL,
|
|
`originating_ip` VARCHAR(64) DEFAULT NULL,
|
|
PRIMARY KEY (`id`),
|
|
UNIQUE KEY `uniq_reset_token_hash` (`token_hash`),
|
|
KEY `idx_reset_user` (`user_id`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
|
|
"CREATE TABLE IF NOT EXISTS `{$prefix}payment_webhook_events` (
|
|
`event_id` VARCHAR(128) NOT NULL,
|
|
`provider` VARCHAR(40) NOT NULL,
|
|
`event_type` VARCHAR(120) DEFAULT NULL,
|
|
`received_at` DATETIME NOT NULL,
|
|
`processed_at` DATETIME DEFAULT NULL,
|
|
`status` VARCHAR(40) NOT NULL DEFAULT 'received',
|
|
`metadata_json` TEXT DEFAULT NULL,
|
|
PRIMARY KEY (`event_id`, `provider`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
|
|
"CREATE TABLE IF NOT EXISTS `{$prefix}provisioning_attempts` (
|
|
`attempt_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
`order_id` INT UNSIGNED NOT NULL,
|
|
`status` VARCHAR(40) NOT NULL,
|
|
`message` TEXT DEFAULT NULL,
|
|
`created_at` DATETIME NOT NULL,
|
|
PRIMARY KEY (`attempt_id`),
|
|
KEY `idx_provision_order` (`order_id`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
|
|
];
|
|
}
|
|
|
|
function website_run_billing_migrations(): array
|
|
{
|
|
$db = website_db();
|
|
if (!$db instanceof mysqli) {
|
|
return ['Database connection is unavailable.'];
|
|
}
|
|
|
|
$errors = [];
|
|
foreach (website_billing_migrations() as $sql) {
|
|
if (!$db->query($sql)) {
|
|
$errors[] = $db->error;
|
|
}
|
|
}
|
|
$serviceTable = website_table('billing_services');
|
|
if (website_table_exists($serviceTable)) {
|
|
$serviceColumns = [
|
|
'description' => 'TEXT DEFAULT NULL',
|
|
'img_url' => 'VARCHAR(255) DEFAULT NULL',
|
|
'price_monthly' => 'DECIMAL(10,2) NOT NULL DEFAULT 0.00',
|
|
'slot_min_qty' => 'INT NOT NULL DEFAULT 16',
|
|
'slot_max_qty' => 'INT NOT NULL DEFAULT 0',
|
|
'enabled' => 'TINYINT(1) NOT NULL DEFAULT 1',
|
|
'remote_server_id' => 'VARCHAR(255) DEFAULT NULL',
|
|
];
|
|
$existing = website_table_columns($serviceTable);
|
|
foreach ($serviceColumns as $column => $definition) {
|
|
if (isset($existing[$column])) {
|
|
continue;
|
|
}
|
|
$safeTable = str_replace('`', '``', $serviceTable);
|
|
$safeColumn = str_replace('`', '``', $column);
|
|
if (!$db->query("ALTER TABLE `{$safeTable}` ADD COLUMN `{$safeColumn}` {$definition}")) {
|
|
$errors[] = $db->error;
|
|
}
|
|
}
|
|
}
|
|
return $errors;
|
|
}
|
|
|
|
function website_setting(string $key, ?string $default = null): ?string
|
|
{
|
|
$db = website_db();
|
|
if (!$db instanceof mysqli || !website_table_exists(website_table('website_settings'))) {
|
|
return $default;
|
|
}
|
|
$table = website_table('website_settings');
|
|
$stmt = $db->prepare("SELECT `setting_value` FROM `{$table}` WHERE `setting_key` = ? LIMIT 1");
|
|
if (!$stmt) {
|
|
return $default;
|
|
}
|
|
$stmt->bind_param('s', $key);
|
|
$stmt->execute();
|
|
$result = $stmt->get_result();
|
|
$row = $result instanceof mysqli_result ? $result->fetch_assoc() : null;
|
|
$stmt->close();
|
|
return is_array($row) ? (string)$row['setting_value'] : $default;
|
|
}
|
|
|
|
function website_set_setting(string $key, string $value, bool $secret = false): bool
|
|
{
|
|
$db = website_db();
|
|
if (!$db instanceof mysqli || !website_table_exists(website_table('website_settings'))) {
|
|
return false;
|
|
}
|
|
$table = website_table('website_settings');
|
|
$isSecret = $secret ? 1 : 0;
|
|
$stmt = $db->prepare("INSERT INTO `{$table}` (`setting_key`, `setting_value`, `is_secret`, `updated_at`) VALUES (?, ?, ?, NOW()) ON DUPLICATE KEY UPDATE `setting_value` = VALUES(`setting_value`), `is_secret` = VALUES(`is_secret`), `updated_at` = VALUES(`updated_at`)");
|
|
if (!$stmt) {
|
|
return false;
|
|
}
|
|
$stmt->bind_param('ssi', $key, $value, $isSecret);
|
|
$ok = $stmt->execute();
|
|
$stmt->close();
|
|
return $ok;
|
|
}
|
|
|
|
function website_paypal_config(): array
|
|
{
|
|
return [
|
|
'enabled' => website_setting('paypal_enabled', '0') === '1',
|
|
'sandbox' => website_setting('paypal_sandbox', '1') === '1',
|
|
'client_id' => (string)(getenv('GSP_WEBSITE_PAYPAL_CLIENT_ID') ?: website_setting('paypal_client_id', '')),
|
|
'client_secret' => (string)(getenv('GSP_WEBSITE_PAYPAL_CLIENT_SECRET') ?: website_setting('paypal_client_secret', '')),
|
|
'webhook_id' => (string)(getenv('GSP_WEBSITE_PAYPAL_WEBHOOK_ID') ?: website_setting('paypal_webhook_id', '')),
|
|
'currency' => (string)website_setting('paypal_currency', 'USD'),
|
|
'description_prefix' => (string)website_setting('paypal_description_prefix', 'Gameservers.World order'),
|
|
];
|
|
}
|
|
|
|
function website_paypal_api_base(array $config): string
|
|
{
|
|
return $config['sandbox'] ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';
|
|
}
|
|
|
|
function website_paypal_oauth(array $config): ?string
|
|
{
|
|
if ($config['client_id'] === '' || $config['client_secret'] === '') {
|
|
return null;
|
|
}
|
|
$ch = curl_init(website_paypal_api_base($config) . '/v1/oauth2/token');
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_POST => true,
|
|
CURLOPT_POSTFIELDS => 'grant_type=client_credentials',
|
|
CURLOPT_HTTPHEADER => ['Accept: application/json'],
|
|
CURLOPT_USERPWD => $config['client_id'] . ':' . $config['client_secret'],
|
|
CURLOPT_TIMEOUT => 20,
|
|
]);
|
|
$response = curl_exec($ch);
|
|
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
if ($http !== 200 || !is_string($response)) {
|
|
return null;
|
|
}
|
|
$json = json_decode($response, true);
|
|
return is_array($json) ? (string)($json['access_token'] ?? '') : null;
|
|
}
|
|
|
|
function website_services_have_columns(array $columns): bool
|
|
{
|
|
$table = website_table('billing_services');
|
|
if (!website_table_exists($table)) {
|
|
return false;
|
|
}
|
|
$existing = website_table_columns($table);
|
|
foreach ($columns as $column) {
|
|
if (!isset($existing[$column])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function website_fetch_remote_servers(): array
|
|
{
|
|
$db = website_db();
|
|
$table = website_table('remote_servers');
|
|
if (!$db instanceof mysqli || !website_table_exists($table)) {
|
|
return [];
|
|
}
|
|
$rows = [];
|
|
$result = @$db->query("SELECT `remote_server_id`, `remote_server_name`, `agent_ip`, `enabled` FROM `{$table}` ORDER BY `remote_server_name` ASC");
|
|
if ($result instanceof mysqli_result) {
|
|
while ($row = $result->fetch_assoc()) {
|
|
$rows[] = $row;
|
|
}
|
|
$result->free();
|
|
}
|
|
return $rows;
|
|
}
|
|
|
|
function website_fetch_invoices_for_user(int $userId): array
|
|
{
|
|
$db = website_db();
|
|
$table = website_table('billing_invoices');
|
|
if (!$db instanceof mysqli || !website_table_exists($table)) {
|
|
return [];
|
|
}
|
|
$stmt = $db->prepare("SELECT * FROM `{$table}` WHERE `user_id` = ? ORDER BY `invoice_id` DESC");
|
|
if (!$stmt) {
|
|
return [];
|
|
}
|
|
$stmt->bind_param('i', $userId);
|
|
$stmt->execute();
|
|
$result = $stmt->get_result();
|
|
$rows = [];
|
|
while ($result instanceof mysqli_result && ($row = $result->fetch_assoc())) {
|
|
$rows[] = $row;
|
|
}
|
|
$stmt->close();
|
|
return $rows;
|
|
}
|
|
|
|
function website_fetch_orders_for_user(int $userId): array
|
|
{
|
|
$db = website_db();
|
|
$table = website_table('billing_orders');
|
|
if (!$db instanceof mysqli || !website_table_exists($table)) {
|
|
return [];
|
|
}
|
|
$stmt = $db->prepare("SELECT * FROM `{$table}` WHERE `user_id` = ? ORDER BY `order_id` DESC");
|
|
if (!$stmt) {
|
|
return [];
|
|
}
|
|
$stmt->bind_param('i', $userId);
|
|
$stmt->execute();
|
|
$result = $stmt->get_result();
|
|
$rows = [];
|
|
while ($result instanceof mysqli_result && ($row = $result->fetch_assoc())) {
|
|
$rows[] = $row;
|
|
}
|
|
$stmt->close();
|
|
return $rows;
|
|
}
|
|
|
|
function website_create_invoice_from_cart(int $userId): ?int
|
|
{
|
|
$db = website_db();
|
|
if (!$db instanceof mysqli || !website_table_exists(website_table('billing_invoices')) || !website_table_exists(website_table('billing_orders'))) {
|
|
return null;
|
|
}
|
|
|
|
$items = website_cart_items();
|
|
if (empty($items)) {
|
|
return null;
|
|
}
|
|
|
|
$validItems = [];
|
|
$total = 0.0;
|
|
foreach ($items as $item) {
|
|
$service = website_fetch_service_by_id((int)($item['service_id'] ?? 0));
|
|
if (!$service) {
|
|
continue;
|
|
}
|
|
$locations = website_service_locations($service);
|
|
$locationId = (string)($item['location_id'] ?? '');
|
|
$slots = (int)($item['slots'] ?? 0);
|
|
$min = website_service_min_slots($service);
|
|
$max = website_service_max_slots($service);
|
|
if ($slots < $min || ($max > 0 && $slots > $max) || !isset($locations[$locationId])) {
|
|
continue;
|
|
}
|
|
$price = (float)($service['price_monthly'] ?? 0);
|
|
$durationMonths = max(1, (int)($item['duration_months'] ?? 1));
|
|
$line = [
|
|
'service_id' => (int)$service['service_id'],
|
|
'service_name' => website_service_name($service),
|
|
'home_name' => trim((string)($item['server_name'] ?? $item['service_name'] ?? website_service_name($service))),
|
|
'remote_server_id' => (int)$locationId,
|
|
'slots' => $slots,
|
|
'duration_months' => $durationMonths,
|
|
'price' => $price * $durationMonths,
|
|
'price_monthly' => $price,
|
|
];
|
|
$validItems[] = $line;
|
|
$total += (float)$line['price'];
|
|
}
|
|
if (empty($validItems)) {
|
|
return null;
|
|
}
|
|
|
|
$invoiceTable = website_table('billing_invoices');
|
|
$orderTable = website_table('billing_orders');
|
|
$currency = website_paypal_config()['currency'] ?: 'USD';
|
|
$metadata = json_encode(['items' => $validItems], JSON_UNESCAPED_SLASHES);
|
|
$db->begin_transaction();
|
|
try {
|
|
$description = 'Gameservers.World server order';
|
|
$stmt = $db->prepare("INSERT INTO `{$invoiceTable}` (`user_id`, `status`, `amount`, `currency`, `description`, `invoice_date`, `due_date`, `metadata_json`) VALUES (?, 'due', ?, ?, ?, NOW(), DATE_ADD(NOW(), INTERVAL 3 DAY), ?)");
|
|
if (!$stmt) {
|
|
throw new RuntimeException('invoice prepare failed');
|
|
}
|
|
$stmt->bind_param('idsss', $userId, $total, $currency, $description, $metadata);
|
|
$stmt->execute();
|
|
$invoiceId = (int)$db->insert_id;
|
|
$stmt->close();
|
|
|
|
foreach ($validItems as $line) {
|
|
$status = 'pending_payment';
|
|
$duration = (string)$line['duration_months'];
|
|
$stmt = $db->prepare("INSERT INTO `{$orderTable}` (`invoice_id`, `user_id`, `service_id`, `home_name`, `remote_server_id`, `max_players`, `qty`, `invoice_duration`, `price`, `status`, `order_date`, `metadata_json`) VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, NOW(), ?)");
|
|
if (!$stmt) {
|
|
throw new RuntimeException('order prepare failed');
|
|
}
|
|
$lineMeta = json_encode($line, JSON_UNESCAPED_SLASHES);
|
|
$stmt->bind_param('iiisiisdss', $invoiceId, $userId, $line['service_id'], $line['home_name'], $line['remote_server_id'], $line['slots'], $duration, $line['price'], $status, $lineMeta);
|
|
$stmt->execute();
|
|
$stmt->close();
|
|
}
|
|
|
|
$db->commit();
|
|
$_SESSION['website_cart'] = [];
|
|
website_log_activity('Website invoice created from cart #' . $invoiceId, $userId, 'invoice_created');
|
|
return $invoiceId;
|
|
} catch (Throwable $e) {
|
|
$db->rollback();
|
|
website_log('Invoice creation failed: ' . $e->getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function website_mark_invoice_paid(int $invoiceId, string $provider, string $providerOrderId, string $captureId, string $payerEmail, array $metadata = []): bool
|
|
{
|
|
$db = website_db();
|
|
if (!$db instanceof mysqli) {
|
|
return false;
|
|
}
|
|
$invoiceTable = website_table('billing_invoices');
|
|
$orderTable = website_table('billing_orders');
|
|
$paymentTable = website_table('billing_payments');
|
|
if (!website_table_exists($invoiceTable) || !website_table_exists($orderTable) || !website_table_exists($paymentTable)) {
|
|
return false;
|
|
}
|
|
|
|
$db->begin_transaction();
|
|
try {
|
|
$stmt = $db->prepare("SELECT `user_id`, `amount`, `currency`, `status` FROM `{$invoiceTable}` WHERE `invoice_id` = ? FOR UPDATE");
|
|
$stmt->bind_param('i', $invoiceId);
|
|
$stmt->execute();
|
|
$row = $stmt->get_result()->fetch_assoc();
|
|
$stmt->close();
|
|
if (!is_array($row) || (string)$row['status'] === 'paid') {
|
|
$db->commit();
|
|
return true;
|
|
}
|
|
|
|
$userId = (int)$row['user_id'];
|
|
$amount = (float)$row['amount'];
|
|
$currency = (string)$row['currency'];
|
|
$meta = json_encode($metadata, JSON_UNESCAPED_SLASHES);
|
|
|
|
$stmt = $db->prepare("UPDATE `{$invoiceTable}` SET `status` = 'paid', `paid_date` = NOW(), `payment_method` = ?, `payment_txid` = ?, `paypal_order_id` = ?, `paypal_capture_id` = ?, `payer_email` = ? WHERE `invoice_id` = ?");
|
|
$stmt->bind_param('sssssi', $provider, $captureId, $providerOrderId, $captureId, $payerEmail, $invoiceId);
|
|
$stmt->execute();
|
|
$stmt->close();
|
|
|
|
$paid = 'paid';
|
|
$provisioning = 'paid';
|
|
$stmt = $db->prepare("UPDATE `{$orderTable}` SET `status` = ?, `payment_txid` = ?, `paid_ts` = NOW() WHERE `invoice_id` = ? AND `status` IN ('pending_payment', 'paid')");
|
|
$stmt->bind_param('ssi', $provisioning, $captureId, $invoiceId);
|
|
$stmt->execute();
|
|
$stmt->close();
|
|
|
|
$stmt = $db->prepare("INSERT IGNORE INTO `{$paymentTable}` (`invoice_id`, `user_id`, `provider`, `provider_order_id`, `provider_capture_id`, `amount`, `currency`, `status`, `payer_email`, `created_at`, `metadata_json`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), ?)");
|
|
$stmt->bind_param('iisssdssss', $invoiceId, $userId, $provider, $providerOrderId, $captureId, $amount, $currency, $paid, $payerEmail, $meta);
|
|
$stmt->execute();
|
|
$stmt->close();
|
|
|
|
$db->commit();
|
|
website_log_activity('Website invoice paid #' . $invoiceId, $userId, 'invoice_paid');
|
|
return true;
|
|
} catch (Throwable $e) {
|
|
$db->rollback();
|
|
website_log('Payment mark-paid failed: ' . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|