Skip to content

Pre-apply Stripe coupons to product prices#431

Merged
simonhamp merged 2 commits into
mainfrom
checkout-promo-codes
Jul 16, 2026
Merged

Pre-apply Stripe coupons to product prices#431
simonhamp merged 2 commits into
mainfrom
checkout-promo-codes

Conversation

@simonhamp

Copy link
Copy Markdown
Member

What & why

Lets admins offer a discount on a product (e.g. subscriber pricing for the Masterclass) by attaching an existing Stripe coupon to a product price — no need to create a separate Stripe price. When a buyer's resolved price carries a coupon, it is pre-applied to the Stripe checkout session so the discount happens automatically.

This started from wanting to give subscribers ~$100 off the Masterclass without duplicating prices in Stripe.

Changes

  • Schema: nullable stripe_coupon_id column on product_prices.
  • Checkout (CartController + course.checkout route): when the resolved price has a coupon, the session is created with discounts => [['coupon' => ...]]. The full price is charged as the line item and Stripe deducts the discount itself. Because Stripe rejects allow_promotion_codes alongside discounts, the manual promo-code box is dropped on those sessions only. Multiple coupons in one cart → first applied, rest logged.
  • Display: the course pages (/course and the /dashboard/course Livewire component) now show the real price from the database per user. The discounted amount is derived from the coupon's actual Stripe configuration (amount_off / percent_off), fetched and cached for 10 minutes, with a safe fallback to full price if the coupon can't be resolved. This replaces the previously hardcoded 199/299 values.
  • Price resolution: Product::getBestPriceForUser() now tie-breaks equal amounts by tier priority, so a coupon-bearing subscriber price wins over an equal-priced regular row.
  • Admin: the ProductResource pricing form and table expose the coupon field, with helper text explaining that the amount stays at full price.

Admin usage

  1. Create a coupon in the Stripe dashboard and copy its coupon ID.
  2. On the product's pricing, add a price row for the relevant tier at full price and paste the coupon ID.
  3. Buyers on that price see the discounted amount on-site and get the coupon pre-applied at checkout.

Notes / caveats

  • A pre-applied coupon means buyers can't also type a promotion code on that checkout (Stripe allows one or the other).
  • If a coupon is deleted/expired in Stripe, clear the field in the admin at the same time — otherwise a checkout attempt within the 10-minute cache window would error.

Testing

  • New CheckoutCouponTest (9 tests): tier resolution, amount_off / percent_off display math, coupon-fetch-failure fallback, and both checkout flows with/without a coupon.
  • Updated CoursePageTest and CourseContentTest to assert DB-driven and subscriber-discounted pricing.
  • All affected suites pass; pint clean.

🤖 Generated with Claude Code

simonhamp and others added 2 commits July 16, 2026 11:09
Add an optional Stripe coupon ID to product prices so admins can offer a
discount (e.g. subscriber pricing for the Masterclass) without creating a
separate Stripe price. When a buyer's resolved price carries a coupon, it is
pre-applied to the checkout session via `discounts`.

- Add nullable stripe_coupon_id column to product_prices
- Course and cart checkout pre-apply the coupon (dropping the manual promo
  code box, which Stripe disallows alongside discounts)
- Course pages now display the real price from the database per user, with
  the discounted amount derived from the coupon's Stripe config (cached),
  replacing the hardcoded 199/299 values
- Tie-break equal-amount prices by tier priority so a coupon-bearing
  subscriber price wins over an equal regular price
- Filament pricing form/table expose the coupon field

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an optional stripe_price_id to product prices. When set, the amount and
currency are locked in the admin and synced from Stripe on every save, and
checkout charges that Stripe price instead of an ad-hoc amount (course
checkout falls back to the legacy env price IDs when unset).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@simonhamp
simonhamp marked this pull request as ready for review July 16, 2026 16:43
@simonhamp
simonhamp merged commit 5cfdd3b into main Jul 16, 2026
2 checks passed
@simonhamp
simonhamp deleted the checkout-promo-codes branch July 16, 2026 16:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant