Project

General

Profile

Support #10285 ยป SUBSCRIPTION_END_TO_END_REVIEW_2026-08-10.md

Yalavarthi Thriveni, 08/10/2026 07:55 AM

 

Subscription End-to-End Review

Review date: August 10, 2026

Branch: main

Scope: Contract creation, Stripe payments and webhooks, renewals, automatic visit/job generation, cancellation, and failed payments.

Code modifications during review: None.

Executive Summary

The current subscription implementation cannot be classified as fully working or production-safe.

  • Normal manual invoice-payment job creation is commented out.
  • Service subscription payments have a separate Stripe invoice.paid auto-job implementation, but some failures can permanently prevent jobs from being created.
  • Product subscription payments create order-tracking entries, but the delivery-job creation block is commented out.
  • Most webhook processing failures are returned to Stripe as HTTP 200, preventing automatic retries.
  • Cancellation can be shown as successful locally even when cancellation failed in Stripe.
  • Product failed-payment handling may ignore Stripe invoices that use the newer nested subscription reference.

Critical Findings

1. Stripe webhook failures are acknowledged as successful

Both service and product webhook controllers return HTTP 200 for most handler errors.

Files:

  • evergreen_pos_be/src/controllers/serviceWebhook.controller.js:143
  • evergreen_pos_be/src/controllers/webhook.controller.js:77

Impact:

Stripe sends invoice.paid
-> database, invoice, or job operation fails
-> backend returns HTTP 200
-> Stripe considers the event delivered
-> Stripe does not retry
-> payment may succeed while local processing remains incomplete

This can affect contract creation, invoice recording, renewal processing, auto-job generation, failed-payment handling, and cancellation finalization.

2. Service auto-job lock can permanently block job creation

The service invoice.paid handler sets initialJobsScheduled = true before validating the subscription plan, variant, or services.

File:

  • evergreen_pos_be/src/service-webhook-handlers/invoicePaid.js:65

The function then returns normally when:

  • The subscription plan cannot be found.
  • The selected variant cannot be found.
  • Neither the quotation nor plan contains usable services.

Files:

  • evergreen_pos_be/src/service-webhook-handlers/invoicePaid.js:88
  • evergreen_pos_be/src/service-webhook-handlers/invoicePaid.js:97
  • evergreen_pos_be/src/service-webhook-handlers/invoicePaid.js:182

Because these paths return instead of throwing an error, the lock is not reverted.

Payment succeeds
-> initialJobsScheduled lock is acquired
-> plan, variant, or service lookup fails
-> zero jobs are created
-> initialJobsScheduled remains true
-> subsequent attempts skip job generation permanently

3. An empty visit schedule is treated as successful zero-job scheduling

When visitSchedule exists but contains no month entries, the helper returns zero dates with hasSchedule: true.

Files:

  • evergreen_pos_be/src/utils/visitScheduleHelper.js:94
  • evergreen_pos_be/src/service-webhook-handlers/invoicePaid.js:341

The caller can then:

  • Create zero jobs.
  • Set invoice.jobCreated = true.
  • Keep contract.initialJobsScheduled = true.
  • Send a jobs-scheduled notification reporting zero jobs.

This behavior was reproduced through a targeted helper execution.

4. Product subscription delivery-job creation is disabled

Product subscription renewals create an order-tracking entry, but the actual Job.create() implementation is commented out.

Files:

  • evergreen_pos_be/src/webhook-handlers/invoicePaid.js:173
  • evergreen_pos_be/src/webhook-handlers/invoicePaid.js:191

If a delivery or pickup job is expected for every product subscription cycle, that functionality is not active in the current branch.

Lifecycle Status

Subscription flow Result Findings
Service contract creation At risk Contract can be created by checkout completion or the invoice-paid fallback, but handler failures are acknowledged with HTTP 200.
Product subscription creation Partially working Checkout creates the subscription and order tracking. Most non-race failures are still acknowledged with HTTP 200.
Initial Stripe payment At risk Service payment can generate jobs, but permanent-lock and zero-job defects exist.
Renewal payment Partially working Transactions and cycle counters are updated, but product delivery jobs remain disabled.
Service visit/job generation Blocked in some cases Depends on successful plan, variant, service, address, and schedule resolution on the first processing attempt.
Product delivery-job generation Not active The job creation code is commented out; only order tracking is created.
Cancellation At risk Local cancellation can be saved even when Stripe cancellation fails.
Failed payments Incomplete Product handler can silently ignore newer nested Stripe subscription references.
Automatic tenure completion Present but at risk Daily cron scheduling exists, but Stripe/API failures can leave local and Stripe states inconsistent.

Contract Creation Review

Service subscription contracts can be created through:

  1. checkout.session.completed
  2. An invoice.paid fallback using the pending subscription record

The use of an upsert keyed by stripeSubscriptionId helps prevent duplicate contracts when Stripe events arrive concurrently.

However, configuring the one-year Stripe cancellation date is best-effort. If the Stripe cancel_at update fails, contract creation continues.

File:

  • evergreen_pos_be/src/service-webhook-handlers/checkoutCompleted.js:251

The local contract may therefore have a one-year end date while Stripe continues charging after that date.

Stripe Payment and Webhook Review

Service subscriptions

The service webhook handles:

  • checkout.session.completed
  • invoice.paid
  • invoice.payment_failed
  • Invoice-related payment_intent.payment_failed
  • Invoice-related charge.failed
  • customer.subscription.deleted

invoice.payment_succeeded and invoice_payment.paid are intentionally skipped to avoid duplicate handling.

The primary reliability defect is that handler exceptions are acknowledged with HTTP 200. Consequently, Stripe will not retry recoverable database, notification, invoice, or job-generation errors.

Product subscriptions

The product webhook handles:

  • checkout.session.completed
  • checkout.session.expired
  • invoice.paid
  • invoice.payment_failed
  • customer.subscription.updated
  • customer.subscription.deleted

It returns HTTP 500 only for the recognized initial ordering race where invoice.paid arrives before the local subscription exists. Most other failures receive HTTP 200 and are not retried.

Renewal Review

Service subscriptions

Renewal payments update the invoice and contract cycle information and send payment notifications.

Jobs are normally scheduled for the full configured visit schedule during the initial payment. Later renewals do not recreate jobs after initialJobsScheduled becomes true. Therefore, missing jobs during initial scheduling are not automatically repaired by subsequent renewals.

Product subscriptions

Renewal processing:

  • Creates an idempotent transaction.
  • Creates an order-tracking entry.
  • Increments cycleNumber.
  • Updates nextJobDate.
  • Sends renewal receipt and notifications.

It does not create a delivery job because that block is commented out.

The next cycle date is calculated by adding fixed values such as 30, 90, or 182 days.

File:

  • evergreen_pos_be/src/webhook-handlers/invoicePaid.js:227

This can drift from Stripe calendar billing. For example, repeatedly adding 30 days does not consistently preserve a monthly billing day. Stripe invoice period dates would be a more reliable source of truth.

Automatic Service Visit and Job Generation

The service subscription auto-job function:

  • Loads the plan and selected variant.
  • Resolves company holidays and weekend settings.
  • Applies per-day capacity and customer-spacing rules.
  • Uses quotation services when available.
  • Falls back to services configured on the plan variant.
  • Creates one job per service per scheduled visit.
  • Attempts to prevent duplicates using subscription, service, and start-date information.

Functional risks:

  1. The contract lock is acquired too early.
  2. Missing plan, variant, or services permanently retains the lock.
  3. An empty schedule is accepted as a valid zero-job result.
  4. Saving invoice.jobCreated errors are logged and ignored.
  5. Webhook-level errors are acknowledged with HTTP 200.
  6. Later renewals do not repair incomplete initial job generation.

Cancellation Review

Service subscriptions

Service cancellation normally schedules Stripe cancellation at period end.

  • Jobs after the cancellation cutoff are changed to auto cancelled.
  • Immediate/local cancellation soft-deletes future jobs.
  • Reactivation restores auto cancelled jobs to New.

However, unexpected Stripe cancellation failures are caught and local processing continues.

Files:

  • evergreen_pos_be/src/services/subscripiton.service.js:4284
  • evergreen_pos_be/src/services/subscripiton.service.js:4301

The UI can therefore report a scheduled cancellation even while Stripe continues billing.

Job cleanup failures are also caught and returned as zero updated jobs, so cancellation may report success while future jobs remain active.

Product subscriptions

Product cancellation similarly continues with the local status update even if Stripe cancellation fails.

Files:

  • evergreen_pos_be/src/services/subscripiton.service.js:9031
  • evergreen_pos_be/src/services/subscripiton.service.js:9038

Immediate cancellation changes future delivery-job status to Canceled, but this only helps if delivery jobs were previously created.

Failed-Payment Review

Service subscriptions

The service handler:

  • Resolves multiple possible Stripe subscription ID locations.
  • Marks the contract past_due.
  • Marks a pending local invoice attempt as failed when one exists.
  • Sends customer and administrator notifications.

It does not automatically cancel or suspend already-created future service visits. A past-due customer can retain scheduled jobs until handled operationally.

Product subscriptions

The product failed-payment handler reads only:

const stripeSubscriptionId = invoice.subscription;

File:

  • evergreen_pos_be/src/webhook-handlers/invoicePaymentFailed.js:9

The successful-payment handler already supports newer nested Stripe structures such as parent.subscription_details.subscription, but the failed-payment handler does not.

When Stripe provides only the nested reference, the failed-payment handler exits without:

  • Marking the subscription past_due.
  • Recording a failed transaction.
  • Sending the customer payment-failure email.
  • Sending administrator notifications.

Automatic Tenure Completion

A daily cron runs at 1:15 AM using SUBSCRIPTION_END_TZ or America/New_York.

For service subscriptions ending the following day:

  • Contracts with a Stripe subscription ID are scheduled to cancel at the custom end date.
  • Contracts without a Stripe ID are marked completed locally.

For product subscriptions, the cron requests immediate cancellation.

Risks:

  • Stripe scheduling failure is logged but does not automatically retry through a durable queue.
  • Webhook finalization errors can be acknowledged with HTTP 200.
  • A local completed/cancelled state can become inconsistent with Stripe billing state.

Verification Performed

  • Frontend production build: Passed.
  • Backend subscription/webhook JavaScript syntax: No syntax defect identified.
  • Normal visit-scheduling helper execution: Passed and produced expected business-day dates.
  • Empty visit schedule: Confirmed defect; returned zero dates with hasSchedule: true.
  • Existing backend Jest suite: Not green.
  • Subscription-specific Jest or integration tests: No dedicated test suite exists in the repository.

Real Stripe payments were not executed because safe integration testing requires:

  • Stripe test-mode credentials.
  • The service and product webhook signing secrets.
  • A non-production tenant database.
  • Test customers, plans, variants, quotations, services, and invoices.
  • A test webhook endpoint accessible to Stripe or Stripe CLI forwarding.

Recommended Priority

Priority 1

  1. Return a retryable non-2xx response for recoverable webhook failures.
  2. Acquire or finalize initialJobsScheduled only after validating inputs and successfully creating all expected jobs.
  3. Reject zero-job schedules when the plan expects visits.
  4. Restore product subscription delivery-job creation if delivery jobs are required.

Priority 2

  1. Do not report cancellation success when Stripe cancellation failed unexpectedly.
  2. Support nested Stripe subscription references in product failed-payment handling.
  3. Use Stripe invoice period dates for product renewal scheduling.
  4. Define whether past-due service subscriptions should suspend future jobs.

Priority 3

  1. Add automated webhook tests for duplicate delivery, event ordering, database failures, and retries.
  2. Add end-to-end test fixtures for service and product subscription lifecycles.

Overall Assessment

Subscriptions are not fully verified or fully functional in the current branch.

The highest-priority production risks are:

  1. Webhook failures being acknowledged as successful.
  2. Permanent blocking of service auto-job generation.
  3. Empty schedules being recorded as successful.
  4. Product subscription delivery jobs not being created.
  5. Local cancellation succeeding while Stripe cancellation failed.
  6. Product failed-payment events being silently ignored for nested Stripe invoice structures.
    (1-1/1)