antiwork/gumroad · error · ResponseError

responseData.error_message

Error message

responseData.error_message

What it means

createDiscount POSTs checkout_discounts_path; typia.assert narrows the reply, and the { success: false, error_message } branch throws ResponseError carrying the server's error_message. This is the endpoint's validation channel: the discount failed to save and e.message states why in the server's own words.

Source

Thrown at app/javascript/data/offer_code.ts:141

  duration_in_billing_cycles: payload.durationInBillingCycles,
  minimum_amount_cents: payload.minimumAmount,
  once_per_cart: payload.discount.type === "cents" && payload.oncePerCart,
  existing_customers_only: payload.existingCustomersOnly,
  ownership_product_ids: payload.existingCustomersOnly ? payload.ownershipProductIds : [],
  ownership_duration_tiers: payload.ownershipDurationTiers,
});

export const createDiscount = async (payload: DiscountPayload) => {
  const response = await request({
    method: "POST",
    accept: "json",
    url: Routes.checkout_discounts_path(),
    data: buildDiscountPayload(payload),
  });
  const responseData = typia.assert<
    { success: true; offer_codes: OfferCode[]; pagination: PaginationProps } | { success: false; error_message: string }
  >(await response.json());
  if (!responseData.success) throw new ResponseError(responseData.error_message);
  return responseData;
};

export const updateDiscount = async (id: string, payload: DiscountPayload) => {
  const response = await request({
    method: "PUT",
    accept: "json",
    url: Routes.checkout_discount_path(id),
    data: buildDiscountPayload(payload),
  });
  const responseData = typia.assert<
    { success: true; offer_codes: OfferCode[]; pagination: PaginationProps } | { success: false; error_message: string }
  >(await response.json());
  if (!responseData.success) throw new ResponseError(responseData.error_message);
  return responseData;
};

export const deleteDiscount = async (id: string) => {

View on GitHub (pinned to afeacbd394)

Solutions

  1. Show e.message next to the offending form field — the server text names the problem.
  2. Pre-validate client-side: non-empty code, percent 1–100, at least one product selected when not universal.
  3. On a code collision, pick a different code rather than resubmitting.
  4. Verify excludedProductIds and selectedProductIds don't intersect.

Example fix

// before
await createDiscount(payload);

// after — catch cheap mistakes before the POST
if (!payload.code.trim()) throw new Error('Enter a code.');
if (payload.discount.type === 'percent' && (payload.discount.value <= 0 || payload.discount.value > 100)) throw new Error('Percent off must be between 1 and 100.');
if (!payload.universal && payload.selectedProductIds.length === 0) throw new Error('Select at least one product.');
await createDiscount(payload);
Defensive patterns

Strategy: validation

Validate before calling

if (!payload.code.trim()) throw new Error('Enter a code.');
if (payload.discount.type === 'percent' && (payload.discount.value <= 0 || payload.discount.value > 100)) throw new Error('Percent off must be between 1 and 100.');
if (!payload.universal && payload.selectedProductIds.length === 0) throw new Error('Select at least one product.');
if (payload.selectedProductIds.some((id) => payload.excludedProductIds.includes(id))) throw new Error('A product cannot be both selected and excluded.');

Type guard

const isDiscountFailure = (json: unknown): json is { success: false; error_message: string } =>
  typeof json === 'object' && json !== null && (json as { success?: unknown }).success === false && typeof (json as { error_message?: unknown }).error_message === 'string';

Try / catch

try {
  const result = await createDiscount(payload);
} catch (e) {
  assertResponseError(e);
  showFieldError(e.message); // server's error_message names the offending rule
}

Prevention

When it happens

Trigger: Offer code already used by another discount; percent value outside 1–100; a non-universal discount with empty selectedProductIds; excluded products overlapping selected ones; currencyCode missing for a universal discount.

Common situations: Copying an existing code name; creating a universal percent discount without a currency; excluding variants while selecting their parent product.

Related errors


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