Diff -- /var/www/vhosts/3dshawn.com/affiliate.3dshawn.com/TEMPLATES/webstls_billing_payment.html
Diff

/var/www/vhosts/3dshawn.com/affiliate.3dshawn.com/TEMPLATES/webstls_billing_payment.html

added on local at 2026-07-01 13:48:00

Added
+220
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 29dd245c4d48
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1<!-- =====================================================================
2 Manage card on file.
3 1. Flash message strip
4 2. Saved cards list (set default / remove)
5 3. Stripe Elements card-entry form (SetupIntent)
6 Backed by /billing_payment.cgi. PAN never reaches our server --
7 Stripe.js handles the card field; we only see the resulting
8 pm_xxx id that we attach + persist.
9 The whole interactive surface is wrapped with [ptag:manage_billing]
10 so a team member without the grant lands on a read-only "not
11 authorized" notice from require_feature's redirect upstream.
12===================================================================== -->
13
14<style>
15 .pay-wrap { max-width: 720px; margin: 0 auto; }
16 .pay-card {
17 background: var(--col-surface-1);
18 border: 1px solid var(--col-border);
19 border-radius: 12px;
20 padding: 18px 20px;
21 margin-bottom: 18px;
22 }
23 .pay-card h2 {
24 font-size: 16px; font-weight: 600; color: var(--col-text);
25 margin: 0 0 12px;
26 }
27 .pay-row {
28 display: flex; align-items: center; justify-content: space-between;
29 gap: 14px; padding: 10px 0; border-top: 1px solid var(--col-border);
30 }
31 .pay-row:first-of-type { border-top: 0; }
32 .pay-pill {
33 display: inline-block; font-size: 11px; font-weight: 600;
34 background: var(--col-brand); color: #fff; padding: 2px 8px;
35 border-radius: 999px; margin-left: 8px;
36 }
37 .pay-brand-tile {
38 width: 48px; height: 32px; border-radius: 6px;
39 background: linear-gradient(135deg,#1e293b,#334155);
40 display: grid; place-items: center;
41 font-size: 11px; font-weight: 700; color: #fff;
42 }
43 #card-element {
44 background: #fff; padding: 12px 14px; border-radius: 8px;
45 border: 1px solid var(--col-border);
46 }
47 #card-errors {
48 color: #f87171; font-size: 13px; margin-top: 8px; min-height: 18px;
49 }
50 .pay-flash {
51 border-radius: 8px; padding: 10px 14px; font-size: 13px;
52 margin-bottom: 16px;
53 }
54 .pay-flash.ok { background: rgba(34,197,94,0.15); color: #4ade80; border: 1px solid rgba(34,197,94,0.35); }
55 .pay-flash.danger { background: rgba(239,68,68,0.15); color: #fca5a5; border: 1px solid rgba(239,68,68,0.35); }
56 .pay-not-config {
57 border: 1px dashed var(--col-border); border-radius: 10px;
58 padding: 20px; text-align: center; color: var(--col-text-2);
59 }
60</style>
61
62<div class="pay-wrap">
63
64 <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:14px">
65 <h1 style="font-size:22px;color:var(--col-text);margin:0">Card on file</h1>
66 <a href="/billing.cgi" class="btn btn-secondary btn-sm">Back to billing</a>
67 </div>
68 <p class="text-sm text-dim" style="margin:0 0 18px">
69 Cards are stored at Stripe; we only keep the brand, last 4 digits, and expiration on our side for display. The card number never reaches our servers.
70 </p>
71
72 [if:$has_flash]
73 <div class="pay-flash $flash_kind">$flash_msg</div>
74 [/if]
75
76 [ptag:manage_billing]
77
78 <!-- Saved cards -->
79 <div class="pay-card">
80 <h2>Saved cards</h2>
81 [if:$has_cards]
82 [loop:@cards]
83 <div class="pay-row">
84 <div style="display:flex;align-items:center;gap:14px">
85 <div class="pay-brand-tile">CARD</div>
86 <div>
87 <div class="text-sm fw-600" style="color:var(--col-text)">
88 $brand_label &middot;&middot;&middot;&middot; $last4
89 [if:$is_default]<span class="pay-pill">Default</span>[/if]
90 </div>
91 <div class="text-xs text-dim">Expires $exp_display</div>
92 </div>
93 </div>
94 <div style="display:flex;gap:8px">
95 [if:$not_default]
96 <form method="POST" action="/billing_payment.cgi" style="margin:0">
97 <input type="hidden" name="act" value="set_default">
98 <input type="hidden" name="pm_id" value="$id">
99 <button class="btn btn-secondary btn-sm" type="submit">Make default</button>
100 </form>
101 [/if]
102 <form method="POST" action="/billing_payment.cgi" style="margin:0"
103 data-confirm-title="Remove this card?"
104 data-confirm-message="Future invoices will fail until you add a replacement card. If this is your only card on file, paid features may pause."
105 data-confirm-label="Remove card"
106 data-confirm-style="danger">
107 <input type="hidden" name="act" value="delete_card">
108 <input type="hidden" name="pm_id" value="$id">
109 <button class="btn btn-danger btn-sm" type="submit">Remove</button>
110 </form>
111 </div>
112 </div>
113 [/loop]
114 [/if]
115 [if:$no_cards]
116 <div class="text-sm text-dim" style="padding:8px 0">
117 No cards on file yet. Add one below to enable paid plans and automatic renewals.
118 </div>
119 [/if]
120 </div>
121
122 <!-- Add a card -->
123 <div class="pay-card">
124 <h2>Add a card</h2>
125
126 [if:$stripe_missing]
127 <div class="pay-not-config">
128 Stripe is not configured for this install yet. Once the platform admin adds Stripe keys, this form will accept new cards.
129 </div>
130 [/if]
131
132 [if:$has_setup]
133 <form id="pay-form">
134 <label class="text-xs text-dim" style="display:block;margin-bottom:6px">Card details</label>
135 <div id="card-element"></div>
136 <div id="card-errors" role="alert"></div>
137 <button id="pay-submit" type="submit" class="btn btn-primary" style="margin-top:14px">
138 <span id="pay-btn-text">Save card</span>
139 </button>
140 </form>
141
142 <p class="text-xs text-dim" style="margin:14px 0 0">
143 We use Stripe Elements (Stripe's PCI-compliant hosted card field) so the card number is sent directly to Stripe over TLS. We receive only a token we use to charge future invoices.
144 </p>
145
146 <script src="https://js.stripe.com/v3/"></script>
147 <script>
148 (function () {
149 var pubKey = '$publishable_key';
150 var clientSecret = '$client_secret';
151 if (!pubKey || !clientSecret) { return; }
152 var stripe = Stripe(pubKey);
153 var elements = stripe.elements();
154 var card = elements.create('card', {
155 style: {
156 base: { fontSize: '16px', color: '#0f172a' },
157 invalid: { color: '#dc2626' }
158 }
159 });
160 card.mount('#card-element');
161
162 var errBox = document.getElementById('card-errors');
163 card.on('change', function (ev) {
164 errBox.textContent = ev.error ? ev.error.message : '';
165 });
166
167 var form = document.getElementById('pay-form');
168 var btn = document.getElementById('pay-submit');
169 var txt = document.getElementById('pay-btn-text');
170 form.addEventListener('submit', function (ev) {
171 ev.preventDefault();
172 btn.disabled = true;
173 txt.textContent = 'Saving...';
174 stripe.confirmCardSetup(clientSecret, {
175 payment_method: { card: card }
176 }).then(function (result) {
177 if (result.error) {
178 errBox.textContent = result.error.message || 'Could not save card.';
179 btn.disabled = false;
180 txt.textContent = 'Save card';
181 return;
182 }
183 var pmId = result.setupIntent && result.setupIntent.payment_method;
184 if (!pmId) {
185 errBox.textContent = 'Card setup did not return a payment method.';
186 btn.disabled = false;
187 txt.textContent = 'Save card';
188 return;
189 }
190 var f = document.createElement('form');
191 f.method = 'POST';
192 f.action = '/billing_payment.cgi';
193 function add(name, value) {
194 var el = document.createElement('input');
195 el.type = 'hidden'; el.name = name; el.value = value;
196 f.appendChild(el);
197 }
198 add('act', 'save_card');
199 add('stripe_payment_method_id', pmId);
200 document.body.appendChild(f);
201 f.submit();
202 });
203 });
204 })();
205 </script>
206 [/if]
207
208 [if:!$has_setup]
209 [if:$stripe_ready]
210 <div class="pay-not-config">
211 We could not start a Stripe SetupIntent right now. Refresh the page in a moment, or contact support if it keeps failing.
212 </div>
213 [/if]
214 [/if]
215
216 </div>
217
218 [/ptag:manage_billing]
219
220</div>
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help