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

/var/www/vhosts/3dshawn.com/repricer.3dshawn.com/index.cgi

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

Added
+442
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to c2eeeaadb894
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 - landing page + small marketing pages.
4#
5# Dispatches on SCRIPT_NAME. index.cgi is the landing page; the small
6# marketing pages (about/contact/faq/features/help/privacy/terms/why)
7# have been folded in as separate branches so we cut file count without
8# losing behavior.
9#
10# Bigger marketing pages (tour, tutorials, pricing, changelog, refer)
11# stay as their own scripts.
12#
13# Pricing block on the landing page pulls from billing_plans so it stays
14# in lockstep with /pricing.cgi and the admin Packages console.
15#
16# Consolidated: former /about.cgi, /contact.cgi, /faq.cgi, /features.cgi,
17# /help.cgi, /privacy.cgi, /terms.cgi, /why.cgi are now wrappers that do
18# this file; SCRIPT_NAME routes execution.
19#======================================================================
20use strict;
21use warnings;
22
23use lib '/var/www/vhosts/3dshawn.com/repricer.3dshawn.com';
24use CGI;
25use MODS::Template;
26use MODS::Login;
27use MODS::RePricer::Wrapper;
28
29$|=1;
30
31my $auth = MODS::Login->new;
32my $wrap = MODS::RePricer::Wrapper->new;
33my $tfile = MODS::Template->new;
34
35my $entry = ($ENV{SCRIPT_NAME} || '') =~ m{/([^/]+)\.cgi$} ? $1 : 'index';
36
37# Simple template-only pages, keyed by entry filename.
38# template = TEMPLATES/<file>.html
39# title = <title> for the wrapper
40# require_login = 1 -> bounce to /login.cgi if unauthenticated
41my %SIMPLE = (
42 about => { template => 'repricer_about.html', title => 'About' },
43 contact => { template => 'repricer_contact.html', title => 'Contact' },
44 features => { template => 'repricer_features.html', title => 'Features' },
45 why => { template => 'repricer_why.html', title => 'Why RePricer' },
46 help => { template => 'repricer_help.html', title => 'Help', require_login => 1, page_key => '' },
47);
48
49my $userinfo = $auth->login_verify(); # may be undef on marketing pages
50
51if ($SIMPLE{$entry}) { _mkt_simple($entry); exit; }
52if ($entry eq 'faq') { _mkt_faq(); exit; }
53if ($entry eq 'privacy') { _mkt_privacy(); exit; }
54if ($entry eq 'terms') { _mkt_terms(); exit; }
55
56# Default: landing page
57_mkt_landing();
58exit;
59
60#======================================================================
61# Landing page (index.cgi)
62#======================================================================
63sub _mkt_landing {
64 require MODS::DBConnect;
65 require MODS::RePricer::Config;
66 require MODS::RePricer::Billing;
67 require MODS::RePricer::Promotions;
68
69 my $db = MODS::DBConnect->new;
70 my $cfg = MODS::RePricer::Config->new;
71 my $bill = MODS::RePricer::Billing->new;
72 my $DB = $cfg->settings('database_name');
73
74 my $dbh = $db->db_connect();
75 my @plans = _mkt_build_landing_plan_cards($db, $dbh, $DB, $bill);
76
77 my $promo_obj = MODS::RePricer::Promotions->new;
78 my $active_promo = $promo_obj->active_plan_promo($db, $dbh, $DB);
79 my $promo_code = ($active_promo && $active_promo->{code}) ? $active_promo->{code} : '';
80 my $promo_label = $active_promo ? $promo_obj->marketing_label($active_promo) : '';
81
82 if ($active_promo) {
83 my @eligible = $promo_obj->eligible_plan_ids($db, $dbh, $DB, $active_promo->{id});
84 my %eligible_set = map { $_ => 1 } @eligible;
85 my $all_paid = !scalar(@eligible);
86 foreach my $c (@plans) {
87 next if ($c->{price_display} || '$0') eq '$0';
88 next unless $all_paid || $eligible_set{ $c->{id} || 0 };
89 $c->{promo_active} = 1;
90 $c->{promo_code} = $promo_code;
91 $c->{promo_label} = $promo_label;
92 $c->{promo_card_line} = $promo_label . ' with code <strong>' . $promo_code . '</strong>';
93 }
94 }
95
96 $db->db_disconnect($dbh);
97
98 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";
99
100 my $tvars = {
101 user_id => $userinfo ? $userinfo->{user_id} : '',
102 plans => \@plans,
103 has_plans => scalar(@plans) ? 1 : 0,
104 has_promo => $active_promo ? 1 : 0,
105 promo_code => $promo_code,
106 promo_label => $promo_label,
107 };
108
109 my $body = join('', $tfile->template('repricer_index.html', $tvars, $userinfo));
110
111 $wrap->render({
112 userinfo => $userinfo,
113 title => 'Reprice your catalog automatically while you sleep',
114 body => $body,
115 });
116}
117
118#----------------------------------------------------------------------
119sub _mkt_build_landing_plan_cards {
120 my ($db, $dbh, $DB, $bill) = @_;
121 return _mkt_landing_fallback_plans() unless $bill->schema_ready($db, $dbh, $DB);
122
123 my @rows = $bill->list_plans($db, $dbh, $DB);
124 return _mkt_landing_fallback_plans() unless scalar @rows;
125
126 my $entitle_ready = $bill->plan_features_ready($db, $dbh, $DB);
127 my @catalog = $bill->feature_catalog;
128
129 my %by_tier;
130 foreach my $r (@rows) { push @{ $by_tier{ $r->{tier} } }, $r; }
131
132 my @tiers_seen;
133 foreach my $r (@rows) {
134 next if grep { $_ eq $r->{tier} } @tiers_seen;
135 push @tiers_seen, $r->{tier};
136 }
137
138 my @out;
139 foreach my $tier (@tiers_seen) {
140 my $monthly = (grep { ($_->{cadence}||'') eq 'monthly' } @{ $by_tier{$tier} })[0]
141 || $by_tier{$tier}->[0];
142 next unless $monthly;
143
144 my @bullets = _mkt_parse_features_for_landing($monthly->{features});
145 if (!scalar @bullets && $entitle_ready) {
146 my $set = $bill->plan_feature_set($db, $dbh, $DB, $monthly->{id});
147 foreach my $f (@catalog) {
148 next unless $set->{ $f->{key} };
149 push @bullets, $f->{name} . ' &mdash; ' . $f->{blurb};
150 }
151 }
152 @bullets = @bullets[0 .. ( $#bullets > 4 ? 4 : $#bullets )];
153 my $bullets_html = '';
154 foreach my $b (@bullets) {
155 $bullets_html .= '<li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">'
156 . '<polyline points="20 6 9 17 4 12"/></svg> ' . $b . '</li>';
157 }
158
159 my $tier_slug = lc($monthly->{tier} || ''); $tier_slug =~ s/[^a-z0-9_\-]//g;
160
161 push @out, {
162 id => $monthly->{id},
163 tier => $monthly->{tier},
164 display_name => $monthly->{display_name} || ucfirst($monthly->{tier} || ''),
165 price_display => _mkt_fmt_price_short($monthly->{price_cents}),
166 is_featured => $monthly->{is_featured} ? 1 : 0,
167 not_featured => $monthly->{is_featured} ? 0 : 1,
168 tagline => $monthly->{tagline} || '',
169 has_tagline => $monthly->{tagline} ? 1 : 0,
170 cta_label => $monthly->{cta_label} || ($monthly->{price_cents} ? 'Start trial' : 'Start free'),
171 cta_href => $monthly->{price_cents} ? ('signup.cgi?plan=' . $tier_slug) : 'signup.cgi',
172 bullets_html => $bullets_html,
173 };
174 }
175 return @out;
176}
177
178sub _mkt_fmt_price_short {
179 my ($cents) = @_;
180 $cents = 0 unless defined $cents;
181 if ($cents % 100 == 0) {
182 return '$' . int($cents / 100);
183 }
184 return '$' . sprintf('%.2f', $cents / 100);
185}
186
187sub _mkt_parse_features_for_landing {
188 my ($raw) = @_;
189 return () unless defined $raw && $raw =~ /\S/;
190 my @out;
191 while ($raw =~ m/\{\s*"name"\s*:\s*"((?:\\.|[^"\\])*)"\s*,\s*"included"\s*:\s*(true|false|0|1)\s*\}/gi) {
192 my $name = $1; my $inc = lc $2;
193 $name =~ s/\\"/"/g; $name =~ s/\\\\/\\/g;
194 push @out, $name if $inc eq 'true' || $inc eq '1';
195 }
196 return @out;
197}
198
199sub _mkt_landing_fallback_plans {
200 my $li = sub {
201 my @items = @_;
202 my $h = '';
203 foreach my $t (@items) {
204 $h .= '<li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">'
205 . '<polyline points="20 6 9 17 4 12"/></svg> ' . $t . '</li>';
206 }
207 return $h;
208 };
209 return (
210 { tier=>'pro', display_name=>'Pro', price_display=>'$49',
211 is_featured=>1, not_featured=>0,
212 tagline=>'Defend Buy Box on Amazon.',
213 has_tagline=>1, cta_label=>'Start 14-day free trial', cta_href=>'signup.cgi?plan=pro',
214 bullets_html => $li->('Up to 1,000 SKUs', 'Reprice every 5 minutes', 'Amazon marketplace', 'All 8 strategies + MAP guardrails', 'Full audit log') },
215 { tier=>'business', display_name=>'Business', price_display=>'$149',
216 is_featured=>0, not_featured=>1,
217 tagline=>'Multi-marketplace, full automation.',
218 has_tagline=>1, cta_label=>'Start 14-day free trial', cta_href=>'signup.cgi?plan=business',
219 bullets_html => $li->('Everything in Pro', 'Up to 10,000 SKUs', 'Reprice every minute', 'Amazon + Walmart', 'Audit log CSV export', 'Priority support') },
220 { tier=>'scale', display_name=>'Scale', price_display=>'$349',
221 is_featured=>0, not_featured=>1,
222 tagline=>'Enterprise scale, custom strategies, SLA.',
223 has_tagline=>1, cta_label=>'Start 14-day free trial', cta_href=>'signup.cgi?plan=scale',
224 bullets_html => $li->('Everything in Business', 'Unlimited SKUs', 'Amazon + Walmart + eBay', 'Custom strategies', '99.95% uptime SLA', 'Dedicated CSM + SAML SSO') },
225 );
226}
227
228#======================================================================
229# Simple template-driven marketing pages
230#======================================================================
231sub _mkt_simple {
232 my ($key) = @_;
233 my $spec = $SIMPLE{$key};
234
235 if ($spec->{require_login} && !$userinfo) {
236 print "Status: 302 Found\nLocation: /login.cgi\n\n";
237 return;
238 }
239
240 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";
241 my $body = join('', $tfile->template($spec->{template}, {}, $userinfo));
242 my %args = (
243 userinfo => $userinfo,
244 title => $spec->{title},
245 body => $body,
246 );
247 $args{page_key} = $spec->{page_key} if defined $spec->{page_key};
248 $wrap->render(\%args);
249}
250
251#======================================================================
252# FAQ (inline HTML -- template engine hangs on this content)
253#======================================================================
254sub _mkt_faq {
255 my @FAQ = (
256 { q => 'What does RePricer do?',
257 a => 'It automatically watches your Amazon, eBay, and Walmart listings, sees what the competing sellers are charging, and adjusts your price to win the Buy Box (or whatever target you choose) without ever dropping below the floor you set.' },
258 { q => 'Which marketplaces are supported?',
259 a => 'Amazon (SP-API, all 10 regions), eBay (Sell + Browse API, all major marketplaces), and Walmart (Marketplace API, US/Canada/Mexico). Each is wired through the official API -- no browser scraping, no rate-limit dodging.' },
260 { q => 'What is the Buy Box and why does it matter?',
261 a => 'On Amazon, ~83% of all sales go through the Buy Box -- the "Add to Cart" button at the top of a product page. Walmart has a near-identical concept. Whichever seller is "winning the Buy Box" gets the lion\'s share of orders. The Buy Box winner is chosen by an algorithm that weighs price, shipping speed, seller rating, and stock. Price is the easiest of those for a small seller to control -- which is what RePricer automates.' },
262 { q => 'How does RePricer protect me from a race to the bottom?',
263 a => 'Every product has a <strong>floor</strong> (defaults to your cost, configurable to anything you want). The repricer will never go below your floor, full stop -- even if it means giving up the Buy Box. You can also enforce a <strong>ceiling</strong> and a <strong>MAP</strong> (Minimum Advertised Price) for brand-restricted goods.' },
264 { q => 'What pricing strategies are available?',
265 a => 'Eight built-in: <em>beat the lowest competitor</em>, <em>match the Buy Box</em>, <em>beat the Buy Box</em>, <em>target a specific search position</em>, <em>cost + markup</em> (defensive), <em>fixed price</em>, <em>Bayesian competitor score</em> (weights rivals by historical reliability), and <em>velocity-aware</em> (adjusts based on sell-through). You can apply rules per-account or per-product, with a priority system for when multiple rules match.' },
266 { q => 'How often does it reprice?',
267 a => 'Pro: every 15 minutes. Business: every 5 minutes. Scale: every 1 minute. Each SKU also has a per-product cooldown so you don\'t hammer the same SKU 12 times an hour and get throttled by the marketplace.' },
268 { q => 'Does Amazon / eBay / Walmart penalize sellers who reprice this often?',
269 a => 'No -- repricing is a first-class workflow on all three marketplaces, with API endpoints designed for exactly this. Their actual concern is API rate limits, which the cooldowns above are tuned to stay safely under.' },
270 { q => 'What happens if a competitor goes below my floor?',
271 a => 'RePricer holds at your floor and you don\'t win the Buy Box that cycle. The next cycle, if they have raised again, you go back to beating them. Optional email alert: "Floor hit on SKU X" so you can decide whether to lower your floor or just leave the sale on the table.' },
272 { q => 'How do I connect my marketplace accounts?',
273 a => 'Amazon and eBay use OAuth -- click "Connect" on the Marketplaces page, sign in on the marketplace\'s own consent screen, done. Walmart uses a Consumer ID + Private Key (Walmart\'s standard) that you paste in. We <em>never</em> store your marketplace login -- only the API tokens you delegate to us.' },
274 { q => 'Is RePricer secure?',
275 a => 'Sign-in supports two-factor authentication (TOTP). All passwords are SHA-256 hashed. Marketplace OAuth tokens are stored encrypted at rest. Stripe handles all card data via Elements -- card numbers never touch our server. Audit log on every price change.' },
276 { q => 'How does billing work?',
277 a => 'RePricer is a flat-fee SaaS subscription billed via Stripe. Monthly or annual (annual is roughly 2 months free). 14-day free trial on every plan, no card required to start. <strong>We never take a percentage of your marketplace revenue</strong> &mdash; your Amazon / Walmart / eBay payouts land directly in your seller-account bank, exactly as today. Cancel anytime; cancellations take effect at the end of your current cycle.' },
278 { q => 'What if my SKU isn\'t listed correctly on the marketplace?',
279 a => 'RePricer only changes the <strong>price</strong> field. If the listing itself is wrong (title, image, category), you need to fix it on the marketplace directly -- our SP-API permissions intentionally don\'t include catalog editing.' },
280 { q => 'Can I see what changed and why?',
281 a => 'Yes -- every price change is logged with the old price, new price, the rule that fired, the marketplace API response, and a timestamp. View on the per-product detail page or export the full log to CSV.' },
282 { q => 'What if Amazon rejects a price update?',
283 a => 'It gets logged as <code>sync_status=failed</code> with the exact API error, and the worker retries on the next cycle. If a SKU keeps failing, it surfaces in the Errored Accounts panel on /admin_health.cgi.' },
284 { q => 'Can I pause repricing temporarily?',
285 a => 'Yes -- "Pause all repricing" toggle on your dashboard. The worker skips your entire account until you flip it back. Useful during a Stripe billing issue, an Amazon account review, or a holiday weekend.' },
286 { q => 'Can my team have access too?',
287 a => 'Business tier and up: up to 5 team seats with permission groups (manage billing, edit prices, view-only, etc.). Audit log records which team member made each change.' },
288 { q => 'Do you have an API?',
289 a => 'Scale tier includes API access for custom integrations (read products, push prices, listen for events). Documented at /api_docs once your account is provisioned.' },
290 { q => 'What if I want to leave?',
291 a => 'Cancel your subscription anytime. We keep your data for 90 days after cancellation in case you come back, then it\'s purged. CSV export of everything is one click before you go.' },
292 );
293
294 my $body = '<div style="max-width:780px;margin:0 auto;padding:24px 28px">';
295 $body .= ' <div style="font-size:11px;letter-spacing:2px;color:#14b8a6;text-transform:uppercase">Help center</div>';
296 $body .= ' <h1 style="font-size:30px;color:#fff;margin:6px 0 18px;font-weight:700">Frequently asked questions</h1>';
297
298 foreach my $f (@FAQ) {
299 my $q_h = _mkt_h($f->{q});
300 my $a = $f->{a}; # answers can include simple HTML; not escaping
301 $body .= qq~<details style="background:#0b1226;border:1px solid #1f2a4a;border-radius:10px;padding:14px 18px;margin-bottom:8px">
302 <summary style="cursor:pointer;color:#e6ecf6;font-size:15px;font-weight:600;list-style:none">$q_h</summary>
303 <div style="color:#7e92b6;font-size:14px;line-height:1.6;margin-top:10px">$a</div>
304 </details>~;
305 }
306
307 $body .= '<div style="margin-top:24px;padding:16px 20px;background:#0a0f1f;border:1px dashed #1f2a4a;border-radius:10px;text-align:center;color:#7e92b6;font-size:13px">';
308 $body .= 'Still have questions? Email <a href="mailto:support@repricer.3dshawn.com" style="color:#14b8a6">support@repricer.3dshawn.com</a>.';
309 $body .= '</div>';
310 $body .= '</div>';
311
312 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";
313 $wrap->render({ userinfo => $userinfo, title => 'FAQ', body => $body });
314}
315
316sub _mkt_h { my $s = shift // ''; $s =~ s/&/&amp;/g; $s =~ s/</&lt;/g; $s =~ s/>/&gt;/g; $s =~ s/"/&quot;/g; $s }
317
318#======================================================================
319# Privacy policy (inline HTML)
320#======================================================================
321sub _mkt_privacy {
322 my $body = <<'HTML';
323<div style="max-width:760px;margin:0 auto;padding:24px 28px;line-height:1.7;color:#e6ecf6">
324 <h1 style="font-size:28px;color:#fff;margin:0 0 6px">Privacy policy</h1>
325 <p style="color:#7e92b6">Last updated: June 3, 2026</p>
326
327 <h2 style="color:#14b8a6;margin-top:28px">TL;DR</h2>
328 <p>We store the minimum data needed to reprice your marketplace listings. We never sell your data. We never look at your customer order data &mdash; our marketplace API permissions only request pricing and listing scopes. We use Stripe for billing; card numbers never touch our server.</p>
329
330 <h2 style="color:#14b8a6;margin-top:28px">What we collect</h2>
331 <ul>
332 <li><b>Account info:</b> email, display name, password (hashed), timezone, currency preference.</li>
333 <li><b>Marketplace OAuth tokens</b> (Amazon LWA refresh token, eBay OAuth2 refresh token, Walmart Consumer ID + Private Key). Used only to read your competitor offers and update your prices.</li>
334 <li><b>Your product catalog:</b> SKU, ASIN/Item ID, title, cost, floor/ceiling, repricing rules.</li>
335 <li><b>Pricing telemetry we generate:</b> every price change we make (audit log), competitor offer snapshots, Buy Box history.</li>
336 <li><b>Billing data:</b> Stripe customer ID, subscription ID, invoice history. Card numbers stored on Stripe, not us.</li>
337 <li><b>Login/security:</b> session cookies, IP + user agent of each login, 2FA secret if enabled.</li>
338 </ul>
339
340 <h2 style="color:#14b8a6;margin-top:28px">What we do NOT collect</h2>
341 <ul>
342 <li>Customer order data on your marketplace accounts (scope not requested).</li>
343 <li>Any data from buyers of your products.</li>
344 <li>Credit card numbers / CVV / bank details &mdash; Stripe handles all of those.</li>
345 </ul>
346
347 <h2 style="color:#14b8a6;margin-top:28px">Who we share with</h2>
348 <ul>
349 <li><b>Amazon / eBay / Walmart:</b> we call their APIs on your behalf using your delegated tokens.</li>
350 <li><b>Stripe:</b> subscription billing only.</li>
351 <li><b>SMTP / email provider</b> (your choice via /admin_config.cgi): outbound notification emails.</li>
352 <li><b>That&apos;s it.</b> No ad trackers, no analytics SaaS, no &quot;data partners.&quot;</li>
353 </ul>
354
355 <h2 style="color:#14b8a6;margin-top:28px">Your rights (GDPR / CCPA)</h2>
356 <ul>
357 <li><b>Export your data:</b> request a JSON dump at <a href="/account_export.cgi" style="color:#14b8a6">/account_export.cgi</a>.</li>
358 <li><b>Delete your account:</b> request closure at <a href="/account_close.cgi" style="color:#14b8a6">/account_close.cgi</a>. 30-day grace window in case it&apos;s a mistake; after that everything is purged (Stripe invoice records retained for tax/legal reasons, 7 years).</li>
359 <li><b>Correct your data:</b> edit at <a href="/profile.cgi" style="color:#14b8a6">/profile.cgi</a>.</li>
360 <li><b>Revoke marketplace access:</b> click Disconnect on <a href="/marketplaces.cgi" style="color:#14b8a6">/marketplaces.cgi</a>; tokens wiped immediately.</li>
361 </ul>
362
363 <h2 style="color:#14b8a6;margin-top:28px">Security</h2>
364 <p>Marketplace OAuth tokens encrypted at rest. Passwords SHA-256 hashed. 2FA available with backup codes. Sign-in sessions revocable individually at <a href="/sessions.cgi" style="color:#14b8a6">/sessions.cgi</a>.</p>
365
366 <h2 style="color:#14b8a6;margin-top:28px">Cookies</h2>
367 <p>We use one first-party cookie (<code>repricer_session</code>, set on sign-in). One additional cookie (<code>repricer_cookie_ok</code>) records that you dismissed the cookie banner. No third-party cookies, no advertising pixels.</p>
368
369 <h2 style="color:#14b8a6;margin-top:28px">Children</h2>
370 <p>RePricer is a B2B SaaS. We don&apos;t knowingly collect data from anyone under 16. Email <a href="mailto:privacy@repricer.3dshawn.com" style="color:#14b8a6">privacy@repricer.3dshawn.com</a> if you believe we&apos;ve received data from a minor.</p>
371
372 <h2 style="color:#14b8a6;margin-top:28px">Changes</h2>
373 <p>Material changes are emailed to every active user and posted on <a href="/changelog.cgi" style="color:#14b8a6">/changelog.cgi</a>.</p>
374
375 <h2 style="color:#14b8a6;margin-top:28px">Contact</h2>
376 <p>Privacy questions: <a href="mailto:privacy@repricer.3dshawn.com" style="color:#14b8a6">privacy@repricer.3dshawn.com</a>.</p>
377</div>
378HTML
379
380 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";
381 $wrap->render({ userinfo => $userinfo, title => 'Privacy policy', body => $body });
382}
383
384#======================================================================
385# Terms of service (inline HTML)
386#======================================================================
387sub _mkt_terms {
388 my $body = <<'HTML';
389<div style="max-width:760px;margin:0 auto;padding:24px 28px;line-height:1.7;color:#e6ecf6">
390 <h1 style="font-size:28px;color:#fff;margin:0 0 6px">Terms of service</h1>
391 <p style="color:#7e92b6">Last updated: June 21, 2026</p>
392
393 <h2 style="color:#14b8a6;margin-top:28px">1. The deal</h2>
394 <p>RePricer (&quot;we&quot;) is a SaaS that reads your Amazon / Walmart / eBay marketplace listings and adjusts prices on your behalf, per rules and guardrails you configure. You (&quot;the seller&quot;) sign up, pay a subscription, and grant us API access via OAuth or signed marketplace credentials. RePricer plans are <b>Pro</b>, <b>Business</b>, and <b>Scale</b>; there is no free tier &mdash; every plan begins with a 14-day free trial.</p>
395
396 <h2 style="color:#14b8a6;margin-top:28px">2. Your account</h2>
397 <p>You&apos;re responsible for actions on your RePricer account, including by team members you invite. Keep your password and 2FA secret safe. Team-seat sharing is allowed on Business and Scale; on Pro, each seat requires its own account. We may suspend accounts that violate these Terms, our acceptable-use policy, marketplace policies that apply to your seller account, or applicable law.</p>
398
399 <h2 style="color:#14b8a6;margin-top:28px">3. The pricing engine</h2>
400 <p>The repricer changes prices based on the rules you set. <b>You are responsible for those rules.</b> We provide hard floors, ceilings, and MAP enforcement so misconfigured rules don&apos;t cause infinite undercutting &mdash; but if you set a floor of $0.01 and someone undercuts you, you&apos;ll sell at $0.01.</p>
401 <p>We are not responsible for losses from misconfigured rules, missed Buy Box opportunities, or competitor behavior. Marketplace policy violations (e.g. antitrust laws, brand MAP agreements) are your responsibility as the seller.</p>
402
403 <h2 style="color:#14b8a6;margin-top:28px">4. Marketplace API permissions</h2>
404 <p>You delegate API access via each marketplace&apos;s own OAuth (Amazon, eBay) or signed-credential (Walmart) grant flow. We use it only for pricing, listing-read, and competitor-offer scopes. We do not read customer order data beyond what is required to reconcile sell-through velocity. You can revoke any time via the marketplace&apos;s seller dashboard or our /marketplaces.cgi.</p>
405
406 <h2 style="color:#14b8a6;margin-top:28px">5. Subscription billing &amp; the SaaS relationship</h2>
407 <p>RePricer is a software-as-a-service subscription. Your subscription fee is the only payment you make to us, billed monthly in advance via Stripe to the card on file. <b>RePricer does NOT process buyer transactions on the marketplaces you connect</b>; those run through Amazon&apos;s, Walmart&apos;s, and eBay&apos;s own payment systems, and you receive payouts directly from those marketplaces to your seller-account bank, exactly as you do today. RePricer is not the merchant of record for any marketplace sale, takes no percentage of your marketplace revenue, and is not in the payment path between buyer and seller.</p>
408 <p>14-day free trial on every plan; no card required during trial. Email reminder before trial ends. Cancel any time at <a href="/billing.cgi" style="color:#14b8a6">/billing.cgi</a>; cancellations take effect at the end of the current cycle. Refunds at our discretion within 14 days of a renewal charge, issued as account credit on the next invoice by default.</p>
409
410 <h2 style="color:#14b8a6;margin-top:28px">6. Acceptable use</h2>
411 <ul>
412 <li>Don&apos;t use RePricer to violate marketplace ToS, antitrust laws, or contracts (e.g. brand MAP agreements you are bound by).</li>
413 <li>Don&apos;t reverse-engineer the platform.</li>
414 <li>Don&apos;t scrape, abuse our API, or impersonate other users.</li>
415 </ul>
416
417 <h2 style="color:#14b8a6;margin-top:28px">7. Uptime &amp; outages</h2>
418 <p>We aim for high availability across all paid plans. The <b>Scale plan includes a 99.95% monthly uptime SLA with prorated service credits</b> when we miss it; Pro and Business have no formal SLA but share the same underlying infrastructure. Marketplace API outages are out of our control; the repricer skips and resumes on the next cycle.</p>
419
420 <h2 style="color:#14b8a6;margin-top:28px">8. API rate limits &amp; marketplace API costs</h2>
421 <p>RePricer connects to Amazon SP-API, Walmart Marketplace API, and eBay Trading / REST APIs on your behalf. We comply with each marketplace&apos;s published rate-limit and acceptable-use policies. If your account&apos;s usage exceeds typical patterns for your plan &mdash; for example, reprice attempts above your tier&apos;s documented frequency or excessive competitor-poll volume &mdash; <b>we may queue or throttle excess calls</b> to stay within marketplace limits and protect your seller account from rate-limit warnings or suspension. We are not responsible for marketplace-side rate-limit or velocity actions that originate from non-RePricer traffic on the same seller account.</p>
422
423 <h2 style="color:#14b8a6;margin-top:28px">9. Liability</h2>
424 <p>To the maximum extent permitted by law, our total liability for any claim is capped at the amount you paid us in the 12 months before the claim. We are not liable for indirect, incidental, or consequential damages &mdash; including lost Buy Box revenue, lost margin, or marketplace seller-account suspensions resulting from rules you configured.</p>
425
426 <h2 style="color:#14b8a6;margin-top:28px">10. Termination</h2>
427 <p>Either party can terminate any time. 30 days&apos; notice if we discontinue the service or your tier. Your data, rules, and audit-log archive are retained for 90 days after cancellation so you can re-activate or export; after that permanently purged (Stripe invoice records retained 7 years for tax compliance).</p>
428
429 <h2 style="color:#14b8a6;margin-top:28px">11. Changes to these terms</h2>
430 <p>Material changes are emailed to every active user and posted on <a href="/changelog.cgi" style="color:#14b8a6">/changelog.cgi</a> at least 30 days before they take effect. Continued use after the effective date = acceptance.</p>
431
432 <h2 style="color:#14b8a6;margin-top:28px">12. Governing law</h2>
433 <p>Governed by US laws (state of operation), excluding conflict-of-laws principles. Disputes go to good-faith arbitration first; if that fails, the courts of the state of operation have jurisdiction.</p>
434
435 <h2 style="color:#14b8a6;margin-top:28px">13. Contact</h2>
436 <p>Legal: <a href="mailto:legal@repricer.3dshawn.com" style="color:#14b8a6">legal@repricer.3dshawn.com</a>. Support: <a href="mailto:support@repricer.3dshawn.com" style="color:#14b8a6">support@repricer.3dshawn.com</a>.</p>
437</div>
438HTML
439
440 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";
441 $wrap->render({ userinfo => $userinfo, title => 'Terms of service', body => $body });
442}
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help