#ironbeard_subscription-save-payment-methods

1 messages ยท Page 1 of 1 (latest)

exotic knotBOT
#

๐Ÿ‘‹ Welcome to your new thread!

โฒ๏ธ We'll be here soon! Typically we respond in a few minutes, but sometimes we might take a bit longer if the server is busy or if you have a particularly tricky question.

โฑ๏ธ We close idle threads, which makes them read-only. Once a thread is closed it won't be reopened, but you can always start a new thread if you have another question.

๐Ÿ”— This thread will always be available, even after it's closed. You can find it again using Discord's search, or you can save this link: https://discord.com/channels/841573134531821608/1450163304113635393

๐Ÿ“ Have more to share? Add more details, code, screenshots, videos, etc. below.

Below are links to other discussions we've had with you in the past week in case you want to review that information. If your question is related to one of these previous discussions, please provide a comprehensive summary of the current state and what you need help with now. We help many users simultaneously, so a summary allows us to resolve your issue as soon as possible.

sly ledge
#

Hello, looking in to this. I can get Bank Transfers to appear in my payment element. Trying to figure out what the difference between your API call and mine was here

elder garden
#

I'm calling

setup_intent = stripe.SetupIntent.create(
  customer=customer.id,
  usage='off_session',
  payment_method_types=['card', 'us_bank_account', 'customer_balance'],
)

which is causing the error

#

and

const elements = stripe.elements({
  clientSecret: document.getElementById("id_setup_intent").value,
  customerSessionClientSecret: document.getElementById("id_customer_session").value,
  currency: 'usd',
  appearance: {...},
})
#

but actually the error happens before the JS is loaded

sly ledge
#

Oh now I see, it is because you are using a Setup Intent. There isn't a way to include customer_balance as a PM type for setup intents as far as I am aware. Because they are push-based there isn't really setup to be done on the customer's side, the transfer info will just be included when needed along with whatever payment intents you create that have customer_balance as a payment method option.

#

So if you change that call to be a payment intent, customer_balance will be accepted and it will show up properly on the payment element for that intent. It is just that bank transfers don't really have anything to save, so they aren't considered valid for setup intents specifically

elder garden
#

Ahh, hmm

#

gotcha

#

That makes sense. In my checkout flow I'm using SetupIntent + PaymentElement > customer.invoice_settings.default_payment_method and then Subscription(payment_behavior='default_incomplete') so that I can have a /checkout/review/ endpoint, and then confirm Subscription.latest_invoice.payment_intent on the backend when they click "Submit Order".

So, this flow doesn't really make sense if I want to let someone pay with a push based PM, eh?

exotic knotBOT
gritty river
#

Hi ๐Ÿ‘‹

I'm stepping in as my colleague needs to go. Can you explain why you would generate a Setup Intent to collect payment methods when you can accomplish all of this using the Subscription itself? It's generally considered an anti-pattern for Stripe integrations unless there is a specific need to separate them.

#

That would also allow you to utilize push-based payment method types

elder garden
#

Sure, I appreciate pointing out the anti-pattern! I really want to streamline things "with the grain" so to speak so that I'm not making extra work for myself. Here's the flow chart I've put together:

Checkout Flow

Main considerations:##

  • Want to display a /checkout/review/ page with Customer, Invoice, and PaymentMethod details before letting the customer submit the order.
  • All products are Subscriptions. There's two types: a newsletter and a SaaS. Newsletter has a single Product and Price, the SaaS has many Products,
    two of which are package deals. Due to billing considerations, newsletter and SaaS must be kept separate, so a Customer might have two Subscriptions.
  • Current impl only supports cards, I'm interested in adding support for ACH credit, bank transfer, and possibly Google/Apple Pay.
#

