antiwork/gumroad · error · ResponseError

${responseData.error}

Error message

${responseData.error}

What it means

createUpsell (app/javascript/data/upsells.ts:25-67) POSTs the upsell payload and parses the body unconditionally with typia.assert; when it decodes to success:false it throws ResponseError(responseData.error) carrying the server's own message. This is the checkout dashboard's application-level validation failure path: Rails rejected the upsell (invalid name/text, product_ids containing products that cannot be offered, bad offer_code, upsell_variants referencing variants that don't belong to the products) and explained why in error.

Source

Thrown at app/javascript/data/upsells.ts:64

      description,
      cross_sell: isCrossSell,
      replace_selected_products: replaceSelectedProducts,
      universal,
      product_id: productId,
      variant_id: variantId ?? undefined,
      offer_code: offerCode ?? undefined,
      product_ids: productIds,
      upsell_variants: upsellVariants.map(({ selectedVariantId, offeredVariantId }) => ({
        selected_variant_id: selectedVariantId,
        offered_variant_id: offeredVariantId,
      })),
      paused,
    },
  });
  const responseData = typia.assert<
    { success: true; upsells: Upsell[]; pagination: PaginationProps } | { success: false; error: string }
  >(await response.json());
  if (!responseData.success) throw new ResponseError(responseData.error);

  return responseData;
};

export const updateUpsell = async (
  id: string,
  {
    name,
    text,
    description,
    isCrossSell,
    replaceSelectedProducts,
    universal,
    productId,
    variantId,
    offerCode,
    productIds,
    upsellVariants,

View on GitHub (pinned to afeacbd394)

Solutions

  1. Read e.message — the server's error string names the exact invalid field or rule
  2. Fix the named field (name/text required, valid product/variant ids, in-range offer code) and resubmit
  3. Re-pick the offer products from a fresh picker load so product_ids are current
  4. Validate required fields client-side before POSTing to avoid the round trip
  5. If the error is stale-state (product gone), reload the upsells page and rebuild the selection

Example fix

// client-side gate mirroring server rules before createUpsell
const payloadValid =
  name.trim() !== "" && text.trim() !== "" && productId !== "" &&
  productIds.every((id) => availableProductIds.has(id));
if (!payloadValid) return showError("Fill in name, text, and valid products first.");
await createUpsell(payload);
Defensive patterns

Strategy: validation

Validate before calling

const validUpsellPayload = (p: UpsellPayload) =>
  p.name.trim() !== "" && p.text.trim() !== "" && p.productId !== "" &&
  p.productIds.every((id) => availableProductIds.has(id));
if (!validUpsellPayload(payload)) return showError("Fill in name, text, and valid products.");

Type guard

import typia from "typia";
const isUpsellEnvelope = typia.is<
  { success: true; upsells: Upsell[]; pagination: PaginationProps } | { success: false; error: string }
>;

Try / catch

try {
  setState(await createUpsell(payload));
} catch (e) {
  assertResponseError(e);
  showError(e.message); // names the exact server-side rule that failed
}

Prevention

When it happens

Trigger: Saving a new upsell in CheckoutDashboard/UpsellsPage.tsx with a blank name or text; selecting offer products that are unpublished, deleted, or belong to another seller; an offer_code amount out of range; upsell_variants pairing variant ids from different products; universal upsell rules violated server-side.

Common situations: Product picker state holding ids for products deleted/unpublished after the picker loaded; percentage/cent amounts typed beyond limits; variant dropdowns stale after the product's variants were edited; concurrent editors changing the same catalog.

Related errors


AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21). Data as JSON: /api/errors/b6cd7c16000b3ac0. Report an issue: GitHub.