Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

AffData's avatar

How does Laravel Cashier store payment methods in the db?

I'm a little confused as the documentation describes ways to store multiple payment methods.

$user->addPaymentMethod($paymentMethod);

But Cashier doesn't come with a database collection capable of storing multiple payment methods. Also, the payment method token starts with pm_, but I don't see any traces of a value which starts with pm_ in the database.

All I see the is the pm_type and pm_last_four columns, but no payment method identifiers.

  1. So how does it work exactly?
  2. Doesn't Cashier come with payment method identifiers storing out of the box?
  3. How does plan swapping (swap()) work if there's no pm_ type payment method identifier in the db?
0 likes
9 replies
LaryAI's avatar
Level 58

Laravel Cashier is a package for handling subscription billing services, and it integrates with payment services like Stripe and Paddle. When you use Cashier with Stripe, for example, it doesn't store the actual payment methods (like credit card details) in your application's database due to security and compliance reasons. Instead, it interacts with the Stripe API to handle payment methods.

Here's how it works:

  1. Storing Payment Methods: When you call $user->addPaymentMethod($paymentMethod);, Cashier communicates with the Stripe API and stores the payment method in Stripe's secure environment. The $paymentMethod is a token representing the payment method details, which is generated by Stripe and starts with pm_.

  2. Database Columns: The pm_type and pm_last_four columns in your database are there to store a representation of the payment method, not the actual payment method itself. These columns typically store the type of the payment method (e.g., 'card') and the last four digits of the card number, respectively. This information is used for display purposes only, so you can show users which payment method they have on file without revealing sensitive information.

  3. Payment Method Identifiers: Cashier does not store the full Stripe payment method ID (pm_ token) in your database. Instead, it stores a reference to the customer's default payment method in the card_brand, card_last_four, and card_expiration columns on the billable model (usually the User model). When you interact with Stripe through Cashier, it uses the Stripe customer ID stored in your database to retrieve the customer's payment methods from Stripe.

  4. Plan Swapping: When you call the swap() method to change a user's subscription plan, Cashier communicates with Stripe to update the subscription details. It doesn't need the full payment method ID for this because the subscription is already associated with a customer in Stripe, and that customer has a default payment method set.

Here's a simplified example of how you might swap a plan:

$user = Auth::user(); // Get the authenticated user
$user->subscription('default')->swap('new-plan-id'); // Swap to the new plan

In summary, Laravel Cashier handles payment methods by using the Stripe API to store and manage them securely. It only keeps a reference to the customer and some non-sensitive details about the payment method in your database. All sensitive information is handled and stored by Stripe, and Cashier uses the Stripe customer ID to interact with the customer's payment methods and subscriptions.

martinbean's avatar

@affdata Cashier stores the default payment method for a customer in your users table, since that is what will be used to try and automatically pay invoices for subscription renewals.

AffData's avatar

@martinbean I can see that, but as I described in my question there is no payment method identifier in the teams table (my Billable model). Can you be more precise? How is Cashier using the payment type and last_four digits to automatically pay invoices? After heavy research it now looks to me that these columns are only there to show the current default payment method to the user.

Can I display multiple user's payment methods out of the box or I need to query Stripe for all user's payment methods to do that?

For instance, what if a user has 3 cards and I want to show them on the front-end and allow the user to change the default?

P.s the AI answer covered most of my questions but the ones above remain open for me.

martinbean's avatar

@AffData If you want to allow customers to manage their Stripe payment methods in your application then you need to build that. It’s not something Cashier does out of the box. Cashier isn’t a complete wrapper around Stripe’s API; it’s focused on creating and handling subscriptions.

AffData's avatar

@martinbean What about managing multiple plan tiers? There's no name column in the subscriptions table. How is it advised to go at differentiating user plans in a case where 3 plans exist (e.g. standard, advanced, pro)?

If the type is always default it's impossible to know which plan the user is subscribed to without querying the Stripe API every single time.

Making a unique type per plan makes sense but it also confuses the logic while checking for the user's existing plan before swapping to another plan.

What is the standard approach to get user's current plan name to show it on the frontend, and to use it to limit access to various app features based on the plan?

martinbean's avatar

@AffData That’s what the stripe_price column in your subscriptions table is for.

You’d have a subscription product (i.e. “Website Subscription”) and then multiple prices under that represents your different plans for that product such as monthly, quarterly, yearly, etc.

AffData's avatar

@martinbean yeah I use it currently but it's inconvenient as I can't possibly remember the price values. Is it advised to map the prices to plan names and terms (either on a new collection for plans or a config file)?

Because without mapping it somehow locally it would require getting the price_id for the Billable model from the subscriptions table, then querying Stripe to get the plan name and term (monthly/yearly) and only then show it on the frontend. And while it's a viable approach, it doesn't sound like a good idea to request the Stripe API every time a backend feature requires this information.

martinbean's avatar

@AffData Price IDs in Stripe don’t change so yeah, it’s pretty safe to map Stripe price IDs to some sort of plan entity in your application.

I personally just have an array in my application’s config similar to Spark that defines plans and their data, which makes requests to Stripe’s API unnecessary:

return [
    'plans' => [
        [
            'id' => 'price_monthly',
            'name' => 'Monthly',
            'unit_amount' => 499,
            'recurring_interval' => 'month',
        ],
        [
            'id' => 'price_yearly',
            'name' => 'Yearly',
            'unit_amount' => 4999,
            'recurring_interval' => 'year',
        ],
    ],
];
AffData's avatar

@martinbean alright so I was in the correct direction with my thoughts. Thanks for the info.

1 like

Please or to participate in this conversation.