{"record":{"id":"f2b3edc73a5127fe","repo":"antiwork/gumroad","slug":"upi-recurring-authorization-required-f2b3ed","errorCode":"upi_recurring_authorization_required","errorMessage":"Your saved UPI payment method can no longer be used. Please update your payment method to continue your membership.","messagePattern":"Your saved UPI payment method can no longer be used\\. Please update your payment method to continue your membership\\.","errorType":"exception","errorClass":"ChargeProcessorCardError","httpStatus":null,"severity":"error","filePath":"app/services/purchase/later_charge_presentment_service.rb","lineNumber":234,"sourceCode":"    end\n\n    def fallback(reason, transient: false)\n      @fallback_reason = reason\n      Rails.logger.info(\"Later-charge presentment fallback for #{charge.present? ? \"charge #{charge.external_id}\" : \"purchase #{purchases.first&.id}\"}: #{reason}\")\n      if required_currency.present?\n        notification = transient ? \"Required-currency renewal deferred before processor submit\" : \"Required-currency renewal rejected before processor submit\"\n        ErrorNotifier.notify(\n          notification,\n          reason:,\n          required_currency:,\n          purchase_id: purchases.first&.id,\n          charge_id: charge&.id\n        )\n        if transient\n          raise ChargeProcessorUnavailableError, \"The required-currency quote is temporarily unavailable\"\n        end\n\n        raise ChargeProcessorCardError.new(\n          required_currency_error_code,\n          required_currency_error_message\n        )\n      end\n\n      nil\n    end\nend\n","sourceCodeStart":216,"sourceCodeEnd":243,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/services/purchase/later_charge_presentment_service.rb#L216-L243","documentation":"Raised as ChargeProcessorCardError from Purchase::LaterChargePresentmentService#fallback when a delayed charge (subscription renewal, installment, preorder release, commission completion) that must settle in a required currency (INR for UPI rails by default — required_currency_error_code defaults to UPI_RECURRING_AUTHORIZATION_REQUIRED) cannot use its stored presentment fixing. Non-transient reasons (no_stored_presentment, required_currency_mismatch, unsupported_currency, unsupported_charge_model, settlement_currency_mismatch, stale_fixing, non_positive_total) raise this card error; transient ones (quote_unavailable, wrapped exceptions) raise ChargeProcessorUnavailableError instead. Unlike ordinary cards, a required-currency rail cannot silently fall back to USD, so the buyer must update their payment method.","triggerScenarios":"Performing Purchase::LaterChargePresentmentService with required_currency set (saved UPI method on an INR-priced link) where: the stored later-charge presentment is missing or in a different currency, the merchant account stopped being a supported/USD-settling account, the stored fixing's canonical price no longer matches and cannot be re-fixed (stale_fixing), or the computed presentment total is non-positive.","commonSituations":"INR subscription originally sold via UPI but the presentment record was never created or was for a different currency after price changes; seller's merchant account changed charging model or settlement configuration after sign-up; price change on the link making the stored fixing stale and the re-fix path returning nil; amount/tax combos producing a zero total after conversion.","solutions":["Treat it as terminal for the saved payment method: surface the UPI 'update your payment method' message so the buyer re-authenticates on a working INR rail.","Inspect the ErrorNotifier payload (reason, required_currency, purchase_id, charge_id) — 'stale_fixing' points at a price-change/data issue you can repair (re-create a correct LaterChargePresentment) rather than a buyer action.","For settlement_currency_mismatch / unsupported_charge_model, fix the merchant account's charge model or settlement config so the account is again eligible for buyer-currency presentment before the next renewal wave.","Confirm transient sibling errors (ChargeProcessorUnavailableError 'The required-currency quote is temporarily unavailable') are retried by the billing loop rather than shown to the buyer — only this non-transient class should reach members."],"exampleFix":"# before: treating later-charge presentment failure as a generic retryable error\nresult = Purchase::LaterChargePresentmentService.new(...).perform\nrescue ChargeProcessorError => e\n  retry_later\n# after: split transient vs terminal\nbegin\n  result = Purchase::LaterChargePresentmentService.new(merchant_account:, purchases:, amount_cents:, gumroad_amount_cents:, required_currency: \"inr\").perform\nrescue ChargeProcessorUnavailableError\n  retry_later # quote temporarily unavailable\nrescue ChargeProcessorCardError => e\n  request_payment_method_update(e.error_code) # terminal for the saved UPI method\nend","handlingStrategy":"try-catch","validationCode":"# pre-check the conditions the service validates, before performing it\npresentment = owner.current_later_charge_presentment\nok = presentment.present? &&\n     (required_currency.blank? || presentment.presentment_currency == required_currency) &&\n     StripeChargeProcessor.charge_minor_units_compatible?(presentment.presentment_currency) &&\n     Checkout::BuyerCurrencyEligibility.supported_merchant_account?(merchant_account)\nrequest_payment_method_update unless ok","typeGuard":"def later_charge_presentment_usable?(owner, merchant_account, required_currency)\n  p = owner&.current_later_charge_presentment\n  return false if p.nil?\n  return false if required_currency.present? && p.presentment_currency != required_currency.to_s.downcase\n  Checkout::BuyerCurrencyEligibility.supported_merchant_account?(merchant_account) &&\n    Checkout::BuyerCurrencyEligibility.usd_settling_merchant_account?(merchant_account, presentment_currency: p.presentment_currency)\nend","tryCatchPattern":"begin\n  result = svc.perform\nrescue ChargeProcessorUnavailableError\n  schedule_retry_with_backoff # transient quote issue\nrescue ChargeProcessorCardError => e\n  if e.error_code == PurchaseErrorCode::UPI_RECURRING_AUTHORIZATION_REQUIRED\n    request_payment_method_update # terminal for the saved UPI method\n  else\n    raise\n  end\nend","preventionTips":["Keep required-currency (INR) products on supported, USD-settling merchant accounts so renewals don't lose eligibility.","Re-fix stale presentments when link prices change so canonical_price_cents stays matched.","Alert separately on the fallback reason values (stale_fixing, settlement_currency_mismatch) — they indicate data/config drift, not buyer error."],"tags":["currency","fx","upi","subscriptions","renewal","presentment"],"backgroundTag":"charge-currency-mismatch","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}