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

O Operator
Diff

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

added on local at 2026-07-11 18:31:58

Added
+501
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 504ad13f77d0
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1<style>
2 /* Self-contained module styling so this template renders the same on
3 every site regardless of host CSS. Falls back to local CSS variables
4 when defined; otherwise uses safe dark-theme defaults. */
5 .module {
6 background:var(--col-surface, #161924);
7 border:1px solid var(--col-border, rgba(255,255,255,.08));
8 border-radius:12px;
9 overflow:hidden;
10 }
11 .module-head {
12 display:flex; align-items:center; gap:14px;
13 padding:14px 18px;
14 border-bottom:1px solid var(--col-border, rgba(255,255,255,.06));
15 background:rgba(255,255,255,.015);
16 }
17 .module-head .left {
18 display:flex; align-items:center; gap:14px; flex:1;
19 }
20 .module-icon {
21 width:36px; height:36px; flex:0 0 auto;
22 border-radius:8px;
23 background:rgba(99,102,241,.15);
24 color:#a5b4fc;
25 display:flex; align-items:center; justify-content:center;
26 }
27 .module-title {
28 font-size:15px; font-weight:600;
29 color:var(--col-text, #f1f5f9);
30 }
31 .module-sub {
32 font-size:12px; color:var(--col-text-3, #94a3b8);
33 margin-top:2px; line-height:1.5;
34 }
35 .module-body { padding:14px 18px; }
36 .mb-3 { margin-bottom:18px; }
37 .page-head { margin-bottom:18px; }
38 .page-eyebrow {
39 display:inline-flex; align-items:center; gap:6px;
40 font-size:11px; color:#a5b4fc;
41 text-transform:uppercase; letter-spacing:.08em;
42 font-weight:600; margin-bottom:8px;
43 }
44 .page-eyebrow .dot {
45 width:6px; height:6px; border-radius:50%;
46 background:#a5b4fc;
47 }
48 .page-title {
49 margin:0 0 6px; font-size:26px; letter-spacing:-.01em;
50 color:var(--col-text, #f1f5f9);
51 }
52 .page-subtitle {
53 margin:0 0 0; font-size:13px; color:var(--col-text-2, #cbd5e1);
54 line-height:1.55; max-width:780px;
55 }
56 .tc-card-row {
57 display:flex; align-items:center; gap:14px;
58 padding:14px 16px;
59 background:var(--col-surface-1);
60 border:1px solid var(--col-border);
61 border-radius:10px;
62 margin-bottom:10px;
63 }
64 .tc-card-row.is-decline { border-left:3px solid #ef4444; }
65 .tc-card-row.is-approve { border-left:3px solid #4ade80; }
66 .tc-card-row.is-inactive {
67 opacity:.65;
68 background:rgba(255,255,255,.02);
69 border-left:3px solid #6b7280;
70 }
71 .tc-card-glyph {
72 width:48px; height:32px; border-radius:4px;
73 display:grid; place-items:center; flex:0 0 auto;
74 font:700 10px/1 var(--font-mono, monospace);
75 color:#fff; text-transform:uppercase;
76 letter-spacing:1px;
77 }
78 .tc-card-glyph.b-visa { background:linear-gradient(135deg, #1a1f71, #2a3499); }
79 .tc-card-glyph.b-mastercard { background:linear-gradient(135deg, #eb001b, #f79e1b); }
80 .tc-card-glyph.b-amex { background:linear-gradient(135deg, #2e77bb, #1c4f8c); }
81 .tc-card-glyph.b-discover { background:linear-gradient(135deg, #ff6000, #d44d00); }
82 .tc-card-glyph.b-jcb { background:linear-gradient(135deg, #0e4c92, #0a3a72); }
83 .tc-card-glyph.b-diners { background:linear-gradient(135deg, #0079be, #005c8e); }
84 .tc-card-glyph.b-unionpay,
85 .tc-card-glyph.b-default { background:linear-gradient(135deg, #475569, #334155); }
86 .tc-card-num {
87 font:600 14px/1.2 var(--font-mono, monospace);
88 color:var(--col-text);
89 letter-spacing:0.5px;
90 }
91 .tc-card-meta { font-size:12px; color:var(--col-text-3); margin-top:2px; }
92 .tc-pill {
93 font:700 10px/1 inherit;
94 letter-spacing:1px; text-transform:uppercase;
95 padding:4px 8px; border-radius:999px;
96 }
97 .tc-pill.is-approve { background:rgba(34,197,94,0.16); color:#86efac; border:1px solid rgba(34,197,94,0.3); }
98 .tc-pill.is-decline { background:rgba(239,68,68,0.16); color:#fca5a5; border:1px solid rgba(239,68,68,0.3); }
99 .tc-pill.is-inactive { background:rgba(107,114,128,0.18); color:#cbd5e1; border:1px solid rgba(107,114,128,0.35); }
100 .tc-owner-pill {
101 font-size:11px; padding:3px 8px; border-radius:999px;
102 background:rgba(139,92,246,0.13); color:#c4b5fd;
103 border:1px solid rgba(139,92,246,0.30);
104 margin-left:6px;
105 }
106 .tc-card-actions { margin-left:auto; display:flex; gap:6px; align-items:center; flex-wrap:wrap; }
107 .tc-quickform { display:flex; gap:6px; align-items:center; margin:0; }
108 .tc-quickform .select, .tc-quickform .input { padding:6px 8px; font-size:12px; min-width:120px; }
109 .tc-create-form {
110 display:grid;
111 grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
112 gap:12px;
113 margin-top:10px;
114 }
115 .tc-presets { display:flex; flex-wrap:wrap; gap:8px; margin-top:10px; }
116 .tc-preset-btn {
117 background:rgba(255,255,255,0.03);
118 border:1px solid var(--col-border);
119 color:var(--col-text-2);
120 padding:6px 10px; border-radius:6px; font-size:12px;
121 cursor:pointer; transition:background 0.12s, color 0.12s;
122 }
123 .tc-preset-btn:hover { background:rgba(99,102,241,0.12); color:#fff; }
124 .tc-section-title {
125 font-size:11px; text-transform:uppercase; letter-spacing:1px;
126 color:var(--col-text-3); font-weight:700;
127 margin:18px 0 10px;
128 }
129 .tc-bulk-row {
130 display:flex; align-items:center; gap:10px;
131 padding:10px 14px;
132 background:rgba(239,68,68,0.04);
133 border:1px solid rgba(239,68,68,0.18);
134 border-radius:8px;
135 margin-bottom:8px;
136 }
137 .tc-bulk-row .name { color:#fca5a5; font-weight:600; font-size:13px; }
138 .tc-bulk-row .count { color:#cbd5e1; font-size:12px; }
139 .tc-audit-row {
140 display:flex; align-items:center; gap:12px;
141 padding:8px 12px;
142 border-bottom:1px solid rgba(255,255,255,.05);
143 font-size:12px;
144 }
145 .tc-audit-row:last-child { border-bottom:none; }
146 .tc-audit-action {
147 font-size:10px; padding:2px 7px; border-radius:4px;
148 text-transform:uppercase; letter-spacing:.06em; font-weight:700;
149 background:rgba(99,102,241,.15); color:#a5b4fc;
150 }
151 .tc-audit-when { color:var(--col-text-3); font-family:var(--font-mono,monospace); font-size:11px; margin-left:auto; }
152</style>
153
154<div class="page-head">
155 <div>
156 <span class="page-eyebrow"><span class="dot"></span> Admin console</span>
157 <h1 class="page-title">Sandbox Test Credit Cards</h1>
158 <p class="page-subtitle">Fake credit cards for end-to-end billing QA. Cards attached here bypass Stripe entirely &mdash; a simulated PaymentIntent is returned with the behavior you choose. Tag cards by owner so you can deactivate someone&rsquo;s set in one click when they leave the team.</p>
159 </div>
160</div>
161
162[if:$has_flash]
163<div class="banner [if:$flash_is_ok]success[/if][if:$flash_is_err]error[/if] mb-3" style="padding:12px 14px;border-radius:10px">
164 <div class="text-sm fw-600">$flash_msg</div>
165</div>
166[/if]
167
168[if:$schema_missing]
169<div class="banner warn mb-3" style="padding:12px 14px;border-radius:10px;background:rgba(245,158,11,0.10);border:1px solid rgba(245,158,11,0.30)">
170 <strong>Schema not installed.</strong> Run the latest <code>_test_cards_migration.sql</code> to create the <code>admin_test_cards</code> table.
171</div>
172[/if]
173
174[if:$schema_ready]
175
176<!-- Bulk deactivate-by-owner -->
177[if:$has_owner_groups]
178<div class="module mb-3">
179 <div class="module-head">
180 <div class="left">
181 <div class="module-icon" style="background:rgba(239,68,68,0.12);color:#fca5a5"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" style="display:inline-block;vertical-align:middle"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="8.5" cy="7" r="4"/><line x1="17" y1="8" x2="22" y2="13"/><line x1="22" y1="8" x2="17" y2="13"/></svg></div>
182 <div>
183 <div class="module-title">Offboard a team member</div>
184 <div class="module-sub">If someone leaves, deactivate every active card they own in one click. Cards stay in the audit log so you can see who and when.</div>
185 </div>
186 </div>
187 </div>
188 <div class="module-body">
189 [loop:@owner_groups]
190 <form method="POST" action="/admin_test_cards.cgi" class="tc-bulk-row"
191 data-confirm-title="Deactivate all of $loop1.owner_name's test cards?"
192 data-confirm-message="All $loop1.n active card(s) owned by $loop1.owner_name will be marked inactive and detached from any user account they're attached to. You can reactivate them later if needed."
193 data-confirm-label="Deactivate $loop1.n card(s)"
194 data-confirm-style="danger">
195 <input type="hidden" name="action" value="bulk_deactivate_owner">
196 <input type="hidden" name="owner_user_id" value="$loop1.owner_user_id">
197 <div class="name">$loop1.owner_name</div>
198 <div class="count">owns $loop1.n active card(s)</div>
199 <button type="submit" class="btn btn-danger btn-sm" style="margin-left:auto;font-size:11px">Deactivate all</button>
200 </form>
201 [/loop]
202 </div>
203</div>
204[/if]
205
206<!-- Active cards -->
207<div class="module mb-3">
208 <div class="module-head">
209 <div class="left">
210 <div class="module-icon"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" style="display:inline-block;vertical-align:middle"><rect x="2" y="5" width="20" height="14" rx="2"/><line x1="2" y1="10" x2="22" y2="10"/></svg></div>
211 <div>
212 <div class="module-title">Active test cards ($n_active)</div>
213 <div class="module-sub">Click <strong>Assign to me</strong> to make any card your own default. The next upgrade or charge runs as the simulated behavior.</div>
214 </div>
215 </div>
216 </div>
217 <div class="module-body">
218 [if:$has_active]
219 [loop:@cards_active]
220 <div class="tc-card-row [if:$loop1.is_approve]is-approve[/if][if:$loop1.is_decline]is-decline[/if]">
221 <div class="tc-card-glyph b-$loop1.brand">$loop1.brand_label</div>
222 <div>
223 <div class="tc-card-num">$loop1.number_full[if:$loop1.has_owner]<span class="tc-owner-pill">owner: $loop1.owner_name</span>[/if]</div>
224 <div class="tc-card-meta">$loop1.holder_name &middot; exp $loop1.exp_display &middot; cvc $loop1.cvc[if:$loop1.has_label] &middot; $loop1.label[/if]</div>
225 </div>
226 <div class="tc-card-actions">
227 <span class="tc-pill [if:$loop1.is_approve]is-approve[/if][if:$loop1.is_decline]is-decline[/if]">$loop1.behavior_label</span>
228
229 <form method="POST" action="/admin_test_cards.cgi" class="tc-quickform">
230 <input type="hidden" name="action" value="update_behavior">
231 <input type="hidden" name="card_id" value="$loop1.id">
232 <select class="select" name="behavior" onchange="this.form.submit()" title="Change simulated behavior">
233 [loop:@behavior_options]<option value="$loop2.value"$loop2.selected_attr>$loop2.label</option>[/loop]
234 </select>
235 </form>
236
237 <form method="POST" action="/admin_test_cards.cgi" class="tc-quickform">
238 <input type="hidden" name="action" value="assign">
239 <input type="hidden" name="card_id" value="$loop1.id">
240 <input type="hidden" name="assign_to" value="me">
241 <button type="submit" class="btn btn-secondary btn-sm" style="font-size:11px">Assign to me</button>
242 </form>
243
244 [if:$has_users]
245 <form method="POST" action="/admin_test_cards.cgi" class="tc-quickform">
246 <input type="hidden" name="action" value="assign">
247 <input type="hidden" name="card_id" value="$loop1.id">
248 <select class="select" name="target_user_id" required>
249 <option value="">Assign to user...</option>
250 [loop:@user_options]<option value="$loop2.id">$loop2.label</option>[/loop]
251 </select>
252 <button type="submit" class="btn btn-secondary btn-sm" style="font-size:11px">Assign</button>
253 </form>
254 [/if]
255
256 <form method="POST" action="/admin_test_cards.cgi"
257 data-confirm-title="Deactivate this test card?"
258 data-confirm-message="The card is marked inactive and detached from every user it was attached to. You can reactivate it later. Use Delete if you want it gone for good."
259 data-confirm-label="Deactivate"
260 data-confirm-style="warn"
261 style="margin:0">
262 <input type="hidden" name="action" value="deactivate">
263 <input type="hidden" name="card_id" value="$loop1.id">
264 <button type="submit" class="btn btn-secondary btn-sm" style="font-size:11px;background:rgba(245,158,11,.12);border-color:rgba(245,158,11,.3);color:#fcd34d">Deactivate</button>
265 </form>
266
267 <form method="POST" action="/admin_test_cards.cgi"
268 data-confirm-title="Delete this test credit card?"
269 data-confirm-message="The card is removed and detached from every user it was attached to. The audit log keeps a record. This is permanent."
270 data-confirm-label="Delete card"
271 data-confirm-style="danger"
272 style="margin:0">
273 <input type="hidden" name="action" value="delete">
274 <input type="hidden" name="card_id" value="$loop1.id">
275 <button type="submit" class="btn btn-danger btn-sm" style="font-size:11px">Delete</button>
276 </form>
277 </div>
278 </div>
279 [/loop]
280 [/if]
281 [if:!$has_active]
282 <div class="text-sm text-dim" style="text-align:center;padding:24px 12px">No active test credit cards. Create one below or use a preset to get started.</div>
283 [/if]
284 </div>
285</div>
286
287<!-- Inactive cards -->
288[if:$has_inactive]
289<div class="module mb-3">
290 <div class="module-head">
291 <div class="left">
292 <div class="module-icon" style="background:rgba(107,114,128,.15);color:#cbd5e1"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" style="display:inline-block;vertical-align:middle"><rect x="2" y="5" width="20" height="14" rx="2"/><line x1="2" y1="10" x2="22" y2="10"/><line x1="2" y1="2" x2="22" y2="22"/></svg></div>
293 <div>
294 <div class="module-title">Deactivated cards ($n_inactive)</div>
295 <div class="module-sub">Kept for audit history. Reactivate to bring one back, or delete to remove permanently.</div>
296 </div>
297 </div>
298 </div>
299 <div class="module-body">
300 [loop:@cards_inactive]
301 <div class="tc-card-row is-inactive">
302 <div class="tc-card-glyph b-$loop1.brand">$loop1.brand_label</div>
303 <div>
304 <div class="tc-card-num">$loop1.number_full[if:$loop1.has_owner]<span class="tc-owner-pill">owner: $loop1.owner_name</span>[/if]</div>
305 <div class="tc-card-meta">$loop1.holder_name &middot; exp $loop1.exp_display[if:$loop1.has_deact] &middot; deactivated by $loop1.deact_name on <span class="ts" data-ts="$loop1.deact_epoch" data-fmt="datetime">$loop1.deact_at</span>[/if]</div>
306 </div>
307 <div class="tc-card-actions">
308 <span class="tc-pill is-inactive">Inactive</span>
309
310 <form method="POST" action="/admin_test_cards.cgi" style="margin:0">
311 <input type="hidden" name="action" value="reactivate">
312 <input type="hidden" name="card_id" value="$loop1.id">
313 <button type="submit" class="btn btn-secondary btn-sm" style="font-size:11px;background:rgba(34,197,94,.12);border-color:rgba(34,197,94,.3);color:#86efac">Reactivate</button>
314 </form>
315
316 <form method="POST" action="/admin_test_cards.cgi"
317 data-confirm-title="Delete this test credit card?"
318 data-confirm-message="Permanently removes the card. The audit log entries stay."
319 data-confirm-label="Delete card"
320 data-confirm-style="danger"
321 style="margin:0">
322 <input type="hidden" name="action" value="delete">
323 <input type="hidden" name="card_id" value="$loop1.id">
324 <button type="submit" class="btn btn-danger btn-sm" style="font-size:11px">Delete</button>
325 </form>
326 </div>
327 </div>
328 [/loop]
329 </div>
330</div>
331[/if]
332
333<!-- Create form -->
334<div class="module mb-3">
335 <div class="module-head">
336 <div class="left">
337 <div class="module-icon" style="background:rgba(34,197,94,0.15);color:#4ade80"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" style="display:inline-block;vertical-align:middle"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></div>
338 <div>
339 <div class="module-title">Add a test credit card</div>
340 <div class="module-sub">Numbers are fake by design &mdash; nothing leaves the platform. Tag an owner so you can offboard their cards in one click later.</div>
341 </div>
342 </div>
343 </div>
344 <div class="module-body">
345 <form method="POST" action="/admin_test_cards.cgi" id="tc-create-form">
346 <input type="hidden" name="action" value="create">
347 <div class="tc-create-form">
348 <label class="text-xs text-dim">
349 Brand
350 <select class="select" name="card_brand" id="tc-brand" style="margin-top:4px">
351 <option value="visa">Visa</option>
352 <option value="mastercard">Mastercard</option>
353 <option value="amex">American Express</option>
354 <option value="discover">Discover</option>
355 <option value="jcb">JCB</option>
356 <option value="diners">Diners Club</option>
357 <option value="unionpay">UnionPay</option>
358 </select>
359 </label>
360 <label class="text-xs text-dim" style="grid-column:span 2">
361 Card number
362 <input class="input" type="text" name="card_number" id="tc-number"
363 placeholder="4242 4242 4242 4242" required maxlength="23"
364 style="margin-top:4px;font-family:var(--font-mono,monospace);letter-spacing:1px">
365 </label>
366 <label class="text-xs text-dim">
367 Holder name
368 <input class="input" type="text" name="holder_name" value="Test User"
369 maxlength="120" style="margin-top:4px">
370 </label>
371 <label class="text-xs text-dim">
372 Exp month
373 <input class="input" type="number" name="exp_month" value="12" min="1" max="12" style="margin-top:4px">
374 </label>
375 <label class="text-xs text-dim">
376 Exp year
377 <input class="input" type="number" name="exp_year" value="2030" min="2024" max="2099" style="margin-top:4px">
378 </label>
379 <label class="text-xs text-dim">
380 CVC
381 <input class="input" type="text" name="cvc" value="123" maxlength="4" style="margin-top:4px">
382 </label>
383 <label class="text-xs text-dim">
384 Behavior
385 <select class="select" name="behavior" style="margin-top:4px">
386 [loop:@behavior_options]<option value="$loop1.value">$loop1.label</option>[/loop]
387 </select>
388 </label>
389 [if:$has_users]
390 <label class="text-xs text-dim" style="grid-column:span 2">
391 Owner (optional)
392 <select class="select" name="owner_user_id" style="margin-top:4px">
393 <option value="">&mdash; No owner / shared &mdash;</option>
394 [loop:@user_options]<option value="$loop1.id">$loop1.label</option>[/loop]
395 </select>
396 </label>
397 [/if]
398 <label class="text-xs text-dim" style="grid-column:span 2">
399 Label (optional)
400 <input class="input" type="text" name="label" maxlength="120"
401 placeholder="e.g. Decline path test" style="margin-top:4px">
402 </label>
403 </div>
404
405 <div class="tc-presets">
406 <div class="text-xs text-dim" style="display:flex;align-items:center;margin-right:4px">Quick presets:</div>
407 <button type="button" class="tc-preset-btn" data-preset="visa-approve">Visa &middot; approve</button>
408 <button type="button" class="tc-preset-btn" data-preset="visa-decline">Visa &middot; decline</button>
409 <button type="button" class="tc-preset-btn" data-preset="mc-approve">Mastercard &middot; approve</button>
410 <button type="button" class="tc-preset-btn" data-preset="amex-approve">Amex &middot; approve</button>
411 <button type="button" class="tc-preset-btn" data-preset="discover-approve">Discover &middot; approve</button>
412 <button type="button" class="tc-preset-btn" data-preset="insufficient">Visa &middot; insufficient funds</button>
413 <button type="button" class="tc-preset-btn" data-preset="expired">Visa &middot; expired card</button>
414 <button type="button" class="tc-preset-btn" data-preset="processing">Visa &middot; processing error</button>
415 <button type="button" class="tc-preset-btn" data-preset="lost">Visa &middot; lost card</button>
416 <button type="button" class="tc-preset-btn" data-preset="stolen">Visa &middot; stolen card</button>
417 <button type="button" class="tc-preset-btn" data-preset="3ds">Visa &middot; 3DS required</button>
418 </div>
419
420 <div style="margin-top:14px">
421 <button type="submit" class="btn btn-primary">Create test credit card</button>
422 </div>
423 </form>
424 </div>
425</div>
426
427<!-- Audit log -->
428[if:$has_audit]
429<div class="module mb-3">
430 <div class="module-head">
431 <div class="left">
432 <div class="module-icon" style="background:rgba(99,102,241,.15);color:#a5b4fc"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" style="display:inline-block;vertical-align:middle"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><line x1="10" y1="9" x2="8" y2="9"/></svg></div>
433 <div>
434 <div class="module-title">Recent activity</div>
435 <div class="module-sub">Last 50 events. Who created / deactivated / assigned each card, and when.</div>
436 </div>
437 </div>
438 </div>
439 <div class="module-body" style="padding:0">
440 [loop:@audit_rows]
441 <div class="tc-audit-row">
442 <span class="tc-audit-action">$loop1.action</span>
443 <span style="color:var(--col-text);font-weight:500">$loop1.actor_name</span>
444 <span style="color:var(--col-text-2)">$loop1.card_label</span>
445 [if:$loop1.has_detail]<span style="color:var(--col-text-3);font-size:11px">$loop1.detail</span>[/if]
446 <span class="tc-audit-when"><span class="ts" data-ts="$loop1.created_epoch" data-fmt="datetime">$loop1.created_at</span></span>
447 </div>
448 [/loop]
449 </div>
450</div>
451[/if]
452
453[/if]
454
455<script>
456(function () {
457 // Auto-format the card-number input as 4-4-4-4 groups while typing.
458 var num = document.getElementById('tc-number');
459 if (num) {
460 num.addEventListener('input', function () {
461 var digits = num.value.replace(/[^0-9]/g, '').slice(0, 19);
462 num.value = digits.replace(/(\d{4})(?=\d)/g, '\$1 ');
463 });
464 }
465
466 var PRESETS = {
467 'visa-approve': { brand:'visa', number:'4242 4242 4242 4242', name:'Test Visa Approve', behavior:'approve', label:'Approves on charge' },
468 'visa-decline': { brand:'visa', number:'4000 0000 0000 0002', name:'Test Visa Decline', behavior:'decline', label:'Generic decline' },
469 'mc-approve': { brand:'mastercard', number:'5555 5555 5555 4444', name:'Test MC Approve', behavior:'approve', label:'Approves on charge' },
470 'amex-approve': { brand:'amex', number:'3782 822463 10005', name:'Test Amex Approve', behavior:'approve', label:'Approves on charge' },
471 'discover-approve': { brand:'discover', number:'6011 0000 0000 0004', name:'Test Discover Approve', behavior:'approve', label:'Approves on charge' },
472 'insufficient': { brand:'visa', number:'4000 0000 0000 9995', name:'Test Insufficient', behavior:'insufficient_funds', label:'Insufficient funds path' },
473 'expired': { brand:'visa', number:'4000 0000 0000 0069', name:'Test Expired', behavior:'expired_card', label:'Expired card path' },
474 'processing': { brand:'visa', number:'4000 0000 0000 0119', name:'Test Processing Err', behavior:'processing_error', label:'Processing error path' },
475 'lost': { brand:'visa', number:'4000 0000 0000 9987', name:'Test Lost', behavior:'lost_card', label:'Lost card path' },
476 'stolen': { brand:'visa', number:'4000 0000 0000 9979', name:'Test Stolen', behavior:'stolen_card', label:'Stolen card path' },
477 '3ds': { brand:'visa', number:'4000 0027 6000 3184', name:'Test 3DS', behavior:'3ds_required', label:'3D Secure auth required' }
478 };
479
480 document.querySelectorAll('.tc-preset-btn').forEach(function (btn) {
481 btn.addEventListener('click', function () {
482 var p = PRESETS[btn.getAttribute('data-preset')];
483 if (!p) return;
484 var f = document.getElementById('tc-create-form');
485 f.card_brand.value = p.brand;
486 f.card_number.value = p.number;
487 f.holder_name.value = p.name;
488 f.behavior.value = p.behavior;
489 f.label.value = p.label;
490 f.exp_month.value = 12;
491 f.exp_year.value = 2030;
492 f.cvc.value = (p.brand === 'amex') ? '1234' : '123';
493 f.card_number.style.transition = 'background 0.4s';
494 f.card_number.style.background = 'rgba(99,102,241,0.18)';
495 setTimeout(function () { f.card_number.style.background = ''; }, 600);
496 });
497 });
498})();
499</script>
500
501<div class="page-foot-spacer"></div>