Separate payment confirmation from download delivery
Model paid orders, access grants, and email delivery independently so Stripe retries and provider failures do not turn into duplicate fulfillment.
A buyer can pay successfully while an access grant or delivery email fails. A single fulfilled boolean cannot explain that situation. It encourages the application either to report success too early or to repeat every side effect on the next retry.
The design below concerns one-time digital purchases backed by an order record. It separates payment evidence from delivery progress. Sources were checked on September 9, 2026. The state sketch is pseudocode, not a complete payment integration, and no live purchase or email was sent to validate this article.
Confirm the payment on the server
A success-page URL is not evidence that an order is paid. The browser may arrive with edited parameters, or may never arrive at all. Stripe recommends webhook-based fulfillment and warns that fulfillment may be invoked more than once, including concurrently, for the same Checkout Session.
Retrieve and validate the relevant session on the server, including its payment state, environment, configured product or price, and order association. A delayed payment method needs its eventual success or failure handled too. The UI should read the resulting order state; it should not grant access because checkout=success appears in a URL.
Record each delivery obligation separately
Use a stable order key that distinguishes test and live data. Track the access grant and the notification independently. If access exists but email delivery fails, the next attempt should retry the notification without recreating access. If payment is confirmed but neither side effect has completed, show that preparation is still in progress.
A useful record stores attempts, timestamps, and sanitized failure classes alongside those states. Keep private provider details in operational logs with controlled access, not in a browser banner. Email acceptance means the provider accepted the request; it does not prove the message reached the inbox.
order key = environment + checkout session ID
payment: pending | confirmed | failed
access: pending | granted | retryable_failure
email: pending | accepted | retryable_failure
confirmed payment
-> claim bounded delivery work
-> ensure access, record result
-> request notification, record provider result
-> expose only the customer-facing statusKeep external side effects outside transaction callbacks
Firestore may rerun a transaction callback when a read document changes concurrently. Sending an email inside that callback can therefore repeat an external operation even if only one database result is eventually committed. Use the transaction to claim or update work, then perform the external action outside it.
A lease needs an owner token and an expiry. On completion, verify that the worker still owns the claim before writing its result. A crashed worker must not leave an order blocked forever, and an expired worker must not overwrite a newer attempt. The database claim coordinates workers; it cannot make a remote API call part of the same atomic commit.
Account for the gap between sending and recording
Consider a worker that sends an email successfully and crashes before saving the provider result. The next worker sees email=pending. A lease alone cannot tell whether another message would duplicate a previous one. Use provider idempotency where available and a stable key derived from the same order and operation.
Resend currently retains idempotency keys for 24 hours. That makes the retention window part of the recovery design, not an implementation footnote. A retry outside that window needs reconciliation or a deliberate resend policy; it must not be described as an exactly-once guarantee. Reuse the same operation key and payload for retries within its intended window.
Test recovery paths before calling delivery complete
Review the workflow with explicit failure injection: duplicate events, two workers claiming the same order, an access provider failure, an email failure after access succeeds, and a crash after a provider accepts the request. Also test an expired lease and a test-mode session presented to the live path.
These scenarios are a verification plan, not reported production results. The important observations are which side effects occurred, which record is authoritative, and whether the next attempt can safely make progress. Counting successful HTTP responses alone cannot establish those properties.
- Do not grant access from client-supplied payment status.
- Do not turn an email retry into a second access grant.
- Do not equate provider acceptance with inbox delivery.
- Do not claim exactly-once delivery across systems without a matching guarantee.