Diff -- /var/www/vhosts/3dshawn.com/repricer.3dshawn.com/_attic/checkout.cgi
Diff

/var/www/vhosts/3dshawn.com/repricer.3dshawn.com/_attic/checkout.cgi

added on local at 2026-07-01 21:47:31

Added
+195
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to e37bf4d11374
Restore this content →
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 - Checkout page (buyer-facing).
4#
5# GET /checkout.cgi?id=<storefront_id>
6#
7# When the storefront is connected to Stripe, this page renders the
8# real Stripe Elements payment form. The actual PaymentIntent is
9# created on demand by /checkout_intent.cgi (POSTed from the form) so
10# we don't generate intents for buyers who never type their email.
11#
12# When Stripe is not configured (platform-wide or seller-side), this
13# page falls back to a "checkout coming soon" panel.
14#======================================================================
15use strict;
16use warnings;
17
18use lib '/var/www/vhosts/3dshawn.com/repricer.3dshawn.com';
19use CGI;
20use MODS::Template;
21use MODS::DBConnect;
22use MODS::RePricer::Config;
23use MODS::RePricer::Themes;
24use MODS::RePricer::Cart;
25use MODS::RePricer::Stripe;
26use MODS::RePricer::BuyerAuth;
27
28$| = 1;
29
30my $q = CGI->new;
31my $form = $q->Vars;
32my $tfile = MODS::Template->new;
33my $db = MODS::DBConnect->new;
34my $config = MODS::RePricer::Config->new;
35my $themes = MODS::RePricer::Themes->new;
36my $cart = MODS::RePricer::Cart->new;
37my $stripe = MODS::RePricer::Stripe->new;
38my $buyer_auth = MODS::RePricer::BuyerAuth->new;
39my $DB = $config->settings('database_name');
40
41my $sid = $form->{id} || $form->{storefront_id} || 0;
42$sid =~ s/[^0-9]//g;
43unless ($sid) {
44 print "Status: 302 Found\nLocation: /\n\n";
45 exit;
46}
47
48my $dbh = $db->db_connect();
49my $store = $db->db_readwrite($dbh,
50 qq~SELECT id, name, subdomain, theme_id, stripe_account_id, stripe_onboarded_at
51 FROM `${DB}`.storefronts WHERE id='$sid' AND status='live' LIMIT 1~,
52 $ENV{SCRIPT_NAME}, __LINE__);
53unless ($store && $store->{id}) {
54 $db->db_disconnect($dbh);
55 print "Status: 302 Found\nLocation: /\n\n";
56 exit;
57}
58
59my $theme = $themes->by_id($store->{theme_id});
60my $css_vars = $themes->css_vars($theme);
61
62my $token = $cart->read_token($q);
63my @rows = $cart->items($db, $dbh, $DB, $token, $sid);
64my $total = $cart->total_cents($db, $dbh, $DB, $token, $sid);
65
66# Detect whether any cart line requires shipping (a kind='physical'
67# row). When so, the checkout page renders a shipping address form
68# and a shipping-fee selector. Rates come from the seller's
69# shipping_options_json (zone=cents lines on the model row).
70my $needs_shipping = $cart->requires_shipping($db, $dbh, $DB, $token, $sid);
71
72# Resolve the storefront's shipping zones. We probe models touched by
73# this cart and use the FIRST physical line's shipping_options_json
74# as the menu -- realistic for single-seller storefronts (the common
75# case here). A multi-line cart with diverging rates per model would
76# need a more elaborate "max per line" calc later.
77my @ship_zones;
78my $ship_from_country = '';
79if ($needs_shipping) {
80 foreach my $r (@rows) {
81 next unless ($r->{purchase_kind} || '') eq 'physical';
82 my $mid = $r->{model_id}; $mid =~ s/[^0-9]//g;
83 next unless $mid;
84 my $mr = $db->db_readwrite($dbh, qq~
85 SELECT shipping_options_json, ship_from_country
86 FROM `${DB}`.models WHERE id='$mid' LIMIT 1
87 ~, $ENV{SCRIPT_NAME}, __LINE__);
88 next unless $mr;
89 $ship_from_country ||= $mr->{ship_from_country} || '';
90 my $raw = $mr->{shipping_options_json} || '';
91 foreach my $line (split /\r?\n/, $raw) {
92 $line =~ s/^\s+|\s+$//g;
93 next unless $line =~ /^([A-Za-z0-9_\-]{1,40})\s*=\s*(\d+)$/;
94 push @ship_zones, { slug => $1, cents => $2 + 0 };
95 }
96 last; # take the first physical model's rate set
97 }
98 # No rates? Fall back to a single free domestic zone so the
99 # buyer can still complete the order.
100 unless (@ship_zones) {
101 @ship_zones = ({ slug => 'standard', cents => 0 });
102 }
103}
104
105# Decorate shipping zones for the radio list. First zone is the
106# default selection so the form always submits a valid ship_zone.
107my @ship_options;
108my $ship_idx = 0;
109foreach my $z (@ship_zones) {
110 my $label = ucfirst($z->{slug});
111 $label =~ s/_/ /g;
112 push @ship_options, {
113 slug => $z->{slug},
114 cents => $z->{cents},
115 label => $label,
116 cost_label=> $z->{cents} > 0
117 ? '$' . sprintf('%.2f', $z->{cents} / 100)
118 : 'Free',
119 is_first => ($ship_idx == 0) ? 1 : 0,
120 checked_attr => ($ship_idx == 0) ? 'checked' : '',
121 };
122 $ship_idx++;
123}
124
125# Pull buyer email from session if they're logged in. Used to prefill
126# the email field; buyer is free to overwrite.
127my $buyer = $buyer_auth->verify($q, $db, $dbh, $DB);
128my $prefill_email = ($buyer && $buyer->{email}) ? $buyer->{email} : '';
129
130$db->db_disconnect($dbh);
131
132# Three checkout states the template renders against.
133my $platform_ready = $stripe->is_configured ? 1 : 0;
134my $seller_ready = ($store->{stripe_account_id} && $store->{stripe_onboarded_at}) ? 1 : 0;
135my $is_payable = ($platform_ready && $seller_ready && scalar(@rows) && $total > 0) ? 1 : 0;
136
137# Decorate cart items for display.
138my @items;
139foreach my $r (@rows) {
140 my $title = $r->{title} || '';
141 $title =~ s/&/&amp;/g; $title =~ s/</&lt;/g; $title =~ s/>/&gt;/g; $title =~ s/"/&quot;/g;
142 push @items, {
143 title => $title,
144 qty => $r->{quantity} || 1,
145 line_total => '$' . sprintf('%.2f', ($r->{price_cents} || 0) * ($r->{quantity} || 1) / 100),
146 };
147}
148
149# Storefront header chrome (brand + search + sign in + cart) -- same
150# shared helper every buyer-facing page uses so the header stays
151# consistent across cart/checkout/listing_details/my_orders.
152require MODS::RePricer::StoreChrome;
153# Re-open a connection just for the nav-pages lookup -- the main
154# checkout work already closed $dbh above.
155my $chrome_dbh = $db->db_connect();
156my $chrome_html = MODS::RePricer::StoreChrome->header_html({
157 store_id => $store->{id},
158 store_name => $store->{name} || $store->{subdomain},
159 is_buyer_in => ($prefill_email ? 1 : 0),
160 cart_count => scalar(@rows),
161 db => $db,
162 dbh => $chrome_dbh,
163 DB => $DB,
164});
165$db->db_disconnect($chrome_dbh);
166
167my $tvars = {
168 theme_css_vars => $css_vars,
169 store_id => $store->{id},
170 store_name => $store->{name} || $store->{subdomain},
171 store_chrome_html => $chrome_html,
172 item_count => scalar(@rows),
173 has_items => scalar(@rows) ? 1 : 0,
174 items => \@items,
175 total_display => '$' . sprintf('%.2f', $total / 100),
176 total_cents => $total,
177 cart_href => "/cart.cgi?id=$sid",
178 back_href => "/store.cgi?id=$sid",
179
180 is_payable => $is_payable,
181 not_payable => $is_payable ? 0 : 1,
182 seller_not_ready => ($platform_ready && !$seller_ready) ? 1 : 0,
183 platform_not_ready => $platform_ready ? 0 : 1,
184
185 stripe_pk => $stripe->publishable_key,
186 prefill_email => $prefill_email,
187
188 needs_shipping => $needs_shipping,
189 ship_options => \@ship_options,
190 has_ship_options => $needs_shipping ? 1 : 0,
191 ship_from_country => $ship_from_country,
192};
193
194print "Content-Type: text/html; charset=utf-8\nCache-Control: no-cache\n\n";
195print join('', $tfile->template('repricer_checkout.html', $tvars, undef));
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help