From e091ed29ecda6f648adba6f92b2aa84b8b2f48b6 Mon Sep 17 00:00:00 2001 From: rrader2890 Date: Tue, 16 Jun 2026 20:56:45 -0400 Subject: [PATCH] feat(white-label): show recurring price on gate + Stripe checkout Setup-mode Stripe checkout shows no amount, so it looked like a blank purchase. Now the gate page shows a "$X/month" pricing box (plan name + amount + authorization note), and the Stripe Checkout Session carries custom_text near the submit button stating the recurring charge. Price is pulled from the agreement's plan (amount_cents/interval), so test ($1) and live ($2,000) render correctly. Co-Authored-By: Claude Opus 4.8 (1M context) --- app/controllers/white_label_controller.rb | 48 ++++++++++++++++++----- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/app/controllers/white_label_controller.rb b/app/controllers/white_label_controller.rb index 0921a269d39e..bbdaf7803b4e 100644 --- a/app/controllers/white_label_controller.rb +++ b/app/controllers/white_label_controller.rb @@ -69,6 +69,8 @@ def render_gate(agreement, error: nil) terms = doc_body("terms", agreement.terms_version) company = agreement.customer.name err_html = error ? %(

#{ERB::Util.html_escape(error)}

) : "" + price = plan_price_text(agreement) + price_html = price ? %(
#{ERB::Util.html_escape(plan_for(agreement)&.name.to_s)}
#{ERB::Util.html_escape(price)}
By accepting and adding a card, you authorize Flobyte to charge this amount each period, in advance, until cancelled.
) : "" body = <<~HTML

White-Label / SDK Agreement

@@ -80,6 +82,7 @@ def render_gate(agreement, error: nil)
#{markdown_to_html(msa)}

Acceptable Use & Service Terms (#{agreement.terms_version})

#{markdown_to_html(terms)}
+ #{price_html}
@@ -124,22 +127,43 @@ def white_label_checkout_url(agreement) return if provider.nil? || pc&.provider_customer_id.blank? base = WhiteLabelAgreement.gate_base_url - session = ::Stripe::Checkout::Session.create( - { - mode: "setup", - customer: pc.provider_customer_id, - payment_method_types: ["card"], - success_url: "#{base}/white-label/done", - cancel_url: agreement.signing_url - }, - {api_key: provider.secret_key} - ) + params = { + mode: "setup", + customer: pc.provider_customer_id, + payment_method_types: ["card"], + success_url: "#{base}/white-label/done", + cancel_url: agreement.signing_url + } + # Setup mode shows no amount; surface the recurring price near the submit + # button so it doesn't look like a blank purchase. + if (price = plan_price_text(agreement)) + params[:custom_text] = {submit: {message: + "You authorize Flobyte to charge #{price} for the #{plan_for(agreement)&.name} plan " \ + "to this card, billed in advance until cancelled."}} + end + session = ::Stripe::Checkout::Session.create(params, {api_key: provider.secret_key}) session.url rescue => e Rails.logger.error("[white-label] checkout session failed: #{e.message}") nil end + def plan_for(agreement) + @plan_for ||= agreement.organization.plans.find_by(code: agreement.plan_code) + end + + # e.g. "$2,000.00/month" — the recurring price Lago will charge each period. + def plan_price_text(agreement) + plan = plan_for(agreement) + return if plan.nil? + + amount = plan.amount_cents.to_i / 100.0 + unit = (plan.amount_currency.presence || "USD") == "USD" ? "$" : "#{plan.amount_currency} " + price = ActiveSupport::NumberHelper.number_to_currency(amount, unit:, precision: 2) + interval = (plan.interval.to_s == "yearly") ? "year" : "month" + "#{price}/#{interval}" + end + # Lago records *which* provider to use at customer-create but may not create # the Stripe customer; do it now so GenerateCheckoutUrlService can succeed. def ensure_provider_customer(customer) @@ -239,6 +263,10 @@ def layout(inner) .doc h3{font-size:14px;margin:14px 0 4px} .doc p{margin:0 0 10px;font-size:14px;color:#333} .doc ul{margin:0 0 10px 18px;padding:0} .doc li{font-size:14px;color:#333;margin:3px 0} .doc hr{border:0;border-top:1px solid #eee;margin:14px 0} .doc strong{font-weight:600} + .pricing{background:#f0f7ff;border:1px solid #cfe3ff;border-radius:10px;padding:16px 18px;margin-top:24px;text-align:center} + .pricing .plan{font-size:13px;color:#555;text-transform:uppercase;letter-spacing:.04em} + .pricing .amt{font-size:24px;font-weight:700;color:#0b3d91;margin:2px 0 6px} + .pricing .note{font-size:12px;color:#555} form{background:#fff;border:1px solid #e2e2e2;border-radius:10px;padding:18px;margin-top:24px} label{display:block;margin:0 0 14px;font-weight:600} label.check{font-weight:400;display:flex;gap:8px;align-items:flex-start}