Release v1.5.1
This commit is contained in:
@@ -21,6 +21,9 @@ ob_start();
|
||||
$key = (string)($item['key'] ?? '');
|
||||
$title = (string)($item['title'] ?? 'Item');
|
||||
$coverUrl = (string)($item['cover_url'] ?? '');
|
||||
$itemType = (string)($item['item_type'] ?? 'track');
|
||||
$releaseCount = (int)($item['release_count'] ?? 0);
|
||||
$trackCount = (int)($item['track_count'] ?? 0);
|
||||
$qty = max(1, (int)($item['qty'] ?? 1));
|
||||
$price = (float)($item['price'] ?? 0);
|
||||
$currency = (string)($item['currency'] ?? ($totals['currency'] ?? 'GBP'));
|
||||
@@ -35,6 +38,18 @@ ob_start();
|
||||
</div>
|
||||
<div style="min-width:0;">
|
||||
<div style="font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;"><?= htmlspecialchars($title, ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<?php if ($itemType === 'bundle' && ($releaseCount > 0 || $trackCount > 0)): ?>
|
||||
<div style="font-size:12px; color:var(--muted); margin-top:4px;">
|
||||
Includes
|
||||
<?php if ($releaseCount > 0): ?>
|
||||
<?= $releaseCount ?> release<?= $releaseCount === 1 ? '' : 's' ?>
|
||||
<?php endif; ?>
|
||||
<?php if ($releaseCount > 0 && $trackCount > 0): ?> · <?php endif; ?>
|
||||
<?php if ($trackCount > 0): ?>
|
||||
<?= $trackCount ?> track<?= $trackCount === 1 ? '' : 's' ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div style="font-size:12px; color:var(--muted); margin-top:4px;"><?= htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($price, 2) ?> x <?= $qty ?></div>
|
||||
</div>
|
||||
<div style="font-weight:700;"><?= htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($price * $qty, 2) ?></div>
|
||||
|
||||
@@ -1,208 +1,242 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
$pageTitle = $title ?? 'Checkout';
|
||||
$items = is_array($items ?? null) ? $items : [];
|
||||
$total = (float)($total ?? 0);
|
||||
$subtotal = (float)($subtotal ?? $total);
|
||||
$discountAmount = (float)($discount_amount ?? 0);
|
||||
$discountCode = (string)($discount_code ?? '');
|
||||
$currency = (string)($currency ?? 'GBP');
|
||||
$success = (string)($success ?? '');
|
||||
$orderNo = (string)($order_no ?? '');
|
||||
$error = (string)($error ?? '');
|
||||
$downloadLinks = is_array($download_links ?? null) ? $download_links : [];
|
||||
$downloadNotice = (string)($download_notice ?? '');
|
||||
$downloadLimit = (int)($download_limit ?? 5);
|
||||
$downloadExpiryDays = (int)($download_expiry_days ?? 30);
|
||||
ob_start();
|
||||
?>
|
||||
<section class="card checkout-wrap">
|
||||
<div class="badge">Store</div>
|
||||
<h1 style="margin:0; font-size:32px;">Checkout</h1>
|
||||
<?php if ($success !== ''): ?>
|
||||
<div style="padding:14px; border-radius:12px; border:1px solid rgba(34,242,165,.4); background:rgba(34,242,165,.12);">
|
||||
<div style="font-weight:700;">Order complete</div>
|
||||
<?php if ($orderNo !== ''): ?>
|
||||
<div style="margin-top:4px; color:var(--muted);">Order: <?= htmlspecialchars($orderNo, ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="checkout-panel">
|
||||
<div class="badge" style="font-size:9px;">Your Downloads</div>
|
||||
<?php if ($downloadLinks): ?>
|
||||
<div style="display:grid; gap:10px; margin-top:10px;">
|
||||
<?php foreach ($downloadLinks as $link): ?>
|
||||
<?php
|
||||
$label = trim((string)($link['label'] ?? 'Download'));
|
||||
$url = trim((string)($link['url'] ?? ''));
|
||||
if ($url === '') {
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
<a href="<?= htmlspecialchars($url, ENT_QUOTES, 'UTF-8') ?>" class="checkout-download-link">
|
||||
<span><?= htmlspecialchars($label !== '' ? $label : 'Download', ENT_QUOTES, 'UTF-8') ?></span>
|
||||
<span class="checkout-download-link-arrow">Download</span>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<p style="margin:10px 0 0; color:var(--muted); font-size:13px;">
|
||||
<?= htmlspecialchars($downloadNotice !== '' ? $downloadNotice : 'No downloads available for this order yet.', ENT_QUOTES, 'UTF-8') ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($error !== ''): ?>
|
||||
<div style="padding:14px; border-radius:12px; border:1px solid rgba(243,176,176,.45); background:rgba(243,176,176,.12); color:#ffd6d6;">
|
||||
<?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (!$items): ?>
|
||||
<div style="padding:14px; border-radius:12px; border:1px solid rgba(255,255,255,.1); background:rgba(0,0,0,.2); color:var(--muted);">
|
||||
Your cart is empty.
|
||||
</div>
|
||||
<div><a href="/releases" class="btn">Browse releases</a></div>
|
||||
<?php else: ?>
|
||||
<div class="checkout-grid">
|
||||
<div class="checkout-panel">
|
||||
<div class="badge" style="font-size:9px;">Order Summary</div>
|
||||
<div style="display:grid; gap:10px; margin-top:10px;">
|
||||
<?php foreach ($items as $item): ?>
|
||||
<?php
|
||||
$title = (string)($item['title'] ?? 'Item');
|
||||
$qty = max(1, (int)($item['qty'] ?? 1));
|
||||
$price = (float)($item['price'] ?? 0);
|
||||
$lineCurrency = (string)($item['currency'] ?? $currency);
|
||||
?>
|
||||
<div class="checkout-line">
|
||||
<div class="checkout-line-title"><?= htmlspecialchars($title, ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<div class="checkout-line-meta"><?= htmlspecialchars($lineCurrency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($price, 2) ?> x <?= $qty ?></div>
|
||||
<div class="checkout-line-total"><?= htmlspecialchars($lineCurrency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($price * $qty, 2) ?></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="checkout-total">
|
||||
<span>Subtotal</span>
|
||||
<strong><?= htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($subtotal, 2) ?></strong>
|
||||
</div>
|
||||
<?php if ($discountAmount > 0): ?>
|
||||
<div class="checkout-total" style="margin-top:8px;">
|
||||
<span>Discount (<?= htmlspecialchars($discountCode, ENT_QUOTES, 'UTF-8') ?>)</span>
|
||||
<strong style="color:#9be7c6;">-<?= htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($discountAmount, 2) ?></strong>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="checkout-total" style="margin-top:8px;">
|
||||
<span>Order total</span>
|
||||
<strong><?= htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($total, 2) ?></strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkout-panel">
|
||||
<div class="badge" style="font-size:9px;">Buyer Details</div>
|
||||
<form method="post" action="/checkout/place" style="display:grid; gap:12px; margin-top:10px;">
|
||||
<label style="font-size:12px; color:var(--muted); text-transform:uppercase; letter-spacing:.18em;">Email</label>
|
||||
<input name="email" type="email" value="" placeholder="you@example.com" class="checkout-input" required>
|
||||
|
||||
<div class="checkout-terms">
|
||||
<div class="badge" style="font-size:9px;">Terms</div>
|
||||
<p style="margin:8px 0 0; color:var(--muted); font-size:13px; line-height:1.5;">
|
||||
Digital download products are non-refundable once purchased and delivered. Please check your order details before placing the order.
|
||||
Files are limited to <?= $downloadLimit ?> downloads and expire after <?= $downloadExpiryDays ?> days.
|
||||
</p>
|
||||
<label style="margin-top:10px; display:flex; gap:8px; align-items:flex-start; color:#d7def2; font-size:13px;">
|
||||
<input type="checkbox" name="accept_terms" value="1" required style="margin-top:2px;">
|
||||
<span>I agree to the terms and understand all sales are final.</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="checkout-place-btn">Place Order</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
<style>
|
||||
.checkout-wrap { display:grid; gap:14px; }
|
||||
.checkout-grid { display:grid; grid-template-columns: minmax(0,1fr) 420px; gap:14px; }
|
||||
.checkout-panel {
|
||||
padding:14px;
|
||||
border-radius:12px;
|
||||
border:1px solid rgba(255,255,255,.1);
|
||||
background:rgba(0,0,0,.2);
|
||||
}
|
||||
.checkout-line {
|
||||
display:grid;
|
||||
grid-template-columns:minmax(0,1fr) auto;
|
||||
gap:8px;
|
||||
padding:10px;
|
||||
border-radius:10px;
|
||||
border:1px solid rgba(255,255,255,.08);
|
||||
background:rgba(255,255,255,.03);
|
||||
}
|
||||
.checkout-line-title { font-weight:600; }
|
||||
.checkout-line-meta { color:var(--muted); font-size:12px; margin-top:4px; grid-column:1/2; }
|
||||
.checkout-line-total { font-weight:700; grid-column:2/3; grid-row:1/3; align-self:center; }
|
||||
.checkout-total {
|
||||
margin-top:10px;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:space-between;
|
||||
padding:12px;
|
||||
border-radius:10px;
|
||||
border:1px solid rgba(255,255,255,.1);
|
||||
background:rgba(255,255,255,.04);
|
||||
}
|
||||
.checkout-total strong { font-size:22px; }
|
||||
.checkout-input {
|
||||
height:40px;
|
||||
border-radius:10px;
|
||||
border:1px solid rgba(255,255,255,.2);
|
||||
background:rgba(255,255,255,.05);
|
||||
color:#fff;
|
||||
padding:0 12px;
|
||||
}
|
||||
.checkout-terms {
|
||||
padding:12px;
|
||||
border-radius:10px;
|
||||
border:1px solid rgba(255,255,255,.1);
|
||||
background:rgba(255,255,255,.03);
|
||||
}
|
||||
.checkout-place-btn{
|
||||
height:40px;
|
||||
border-radius:999px;
|
||||
border:1px solid rgba(34,242,165,.45);
|
||||
background:rgba(34,242,165,.18);
|
||||
color:#cbfff1;
|
||||
font-weight:700;
|
||||
letter-spacing:.1em;
|
||||
text-transform:uppercase;
|
||||
cursor:pointer;
|
||||
}
|
||||
.checkout-place-btn:hover { background:rgba(34,242,165,.28); }
|
||||
.checkout-download-link {
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:space-between;
|
||||
gap:10px;
|
||||
padding:12px;
|
||||
border-radius:10px;
|
||||
border:1px solid rgba(34,242,165,.35);
|
||||
background:rgba(34,242,165,.1);
|
||||
color:#d7ffef;
|
||||
text-decoration:none;
|
||||
font-weight:600;
|
||||
}
|
||||
.checkout-download-link:hover { background:rgba(34,242,165,.18); }
|
||||
.checkout-download-link-arrow {
|
||||
font-size:11px;
|
||||
text-transform:uppercase;
|
||||
letter-spacing:.14em;
|
||||
color:#8df7d1;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.checkout-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../../../../views/site/layout.php';
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
$pageTitle = $title ?? 'Checkout';
|
||||
$items = is_array($items ?? null) ? $items : [];
|
||||
$total = (float)($total ?? 0);
|
||||
$subtotal = (float)($subtotal ?? $total);
|
||||
$discountAmount = (float)($discount_amount ?? 0);
|
||||
$discountCode = (string)($discount_code ?? '');
|
||||
$currency = (string)($currency ?? 'GBP');
|
||||
$success = (string)($success ?? '');
|
||||
$orderNo = (string)($order_no ?? '');
|
||||
$error = (string)($error ?? '');
|
||||
$downloadLinks = is_array($download_links ?? null) ? $download_links : [];
|
||||
$downloadNotice = (string)($download_notice ?? '');
|
||||
$downloadLimit = (int)($download_limit ?? 5);
|
||||
$downloadExpiryDays = (int)($download_expiry_days ?? 30);
|
||||
$paypalEnabled = (bool)($paypal_enabled ?? false);
|
||||
$paypalCardsEnabled = (bool)($paypal_cards_enabled ?? false);
|
||||
$paypalCardsAvailable = (bool)($paypal_cards_available ?? false);
|
||||
ob_start();
|
||||
?>
|
||||
<section class="card checkout-wrap">
|
||||
<div class="badge">Store</div>
|
||||
<h1 style="margin:0; font-size:32px;">Checkout</h1>
|
||||
|
||||
<?php if ($success !== ''): ?>
|
||||
<div class="checkout-status checkout-status-success">
|
||||
<div style="font-weight:700;">Order complete</div>
|
||||
<?php if ($orderNo !== ''): ?>
|
||||
<div style="margin-top:4px; color:var(--muted);">Order: <?= htmlspecialchars($orderNo, ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="checkout-panel">
|
||||
<div class="badge" style="font-size:9px;">Your Downloads</div>
|
||||
<?php if ($downloadLinks): ?>
|
||||
<div style="display:grid; gap:10px; margin-top:10px;">
|
||||
<?php foreach ($downloadLinks as $link): ?>
|
||||
<?php
|
||||
$label = trim((string)($link['label'] ?? 'Download'));
|
||||
$url = trim((string)($link['url'] ?? ''));
|
||||
if ($url === '') {
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
<a href="<?= htmlspecialchars($url, ENT_QUOTES, 'UTF-8') ?>" class="checkout-download-link">
|
||||
<span><?= htmlspecialchars($label !== '' ? $label : 'Download', ENT_QUOTES, 'UTF-8') ?></span>
|
||||
<span class="checkout-download-link-arrow">Download</span>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<p style="margin:10px 0 0; color:var(--muted); font-size:13px;">
|
||||
<?= htmlspecialchars($downloadNotice !== '' ? $downloadNotice : 'No downloads available for this order yet.', ENT_QUOTES, 'UTF-8') ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($error !== ''): ?>
|
||||
<div class="checkout-status checkout-status-error"><?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!$items): ?>
|
||||
<div class="checkout-status checkout-status-empty">Your cart is empty.</div>
|
||||
<div><a href="/releases" class="btn">Browse releases</a></div>
|
||||
<?php else: ?>
|
||||
<div class="checkout-grid">
|
||||
<div class="checkout-panel">
|
||||
<div class="badge" style="font-size:9px;">Order Summary</div>
|
||||
<div style="display:grid; gap:10px; margin-top:10px;">
|
||||
<?php foreach ($items as $item): ?>
|
||||
<?php
|
||||
$title = (string)($item['title'] ?? 'Item');
|
||||
$itemType = (string)($item['item_type'] ?? 'track');
|
||||
$releaseCount = (int)($item['release_count'] ?? 0);
|
||||
$trackCount = (int)($item['track_count'] ?? 0);
|
||||
$qty = max(1, (int)($item['qty'] ?? 1));
|
||||
$price = (float)($item['price'] ?? 0);
|
||||
$lineCurrency = (string)($item['currency'] ?? $currency);
|
||||
?>
|
||||
<div class="checkout-line">
|
||||
<div class="checkout-line-title"><?= htmlspecialchars($title, ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<?php if ($itemType === 'bundle' && ($releaseCount > 0 || $trackCount > 0)): ?>
|
||||
<div class="checkout-line-meta">
|
||||
Includes
|
||||
<?php if ($releaseCount > 0): ?><?= $releaseCount ?> release<?= $releaseCount === 1 ? '' : 's' ?><?php endif; ?>
|
||||
<?php if ($releaseCount > 0 && $trackCount > 0): ?> · <?php endif; ?>
|
||||
<?php if ($trackCount > 0): ?><?= $trackCount ?> track<?= $trackCount === 1 ? '' : 's' ?><?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="checkout-line-meta"><?= htmlspecialchars($lineCurrency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($price, 2) ?> x <?= $qty ?></div>
|
||||
<div class="checkout-line-total"><?= htmlspecialchars($lineCurrency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($price * $qty, 2) ?></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="checkout-total">
|
||||
<span>Subtotal</span>
|
||||
<strong><?= htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($subtotal, 2) ?></strong>
|
||||
</div>
|
||||
<?php if ($discountAmount > 0): ?>
|
||||
<div class="checkout-total" style="margin-top:8px;">
|
||||
<span>Discount (<?= htmlspecialchars($discountCode, ENT_QUOTES, 'UTF-8') ?>)</span>
|
||||
<strong style="color:#9be7c6;">-<?= htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($discountAmount, 2) ?></strong>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="checkout-total" style="margin-top:8px;">
|
||||
<span>Order total</span>
|
||||
<strong><?= htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($total, 2) ?></strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkout-panel">
|
||||
<div class="badge" style="font-size:9px;">Buyer Details</div>
|
||||
<form method="post" action="/checkout/place" class="checkout-form-stack" id="checkoutMethodForm">
|
||||
<label class="checkout-label" for="checkoutEmail">Email</label>
|
||||
<input id="checkoutEmail" name="email" type="email" value="" placeholder="you@example.com" class="checkout-input" required>
|
||||
|
||||
<div class="checkout-terms">
|
||||
<div class="badge" style="font-size:9px;">Terms</div>
|
||||
<p class="checkout-copy">
|
||||
Digital download products are non-refundable once purchased and delivered. Please check your order details before placing the order.
|
||||
Files are limited to <?= $downloadLimit ?> downloads and expire after <?= $downloadExpiryDays ?> days.
|
||||
</p>
|
||||
<label class="checkout-terms-check">
|
||||
<input id="checkoutTerms" type="checkbox" name="accept_terms" value="1" required>
|
||||
<span>I agree to the terms and understand all sales are final.</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="checkout-payment-chooser">
|
||||
<div class="checkout-payment-option">
|
||||
<div class="badge" style="font-size:9px;">PayPal</div>
|
||||
<div class="checkout-payment-title">Pay via PayPal</div>
|
||||
<div class="checkout-copy">You will be redirected to PayPal to approve the payment.</div>
|
||||
<button type="submit" class="checkout-place-btn"<?= $paypalEnabled ? '' : ' disabled' ?>>Pay via PayPal</button>
|
||||
</div>
|
||||
|
||||
<?php if ($paypalCardsEnabled && $paypalCardsAvailable): ?>
|
||||
<div class="checkout-payment-option">
|
||||
<div class="badge" style="font-size:9px;">Cards</div>
|
||||
<div class="checkout-payment-title">Pay via Credit / Debit Card</div>
|
||||
<div class="checkout-copy">Open a dedicated secure card-payment page powered by PayPal.</div>
|
||||
<button type="button" class="checkout-secondary-btn" id="checkoutCardStartBtn">Continue to card payment</button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
<style>
|
||||
.checkout-wrap { display:grid; gap:14px; }
|
||||
.checkout-grid { display:grid; grid-template-columns:minmax(0,1fr) 460px; gap:14px; }
|
||||
.checkout-panel { padding:14px; border-radius:12px; border:1px solid rgba(255,255,255,.1); background:rgba(0,0,0,.2); }
|
||||
.checkout-status { padding:14px; border-radius:12px; border:1px solid rgba(255,255,255,.1); }
|
||||
.checkout-status-success { border-color:rgba(34,242,165,.4); background:rgba(34,242,165,.12); }
|
||||
.checkout-status-error { border-color:rgba(243,176,176,.45); background:rgba(243,176,176,.12); color:#ffd6d6; }
|
||||
.checkout-status-empty { background:rgba(0,0,0,.2); color:var(--muted); }
|
||||
.checkout-form-stack { display:grid; gap:12px; margin-top:10px; }
|
||||
.checkout-label { font-size:12px; color:var(--muted); text-transform:uppercase; letter-spacing:.18em; }
|
||||
.checkout-input { height:44px; border-radius:12px; border:1px solid rgba(255,255,255,.16); background:rgba(255,255,255,.05); color:#fff; padding:0 14px; }
|
||||
.checkout-line { display:grid; grid-template-columns:minmax(0,1fr) auto; gap:8px; padding:10px; border-radius:10px; border:1px solid rgba(255,255,255,.08); background:rgba(255,255,255,.03); }
|
||||
.checkout-line-title { font-weight:600; }
|
||||
.checkout-line-meta { color:var(--muted); font-size:12px; margin-top:4px; grid-column:1/2; }
|
||||
.checkout-line-total { font-weight:700; grid-column:2/3; grid-row:1/3; align-self:center; }
|
||||
.checkout-total { margin-top:10px; display:flex; align-items:center; justify-content:space-between; padding:12px; border-radius:10px; border:1px solid rgba(255,255,255,.1); background:rgba(255,255,255,.04); }
|
||||
.checkout-total strong { font-size:22px; }
|
||||
.checkout-terms { padding:14px; border-radius:12px; border:1px solid rgba(255,255,255,.1); background:rgba(255,255,255,.03); }
|
||||
.checkout-copy { margin:8px 0 0; color:var(--muted); font-size:13px; line-height:1.5; }
|
||||
.checkout-terms-check { margin-top:10px; display:flex; gap:8px; align-items:flex-start; color:#d7def2; font-size:13px; }
|
||||
.checkout-payment-chooser { display:grid; gap:12px; }
|
||||
.checkout-payment-option { display:grid; gap:8px; padding:14px; border-radius:12px; border:1px solid rgba(255,255,255,.1); background:rgba(255,255,255,.03); }
|
||||
.checkout-payment-title { font-size:18px; font-weight:700; margin-top:4px; }
|
||||
.checkout-place-btn, .checkout-secondary-btn { height:44px; border-radius:999px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; cursor:pointer; }
|
||||
.checkout-place-btn { border:1px solid rgba(34,242,165,.45); background:rgba(34,242,165,.18); color:#cbfff1; }
|
||||
.checkout-place-btn:hover { background:rgba(34,242,165,.28); }
|
||||
.checkout-secondary-btn { border:1px solid rgba(255,255,255,.16); background:rgba(255,255,255,.05); color:#eef3ff; }
|
||||
.checkout-secondary-btn:hover { background:rgba(255,255,255,.1); }
|
||||
.checkout-download-link { display:flex; align-items:center; justify-content:space-between; gap:10px; padding:12px; border-radius:10px; border:1px solid rgba(34,242,165,.35); background:rgba(34,242,165,.1); color:#d7ffef; text-decoration:none; font-weight:600; }
|
||||
.checkout-download-link:hover { background:rgba(34,242,165,.18); }
|
||||
.checkout-download-link-arrow { font-size:11px; text-transform:uppercase; letter-spacing:.14em; color:#8df7d1; }
|
||||
@media (max-width: 900px) { .checkout-grid { grid-template-columns:1fr; } }
|
||||
</style>
|
||||
<script>
|
||||
(function () {
|
||||
var cardBtn = document.getElementById('checkoutCardStartBtn');
|
||||
var form = document.getElementById('checkoutMethodForm');
|
||||
var email = document.getElementById('checkoutEmail');
|
||||
var terms = document.getElementById('checkoutTerms');
|
||||
if (!cardBtn || !form || !email || !terms) {
|
||||
return;
|
||||
}
|
||||
cardBtn.addEventListener('click', function () {
|
||||
if (!email.reportValidity()) {
|
||||
return;
|
||||
}
|
||||
if (!terms.checked) {
|
||||
terms.reportValidity();
|
||||
return;
|
||||
}
|
||||
var tmp = document.createElement('form');
|
||||
tmp.method = 'post';
|
||||
tmp.action = '/checkout/card/start';
|
||||
tmp.style.display = 'none';
|
||||
|
||||
var emailInput = document.createElement('input');
|
||||
emailInput.type = 'hidden';
|
||||
emailInput.name = 'email';
|
||||
emailInput.value = email.value;
|
||||
tmp.appendChild(emailInput);
|
||||
|
||||
var termsInput = document.createElement('input');
|
||||
termsInput.type = 'hidden';
|
||||
termsInput.name = 'accept_terms';
|
||||
termsInput.value = '1';
|
||||
tmp.appendChild(termsInput);
|
||||
|
||||
var methodInput = document.createElement('input');
|
||||
methodInput.type = 'hidden';
|
||||
methodInput.name = 'checkout_method';
|
||||
methodInput.value = 'card';
|
||||
tmp.appendChild(methodInput);
|
||||
|
||||
var csrfMeta = document.querySelector('meta[name=\"csrf-token\"]');
|
||||
if (csrfMeta && csrfMeta.content) {
|
||||
var csrfInput = document.createElement('input');
|
||||
csrfInput.type = 'hidden';
|
||||
csrfInput.name = 'csrf_token';
|
||||
csrfInput.value = csrfMeta.content;
|
||||
tmp.appendChild(csrfInput);
|
||||
}
|
||||
|
||||
document.body.appendChild(tmp);
|
||||
tmp.submit();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../../../../views/site/layout.php';
|
||||
|
||||
310
plugins/store/views/site/checkout_card.php
Normal file
310
plugins/store/views/site/checkout_card.php
Normal file
@@ -0,0 +1,310 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
$pageTitle = $title ?? 'Card Checkout';
|
||||
$items = is_array($items ?? null) ? $items : [];
|
||||
$total = (float)($total ?? 0);
|
||||
$subtotal = (float)($subtotal ?? $total);
|
||||
$discountAmount = (float)($discount_amount ?? 0);
|
||||
$discountCode = (string)($discount_code ?? '');
|
||||
$currency = (string)($currency ?? 'GBP');
|
||||
$email = (string)($email ?? '');
|
||||
$acceptTerms = (bool)($accept_terms ?? false);
|
||||
$downloadLimit = (int)($download_limit ?? 5);
|
||||
$downloadExpiryDays = (int)($download_expiry_days ?? 30);
|
||||
$paypalClientId = trim((string)($paypal_client_id ?? ''));
|
||||
$paypalClientToken = trim((string)($paypal_client_token ?? ''));
|
||||
$paypalMerchantCountry = strtoupper(trim((string)($paypal_merchant_country ?? '')));
|
||||
$paypalCardBrandingText = trim((string)($paypal_card_branding_text ?? 'Pay with card'));
|
||||
$sdkUrl = 'https://www.paypal.com/sdk/js?client-id=' . rawurlencode($paypalClientId)
|
||||
. '¤cy=' . rawurlencode($currency)
|
||||
. '&intent=capture'
|
||||
. '&components=buttons,card-fields';
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<section class="card checkout-wrap">
|
||||
<div class="badge">Cards</div>
|
||||
<div class="checkout-card-header">
|
||||
<div>
|
||||
<h1 style="margin:0; font-size:32px;">Credit / Debit Card</h1>
|
||||
<p class="checkout-card-copy">Secure card payment powered by PayPal. The order completes immediately after capture succeeds.</p>
|
||||
</div>
|
||||
<a href="/checkout" class="btn outline">Back to checkout</a>
|
||||
</div>
|
||||
|
||||
<div class="checkout-grid">
|
||||
<div class="checkout-panel">
|
||||
<div class="badge" style="font-size:9px;">Buyer Details</div>
|
||||
<div class="checkout-form-stack">
|
||||
<label class="checkout-label" for="cardCheckoutEmail">Email</label>
|
||||
<input id="cardCheckoutEmail" type="email" value="<?= htmlspecialchars($email, ENT_QUOTES, 'UTF-8') ?>" placeholder="you@example.com" class="checkout-input" required>
|
||||
|
||||
<div class="checkout-terms">
|
||||
<div class="badge" style="font-size:9px;">Terms</div>
|
||||
<p class="checkout-card-copy">
|
||||
Digital download products are non-refundable once purchased and delivered. Please check your order details before placing the order.
|
||||
Files are limited to <?= $downloadLimit ?> downloads and expire after <?= $downloadExpiryDays ?> days.
|
||||
</p>
|
||||
<label class="checkout-terms-check">
|
||||
<input id="cardCheckoutTerms" type="checkbox" value="1" <?= $acceptTerms ? 'checked' : '' ?>>
|
||||
<span>I agree to the terms and understand all sales are final.</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="cardCheckoutStatus" class="checkout-inline-status" hidden></div>
|
||||
|
||||
<div class="checkout-card-form">
|
||||
<div class="checkout-card-field">
|
||||
<label class="checkout-label">Cardholder name</label>
|
||||
<div id="paypal-name-field" class="checkout-card-shell"></div>
|
||||
</div>
|
||||
<div class="checkout-card-field checkout-card-field-full">
|
||||
<label class="checkout-label">Card number</label>
|
||||
<div id="paypal-number-field" class="checkout-card-shell"></div>
|
||||
</div>
|
||||
<div class="checkout-card-field">
|
||||
<label class="checkout-label">Expiry</label>
|
||||
<div id="paypal-expiry-field" class="checkout-card-shell"></div>
|
||||
</div>
|
||||
<div class="checkout-card-field">
|
||||
<label class="checkout-label">Security code</label>
|
||||
<div id="paypal-cvv-field" class="checkout-card-shell"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" id="paypalCardSubmit" class="checkout-place-btn"><?= htmlspecialchars($paypalCardBrandingText !== '' ? $paypalCardBrandingText : 'Pay with card', ENT_QUOTES, 'UTF-8') ?></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkout-panel">
|
||||
<div class="badge" style="font-size:9px;">Order Summary</div>
|
||||
<div style="display:grid; gap:10px; margin-top:10px;">
|
||||
<?php foreach ($items as $item): ?>
|
||||
<?php
|
||||
$title = (string)($item['title'] ?? 'Item');
|
||||
$qty = max(1, (int)($item['qty'] ?? 1));
|
||||
$price = (float)($item['price'] ?? 0);
|
||||
$lineCurrency = (string)($item['currency'] ?? $currency);
|
||||
?>
|
||||
<div class="checkout-line">
|
||||
<div class="checkout-line-title"><?= htmlspecialchars($title, ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<div class="checkout-line-meta"><?= htmlspecialchars($lineCurrency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($price, 2) ?> x <?= $qty ?></div>
|
||||
<div class="checkout-line-total"><?= htmlspecialchars($lineCurrency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($price * $qty, 2) ?></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="checkout-total">
|
||||
<span>Subtotal</span>
|
||||
<strong><?= htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($subtotal, 2) ?></strong>
|
||||
</div>
|
||||
<?php if ($discountAmount > 0): ?>
|
||||
<div class="checkout-total" style="margin-top:8px;">
|
||||
<span>Discount (<?= htmlspecialchars($discountCode, ENT_QUOTES, 'UTF-8') ?>)</span>
|
||||
<strong style="color:#9be7c6;">-<?= htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($discountAmount, 2) ?></strong>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="checkout-total" style="margin-top:8px;">
|
||||
<span>Order total</span>
|
||||
<strong><?= htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($total, 2) ?></strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<style>
|
||||
.checkout-wrap { display:grid; gap:14px; }
|
||||
.checkout-card-header { display:flex; align-items:start; justify-content:space-between; gap:16px; }
|
||||
.checkout-grid { display:grid; grid-template-columns:minmax(0, 1.1fr) 420px; gap:14px; }
|
||||
.checkout-panel { padding:16px; border-radius:14px; border:1px solid rgba(255,255,255,.1); background:rgba(0,0,0,.2); }
|
||||
.checkout-form-stack { display:grid; gap:12px; margin-top:10px; }
|
||||
.checkout-label { font-size:12px; color:var(--muted); text-transform:uppercase; letter-spacing:.18em; }
|
||||
.checkout-input { height:46px; border-radius:12px; border:1px solid rgba(255,255,255,.16); background:rgba(255,255,255,.05); color:#fff; padding:0 14px; }
|
||||
.checkout-terms { padding:14px; border-radius:12px; border:1px solid rgba(255,255,255,.1); background:rgba(255,255,255,.03); }
|
||||
.checkout-card-copy { margin:8px 0 0; color:var(--muted); font-size:13px; line-height:1.55; }
|
||||
.checkout-terms-check { margin-top:10px; display:flex; gap:8px; align-items:flex-start; color:#d7def2; font-size:13px; }
|
||||
.checkout-inline-status { padding:12px 14px; border-radius:12px; border:1px solid rgba(255,255,255,.1); font-size:13px; }
|
||||
.checkout-inline-status.error { border-color:rgba(243,176,176,.45); background:rgba(243,176,176,.12); color:#ffd6d6; }
|
||||
.checkout-inline-status.info { border-color:rgba(255,255,255,.12); background:rgba(255,255,255,.04); color:#d7def2; }
|
||||
.checkout-card-form { display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:10px; padding:12px; border-radius:16px; border:1px solid rgba(255,255,255,.08); background:rgba(255,255,255,.02); }
|
||||
.checkout-card-field-full { grid-column:1 / -1; }
|
||||
.checkout-card-field { display:grid; gap:6px; }
|
||||
.checkout-card-shell { min-height:44px; border-radius:12px; border:0; background:transparent; box-shadow:none; padding:0; display:flex; align-items:center; }
|
||||
.checkout-card-shell iframe { width:100% !important; min-height:40px !important; border-radius:10px !important; }
|
||||
.checkout-place-btn { height:48px; border-radius:999px; border:1px solid rgba(34,242,165,.45); background:rgba(34,242,165,.18); color:#cbfff1; font-weight:700; letter-spacing:.1em; text-transform:uppercase; cursor:pointer; margin-top:4px; }
|
||||
.checkout-place-btn:hover { background:rgba(34,242,165,.28); }
|
||||
.checkout-line { display:grid; grid-template-columns:minmax(0,1fr) auto; gap:8px; padding:10px; border-radius:10px; border:1px solid rgba(255,255,255,.08); background:rgba(255,255,255,.03); }
|
||||
.checkout-line-title { font-weight:600; }
|
||||
.checkout-line-meta { color:var(--muted); font-size:12px; margin-top:4px; grid-column:1/2; }
|
||||
.checkout-line-total { font-weight:700; grid-column:2/3; grid-row:1/3; align-self:center; }
|
||||
.checkout-total { margin-top:10px; display:flex; align-items:center; justify-content:space-between; padding:12px; border-radius:10px; border:1px solid rgba(255,255,255,.1); background:rgba(255,255,255,.04); }
|
||||
.checkout-total strong { font-size:22px; }
|
||||
@media (max-width: 900px) {
|
||||
.checkout-card-header { flex-direction:column; align-items:stretch; }
|
||||
.checkout-grid { grid-template-columns:1fr; }
|
||||
.checkout-card-form { grid-template-columns:1fr; }
|
||||
.checkout-card-field-full { grid-column:auto; }
|
||||
}
|
||||
</style>
|
||||
<script src="<?= htmlspecialchars($sdkUrl, ENT_QUOTES, 'UTF-8') ?>" data-client-token="<?= htmlspecialchars($paypalClientToken, ENT_QUOTES, 'UTF-8') ?>" data-sdk-integration-source="audiocore"></script>
|
||||
<script>
|
||||
(function () {
|
||||
var emailEl = document.getElementById('cardCheckoutEmail');
|
||||
var termsEl = document.getElementById('cardCheckoutTerms');
|
||||
var statusEl = document.getElementById('cardCheckoutStatus');
|
||||
var submitBtn = document.getElementById('paypalCardSubmit');
|
||||
|
||||
function setStatus(type, message) {
|
||||
if (!statusEl) return;
|
||||
if (!message) {
|
||||
statusEl.hidden = true;
|
||||
statusEl.textContent = '';
|
||||
statusEl.className = 'checkout-inline-status';
|
||||
return;
|
||||
}
|
||||
statusEl.hidden = false;
|
||||
statusEl.textContent = message;
|
||||
statusEl.className = 'checkout-inline-status ' + type;
|
||||
}
|
||||
|
||||
function validateBuyer() {
|
||||
var email = emailEl ? emailEl.value.trim() : '';
|
||||
if (!email) {
|
||||
setStatus('error', 'Enter your email address.');
|
||||
return null;
|
||||
}
|
||||
if (!termsEl || !termsEl.checked) {
|
||||
setStatus('error', 'Accept the terms to continue.');
|
||||
return null;
|
||||
}
|
||||
setStatus('', '');
|
||||
return { email: email, accept_terms: true };
|
||||
}
|
||||
|
||||
function postJson(url, payload) {
|
||||
var csrfMeta = document.querySelector('meta[name=\"csrf-token\"]');
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-Token': csrfMeta ? csrfMeta.content : '' },
|
||||
body: JSON.stringify(payload)
|
||||
}).then(function (response) {
|
||||
return response.json().catch(function () {
|
||||
return { ok: false, error: 'Unexpected server response.' };
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createOrder() {
|
||||
var buyer = validateBuyer();
|
||||
if (!buyer) {
|
||||
return Promise.reject(new Error('Validation failed.'));
|
||||
}
|
||||
setStatus('info', 'Preparing secure card payment...');
|
||||
return postJson('/checkout/paypal/create-order', buyer).then(function (data) {
|
||||
if (!data || data.ok !== true) {
|
||||
throw new Error((data && data.error) || 'Unable to start checkout.');
|
||||
}
|
||||
if (data.completed && data.redirect) {
|
||||
window.location.href = data.redirect;
|
||||
throw new Error('redirect');
|
||||
}
|
||||
return data.orderID || data.paypal_order_id;
|
||||
});
|
||||
}
|
||||
|
||||
function captureOrder(orderID) {
|
||||
setStatus('info', 'Finalizing payment...');
|
||||
return postJson('/checkout/paypal/capture-order', { orderID: orderID }).then(function (data) {
|
||||
if (!data || data.ok !== true) {
|
||||
throw new Error((data && data.error) || 'Unable to finalize payment.');
|
||||
}
|
||||
if (data.redirect) {
|
||||
window.location.href = data.redirect;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initCardFields() {
|
||||
if (!(window.paypal && paypal.CardFields)) {
|
||||
setStatus('error', 'PayPal card fields failed to load. Client token present: <?= $paypalClientToken !== '' ? 'yes' : 'no' ?>. Capability requires Advanced Card Payments on the PayPal account.');
|
||||
if (submitBtn) submitBtn.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var cardFields = paypal.CardFields({
|
||||
style: {
|
||||
'input': {
|
||||
'appearance': 'none',
|
||||
'background': '#151922',
|
||||
'color': '#eef3ff',
|
||||
'font-family': 'Syne, sans-serif',
|
||||
'font-size': '17px',
|
||||
'line-height': '24px',
|
||||
'padding': '12px 14px',
|
||||
'border': '1px solid rgba(255,255,255,0.10)',
|
||||
'border-radius': '10px',
|
||||
'box-shadow': 'none',
|
||||
'outline': 'none',
|
||||
'-webkit-appearance': 'none'
|
||||
},
|
||||
'input::placeholder': {
|
||||
'color': 'rgba(238,243,255,0.42)'
|
||||
},
|
||||
'input:hover': {
|
||||
'border': '1px solid rgba(255,255,255,0.18)'
|
||||
},
|
||||
'input:focus': {
|
||||
'border': '1px solid #22f2a5',
|
||||
'box-shadow': '0 0 0 2px rgba(34,242,165,0.12)'
|
||||
},
|
||||
'.valid': {
|
||||
'color': '#eef3ff'
|
||||
},
|
||||
'.invalid': {
|
||||
'color': '#ffd6d6',
|
||||
'border': '1px solid rgba(255,107,107,0.72)',
|
||||
'box-shadow': '0 0 0 2px rgba(255,107,107,0.10)'
|
||||
}
|
||||
},
|
||||
createOrder: function () {
|
||||
return createOrder();
|
||||
},
|
||||
onApprove: function (data) {
|
||||
return captureOrder(data.orderID);
|
||||
},
|
||||
onError: function (err) {
|
||||
setStatus('error', err && err.message ? err.message : 'Card payment failed.');
|
||||
}
|
||||
});
|
||||
|
||||
if (!cardFields.isEligible()) {
|
||||
setStatus('error', 'Card checkout is not available for this account.');
|
||||
if (submitBtn) submitBtn.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
cardFields.NameField().render('#paypal-name-field');
|
||||
cardFields.NumberField().render('#paypal-number-field');
|
||||
cardFields.ExpiryField().render('#paypal-expiry-field');
|
||||
cardFields.CVVField().render('#paypal-cvv-field');
|
||||
|
||||
if (submitBtn) {
|
||||
submitBtn.addEventListener('click', function () {
|
||||
if (!validateBuyer()) {
|
||||
return;
|
||||
}
|
||||
setStatus('info', 'Submitting card payment...');
|
||||
cardFields.submit({});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initCardFields);
|
||||
} else {
|
||||
initCardFields();
|
||||
}
|
||||
}());
|
||||
</script>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../../../../views/site/layout.php';
|
||||
Reference in New Issue
Block a user