{"record":{"id":"e00c827efb9b9432","repo":"antiwork/gumroad","slug":"net-negative-seller-revenue","errorCode":"net_negative_seller_revenue","errorMessage":"The purchase total is too small for us to process. Please add another item to your order or contact the creator.","messagePattern":"The purchase total is too small for us to process\\. Please add another item to your order or contact the creator\\.","errorType":"exception","errorClass":"ChargeProcessorCardError","httpStatus":null,"severity":"error","filePath":"app/business/payments/charging/implementations/stripe/stripe_intent_charge_routing.rb","lineNumber":82,"sourceCode":"      MINIMUM_DIRECT_CHARGE_SELLER_PROCEEDS_CENTS\n    elsif destination_charge_account?(merchant_account)\n      MINIMUM_DESTINATION_TRANSFER_AMOUNT_CENTS\n    else\n      return\n    end\n\n    seller_amount_cents = amount_cents - amount_for_gumroad_cents\n    return if seller_amount_cents >= minimum_seller_proceeds_cents\n\n    ErrorNotifier.notify(\n      \"Charge rejected before Stripe submit: seller proceeds would be non-positive\",\n      reference:,\n      charge_amount_cents: amount_cents,\n      gumroad_amount_cents: amount_for_gumroad_cents,\n      seller_amount_cents:,\n      currency:\n    )\n    raise ChargeProcessorCardError.new(PurchaseErrorCode::NET_NEGATIVE_SELLER_REVENUE, SELLER_PROCEEDS_ERROR_MESSAGE)\n  end\nend\n","sourceCodeStart":64,"sourceCodeEnd":85,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/business/payments/charging/implementations/stripe/stripe_intent_charge_routing.rb#L64-L85","documentation":"Raised as ChargeProcessorCardError (code net_negative_seller_revenue) by StripeIntentChargeRouting.validate_seller_proceeds! before the PaymentIntent is submitted. For a direct (Stripe Connect) account the application fee must not consume the entire charge (seller proceeds >= 0 subunits); for a destination-charge account the transfer must be at least 1 subunit. If amount_cents - amount_for_gumroad_cents falls below that floor, the charge is rejected locally so Stripe never sees an intent with a zero/negative seller amount.","triggerScenarios":"Calling StripeIntentChargeRouting.fee_params (both client-confirm and server-confirm checkout assemble fees through it) with a tiny amount_cents where the Gumroad fee equals or exceeds the total — e.g. a 30-cent product where the fee is 30+ cents, or a 100%-off-plus-fee combination on a destination-charge seller where the transfer would be 0 subunits.","commonSituations":"Products priced at the minimum (a few cents/currency subunits) with the fee floor exceeding the price; discounts or pay-what-you-want minimums that drop the total below the fee; zero-decimal or low-subunit currencies (JPY) where a 1-unit total is fully consumed by the fee; miscomputed amount_for_gumroad_cents during fee-model changes.","solutions":["Raise the effective charge total above the fee (buyer adds another item, or the creator raises the price / minimum price above the fee floor) — the message already steers buyers there.","If this fires for legitimate prices, audit the fee computation feeding amount_for_gumroad_cents (fee calculation or rounding bug) using the ErrorNotifier payload (charge_amount_cents, gumroad_amount_cents, seller_amount_cents, currency, reference).","For destination-charge sellers, remember the floor is 1 subunit of the intent's currency, not USD — a 1-subunit JPY total still fails because the fee must leave >= 1 subunit for the transfer.","Do not relax the guard: Stripe itself rejects a destination transfer of 0, so bypassing it only moves the failure downstream to a harder Stripe error."],"exampleFix":"# before: submitting the intent and letting the guard raise\nparams = StripeIntentChargeRouting.fee_params(merchant_account:, amount_cents: 30, amount_for_gumroad_cents: 30, currency: \"usd\", reference:)\n# after: validate proceeds first and block at the price layer\nfloor = merchant_account.is_a_stripe_connect_account? ? 0 : 1\nraise PriceTooLow if amount_cents - amount_for_gumroad_cents < floor\nparams = StripeIntentChargeRouting.fee_params(merchant_account:, amount_cents:, amount_for_gumroad_cents:, currency:, reference:)","handlingStrategy":"validation","validationCode":"floor = merchant_account.is_a_stripe_connect_account? ? 0 : 1\nusable = (amount_cents - amount_for_gumroad_cents) >= floor\nraise InvalidCartError, \"total too small\" unless usable","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Enforce a minimum product/tip total at the price layer so fee >= total can never reach checkout.","Unit-test fee_params with the smallest supported amounts per currency.","Remember floors are subunit counts in the intent's currency, not USD amounts."],"tags":["stripe","checkout","fee-calculation","validation","pricing"],"backgroundTag":"charge-below-minimum-amount","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}