> ## Documentation Index
> Fetch the complete documentation index at: https://juo.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Subscription

> Read and mutate subscriptions — items, discounts, schedule, lifecycle.

The subscription service is the core domain API for blocks that show or modify a customer's subscription. It exposes reactive state for the **current** subscription and methods for every mutation a customer-facing block needs.

## Setup

```ts theme={null}
import {
  provideContext,
  createSubscriptionService,
  createApiSubscriptionAdapter,
  createMockSubscriptionAdapter,
  SubscriptionServiceContext,
} from "@juo/blocks";

// Production
provideContext(
  root,
  SubscriptionServiceContext,
  createSubscriptionService(createApiSubscriptionAdapter({ baseUrl: "/api/v1" })),
);

// Development / Storybook
provideContext(
  root,
  SubscriptionServiceContext,
  createSubscriptionService(createMockSubscriptionAdapter()),
);
```

## Shape

```ts theme={null}
type SubscriptionService = {
  // Reactive state
  current: Signal<Subscription | null>;
  hasPendingBilling: Signal<boolean>;

  // Reads
  search(options?: SearchOptions): Promise<Result<PaginatedList<Subscription>>>;
  getById(id: string): Promise<Result<Subscription | null>>;
  getCurrentSubscriptionId(): string | null;
  getDefaultSubscriptionId(): Promise<Result<string | null>>;
  getAvailablePlans(subscriptionId: string): Promise<Result<SellingPlan[]>>;
  getUpsellProducts(params: { subscriptionId: string }): Promise<Result<UpsellProduct[]>>;

  // Items
  addItem(subscriptionId: string, item: PostableSubscriptionItem): Promise<Result>;
  removeItem(subscriptionId: string, itemId: string): Promise<Result>;
  addUpsellProduct(params: {
    subscriptionId: string;
    variantId: string;
    quantity: number;
    oneTime?: boolean;
  }): Promise<Result>;

  // Discounts
  addDiscount(subscriptionId: string, code: string): Promise<Result>;
  removeDiscount(subscriptionId: string, discountId: string): Promise<Result>;

  // Lifecycle
  reschedule(subscriptionId: string, newDate: string): Promise<Result<Subscription>>;
  pause(subscriptionId: string): Promise<Result<Subscription>>;
  resume(subscriptionId: string): Promise<Result<Subscription>>;
  reactivate(subscriptionId: string): Promise<Result<Subscription>>;
  cancel(subscriptionId: string, reason?: string): Promise<Result<Subscription>>;
};
```

A `Subscription` carries items, discounts, status (`active | paused | canceled | expired | failed`), `nextDeliveryDate`, `frequency`, and the owning `customerId`.

## Reactive state

| Signal              | Description                                                                                               |
| ------------------- | --------------------------------------------------------------------------------------------------------- |
| `current`           | The currently selected subscription, or `null` if none is loaded. Updates after mutations.                |
| `hasPendingBilling` | `true` when a billing attempt is in-flight. Use this to guard mutations that would conflict with billing. |

## Example: pause / resume toggle

```ts theme={null}
import {
  defineBlock,
  effect,
  untracked,
  injectContext,
  SubscriptionServiceContext,
} from "@juo/blocks";

defineBlock("PauseToggle", {
  group: "theme",
  schema: { /* ... */ } as const,
  initialValue: () => ({ props: {} }),
  renderer(block) {
    const el = document.createElement("button");
    const subscription = injectContext(el, SubscriptionServiceContext);

    el.addEventListener("click", async () => {
      const current = subscription.current.value;
      if (!current || subscription.hasPendingBilling.value) return;

      const result = current.status === "paused"
        ? await subscription.resume(current.id)
        : await subscription.pause(current.id);

      if (result._tag === "Failure") console.error(result.error);
    });

    effect(() => {
      const current = subscription.current.value;
      const pending = subscription.hasPendingBilling.value;
      untracked(() => {
        el.textContent = current?.status === "paused" ? "Resume" : "Pause";
        el.disabled = pending || !current;
      });
    });

    return el;
  },
});
```

## Example: adding a discount

```ts theme={null}
async function applyCode(id: string, code: string) {
  const result = await subscription.addDiscount(id, code);
  if (result._tag === "Failure") {
    throw new Error("Invalid code");
  }
  // subscription.current updates automatically
}
```
