Stripe vs Braintree Promotions

Copy Markdown View Source

Stripe promotions are processor-backed. Braintree promotions are local-code mappings that point at Control Panel discounts managed outside the customer checkout flow.

That distinction matters because Accrue does not pretend the processors expose the same upstream object model:

  • Stripe promotion codes are created through processor-native coupon and promotion-code APIs, then projected locally.
  • Braintree promotion codes are stored locally in Accrue and resolved to a Braintree discount_id when a subscription is created.

Local-Code Mappings

For Braintree, the supported setup path is:

{:ok, _mapping} =
  Accrue.Billing.upsert_discount_mapping("SPRING25", %{
    discount_id: "bt_discount_25",
    amount_off_minor: 2_500,
    currency: "USD",
    active: true,
    max_redemptions: 100
  })

max_redemptions is enforced through local redemption state. It is not ignored metadata, and it is not delegated to a processor-native Braintree promotion object because that object does not exist.

Preview vs Final Submit

Accrue's portal flow previews savings locally before payment submit, then revalidates the selected code inside Accrue.Billing.subscribe/3. The preview is intentionally provisional. Expiry, deactivation, redemption-cap changes, or operator drift can invalidate a mapping between preview and final submit.

If the local row has drifted into an unusable state, submit returns %Accrue.Error.DiscountMappingInvalid{} and the customer sees safe copy such as "This promotion is temporarily unavailable."

Troubleshooting

Use the [:accrue, :ops, :discount_mapping_invalid] telemetry event to alert operators when a locally valid code points at a broken Braintree discount id. The event carries allowlisted metadata only: mapping id, code, discount id, reason, and the surrounding operation id when available.

When the event fires:

  1. Find the code in the local accrue_discount_mappings row.
  2. Confirm the mapped discount_id still exists and is the intended Control Panel discount.
  3. Repair the row with Accrue.Billing.upsert_discount_mapping/2.
  4. Ask the customer to retry after the mapping is fixed.