toeverything/AFFiNE · error · InvalidCheckoutParameters

invalid_checkout_parameters

invalid_checkout_parameters

Error message

Invalid checkout parameters provided.

What it means

Thrown by SubscriptionService.checkout when CheckoutExtraArgs.safeParse(args) fails. CheckoutExtraArgs is a zod union of UserSubscriptionCheckoutArgs, WorkspaceSubscriptionCheckoutArgs, and SelfhostTeamCheckoutArgs, so the extra args must structurally match the plan's expected shape (e.g. team checkout needs workspace args, selfhost team needs license/seat args).

Source

Thrown at packages/backend/server/src/plugins/payment/service.ts:144

    params: z.infer<typeof CheckoutParams>,
    args: z.infer<typeof CheckoutExtraArgs>
  ) {
    const { plan, recurring, variant } = params;

    if (
      env.namespaces.canary &&
      env.prod &&
      args.user &&
      !this.feature.isStaff(args.user.email)
    ) {
      throw new ActionForbidden();
    }

    const manager = this.select(plan);
    const result = CheckoutExtraArgs.safeParse(args);

    if (!result.success) {
      throw new InvalidCheckoutParameters();
    }

    return manager.checkout(
      {
        plan,
        recurring,
        variant: variant ?? null,
      },
      params,
      args
    );
  }

  async cancelSubscription(
    identity: z.infer<typeof SubscriptionIdentity>,
    idempotencyKey?: string
  ): Promise<Subscription> {
    this.assertSubscriptionIdentity(identity);

View on GitHub (pinned to b4c8548c09)

Solutions

  1. Mirror the server's CheckoutExtraArgs union in the client and zod-parse args before calling checkout; use safeParse to surface which union branch failed.
  2. Match args shape to plan: team -> workspace checkout args, pro/ai -> user checkout args, selfhostedteam -> selfhost team args.
  3. Regenerate/refresh client types from the backend schema after upgrading the payment plugin.

Example fix

// before
await svc.checkout(
  { plan: 'team', recurring: 'monthly' },
  { userId: user.id } // wrong union branch for team
);

// after
await svc.checkout(
  { plan: 'team', recurring: 'monthly' },
  { workspaceId: workspace.id } // matches WorkspaceSubscriptionCheckoutArgs
);
Defensive patterns

Strategy: validation

Validate before calling

import { z } from 'zod';
const CheckoutArgs = z.union([
  z.object({ userId: z.string() }),
  z.object({ workspaceId: z.string() }),
  z.object({ licenseId: z.string(), seats: z.number().int().min(1) }), // mirror selfhost args
]);
const parsed = CheckoutArgs.safeParse(args);
if (!parsed.success) throw new Error(parsed.error.issues.join('; '));
await svc.checkout({ plan, recurring, variant }, parsed.data);

Type guard

function matchesPlanArgs(plan: string, args: unknown): boolean {
  if (plan === 'team') return !!args && typeof (args as any).workspaceId === 'string';
  if (plan === 'selfhostedteam') return !!args && 'licenseId' in (args as object);
  if (plan === 'pro' || plan === 'ai') return !!args && 'userId' in (args as object);
  return false;
}

Try / catch

catch (e) { if (gqlErrorCode(e) === 'invalid_checkout_parameters') { const r = CheckoutArgs.safeParse(args); highlightForm(r.error?.issues); return; } throw e; }

Prevention

When it happens

Trigger: Calling checkout with plan 'team' but user-shaped args (userId instead of workspaceId), or plan 'selfhostedteam' without the required selfhost fields; sending extra/misspelled keys so no union member matches; passing args as a JSON string instead of an object.

Common situations: One generic checkout form reused for all plans; frontend args schema drifted from the server's zod definitions after a backend update; missing idempotency/variant fields newly required.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/a5ae4d6a7e400be8. Report an issue: GitHub.