This commit is contained in:
2026-10-23 12:19:40 -04:00
parent 6cf0f0525f
commit faf6c5f2b3
19 changed files with 785 additions and 12 deletions
+23
View File
@@ -0,0 +1,23 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex">
<title>Crypto membership · eom.dev</title>
<link rel="stylesheet" href="/static/home.css">
<link rel="stylesheet" href="/static/membership.css">
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">
</head>
<body>
<header class="member-nav"><a class="brand" href="/">eom.dev</a><nav aria-label="Membership navigation"><a href="{% url 'members:home' %}">Membership</a></nav></header>
<main id="main-content" class="member-main">
<section class="member-panel form-panel">
<p class="eyebrow">Crypto payments · Powered by SHKeeper</p>
{% block content %}{% endblock %}
<p><a href="{% url 'members:home' %}">Back to membership</a></p>
</section>
</main>
<footer class="member-footer">Payment questions? <a href="mailto:eric@eom.dev">Contact the administrator</a>.</footer>
</body>
</html>
+14
View File
@@ -0,0 +1,14 @@
{% extends "crypto_base.html" %}
{% block content %}
<h1>Prepay {{ tier.label }}</h1>
<p class="tier-price">${{ tier.usd_amount }} USD <span>/ one calendar month</span></p>
<p>Pay once with cryptocurrency. This is <strong>not an automatically renewing subscription</strong>. Renewing the same tier adds one calendar month after your current prepaid term ends.</p>
<form method="post">
{% csrf_token %}
<p><label for="crypto">Cryptocurrency and network</label>
<select id="crypto" name="crypto" required>{% for currency in currencies %}<option value="{{ currency }}">{{ currency }}</option>{% endfor %}</select></p>
<p class="member-small">Choose carefully. Tokens on different networks are not interchangeable. SHKeeper supplies the deposit address and crypto amount. Configured processor fees or exchange-rate changes may affect the required amount.</p>
<button class="button button-primary" type="submit">Create payment invoice</button>
</form>
<p class="member-small">Only one unfinished crypto invoice is allowed per account. Repeated requests reopen it rather than create another deposit address. Stripe subscriptions must end before switching to crypto.</p>
{% endblock %}
+24
View File
@@ -0,0 +1,24 @@
{% extends "crypto_base.html" %}
{% block content %}
<h1>{{ invoice.tier|title }} invoice</h1>
<p class="member-notice" role="status">SHKeeper status: <strong>{{ invoice.status }}</strong></p>
<dl class="crypto-details">
<dt>Invoice reference</dt><dd>{{ invoice.id }}</dd>
<dt>Membership amount</dt><dd>${{ invoice.usd_amount }} USD · one calendar month</dd>
<dt>Cryptocurrency / network</dt><dd><strong>{{ invoice.crypto }}</strong></dd>
{% if invoice.wallet %}<dt>Deposit address</dt><dd><code>{{ invoice.wallet }}</code></dd>
<dt>Initial crypto quote</dt><dd>{{ invoice.crypto_amount }} {{ invoice.crypto }}</dd>{% endif %}
{% if invoice.last_checked_at %}<dt>Last checked</dt><dd>{{ invoice.last_checked_at|date:"M j, Y H:i:s T" }}</dd>{% endif %}
</dl>
{% if invoice.credited_at %}
<p>One prepaid term has been recorded: <strong>{{ invoice.access_start|date:"M j, Y H:i T" }}</strong> through <strong>{{ invoice.access_end|date:"M j, Y H:i T" }}</strong>.</p>
<p>This invoice cannot buy another term, even if more coins arrive. Create a new invoice from Membership to renew. Access requires a confirmed paid status and is removed at expiry.</p>
{% elif invoice.wallet %}
<p>Send only <strong>{{ invoice.crypto }}</strong> on its correct network to this address. Wait for blockchain confirmations and automatic verification before expecting access.</p>
<p class="member-small">The quote is from invoice creation, not a guaranteed permanent exchange rate. SHKeeper may recalculate after its configured rate-lock window. Partial payments do not activate membership. If a balance remains after your transfer, contact the administrator for the current remainder rather than repeatedly sending the initial quote.</p>
{% else %}
<p>The invoice is reserved and awaiting payment details from SHKeeper. The worker retries automatically. Do not send funds until an address appears.</p>
{% endif %}
<p><a class="button button-secondary" href="{% url 'members:crypto_invoice' invoice.id %}">Refresh payment status</a></p>
<p class="member-small">Refreshing this page only reads stored status. The worker checks SHKeeper automatically. Crypto payments are irreversible; do not reuse a paid address for renewal. Overpayments do not automatically buy extra months.</p>
{% endblock %}
+7 -2
View File
@@ -48,6 +48,9 @@
{% for subscription in subscriptions %}
<p class="member-notice">{{ subscription.tier|default:"Membership"|title }} · {{ subscription.status }}{% if subscription.paid_until > now %} · Paid access through {{ subscription.paid_until|date:"M j, Y H:i T" }}{% else %} · No current paid access{% endif %}{% if subscription.cancel_at_period_end %} · Does not renew{% endif %}</p>
{% endfor %}
{% for invoice in crypto_invoices %}
<p class="member-notice">SHKeeper · {{ invoice.tier|title }} · {{ invoice.status }}{% if invoice.access_end > now %} · Prepaid term {{ invoice.access_start|date:"M j, Y" }}–{{ invoice.access_end|date:"M j, Y" }}{% endif %} · <a href="{% url 'members:crypto_invoice' invoice.id %}">View crypto invoice</a></p>
{% endfor %}
<p class="member-small">Billing and directory status update automatically, usually within a few minutes. Signing out here does not sign you out of other Dex applications.</p>
{% else %}
<div class="member-actions">
@@ -69,8 +72,9 @@
<article class="member-panel tier-card">
<p class="card-kicker">Independent work, supported together</p><h3>{{ tier.label }}</h3><p class="tier-price">{{ tier.price_label }}</p>
<p>{{ tier.description|default:"Help keep the projects and community running." }}</p>
<ul><li>Tier-specific community access</li><li>Secure checkout hosted by Stripe</li><li>Manage renewal from your billing portal</li></ul>
{% if account %}<form action="{% url 'members:checkout' key %}" method="post">{% csrf_token %}<button class="button button-primary" type="submit">Choose {{ tier.label }}</button></form>{% else %}<a class="button button-primary" href="{% url 'members:register' %}">Create an account to join</a>{% endif %}
<ul><li>Tier-specific community access</li><li>Recurring payments through Stripe{% if shkeeper_enabled and tier.usd_amount %}, or prepaid crypto through SHKeeper{% endif %}</li><li>Your free account stays when paid access ends</li></ul>
{% if account %}<div class="tier-payment-actions"><form action="{% url 'members:checkout' key %}" method="post">{% csrf_token %}<button class="button button-primary" type="submit">Subscribe with Stripe</button></form>
{% if shkeeper_enabled and tier.usd_amount %}<a class="button button-secondary" href="{% url 'members:crypto_checkout' key %}">Pay with crypto</a>{% endif %}</div>{% else %}<a class="button button-primary" href="{% url 'members:register' %}">Create an account to join</a>{% endif %}
</article>
{% empty %}<div class="member-panel"><h3>Paid memberships are coming soon.</h3><p>You can create your free account now. Paid tiers will appear when their prices and groups are configured.</p></div>{% endfor %}
</div>
@@ -80,6 +84,7 @@
<details><summary>What happens when a membership ends?</summary><p>Your LDAP account stays. Paid group access is removed automatically when the paid service period expires. An immediate cancellation removes access on the next successful sync.</p></details>
<details><summary>Where is my password stored?</summary><p>Only OpenLDAP stores your password hash. This website sends your initial password over verified TLS to OpenLDAP's password-modify operation. Future sign-ins happen through Dex.</p></details>
<details><summary>Can I change or cancel a plan?</summary><p>Use Manage billing after signing in. The billing portal offers the changes enabled for your plan. Cancellation at period end keeps access through the paid expiry date.</p></details>
{% if shkeeper_enabled %}<details><summary>How do crypto memberships renew?</summary><p>SHKeeper payments prepay one calendar month, without automatic charges. Create a new invoice for each renewal. Confirmed payment on the same tier extends its prepaid term. Partial or unconfirmed payments do not grant access. Do not send renewal funds to an old invoice address.</p></details>{% endif %}
</section>
{% endif %}
</main>