mirror of
https://github.com/getnamingo/registry.git
synced 2025-07-27 04:48:26 +02:00
Added support for Nicky.me payment gateway
This commit is contained in:
parent
efdf2e2418
commit
aabe29fb5c
7 changed files with 388 additions and 1 deletions
|
@ -334,6 +334,82 @@ class FinancialsController extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function createNickyPayment(Request $request, Response $response)
|
||||||
|
{
|
||||||
|
$postData = $request->getParsedBody();
|
||||||
|
$amount = $postData['amount']; // Make sure to validate and sanitize this amount
|
||||||
|
|
||||||
|
// Registrar ID and unique identifier
|
||||||
|
$registrarId = $_SESSION['auth_registrar_id'];
|
||||||
|
|
||||||
|
// Generate a 10-character alphanumeric random string for the invoice reference
|
||||||
|
$invoiceReference = strtoupper(bin2hex(random_bytes(5))); // 10 characters, all caps
|
||||||
|
|
||||||
|
// Map currency to Nicky's blockchainAssetId
|
||||||
|
$blockchainAssetId = match ($_SESSION['_currency']) {
|
||||||
|
'USD' => 'USD.USD',
|
||||||
|
'EUR' => 'EUR.EUR',
|
||||||
|
default => throw new Exception('Unsupported currency: ' . $_SESSION['_currency']),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prepare the payload for the API
|
||||||
|
$data = [
|
||||||
|
'blockchainAssetId' => $blockchainAssetId,
|
||||||
|
'amountExpectedNative' => $amount,
|
||||||
|
'billDetails' => [
|
||||||
|
'invoiceReference' => $invoiceReference,
|
||||||
|
'description' => 'Deposit for registrar ' . $registrarId,
|
||||||
|
],
|
||||||
|
'requester' => [
|
||||||
|
'email' => $_SESSION['auth_email'],
|
||||||
|
'name' => $_SESSION['auth_username'],
|
||||||
|
],
|
||||||
|
'sendNotification' => true,
|
||||||
|
'successUrl' => envi('APP_URL') . '/payment-success-nicky',
|
||||||
|
'cancelUrl' => envi('APP_URL') . '/payment-cancel',
|
||||||
|
];
|
||||||
|
|
||||||
|
$url = 'https://api-public.pay.nicky.me/api/public/PaymentRequestPublicApi/create';
|
||||||
|
$apiKey = envi('NICKY_API_KEY');
|
||||||
|
|
||||||
|
$client = new Client();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$apiResponse = $client->request('POST', $url, [
|
||||||
|
'headers' => [
|
||||||
|
'x-api-key' => $apiKey,
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
],
|
||||||
|
'json' => $data,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$body = json_decode($apiResponse->getBody()->getContents(), true);
|
||||||
|
|
||||||
|
if (isset($body['bill']['shortId'])) {
|
||||||
|
$paymentUrl = "https://pay.nicky.me/home?paymentId=" . $body['bill']['shortId'];
|
||||||
|
|
||||||
|
// Store the shortId in the session or database for future reference
|
||||||
|
$_SESSION['nicky_shortId'] = $body['bill']['shortId'];
|
||||||
|
|
||||||
|
// Return the payment URL as JSON
|
||||||
|
$response->getBody()->write(json_encode(['invoice_url' => $paymentUrl]));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
|
||||||
|
} else {
|
||||||
|
throw new Exception('API response does not contain a payment URL.');
|
||||||
|
}
|
||||||
|
} catch (GuzzleException $e) {
|
||||||
|
unset($_SESSION['nicky_shortId']);
|
||||||
|
|
||||||
|
$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)
|
public function successStripe(Request $request, Response $response)
|
||||||
{
|
{
|
||||||
$session_id = $request->getQueryParams()['session_id'] ?? null;
|
$session_id = $request->getQueryParams()['session_id'] ?? null;
|
||||||
|
@ -564,6 +640,117 @@ class FinancialsController extends Controller
|
||||||
return view($response,'admin/financials/success-crypto.twig');
|
return view($response,'admin/financials/success-crypto.twig');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function successNicky(Request $request, Response $response)
|
||||||
|
{
|
||||||
|
$client = new Client();
|
||||||
|
$sessionShortId = $_SESSION['nicky_shortId'] ?? null;
|
||||||
|
|
||||||
|
if (!$sessionShortId) {
|
||||||
|
$this->container->get('flash')->addMessage('info', 'No payment reference found in session.');
|
||||||
|
return view($response, 'admin/financials/success-nicky.twig');
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = 'https://api-public.pay.nicky.me/api/public/PaymentRequestPublicApi/get-by-short-id?shortId=' . urlencode($sessionShortId);
|
||||||
|
$apiKey = envi('NICKY_API_KEY');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$apiResponse = $client->request('GET', $url, [
|
||||||
|
'headers' => [
|
||||||
|
'x-api-key' => $apiKey,
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$statusCode = $apiResponse->getStatusCode();
|
||||||
|
$responseBody = json_decode($apiResponse->getBody()->getContents(), true);
|
||||||
|
|
||||||
|
if ($statusCode === 200 && isset($responseBody['status'])) {
|
||||||
|
$status = $responseBody['status'];
|
||||||
|
$amount = $responseBody['amountNative'] ?? 0;
|
||||||
|
$paymentId = $responseBody['id'] ?? null;
|
||||||
|
$description = $responseBody['bill']['description'] ?? 'No description';
|
||||||
|
|
||||||
|
if ($status === "None" || $status === "PaymentValidationRequired" || $status === "PaymentPending") {
|
||||||
|
return view($response, 'admin/financials/success-nicky.twig', [
|
||||||
|
'status' => $status,
|
||||||
|
'paymentId' => $paymentId
|
||||||
|
]);
|
||||||
|
} elseif ($status === "Finished") {
|
||||||
|
// Record the successful transaction in the database
|
||||||
|
$db = $this->container->get('db');
|
||||||
|
$registrarId = $_SESSION['auth_registrar_id'];
|
||||||
|
|
||||||
|
$currentDateTime = new \DateTime();
|
||||||
|
$date = $currentDateTime->format('Y-m-d H:i:s.v');
|
||||||
|
|
||||||
|
$db->beginTransaction();
|
||||||
|
try {
|
||||||
|
$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 Nicky ('.$paymentId.')',
|
||||||
|
'amount' => $amount,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$db->exec(
|
||||||
|
'UPDATE registrar SET accountBalance = (accountBalance + ?) WHERE id = ?',
|
||||||
|
[
|
||||||
|
$amount,
|
||||||
|
$registrarId,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$db->commit();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$db->rollBack();
|
||||||
|
$this->container->get('flash')->addMessage('error', 'Transaction recording failed: ' . $e->getMessage());
|
||||||
|
return $response->withHeader('Location', '/payment-success-nicky')->withStatus(302);
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($_SESSION['nicky_shortId']);
|
||||||
|
|
||||||
|
// Redirect to success page with details
|
||||||
|
return view($response, 'admin/financials/success-nicky.twig', [
|
||||||
|
'status' => $status,
|
||||||
|
'paymentId' => $paymentId,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
unset($_SESSION['nicky_shortId']);
|
||||||
|
|
||||||
|
// Handle unexpected statuses
|
||||||
|
return view($response, 'admin/financials/success-nicky.twig', [
|
||||||
|
'status' => $status,
|
||||||
|
'paymentId' => $paymentId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unset($_SESSION['nicky_shortId']);
|
||||||
|
$this->container->get('flash')->addMessage('error', 'Failed to retrieve payment information.');
|
||||||
|
return $response->withHeader('Location', '/payment-success-nicky')->withStatus(302);
|
||||||
|
}
|
||||||
|
} catch (GuzzleException $e) {
|
||||||
|
$this->container->get('flash')->addMessage('error', 'Request failed: ' . $e->getMessage());
|
||||||
|
return $response->withHeader('Location', '/payment-success-nicky')->withStatus(302);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function webhookAdyen(Request $request, Response $response)
|
public function webhookAdyen(Request $request, Response $response)
|
||||||
{
|
{
|
||||||
$data = json_decode($request->getBody()->getContents(), true);
|
$data = json_decode($request->getBody()->getContents(), true);
|
||||||
|
|
|
@ -41,4 +41,6 @@ ADYEN_HMAC_KEY='adyen-hmac-key'
|
||||||
|
|
||||||
NOW_API_KEY='now-api-key'
|
NOW_API_KEY='now-api-key'
|
||||||
|
|
||||||
|
NICKY_API_KEY='nicky-api-key'
|
||||||
|
|
||||||
TEST_TLDS=.test,.com.test
|
TEST_TLDS=.test,.com.test
|
26
cp/public/assets/nicky.svg
Normal file
26
cp/public/assets/nicky.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 20 KiB |
|
@ -4,6 +4,46 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<link href="/assets/css/sweetalert2.min.css" rel="stylesheet">
|
<link href="/assets/css/sweetalert2.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
.payment-provider-custom {
|
||||||
|
display: inline-block;
|
||||||
|
background-image: url('/assets/nicky.svg');
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fullscreen overlay */
|
||||||
|
#loading-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(255, 255, 255, 0.8); /* Whiteout effect */
|
||||||
|
z-index: 9999; /* Ensure it appears above all content */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Spinner container for alignment */
|
||||||
|
.spinner-container {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Spinner size adjustment */
|
||||||
|
.spinner-border {
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div id="loading-overlay" style="display: none;">
|
||||||
|
<div class="spinner-container">
|
||||||
|
<div class="spinner-border text-primary" role="status"></div>
|
||||||
|
<p class="text-secondary mt-3">Processing your payment, please wait...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="page-wrapper">
|
<div class="page-wrapper">
|
||||||
<!-- Page header -->
|
<!-- Page header -->
|
||||||
<div class="page-header d-print-none">
|
<div class="page-header d-print-none">
|
||||||
|
@ -67,6 +107,18 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="form-selectgroup-item flex-fill">
|
||||||
|
<input type="radio" name="paymentMethod" value="nicky" class="form-selectgroup-input">
|
||||||
|
<div class="form-selectgroup-label d-flex align-items-center p-3">
|
||||||
|
<div class="me-3">
|
||||||
|
<span class="form-selectgroup-check"></span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="payment payment-provider-custom payment-xs me-2"></span>
|
||||||
|
{{ __('Crypto') }}: Nicky
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
<label class="form-selectgroup-item flex-fill">
|
<label class="form-selectgroup-item flex-fill">
|
||||||
<input type="radio" name="paymentMethod" value="crypto" class="form-selectgroup-input">
|
<input type="radio" name="paymentMethod" value="crypto" class="form-selectgroup-input">
|
||||||
<div class="form-selectgroup-label d-flex align-items-center p-3">
|
<div class="form-selectgroup-label d-flex align-items-center p-3">
|
||||||
|
@ -75,7 +127,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="payment payment-provider-bitcoin-dark payment-xs me-2"></span>
|
<span class="payment payment-provider-bitcoin-dark payment-xs me-2"></span>
|
||||||
{{ __('Crypto') }}
|
{{ __('Crypto') }}: NOW
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
@ -123,6 +175,8 @@ document.getElementById('depositForm').addEventListener('submit', function (e) {
|
||||||
handleAdyenPayment(formData);
|
handleAdyenPayment(formData);
|
||||||
} else if (paymentMethod === 'crypto') {
|
} else if (paymentMethod === 'crypto') {
|
||||||
handleCryptoPayment(formData);
|
handleCryptoPayment(formData);
|
||||||
|
} else if (paymentMethod === 'nicky') {
|
||||||
|
handleNickyPayment(formData);
|
||||||
} else {
|
} else {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
icon: 'error',
|
icon: 'error',
|
||||||
|
@ -223,5 +277,55 @@ function handleCryptoPayment(formData) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to handle Nicky payment
|
||||||
|
function handleNickyPayment(formData) {
|
||||||
|
// Show the loading overlay
|
||||||
|
const overlay = document.getElementById('loading-overlay');
|
||||||
|
overlay.style.display = 'flex';
|
||||||
|
|
||||||
|
fetch('/create-nicky-payment', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
// Hide the loading overlay
|
||||||
|
overlay.style.display = 'none';
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
console.error('Nicky Error:', data.error);
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Payment Error',
|
||||||
|
text: data.error
|
||||||
|
});
|
||||||
|
} else if (data.invoice_url) {
|
||||||
|
console.log('Nicky payment success:', data);
|
||||||
|
window.location.href = data.invoice_url;
|
||||||
|
} else {
|
||||||
|
console.error('Unexpected API response:', data);
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Unexpected Response',
|
||||||
|
text: 'The payment gateway did not return a valid payment URL.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
// Hide the loading overlay
|
||||||
|
overlay.style.display = 'none';
|
||||||
|
|
||||||
|
console.error('Error in Nicky payment:', error);
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Unexpected Error',
|
||||||
|
text: 'An unexpected error occurred. Please try again later. Ensure the payment gateway is properly configured.'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
64
cp/resources/views/admin/financials/success-nicky.twig
Normal file
64
cp/resources/views/admin/financials/success-nicky.twig
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
{% extends "layouts/app.twig" %}
|
||||||
|
|
||||||
|
{% block title %}{{ __('Nicky Payment Status') }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<!-- Page header -->
|
||||||
|
<div class="page-header d-print-none">
|
||||||
|
<div class="container-xl">
|
||||||
|
<div class="row g-2 align-items-center">
|
||||||
|
<div class="col">
|
||||||
|
<!-- Page pre-title -->
|
||||||
|
<div class="page-pretitle">
|
||||||
|
{{ __('Overview') }}
|
||||||
|
</div>
|
||||||
|
<h2 class="page-title">
|
||||||
|
{{ __('Nicky Payment Status') }}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Page body -->
|
||||||
|
<div class="page-body">
|
||||||
|
<div class="container-xl">
|
||||||
|
<div class="col-12">
|
||||||
|
{% include 'partials/flash.twig' %}
|
||||||
|
{% if status is defined and paymentId is defined %}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h4>{{ __('Payment Status:') }} {{ status }}</h4>
|
||||||
|
{% if status == 'None' or status == 'PaymentValidationRequired' or status == 'PaymentPending' %}
|
||||||
|
<p class="text-primary">
|
||||||
|
Your payment is currently <strong>in progress</strong>. Please <a href="#" onclick="window.location.reload(); return false;"><u>check back</u></a> later for updates.
|
||||||
|
</p>
|
||||||
|
{% elseif status == 'Canceled' %}
|
||||||
|
<p class="text-danger">
|
||||||
|
Your payment has been <strong>cancelled</strong>. Please initiate a new payment if you wish to proceed.
|
||||||
|
</p>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-success">
|
||||||
|
Your payment has been <strong>successfully processed</strong> and completed.
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h4>{{ __('Payment Details Not Found') }}</h4>
|
||||||
|
<p class="text-secondary">{{ __('It appears the payment details could not be found in your session. This may happen if the session has expired or if you accessed this page directly without completing the payment process.') }}</p>
|
||||||
|
<p class="text-secondary">{{ __('To proceed, please restart the payment process. If you have already completed a payment and believe this is an error, check your email for the payment confirmation details or contact our support team for assistance.') }}</p>
|
||||||
|
<p class="text-secondary">{{ __('We recommend initiating a new payment to ensure your transaction is properly credited.') }}</p>
|
||||||
|
<p class="text-secondary"><a href="/deposit" class="btn btn-primary">{{ __('Restart Payment') }}</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% include 'partials/footer.twig' %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -117,9 +117,11 @@ $app->group('', function ($route) {
|
||||||
$route->map(['GET', 'POST'], '/create-payment', FinancialsController::class .':createStripePayment')->setName('createStripePayment');
|
$route->map(['GET', 'POST'], '/create-payment', FinancialsController::class .':createStripePayment')->setName('createStripePayment');
|
||||||
$route->map(['GET', 'POST'], '/create-adyen-payment', FinancialsController::class .':createAdyenPayment')->setName('createAdyenPayment');
|
$route->map(['GET', 'POST'], '/create-adyen-payment', FinancialsController::class .':createAdyenPayment')->setName('createAdyenPayment');
|
||||||
$route->map(['GET', 'POST'], '/create-crypto-payment', FinancialsController::class .':createCryptoPayment')->setName('createCryptoPayment');
|
$route->map(['GET', 'POST'], '/create-crypto-payment', FinancialsController::class .':createCryptoPayment')->setName('createCryptoPayment');
|
||||||
|
$route->map(['GET', 'POST'], '/create-nicky-payment', FinancialsController::class .':createNickyPayment')->setName('createNickyPayment');
|
||||||
$route->map(['GET', 'POST'], '/payment-success', FinancialsController::class .':successStripe')->setName('successStripe');
|
$route->map(['GET', 'POST'], '/payment-success', FinancialsController::class .':successStripe')->setName('successStripe');
|
||||||
$route->map(['GET', 'POST'], '/payment-success-adyen', FinancialsController::class .':successAdyen')->setName('successAdyen');
|
$route->map(['GET', 'POST'], '/payment-success-adyen', FinancialsController::class .':successAdyen')->setName('successAdyen');
|
||||||
$route->map(['GET', 'POST'], '/payment-success-crypto', FinancialsController::class .':successCrypto')->setName('successCrypto');
|
$route->map(['GET', 'POST'], '/payment-success-crypto', FinancialsController::class .':successCrypto')->setName('successCrypto');
|
||||||
|
$route->map(['GET', 'POST'], '/payment-success-nicky', FinancialsController::class .':successNicky')->setName('successNicky');
|
||||||
$route->map(['GET', 'POST'], '/payment-cancel', FinancialsController::class .':cancel')->setName('cancel');
|
$route->map(['GET', 'POST'], '/payment-cancel', FinancialsController::class .':cancel')->setName('cancel');
|
||||||
$route->get('/transactions', FinancialsController::class .':transactions')->setName('transactions');
|
$route->get('/transactions', FinancialsController::class .':transactions')->setName('transactions');
|
||||||
$route->get('/overview', FinancialsController::class .':overview')->setName('overview');
|
$route->get('/overview', FinancialsController::class .':overview')->setName('overview');
|
||||||
|
|
|
@ -1086,6 +1086,8 @@ ADYEN_HMAC_KEY='adyen-hmac-key'
|
||||||
|
|
||||||
NOW_API_KEY='now-api-key'
|
NOW_API_KEY='now-api-key'
|
||||||
|
|
||||||
|
NICKY_API_KEY='nicky-api-key'
|
||||||
|
|
||||||
TEST_TLDS=.test,.com.test
|
TEST_TLDS=.test,.com.test
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue