# 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:

```text
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.

```text
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:

```js
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

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

### Priority 3

9. Add automated webhook tests for duplicate delivery, event ordering, database failures, and retries.
10. 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.
