The Craft Commerce config allows you to set updatebillingdetailsurl. The docs say,

The URL for a user to resolve billing issues with their subscription.

There’s an example of this in the example templates that come with Commerce and this uses getBillingIssueResolveFormHtml().

However, rather than resolve an issue, what I’m looking for is a way for customers with a subscription (via Stripe) to be able to update their billing information before there is a billing issue. Often customers realise their card has expired or want to change their card before the subscription renews, especially as we send emails to notify them about renewals. What’s the best way to create that form, or allow someone to do that?

1 Answer 1


I've solved this in the past by just showing the payment sources, and allowing the user to delete existing payment sources:

{% set paymentSources = craft.commerce.paymentSources.getAllPaymentSourcesByUserId(currentUser.id) %}

{% if paymentSources %}
                <th scope="col">
                    Card details
                <th scope="col">
        {% for paymentSource in paymentSources %}
                <td>{{ paymentSource.description }}</td>
                    <form method="post">
                        {{ csrfInput() }}
                        {{ actionInput('commerce/payment-sources/delete') }}
                        {{ redirectInput(entry.uri) }}
                        <input type="hidden" name="id" value="{{ paymentSource.id }}"/>
                        <button type="submit">Delete</button>
        {% endfor %}
{% endif %}

You can then show a form to add a new payment source:

{% set customer = craft.commerce.customers.getCustomer() %}
    Note: this next line fetches a single subscription - if you support multiple 
    subscriptions, you'll need to do it a little differently 
{% set currentSubscription = customer.getSubscriptions()|first %}
{% set plan = craft.commerce.plans.getPlanById(currentSubscription.planId) %}
{% set gateway = plan.getGateway() %}

<form method="post" accept-charset="UTF-8" autocomplete="off" novalidate>
    {{ csrfInput() }}
    {{ actionInput('commerce/payment-sources/add') }}
    {{ redirectInput(entry.uri) }}
    <input type="hidden" name="gatewayId" value="{{ gateway.id }}">

    {{ gateway.getPaymentFormHtml({})|raw }}

    <button type="submit">Add card</button>      

This is probably the simplest answer to your question, though you can get creative with how you present the payment sources and the add card form to the user.

  • Great answer, thank you Martin! Much appreciated.
    – James
    Commented Nov 5, 2020 at 14:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.