Diff -- /var/www/vhosts/3dshawn.com/crm.3dshawn.com/TEMPLATES/cf_billing_payment.html

O Operator
Diff

/var/www/vhosts/3dshawn.com/crm.3dshawn.com/TEMPLATES/cf_billing_payment.html

added on local at 2026-07-01 15:03:11

Added
+0
lines
Removed
-0
lines
Context
220
unchanged
Blobs
from 29dd245c4d48
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.
11<!-- =====================================================================
22 Manage card on file.
33 1. Flash message strip
44 2. Saved cards list (set default / remove)
55 3. Stripe Elements card-entry form (SetupIntent)
66 Backed by /billing_payment.cgi. PAN never reaches our server --
77 Stripe.js handles the card field; we only see the resulting
88 pm_xxx id that we attach + persist.
99 The whole interactive surface is wrapped with [ptag:manage_billing]
1010 so a team member without the grant lands on a read-only "not
1111 authorized" notice from require_feature's redirect upstream.
1212===================================================================== -->
1313
1414<style>
1515 .pay-wrap { max-width: 720px; margin: 0 auto; }
1616 .pay-card {
1717 background: var(--col-surface-1);
1818 border: 1px solid var(--col-border);
1919 border-radius: 12px;
2020 padding: 18px 20px;
2121 margin-bottom: 18px;
2222 }
2323 .pay-card h2 {
2424 font-size: 16px; font-weight: 600; color: var(--col-text);
2525 margin: 0 0 12px;
2626 }
2727 .pay-row {
2828 display: flex; align-items: center; justify-content: space-between;
2929 gap: 14px; padding: 10px 0; border-top: 1px solid var(--col-border);
3030 }
3131 .pay-row:first-of-type { border-top: 0; }
3232 .pay-pill {
3333 display: inline-block; font-size: 11px; font-weight: 600;
3434 background: var(--col-brand); color: #fff; padding: 2px 8px;
3535 border-radius: 999px; margin-left: 8px;
3636 }
3737 .pay-brand-tile {
3838 width: 48px; height: 32px; border-radius: 6px;
3939 background: linear-gradient(135deg,#1e293b,#334155);
4040 display: grid; place-items: center;
4141 font-size: 11px; font-weight: 700; color: #fff;
4242 }
4343 #card-element {
4444 background: #fff; padding: 12px 14px; border-radius: 8px;
4545 border: 1px solid var(--col-border);
4646 }
4747 #card-errors {
4848 color: #f87171; font-size: 13px; margin-top: 8px; min-height: 18px;
4949 }
5050 .pay-flash {
5151 border-radius: 8px; padding: 10px 14px; font-size: 13px;
5252 margin-bottom: 16px;
5353 }
5454 .pay-flash.ok { background: rgba(34,197,94,0.15); color: #4ade80; border: 1px solid rgba(34,197,94,0.35); }
5555 .pay-flash.danger { background: rgba(239,68,68,0.15); color: #fca5a5; border: 1px solid rgba(239,68,68,0.35); }
5656 .pay-not-config {
5757 border: 1px dashed var(--col-border); border-radius: 10px;
5858 padding: 20px; text-align: center; color: var(--col-text-2);
5959 }
6060</style>
6161
6262<div class="pay-wrap">
6363
6464 <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:14px">
6565 <h1 style="font-size:22px;color:var(--col-text);margin:0">Card on file</h1>
6666 <a href="/billing.cgi" class="btn btn-secondary btn-sm">Back to billing</a>
6767 </div>
6868 <p class="text-sm text-dim" style="margin:0 0 18px">
6969 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.
7070 </p>
7171
7272 [if:$has_flash]
7373 <div class="pay-flash $flash_kind">$flash_msg</div>
7474 [/if]
7575
7676 [ptag:manage_billing]
7777
7878 <!-- Saved cards -->
7979 <div class="pay-card">
8080 <h2>Saved cards</h2>
8181 [if:$has_cards]
8282 [loop:@cards]
8383 <div class="pay-row">
8484 <div style="display:flex;align-items:center;gap:14px">
8585 <div class="pay-brand-tile">CARD</div>
8686 <div>
8787 <div class="text-sm fw-600" style="color:var(--col-text)">
8888 $brand_label &middot;&middot;&middot;&middot; $last4
8989 [if:$is_default]<span class="pay-pill">Default</span>[/if]
9090 </div>
9191 <div class="text-xs text-dim">Expires $exp_display</div>
9292 </div>
9393 </div>
9494 <div style="display:flex;gap:8px">
9595 [if:$not_default]
9696 <form method="POST" action="/billing_payment.cgi" style="margin:0">
9797 <input type="hidden" name="act" value="set_default">
9898 <input type="hidden" name="pm_id" value="$id">
9999 <button class="btn btn-secondary btn-sm" type="submit">Make default</button>
100100 </form>
101101 [/if]
102102 <form method="POST" action="/billing_payment.cgi" style="margin:0"
103103 data-confirm-title="Remove this card?"
104104 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."
105105 data-confirm-label="Remove card"
106106 data-confirm-style="danger">
107107 <input type="hidden" name="act" value="delete_card">
108108 <input type="hidden" name="pm_id" value="$id">
109109 <button class="btn btn-danger btn-sm" type="submit">Remove</button>
110110 </form>
111111 </div>
112112 </div>
113113 [/loop]
114114 [/if]
115115 [if:$no_cards]
116116 <div class="text-sm text-dim" style="padding:8px 0">
117117 No cards on file yet. Add one below to enable paid plans and automatic renewals.
118118 </div>
119119 [/if]
120120 </div>
121121
122122 <!-- Add a card -->
123123 <div class="pay-card">
124124 <h2>Add a card</h2>
125125
126126 [if:$stripe_missing]
127127 <div class="pay-not-config">
128128 Stripe is not configured for this install yet. Once the platform admin adds Stripe keys, this form will accept new cards.
129129 </div>
130130 [/if]
131131
132132 [if:$has_setup]
133133 <form id="pay-form">
134134 <label class="text-xs text-dim" style="display:block;margin-bottom:6px">Card details</label>
135135 <div id="card-element"></div>
136136 <div id="card-errors" role="alert"></div>
137137 <button id="pay-submit" type="submit" class="btn btn-primary" style="margin-top:14px">
138138 <span id="pay-btn-text">Save card</span>
139139 </button>
140140 </form>
141141
142142 <p class="text-xs text-dim" style="margin:14px 0 0">
143143 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.
144144 </p>
145145
146146 <script src="https://js.stripe.com/v3/"></script>
147147 <script>
148148 (function () {
149149 var pubKey = '$publishable_key';
150150 var clientSecret = '$client_secret';
151151 if (!pubKey || !clientSecret) { return; }
152152 var stripe = Stripe(pubKey);
153153 var elements = stripe.elements();
154154 var card = elements.create('card', {
155155 style: {
156156 base: { fontSize: '16px', color: '#0f172a' },
157157 invalid: { color: '#dc2626' }
158158 }
159159 });
160160 card.mount('#card-element');
161161
162162 var errBox = document.getElementById('card-errors');
163163 card.on('change', function (ev) {
164164 errBox.textContent = ev.error ? ev.error.message : '';
165165 });
166166
167167 var form = document.getElementById('pay-form');
168168 var btn = document.getElementById('pay-submit');
169169 var txt = document.getElementById('pay-btn-text');
170170 form.addEventListener('submit', function (ev) {
171171 ev.preventDefault();
172172 btn.disabled = true;
173173 txt.textContent = 'Saving...';
174174 stripe.confirmCardSetup(clientSecret, {
175175 payment_method: { card: card }
176176 }).then(function (result) {
177177 if (result.error) {
178178 errBox.textContent = result.error.message || 'Could not save card.';
179179 btn.disabled = false;
180180 txt.textContent = 'Save card';
181181 return;
182182 }
183183 var pmId = result.setupIntent && result.setupIntent.payment_method;
184184 if (!pmId) {
185185 errBox.textContent = 'Card setup did not return a payment method.';
186186 btn.disabled = false;
187187 txt.textContent = 'Save card';
188188 return;
189189 }
190190 var f = document.createElement('form');
191191 f.method = 'POST';
192192 f.action = '/billing_payment.cgi';
193193 function add(name, value) {
194194 var el = document.createElement('input');
195195 el.type = 'hidden'; el.name = name; el.value = value;
196196 f.appendChild(el);
197197 }
198198 add('act', 'save_card');
199199 add('stripe_payment_method_id', pmId);
200200 document.body.appendChild(f);
201201 f.submit();
202202 });
203203 });
204204 })();
205205 </script>
206206 [/if]
207207
208208 [if:!$has_setup]
209209 [if:$stripe_ready]
210210 <div class="pay-not-config">
211211 We could not start a Stripe SetupIntent right now. Refresh the page in a moment, or contact support if it keeps failing.
212212 </div>
213213 [/if]
214214 [/if]
215215
216216 </div>
217217
218218 [/ptag:manage_billing]
219219
220220</div>