container->get('flash')->addMessage('error', 'Invalid invoice number'); return $response->withHeader('Location', '/invoices')->withStatus(302); } $iso3166 = new ISO3166(); $db = $this->container->get('db'); // Get the current URI $uri = $request->getUri()->getPath(); $invoice_details = $db->selectRow('SELECT * FROM invoices WHERE invoice_number = ?', [ $invoiceNumber ] ); $billing = $db->selectRow('SELECT * FROM registrar_contact WHERE id = ?', [ $invoice_details['billing_contact_id'] ] ); $billing_company = $db->selectValue('SELECT companyNumber FROM registrar WHERE id = ?', [ $invoice_details['registrar_id'] ] ); $billing_vat = $db->selectValue('SELECT vatNumber FROM registrar WHERE id = ?', [ $invoice_details['registrar_id'] ] ); $company_name = $db->selectValue("SELECT value FROM settings WHERE name = 'company_name'"); $address = $db->selectValue("SELECT value FROM settings WHERE name = 'address'"); $address2 = $db->selectValue("SELECT value FROM settings WHERE name = 'address2'"); $cc = $db->selectValue("SELECT value FROM settings WHERE name = 'cc'"); $vat_number = $db->selectValue("SELECT value FROM settings WHERE name = 'vat_number'"); $phone = $db->selectValue("SELECT value FROM settings WHERE name = 'phone'"); $email = $db->selectValue("SELECT value FROM settings WHERE name = 'email'"); $issueDate = new \DateTime($invoice_details['issue_date']); $firstDayPrevMonth = (clone $issueDate)->modify('first day of last month')->format('Y-m-d'); $lastDayPrevMonth = (clone $issueDate)->modify('last day of last month')->format('Y-m-d'); $statement = $db->select('SELECT * FROM statement WHERE date BETWEEN ? AND ? AND registrar_id = ?', [ $firstDayPrevMonth, $lastDayPrevMonth, $invoice_details['registrar_id'] ] ); $vatCalculator = new VatCalculator(); $vatCalculator->setBusinessCountryCode(strtoupper($cc)); $grossPrice = $vatCalculator->calculate($invoice_details['total_amount'], strtoupper($billing['cc'])); $taxRate = $vatCalculator->getTaxRate(); $netPrice = $vatCalculator->getNetPrice(); $taxValue = $vatCalculator->getTaxValue(); if ($vatCalculator->shouldCollectVAT(strtoupper($billing['cc']))) { $validVAT = $vatCalculator->isValidVatNumberFormat($vat_number); } else { $validVAT = null; } $totalAmount = $grossPrice + $taxValue; $billing_country = $iso3166->alpha2($billing['cc']); $billing_country = $billing_country['name']; $locale = $_SESSION['_lang'] ?? 'en_US'; // Fallback to 'en_US' if no locale is set $currency = $_SESSION['_currency'] ?? 'USD'; // Fallback to 'USD' if no currency is set // Initialize the number formatter for the locale $numberFormatter = new \NumberFormatter($locale, \NumberFormatter::DECIMAL); $currencyFormatter = new \NumberFormatter($locale, \NumberFormatter::CURRENCY); // Format values explicitly with the session currency $formattedVatRate = $numberFormatter->format($taxRate * 100) . "%"; $formattedVatAmount = $currencyFormatter->formatCurrency($taxValue, $currency); $formattedNetPrice = $currencyFormatter->formatCurrency($netPrice, $currency); $formattedTotalAmount = $currencyFormatter->formatCurrency($totalAmount, $currency); // Pass formatted values to Twig return view($response, 'admin/financials/viewInvoice.twig', [ 'invoice_details' => $invoice_details, 'billing' => $billing, 'billing_company' => $billing_company, 'billing_vat' => $billing_vat, 'statement' => $statement, 'company_name' => $company_name, 'address' => $address, 'address2' => $address2, 'cc' => $cc, 'vat_number' => $vat_number, 'phone' => $phone, 'email' => $email, 'vatRate' => $formattedVatRate, 'vatAmount' => $formattedVatAmount, 'validVAT' => $validVAT, 'netPrice' => $formattedNetPrice, 'total' => $formattedTotalAmount, 'currentUri' => $uri, 'billing_country' => $billing_country, ]); } public function deposit(Request $request, Response $response) { if ($_SESSION["auth_roles"] != 0) { $db = $this->container->get('db'); $balance = $db->selectRow('SELECT name, accountBalance, creditLimit FROM registrar WHERE id = ?', [ $_SESSION["auth_registrar_id"] ] ); $currency = $_SESSION['_currency']; $stripe_key = envi('STRIPE_PUBLISHABLE_KEY'); return view($response,'admin/financials/deposit-registrar.twig', [ 'balance' => $balance, 'currency' => $currency, 'stripe_key' => $stripe_key ]); } if ($request->getMethod() === 'POST') { // Retrieve POST data $data = $request->getParsedBody(); $db = $this->container->get('db'); $registrar_id = $data['registrar']; $registrars = $db->select("SELECT id, clid, name FROM registrar"); $amount = $data['amount']; $description = empty($data['description']) ? "funds added to account balance" : $data['description']; $isPositiveNumberWithTwoDecimals = filter_var($amount, FILTER_VALIDATE_FLOAT) !== false && preg_match('/^\d+(\.\d{1,2})?$/', $amount); if ($isPositiveNumberWithTwoDecimals) { $db->beginTransaction(); try { $currentDateTime = new \DateTime(); $date = $currentDateTime->format('Y-m-d H:i:s.v'); $db->insert( 'statement', [ 'registrar_id' => $registrar_id, 'date' => $date, 'command' => 'create', 'domain_name' => 'deposit', 'length_in_months' => 0, 'fromS' => $date, 'toS' => $date, 'amount' => $amount ] ); $db->insert( 'payment_history', [ 'registrar_id' => $registrar_id, 'date' => $date, 'description' => $description, 'amount' => $amount ] ); $db->exec( 'UPDATE registrar SET accountBalance = (accountBalance + ?) WHERE id = ?', [ $amount, $registrar_id ] ); $db->commit(); } catch (Exception $e) { $db->rollBack(); $this->container->get('flash')->addMessage('error', 'Database failure: '.$e->getMessage()); return $response->withHeader('Location', '/deposit')->withStatus(302); } $this->container->get('flash')->addMessage('success', 'Deposit successfully added. The registrar\'s account balance has been updated.'); return $response->withHeader('Location', '/deposit')->withStatus(302); } else { $this->container->get('flash')->addMessage('error', 'Invalid entry: Deposit amount must be positive. Please enter a valid amount.'); return $response->withHeader('Location', '/deposit')->withStatus(302); } } $db = $this->container->get('db'); $registrars = $db->select("SELECT id, clid, name FROM registrar"); $currency = $_SESSION['_currency']; return view($response,'admin/financials/deposit.twig', [ 'registrars' => $registrars, 'currency' => $currency ]); } public function createStripePayment(Request $request, Response $response) { $postData = $request->getParsedBody(); $amount = $postData['amount']; // Make sure to validate and sanitize this amount // Set Stripe's secret key \Stripe\Stripe::setApiKey(envi('STRIPE_SECRET_KEY')); // Convert amount to cents (Stripe expects the amount in the smallest currency unit) $amountInCents = $amount * 100; // Create Stripe Checkout session $checkout_session = \Stripe\Checkout\Session::create([ 'payment_method_types' => ['card', 'paypal'], 'line_items' => [[ 'price_data' => [ 'currency' => $_SESSION['_currency'], 'product_data' => [ 'name' => 'Registrar Balance Deposit', ], 'unit_amount' => $amountInCents, ], 'quantity' => 1, ]], 'mode' => 'payment', 'success_url' => envi('APP_URL').'/payment-success?session_id={CHECKOUT_SESSION_ID}', 'cancel_url' => envi('APP_URL').'/payment-cancel', ]); // Return session ID to the frontend $response->getBody()->write(json_encode(['id' => $checkout_session->id])); return $response->withHeader('Content-Type', 'application/json'); } public function createAdyenPayment(Request $request, Response $response) { $postData = $request->getParsedBody(); $amount = $postData['amount']; // Make sure to validate and sanitize this amount // Convert amount to cents $amountInCents = $amount * 100; // Your registrar ID and unique identifier $registrarId = $_SESSION['auth_registrar_id']; $uniqueIdentifier = Uuid::uuid4()->toString(); // Generates a unique UUID $delimiter = '|'; $combinedString = $registrarId . $delimiter . $uniqueIdentifier; $merchantReference = bin2hex($combinedString); $client = new \Adyen\Client(); $client->setApplicationName('Namingo'); $client->setEnvironment(\Adyen\Environment::TEST); $client->setXApiKey(envi('ADYEN_API_KEY')); $service = new \Adyen\Service\Checkout($client); $params = array( 'amount' => array( 'currency' => $_SESSION['_currency'], 'value' => $amountInCents ), 'merchantAccount' => envi('ADYEN_MERCHANT_ID'), 'reference' => $merchantReference, 'returnUrl' => envi('APP_URL').'/payment-success-adyen', 'mode' => 'hosted', 'themeId' => envi('ADYEN_THEME_ID') ); $result = $service->sessions($params); $response->getBody()->write(json_encode($result)); return $response->withHeader('Content-Type', 'application/json'); } public function createCryptoPayment(Request $request, Response $response) { $postData = $request->getParsedBody(); $amount = $postData['amount']; // Make sure to validate and sanitize this amount // Your registrar ID and unique identifier $registrarId = $_SESSION['auth_registrar_id']; $uniqueIdentifier = Uuid::uuid4()->toString(); // Generates a unique UUID $delimiter = '|'; $combinedString = $registrarId . $delimiter . $uniqueIdentifier; $merchantReference = bin2hex($combinedString); $data = [ 'price_amount' => $amount, 'price_currency' => $_SESSION['_currency'], 'order_id' => $merchantReference, 'success_url' => envi('APP_URL').'/payment-success-crypto', 'cancel_url' => envi('APP_URL').'/payment-cancel', ]; $client = new Client(); $apiKey = envi('NOW_API_KEY'); try { $apiResponse = $client->request('POST', 'https://api.nowpayments.io/v1/invoice', [ 'headers' => [ 'x-api-key' => $apiKey, 'Content-Type' => 'application/json', ], 'json' => $data, ]); $body = $apiResponse->getBody()->getContents(); $response->getBody()->write($body); return $response->withHeader('Content-Type', 'application/json'); } catch (GuzzleException $e) { $errorResponse = [ 'error' => 'We encountered an issue while processing your payment.', 'details' => $e->getMessage(), ]; $response->getBody()->write(json_encode($errorResponse)); return $response->withStatus(500)->withHeader('Content-Type', 'application/json'); } } public function successStripe(Request $request, Response $response) { $session_id = $request->getQueryParams()['session_id'] ?? null; $db = $this->container->get('db'); if ($session_id) { \Stripe\Stripe::setApiKey(envi('STRIPE_SECRET_KEY')); try { $session = \Stripe\Checkout\Session::retrieve($session_id); $amountPaid = $session->amount_total; // Amount paid, in cents $amount = $amountPaid / 100; $amountPaidFormatted = number_format($amount, 2, '.', ''); $paymentIntentId = $session->payment_intent; $isPositiveNumberWithTwoDecimals = filter_var($amount, FILTER_VALIDATE_FLOAT) !== false && preg_match('/^\d+(\.\d{1,2})?$/', $amount); if ($isPositiveNumberWithTwoDecimals) { $db->beginTransaction(); try { $currentDateTime = new \DateTime(); $date = $currentDateTime->format('Y-m-d H:i:s.v'); $db->insert( 'statement', [ 'registrar_id' => $_SESSION['auth_registrar_id'], 'date' => $date, 'command' => 'create', 'domain_name' => 'deposit', 'length_in_months' => 0, 'fromS' => $date, 'toS' => $date, 'amount' => $amount ] ); $db->insert( 'payment_history', [ 'registrar_id' => $_SESSION['auth_registrar_id'], 'date' => $date, 'description' => 'registrar balance deposit via Stripe ('.$paymentIntentId.')', 'amount' => $amount ] ); $db->exec( 'UPDATE registrar SET accountBalance = (accountBalance + ?) WHERE id = ?', [ $amount, $_SESSION['auth_registrar_id'], ] ); $db->commit(); } catch (Exception $e) { $this->container->get('flash')->addMessage('error', 'Failure: '.$e->getMessage()); return $response->withHeader('Location', '/deposit')->withStatus(302); } $this->container->get('flash')->addMessage('success', 'Deposit successfully added. The registrar\'s account balance has been updated.'); return $response->withHeader('Location', '/deposit')->withStatus(302); } else { $this->container->get('flash')->addMessage('error', 'Invalid entry: Deposit amount must be positive. Please enter a valid amount.'); return $response->withHeader('Location', '/deposit')->withStatus(302); } } catch (\Exception $e) { $this->container->get('flash')->addMessage('error', 'We encountered an issue while processing your payment. Please check your payment details and try again.'); return $response->withHeader('Location', '/deposit')->withStatus(302); } } } public function successAdyen(Request $request, Response $response) { $sessionId = $request->getQueryParams()['sessionId'] ?? null; $sessionResult = $request->getQueryParams()['sessionResult'] ?? null; $db = $this->container->get('db'); $client = new Client([ 'base_uri' => envi('ADYEN_BASE_URI'), 'timeout' => 2.0, ]); try { $apicall = $client->request('GET', "sessions/$sessionId", [ 'query' => ['sessionResult' => $sessionResult], 'headers' => [ 'X-API-Key' => envi('ADYEN_API_KEY'), 'Content-Type' => 'application/json', ], ]); $data = json_decode($apicall->getBody(), true); $status = $data['status'] ?? 'unknown'; if ($status == 'completed') { echo $status; $this->container->get('flash')->addMessage('success', 'Deposit successfully added. The registrar\'s account balance has been updated.'); return $response->withHeader('Location', '/deposit')->withStatus(302); } else { $this->container->get('flash')->addMessage('error', 'We encountered an issue while processing your payment. Please check your payment details and try again.'); return $response->withHeader('Location', '/deposit')->withStatus(302); } } catch (RequestException $e) { $this->container->get('flash')->addMessage('error', 'Failure: '.$e->getMessage()); return $response->withHeader('Location', '/deposit')->withStatus(302); } } public function successCrypto(Request $request, Response $response) { $client = new Client(); $queryParams = $request->getQueryParams(); if (!isset($queryParams['paymentId']) || $queryParams['paymentId'] == 0) { $this->container->get('flash')->addMessage('info', 'No paymentId provided.'); return view($response,'admin/financials/success-crypto.twig'); } else { $paymentId = $queryParams['paymentId']; $apiKey = envi('NOW_API_KEY'); $url = 'https://api.nowpayments.io/v1/payment/' . $paymentId; try { $apiclient = $client->request('GET', $url, [ 'headers' => [ 'x-api-key' => $apiKey, ], ]); $statusCode = $apiclient->getStatusCode(); $body = $apiclient->getBody()->getContents(); $data = json_decode($body, true); if ($statusCode === 200) { // Check if the request was successful if (isset($data['payment_status']) && $data['payment_status'] === 'finished') { try { $amount = $data['pay_amount']; $merchantReference = hex2bin($data['order_description']); $delimiter = '|'; // Split to get the original components list($registrarId, $uniqueIdentifier) = explode($delimiter, $merchantReference, 2); $isPositiveNumberWithTwoDecimals = filter_var($amount, FILTER_VALIDATE_FLOAT) !== false && preg_match('/^\d+(\.\d{1,2})?$/', $amount); if ($isPositiveNumberWithTwoDecimals) { $db->beginTransaction(); try { $currentDateTime = new \DateTime(); $date = $currentDateTime->format('Y-m-d H:i:s.v'); $db->insert( 'statement', [ 'registrar_id' => $registrarId, 'date' => $date, 'command' => 'create', 'domain_name' => 'deposit', 'length_in_months' => 0, 'fromS' => $date, 'toS' => $date, 'amount' => $amount ] ); $db->insert( 'payment_history', [ 'registrar_id' => $registrarId, 'date' => $date, 'description' => 'registrar balance deposit via Crypto ('.$data['payment_id'].')', 'amount' => $amount ] ); $db->exec( 'UPDATE registrar SET accountBalance = (accountBalance + ?) WHERE id = ?', [ $amount, $registrarId, ] ); $db->commit(); } catch (Exception $e) { $this->container->get('flash')->addMessage('success', 'Request failed: ' . $e->getMessage()); return $response->withHeader('Location', '/payment-success-crypto')->withStatus(302); } return view($response, 'admin/financials/success-crypto.twig', [ 'status' => $data['payment_status'], 'paymentId' => $paymentId ]); } else { $this->container->get('flash')->addMessage('success', 'Request failed. Reload page.'); return $response->withHeader('Location', '/payment-success-crypto')->withStatus(302); } } catch (\Exception $e) { $this->container->get('flash')->addMessage('success', 'Request failed: ' . $e->getMessage()); return $response->withHeader('Location', '/payment-success-crypto')->withStatus(302); } } else if (isset($data['payment_status']) && $data['payment_status'] === 'expired') { return view($response, 'admin/financials/success-crypto.twig', [ 'status' => $data['payment_status'], 'paymentId' => $paymentId ]); } else { return view($response, 'admin/financials/success-crypto.twig', [ 'status' => $data['payment_status'], 'paymentId' => $paymentId ]); } } else { $this->container->get('flash')->addMessage('success', 'Failed to retrieve payment information. Status Code: ' . $statusCode); return $response->withHeader('Location', '/payment-success-crypto')->withStatus(302); } } catch (GuzzleException $e) { $this->container->get('flash')->addMessage('success', 'Request failed: ' . $e->getMessage()); return $response->withHeader('Location', '/payment-success-crypto')->withStatus(302); } } return view($response,'admin/financials/success-crypto.twig'); } public function webhookAdyen(Request $request, Response $response) { $data = json_decode($request->getBody()->getContents(), true); $db = $this->container->get('db'); // Basic auth credentials $username = envi('ADYEN_BASIC_AUTH_USER'); $password = envi('ADYEN_BASIC_AUTH_PASS'); // Check for basic auth header if (!isset($_SERVER['PHP_AUTH_USER'])) { return $response->withStatus(401)->withHeader('WWW-Authenticate', 'Basic realm="MyRealm"'); } // Validate username and password if ($_SERVER['PHP_AUTH_USER'] != $username || $_SERVER['PHP_AUTH_PW'] != $password) { $response = $response->withStatus(403)->withHeader('Content-Type', 'application/json'); $response->getBody()->write(json_encode(['forbidden' => true])); return $response; } $hmac = new \Adyen\Util\HmacSignature(); $hmacKey = envi('ADYEN_HMAC_KEY'); foreach ($data['notificationItems'] as $item) { $notificationRequestItem = $item['NotificationRequestItem']; if (isset($notificationRequestItem['eventCode']) && $notificationRequestItem['eventCode'] == 'AUTHORISATION' && $notificationRequestItem['success'] == 'true') { $merchantReference = $notificationRequestItem['merchantReference'] ?? null; $paymentStatus = $notificationRequestItem['success'] ?? null; if ($merchantReference && $paymentStatus && $hmac->isValidNotificationHMAC($hmacKey, $notificationRequestItem)) { try { $amountPaid = $notificationRequestItem['amount']['value']; // Amount paid, in cents $amount = $amountPaid / 100; $amountPaidFormatted = number_format($amount, 2, '.', ''); $paymentIntentId = $notificationRequestItem['reason']; $merchantReference = hex2bin($merchantReference); $delimiter = '|'; // Split to get the original components list($registrarId, $uniqueIdentifier) = explode($delimiter, $merchantReference, 2); $isPositiveNumberWithTwoDecimals = filter_var($amount, FILTER_VALIDATE_FLOAT) !== false && preg_match('/^\d+(\.\d{1,2})?$/', $amount); if ($isPositiveNumberWithTwoDecimals) { $db->beginTransaction(); try { $currentDateTime = new \DateTime(); $date = $currentDateTime->format('Y-m-d H:i:s.v'); $db->insert( 'statement', [ 'registrar_id' => $registrarId, 'date' => $date, 'command' => 'create', 'domain_name' => 'deposit', 'length_in_months' => 0, 'fromS' => $date, 'toS' => $date, 'amount' => $amount ] ); $db->insert( 'payment_history', [ 'registrar_id' => $registrarId, 'date' => $date, 'description' => 'registrar balance deposit via Adyen ('.$paymentIntentId.')', 'amount' => $amount ] ); $db->exec( 'UPDATE registrar SET accountBalance = (accountBalance + ?) WHERE id = ?', [ $amount, $registrarId, ] ); $db->commit(); } catch (Exception $e) { $response = $response->withStatus(500)->withHeader('Content-Type', 'application/json'); $response->getBody()->write(json_encode(['failure' => true])); return $response; } $response->getBody()->write(json_encode(['received' => true])); return $response->withHeader('Content-Type', 'application/json'); } else { $response = $response->withStatus(500)->withHeader('Content-Type', 'application/json'); $response->getBody()->write(json_encode(['failure' => true])); return $response; } } catch (\Exception $e) { $response = $response->withStatus(500)->withHeader('Content-Type', 'application/json'); $response->getBody()->write(json_encode(['failure' => true])); return $response; } } } else { $response = $response->withStatus(500)->withHeader('Content-Type', 'application/json'); $response->getBody()->write(json_encode(['failure' => true])); return $response; } } $response = $response->withStatus(500)->withHeader('Content-Type', 'application/json'); $response->getBody()->write(json_encode(['failure' => true])); return $response; } public function cancel(Request $request, Response $response) { return view($response,'admin/financials/cancel.twig'); } }