Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 102 additions & 2 deletions donation.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function send(type, method, amount, currency) {
};

function updateCurrencyDisplay(currency) {
var symbol = currencySymbols[currency] || currencySymbols[defaultCurrency];
var symbol = currencySymbols[currency] || currencySymbols.eur;

Object.keys(presetValues).forEach(function(key) {
var label = document.querySelector("label[for='" + key + "']");
Expand Down Expand Up @@ -182,7 +182,7 @@ function showAccordion(amount) {
});

currencySelect.addEventListener('change', function() {
currency = currencySelect.value || defaultCurrency;
currency = currencySelect.value;
updateCurrencyDisplay(currency);
});

Expand Down Expand Up @@ -324,6 +324,9 @@ function showAccordion(amount) {
<div id="donationInfo">
<p> <?php echo _('If you are not sure or not able to commit to a regular donation, but still want to help the project, you can do a one-time donation, of any amount.'); ?> </p>
<p> <?php echo _('Choose freely the amount you wish to donate one time only.'); ?> </p>
<div class="mt-4" id="recentDonations">
<ul class="list-group list-group-flush" id="recentDonationsList"></ul>
</div>
</div>
<div id="sponsorInfo" class="hidden">
<p> <?php echo _('You can support FreeCAD by sponsoring it as an individual or organization through various platforms. Sponsorship provides a steady income for developers, allowing the FPA to plan ahead and enabling greater investment in FreeCAD. To encourage sponsorship, we offer different tiers, and unless you choose to remain anonymous, your name or company logo will be featured on our website accordingly.'); ?> </p>
Expand Down Expand Up @@ -377,3 +380,100 @@ function showAccordion(amount) {
</div>
</div>
</div>

<script>
document.addEventListener('DOMContentLoaded', function() {
var recentDonationsLimit = 5;

function formatDonationAmount(amount, currency) {
var symbol = currency === "usd" ? "$" : "€";
var value = Number(amount || 0);

return symbol + "\u00a0" + (Number.isInteger(value) ? value.toString() : value.toFixed(2));
}

function formatDonationDate(timestamp) {
var date = new Date(Number(timestamp || 0) * 1000);
var pad = function(value) {
return String(value).padStart(2, "0");
};

return date.getFullYear() + "-" +
pad(date.getMonth() + 1) + "-" +
pad(date.getDate()) + ", " +
pad(date.getHours()) + ":" +
pad(date.getMinutes()) + ":" +
pad(date.getSeconds());
}

function compactTimeAgo(timestamp) {
var seconds = Math.max(0, Math.floor(Date.now() / 1000 - Number(timestamp || 0)));

if (seconds < 60) {
return "now";
}

var minutes = Math.floor(seconds / 60);

if (minutes < 60) {
return minutes + "m";
}

var hours = Math.floor(minutes / 60);

if (hours < 24) {
return hours + "h";
}

return Math.floor(hours / 24) + "d";
}

function renderRecentDonations(data) {
var list = document.getElementById("recentDonationsList");
var donations = data && Array.isArray(data.donations) ? data.donations.slice(0, recentDonationsLimit) : [];

if (!list) {
return;
}

list.innerHTML = "";

donations.forEach(function(donation) {
var item = document.createElement("li");
var time = document.createElement("span");

item.className = "list-group-item bg-dark text-light border-secondary px-0";
item.appendChild(document.createTextNode("<?php echo _('Someone donated '); ?>" + formatDonationAmount(donation.amount, donation.currency)));

time.className = "text-secondary";
time.title = formatDonationDate(donation.created);
time.textContent = " " + compactTimeAgo(donation.created);

item.appendChild(time);
list.appendChild(item);
});
}

function loadRecentDonations() {
fetch("stripe-donations.json?_=" + Date.now(), { cache: "no-store" })
.then(function(response) {
if (!response.ok) {
throw new Error();
}

return response.json();
})
.then(renderRecentDonations)
.catch(function() {});
}

function refreshRecentDonations() {
fetch("stripe-donations-refresh.php?_=" + Date.now(), { cache: "no-store" })
.then(loadRecentDonations)
.catch(loadRecentDonations);
}

refreshRecentDonations();
setInterval(refreshRecentDonations, 60000);
});
</script>
105 changes: 105 additions & 0 deletions stripe-donations-refresh.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

$stripeSecretKey = getenv('STRIPE_SECRET_KEY');
$outputFile = __DIR__ . '/stripe-donations.json';
$limit = 10;
$minimumRefreshSeconds = 60;

function send_json($data)
{
header('Content-Type: application/json; charset=utf-8');
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
exit;
}

function send_file($file)
{
header('Content-Type: application/json; charset=utf-8');
readfile($file);
exit;
}

if (is_file($outputFile) && time() - filemtime($outputFile) < $minimumRefreshSeconds) {
send_file($outputFile);
}

if (!function_exists('curl_init')) {
send_json([
'ok' => true,
'updated_at' => time(),
'donations' => [],
]);
}

$url = 'https://api.stripe.com/v1/checkout/sessions?' . http_build_query([
'limit' => $limit,
'status' => 'complete',
]);

$ch = curl_init($url);

curl_setopt_array($ch, [
CURLOPT_HTTPGET => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $stripeSecretKey,
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 20,
]);

$response = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = $response === false ? curl_error($ch) : '';

curl_close($ch);

$data = json_decode((string) $response, true);

if ($curlError !== '' || $statusCode < 200 || $statusCode >= 300 || !is_array($data)) {
if (is_file($outputFile)) {
send_file($outputFile);
}

send_json([
'ok' => true,
'updated_at' => time(),
'donations' => [],
]);
}

$donations = [];

foreach (($data['data'] ?? []) as $session) {
if (($session['status'] ?? '') !== 'complete') {
continue;
}

$amountTotal = (int) ($session['amount_total'] ?? 0);
$currency = strtolower((string) ($session['currency'] ?? 'eur'));
$created = (int) ($session['created'] ?? time());

if ($amountTotal <= 0 || !in_array($currency, ['eur', 'usd'], true)) {
continue;
}

$donations[] = [
'id' => $session['id'] ?? '',
'amount' => $amountTotal / 100,
'currency' => $currency,
'created' => $created,
];
}

$output = [
'ok' => true,
'updated_at' => time(),
'donations' => $donations,
];

file_put_contents(
$outputFile,
json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES),
LOCK_EX
);

send_json($output);
Empty file added stripe-donations.json
Empty file.