trucking: compute coupon discounted prices on the fly (true per A/B arm) + fix CTA URL bug
Two correctness fixes that gate enabling the coupon test: 1. On-the-fly pricing. The coupon block hardcoded '$79 $47' (only true at 40% off) — a false claim on the 20/30% arms. Now build_trucking_campaigns.py reads api/src/service-catalog.ts (same source checkout uses) and computes coupon_price_full / coupon_price_deal per recipient as full - round(full*pct/100), exactly matching the server. Service-fee-only; non-discountable services (boc3-filing passthrough) get NO price and fall back to percent-only copy. Quotes the service the email is ABOUT (mcs150 $79, reactivation $149), not the bundle the CTA happens to link to. service-catalog.ts now ships in the worker image; helper degrades to percent-only if it can't be read. 2. CTA URL bug (likely a big driver of the zero-click problem). Main campaign CTAs render '/order/slug&utm_source=...' (no '?') -> HTTP 404, verified live. Deficiency CTAs would double-'?' once a coupon added '?code='. lp_link now owns the query (?dot=...&code=...) so every template appends with a leading '&' and is valid in all 4 states (main/deficiency x coupon on/off), verified against live URLs returning 200. Deficiency _deal_box now shows real was/now prices (percent-only for boc3). Tests: 7/7 pass (adds URL-wellformed + price-matches-checkout cases).
This commit is contained in:
parent
6fce3ec9eb
commit
579919197d
4 changed files with 227 additions and 11 deletions
|
|
@ -78,12 +78,59 @@ def _price_box(headline, sub):
|
|||
)
|
||||
|
||||
|
||||
def _deal_box(headline, sub):
|
||||
"""Coupon-aware offer box.
|
||||
|
||||
When the daily same-day coupon is active (coupon_code attrib set), show the
|
||||
deal. If the build step computed a real discounted price for this service
|
||||
(coupon_priceable set — true for discountable services), show the exact
|
||||
"was $X, now $Y" numbers, which are calculated on the fly to match what
|
||||
checkout charges (service fee only) for the current A/B arm. If the service
|
||||
isn't discountable (e.g. BOC-3, a $25 passthrough) the price attribs are
|
||||
blank and we fall back to percent-only copy so we never print a false price.
|
||||
With no coupon at all it shows the normal price box.
|
||||
"""
|
||||
priced = (
|
||||
f'<p style="font-size:18px;font-weight:700;color:#9a3412;margin:0 0 6px">{headline} '
|
||||
'for <span style="text-decoration:line-through;color:#c2410c;font-weight:600">'
|
||||
'{{ .Subscriber.Attribs.coupon_price_full }}</span> '
|
||||
'<span style="color:#15803d">{{ .Subscriber.Attribs.coupon_price_deal }}</span>.</p>'
|
||||
)
|
||||
unpriced = (
|
||||
f'<p style="font-size:18px;font-weight:700;color:#9a3412;margin:0 0 6px">{headline}</p>'
|
||||
)
|
||||
deal = (
|
||||
'<div style="background:#fff7ed;border:2px solid #f97316;border-radius:10px;'
|
||||
'padding:20px;margin:20px 0;text-align:center">'
|
||||
'<p style="font-size:13px;font-weight:700;color:#9a3412;letter-spacing:.04em;'
|
||||
'margin:0 0 6px">TODAY ONLY - {{ .Subscriber.Attribs.coupon_pct }}% OFF OUR SERVICE FEE</p>'
|
||||
'{{ if .Subscriber.Attribs.coupon_priceable }}' + priced + '{{ else }}' + unpriced + '{{ end }}'
|
||||
'<p style="font-size:14px;color:#9a3412;margin:0 0 4px">Use code '
|
||||
'<strong style="font-size:16px;letter-spacing:.08em">{{ .Subscriber.Attribs.coupon_code }}</strong> '
|
||||
'(already applied when you click below).</p>'
|
||||
'<p style="font-size:12px;color:#b91c1c;font-weight:700;margin:0">'
|
||||
'Expires {{ .Subscriber.Attribs.coupon_expires }}. Discount applies to our '
|
||||
'service fee; government filing fees are billed at cost.</p></div>'
|
||||
)
|
||||
return (
|
||||
'{{ if .Subscriber.Attribs.coupon_code }}'
|
||||
+ deal
|
||||
+ '{{ else }}'
|
||||
+ _price_box(headline, sub)
|
||||
+ '{{ end }}'
|
||||
)
|
||||
|
||||
|
||||
def _cta(label):
|
||||
# Clicks land on the per-subscriber order page (resolved per row, incl.
|
||||
# per-state overrides) via the lp_link attrib, with UTM tracking.
|
||||
# per-state overrides) via the lp_link attrib. lp_link already carries the
|
||||
# carrier's `?dot=` query (and the daily `?code=` when a coupon is active),
|
||||
# so the template appends its own params with a leading `&` — correct whether
|
||||
# or not the coupon is on. (Previously this used `?dot=`, which double-`?`d
|
||||
# the URL once the coupon added its own query.)
|
||||
return (
|
||||
'<div style="text-align:center;margin:24px 0">'
|
||||
'<a href="{{ .Subscriber.Attribs.lp_link }}?dot={{ .Subscriber.Attribs.dot_number }}'
|
||||
'<a href="{{ .Subscriber.Attribs.lp_link }}'
|
||||
'&utm_source=listmonk&utm_medium=email&utm_campaign=deficiency@TrackLink" '
|
||||
'style="display:inline-block;padding:14px 36px;background:#f97316;color:#fff;'
|
||||
f'font-weight:700;border-radius:8px;text-decoration:none;font-size:16px">{label} →</a></div>'
|
||||
|
|
@ -124,7 +171,7 @@ def _body(headline, intro, bullets, price_headline, price_sub, cta_label):
|
|||
'computer. No Login.gov, no government portals, no hours on hold. We handle the paperwork so you can get '
|
||||
'back to trucking and making money.</p></div>'
|
||||
)
|
||||
return (_HEADER + alert + _price_box(price_headline, price_sub) + reassure
|
||||
return (_HEADER + alert + _deal_box(price_headline, price_sub) + reassure
|
||||
+ _cta(cta_label) + _dot_check_cta() + _FOOTER)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue