Diff -- /var/www/vhosts/3dshawn.com/repricer.3dshawn.com/tax_settings.cgi
Diff
/var/www/vhosts/3dshawn.com/repricer.3dshawn.com/tax_settings.cgi
added on local at 2026-07-01 21:47:20
Added
+120
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to ed986962df87
to ed986962df87
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.| 1 | #!/usr/bin/perl | |
| 2 | #====================================================================== | |
| 3 | # RePricer -- per-seller tax / VAT / GST settings. | |
| 4 | # | |
| 5 | # Stripe applies tax server-side via Tax IDs on the customer object, but we | |
| 6 | # also need a local record so we can: | |
| 7 | # - show the right tax-inclusive vs tax-exclusive price on the pricing page, | |
| 8 | # - print the tax-id on PDF invoices, | |
| 9 | # - filter out tax-id holders from the consumer dunning email copy. | |
| 10 | # | |
| 11 | # Columns used (added by _features_migration.sql): | |
| 12 | # users.tax_mode ENUM('none','consumer','business') default 'none' | |
| 13 | # users.tax_country CHAR(2) ISO-3166-1 alpha-2 | |
| 14 | # users.tax_id_number VARCHAR(40) VAT/GST/etc. | |
| 15 | #====================================================================== | |
| 16 | use strict; | |
| 17 | use warnings; | |
| 18 | use lib '/var/www/vhosts/3dshawn.com/repricer.3dshawn.com'; | |
| 19 | use CGI; | |
| 20 | use MODS::DBConnect; | |
| 21 | use MODS::Login; | |
| 22 | use MODS::RePricer::Config; | |
| 23 | use MODS::RePricer::Wrapper; | |
| 24 | ||
| 25 | $|=1; | |
| 26 | my $q = CGI->new; | |
| 27 | my $form = $q->Vars; | |
| 28 | my $auth = MODS::Login->new; | |
| 29 | my $wrap = MODS::RePricer::Wrapper->new; | |
| 30 | my $db = MODS::DBConnect->new; | |
| 31 | my $cfg = MODS::RePricer::Config->new; | |
| 32 | my $DB = $cfg->settings('database_name'); | |
| 33 | ||
| 34 | my $userinfo = $auth->login_verify(); | |
| 35 | unless ($userinfo) { print "Status: 302 Found\nLocation: /login.cgi\n\n"; exit; } | |
| 36 | my $uid = $userinfo->{user_id}; $uid =~ s/[^0-9]//g; | |
| 37 | ||
| 38 | sub _h { my $s = shift // ''; $s =~ s/&/&/g; $s =~ s/</</g; $s =~ s/>/>/g; $s =~ s/"/"/g; $s } | |
| 39 | sub _q { my $s = shift // ''; $s =~ s/'/''/g; $s } | |
| 40 | ||
| 41 | my $dbh = $db->db_connect(); | |
| 42 | my $flash = ''; my $kind = ''; | |
| 43 | ||
| 44 | if (($ENV{REQUEST_METHOD} || '') eq 'POST') { | |
| 45 | my $mode = $form->{tax_mode} || 'none'; | |
| 46 | $mode = 'none' unless $mode =~ /^(none|consumer|business)$/; | |
| 47 | my $country = uc($form->{tax_country} // ''); $country =~ s/[^A-Z]//g; $country = substr($country, 0, 2); | |
| 48 | my $tax_id = $form->{tax_id_number} // ''; $tax_id =~ s/\s+//g; $tax_id = substr($tax_id, 0, 40); | |
| 49 | ||
| 50 | if ($mode eq 'business') { | |
| 51 | if (!$country || length($country) != 2) { | |
| 52 | $flash = 'Business tax mode requires a country code (e.g. GB, DE, US).'; $kind = 'error'; | |
| 53 | } elsif (!$tax_id) { | |
| 54 | $flash = 'Business tax mode requires your VAT / GST / sales-tax ID.'; $kind = 'error'; | |
| 55 | } | |
| 56 | } | |
| 57 | ||
| 58 | if (!$flash) { | |
| 59 | my $qc = _q($country); my $qi = _q($tax_id); | |
| 60 | $db->db_readwrite($dbh, qq~ | |
| 61 | UPDATE `${DB}`.users SET | |
| 62 | tax_mode='$mode', | |
| 63 | tax_country='$qc', | |
| 64 | tax_id_number='$qi' | |
| 65 | WHERE id='$uid' | |
| 66 | ~, $ENV{SCRIPT_NAME}, __LINE__); | |
| 67 | $flash = 'Tax settings saved.'; $kind = 'success'; | |
| 68 | } | |
| 69 | } | |
| 70 | ||
| 71 | my $u = $db->db_readwrite($dbh, qq~ | |
| 72 | SELECT tax_mode, tax_country, tax_id_number FROM `${DB}`.users WHERE id='$uid' | |
| 73 | ~, $ENV{SCRIPT_NAME}, __LINE__); | |
| 74 | $db->db_disconnect($dbh); | |
| 75 | ||
| 76 | my $mode = _h($u->{tax_mode} // 'none'); | |
| 77 | my $country = _h($u->{tax_country} // ''); | |
| 78 | my $tax_id = _h($u->{tax_id_number} // ''); | |
| 79 | ||
| 80 | my $sel_none = $mode eq 'none' ? 'selected' : ''; | |
| 81 | my $sel_consumer = $mode eq 'consumer' ? 'selected' : ''; | |
| 82 | my $sel_business = $mode eq 'business' ? 'selected' : ''; | |
| 83 | ||
| 84 | my $body = '<div style="max-width:680px;margin:0 auto;padding:24px 28px;color:#e6ecf6">'; | |
| 85 | $body .= '<div style="margin-bottom:18px"><div style="font-size:11px;letter-spacing:2px;color:#14b8a6;text-transform:uppercase">Billing</div><h1 style="font-size:26px;color:#fff;margin:6px 0 4px">Tax settings</h1><p style="color:#7e92b6;margin:0">For invoices and tax-inclusive pricing displays. Stripe Tax handles the actual collection.</p></div>'; | |
| 86 | ||
| 87 | if (length $flash) { | |
| 88 | my $color = $kind eq 'success' ? '#10b981' : ($kind eq 'error' ? '#fca5a5' : '#10b981'); | |
| 89 | $body .= qq~<div style="background:${color}1a;border-left:3px solid $color;color:$color;padding:12px 16px;border-radius:8px;margin-bottom:16px;font-size:14px">~ . _h($flash) . qq~</div>~; | |
| 90 | } | |
| 91 | ||
| 92 | $body .= '<form method="POST" style="background:#0b1226;border:1px solid #1f2a4a;border-radius:12px;padding:18px 22px;display:flex;flex-direction:column;gap:14px">'; | |
| 93 | $body .= qq~ <label style="display:flex;flex-direction:column;gap:4px"> | |
| 94 | <span style="color:#7e92b6;font-size:12px">Tax mode</span> | |
| 95 | <select name="tax_mode" style="padding:8px 10px;background:#0a0f1f;border:1px solid #1f2a4a;color:#e6ecf6;border-radius:6px"> | |
| 96 | <option value="none" $sel_none>None — pay tax-exclusive</option> | |
| 97 | <option value="consumer" $sel_consumer>Consumer — tax-inclusive pricing</option> | |
| 98 | <option value="business" $sel_business>Business — reverse-charge with my tax ID</option> | |
| 99 | </select> | |
| 100 | </label> | |
| 101 | <label style="display:flex;flex-direction:column;gap:4px"> | |
| 102 | <span style="color:#7e92b6;font-size:12px">Country (ISO 3166-1 alpha-2, e.g. GB, US, DE, AU)</span> | |
| 103 | <input type="text" name="tax_country" value="$country" maxlength="2" style="padding:8px 10px;background:#0a0f1f;border:1px solid #1f2a4a;color:#e6ecf6;border-radius:6px;text-transform:uppercase;width:80px"> | |
| 104 | </label> | |
| 105 | <label style="display:flex;flex-direction:column;gap:4px"> | |
| 106 | <span style="color:#7e92b6;font-size:12px">Tax ID number (VAT / GST / state sales-tax ID)</span> | |
| 107 | <input type="text" name="tax_id_number" value="$tax_id" maxlength="40" placeholder="e.g. GB123456789 or DE123456789" style="padding:8px 10px;background:#0a0f1f;border:1px solid #1f2a4a;color:#e6ecf6;border-radius:6px"> | |
| 108 | </label> | |
| 109 | <div style="text-align:right"> | |
| 110 | <button type="submit" style="background:#064e3b;color:#a7f3d0;border:0;padding:8px 18px;border-radius:6px;font-weight:600;cursor:pointer">Save tax settings</button> | |
| 111 | </div> | |
| 112 | ~; | |
| 113 | $body .= '</form>'; | |
| 114 | ||
| 115 | $body .= '<div style="margin-top:18px;color:#7e92b6;font-size:12px">EU/UK customers with valid VAT IDs are zero-rated under reverse-charge. US customers see tax-exclusive prices and are charged sales tax in nexus states only. Need a different setup? <a href="/support.cgi" style="color:#14b8a6">Contact support</a>.</div>'; | |
| 116 | $body .= '</div>'; | |
| 117 | ||
| 118 | print "Content-Type: text/html; charset=utf-8\nCache-Control: no-store, no-cache, must-revalidate, max-age=0\nPragma: no-cache\nExpires: 0\n\n"; | |
| 119 | $wrap->render({ userinfo => $userinfo, title => 'Tax settings', body => $body }); | |
| 120 | exit; |