Flow:##

  • Endpoint: /checkout/signup/
    • GET: Display user form (email, password)
    • POST: Create User instance in db, redirect
  • Endpoint: /checkout/info/
    • GET: Display info form (first name, last name, company name, job title)
    • POST: stripe.Customer.create(...) (API call #1, Python) and in db, redirect
  • Endpoint: /checkout/payment/
    • GET:
      • `stripe.SetupIntent.create(customer=cus_id, usage='off_session') on Stripe (API call #2, Python), pass client secret to frontend.
      • If customer already has a PM, also call:
        stripe.CustomerSession.create(customer=cus_id, components={
          'payment_element': {
            'enabled: True,
            'features': {
              'payment_method_allow_redisplay_filters': ['always', 'unspecified', 'limited'],
              'payment_method_redisplay': 'enabled',
              'payment_method_remove': 'enabled',
            },
          }
        })  
        
        and pass client secret to frontend (another API call, python)
    • Frontend:
      • call stripe.elements(clientSecret=..., customerSessionClientSecret=..., currency='usd', appearance: ...) to initialize PaymentElement
      • onsubmit handler that runs (API call #3, JavaScript)
        const { setupIntent, error } = await stripe.confirmSetup({
          elements,
          redirect: 'if_required',
          confirmParams: {
            expand: ['payment_method'],
            return_url: window.location.origin + "/checkout/review/",
          }
        })
        
      • store PM details in hidden fields and then POST the form.
    • POST:
      • Store PM details in local db.
      • stripe.Customer.modify(cus_id, invoice_settings={'default_payment_method': pm_id}) (API call #4, Python)
#
  • Endpoint: /checkout/review/
    • GET:
      • fetch Customer and PaymentMethod details from db for display
      • create Subscription(s) with payment_behavior='default_incomplete', off_session=True, expand=['latest_invoice.payment_intent'] (API call #5, Python)
      • if PaymentIntents locally?
    • POST:
      • Fetch PaymentIntent(s) locally and call stripe.PaymentIntent.confirm() (API call #6 / 7, Python)
      • Redirect to /checkout/done, or display errors for the PaymentIntent(s).
      • What do we do if there are errors?
#

(sorry for the text dump ๐Ÿ˜ฌ )

gritty river
#

Sorry but most of this detail is irrelevant. Can you specifically respond to my question? Why do you want to save the Payment Method to the Customer in a separate action from the Subscription? Did you know it was possible to do both?

#

Sorry but, to be honest, I'm a bit under the weather and tying all this together is not working for me right now.

elder garden
#

I completely understand, no worries ๐Ÿ™‚ I'll be more direct to your question

#

I want to avoid charging the customer until after they review all the details, which I took to mean that I'd need to create the PaymentMethod before hand (using SetupIntent) and create the Subscription beforehand (using default_incomplete).

gritty river
#

Okay, I get that. Does the type of Payment Method change these details? E.g. are you changing the amount based on PMTs?

elder garden
#

I don't think so. If you mean something along the lines of "do you charge more to use card vs bank transfer", then no.

gritty river
#

Okay, so is there something about your UI that you want to collect payment method data before displaying all the details?

elder garden
#

I under the impression that, particularly for accessibility reasons, having a "confirm / review" page is best practices, so since checkout is a multi-step process (email+pw, contact info, payment method), I was going to collect that info to display before letting the customer click "Submit Order"

gritty river
#

The reason I ask is, it's the canonical Stripe Subscription integration pattern to create a Subscription with payment_settings={'save_default_payment_method': 'on_subscription'}, to automatically save the provided payment method to the Customer, and set is as the default for future Invoice payments, all as part of starting the Subscription.

It's what we document here

#

As for using a multi-step process, I can understand that making sense. In that case it could make sense to collect the Customers payment data prior to the final approval. However, you would still need to set it as the default for the customer after you save the payment method with a Setup Intent.

elder garden
#

Oh! Wait, I remember why I need to use SetupIntents (at least, my original understanding). it's possible for our customer's shopping cart to have multiple disjoint Subscriptions (a newsletter and a SaaS); so I can't pass a single payment intent to PaymentElements.

gritty river
#

AH yes that does make sense.

#

However, may I ask why you are creating multiple Subscriptions? It's possible that our new flexibile billing mode would allow you to create a singe Subscription instead.

elder garden
#

That would be nice. The long and short of it is: when someone upgrades their SaaS subscription, I have to set billing_cycle_anchor: 'now', proration_behavior: 'always_invoice'. Our SaaS is data-based, so those settings are needed to prevent someone from buying a cheap subscription and then upgrading during the last hour of their plan and accessing all the data and cancelling before the plan is over.

Those settings are fine, but then if someone buys both newsletter+SaaS (1 year plan) on Jan 1st 2026 and then upgrades SaaS on June 1st 2026, these settings would ask them to pay in advance for the newsletter from Jan 1st 2027 to June 1st 2027.

gritty river
#

Gotcha, okay. I thought it might have to do with different billing periods (e.g. monthly vs yearly) which is something the flexible billing mode supports.

#

We did fix an issue with multiple ugrades in a single billing cycle being used as a hack to get large prorations for invoices that were never paid

elder garden
#

But either way, if I don't charge them the full amount for the upgraded price at upgrade time, then I think it's a nonstarter.

For example, if Plan A cost $100 for a year and Plan B is an upgrade that cost $1,200 for a year, and customer subscribes on Jan 1st and upgrades on July 1st, is there a way to do "Keep the same billing cycle anchor but make them pay $600 immediately"? ($600 being the cost of the upgrade for the remaining billing cycle)

Edit: actually, that's what I want to avoid, as someone could pay for 1 hour of the big plan right before the term is up then cancel.

gritty river
#

Okay but they actually pay for it?

#

I don't think that's what we were fixing. We fixed an issue where customer Subscribes to Price A($10) -> upgrades to Price B($100) and this generates prorations and does not bill immediately -> downgrades to Price C($5). This would generate prorations that credit the Customer for going from Price B -> Price C, even though the never paid for it

#

billing_mode: "flexible" prevents that

elder garden
#

oh cool

#

but yeah, I'm trying to prevent "pay $100 on Jan 1st, and then on Dec 31st 11pm they upgrade and pay $1,200 / # of hours in a a year, download the full data, then cancel before Jan 1st"

gritty river
#

We actually have a somewhat readable doc about how both modes compare here but I find I need to code simulation scripts using Test Clocks in order to really understand what it means.

#

Ah, yeah. In your case it's the access they are paying for, right?

elder garden
#

yeah, that's a good way to put it

#

but not with the newsletter subscription, that's just 12 issues a year for $x

gritty river
#

Gotcha. Yeah that makes sense

elder garden
#

(honestly, just providing SaaS subscribers with a complementary newsletter subscription would make my life so much easier lol)

#

so, that's why customers can have multiple subscriptions, which means I can't use a single payment intent for PaymentElement.

#

But also, might be why accepting push payments is difficult to have a flow for.

gritty river
#

Yeah, also push payments are their own kind of headache.

#

Since it's up to the Customer to push the funds, they specify the amount.

#

So you have to deal with under/over payment scenarios

elder garden
#

We do currently have one customer that requires a push flow for purposes, but I just create the Subscription on stripe.com and send them the invoice. Was hoping I could make a flow that allowed people to handle that themselves but ๐Ÿคท

#

with 80/20 and all, maybe I just keep doing it manually for cases like them. But our newsletter is sold by a few Subscription Services that handle selling subscriptions to libraries etc, and they often prefer to do things with bank accounts instead of cards

gritty river
#

To be honest, I think the Invoices approach using Bank Transfer payment method makes the most sense. https://docs.stripe.com/payments/bank-transfers/accept-a-payment

But there are still many ways it can go wrong (e.g. someone is multiple billing cycles late and the transfer gets applied to a different invoice than you exepect).

#

But, to get back to the multiple subscriptions, I think you are right. I don't see a way around using multiple Subs given the need to reset the billing cycle anchor for the data access Sub while not re-charging customers for the newsletter Sub

elder garden
#

thanks for the sanity check there!

#

Final question: even if I did find a way to accept a push payment, I would need to do potentially do it twice, once for each paymentintent/subscription, right?

gritty river
#

Once it's set up, the Customer has a dedicated VBAN (Virtual Bank Account Number) within Stripe they can use to push funds. But it's up to the Customer to push the funds each billing cycle.

#

I recommed building some end-to-end tests to make sure you are familiar with the flow though

elder garden
#

yeah, i need to test around a bit. Thanks for your help!

gritty river
#

Sure thing! It's why we're here ๐Ÿ™‚