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

cristian9509's avatar

Stripe billing. Design so it would be easy to change billing provider.

First of all I don't think I will put stripe's required fields in the users table. I would make another table 'stripe_billing' with foreign key 'user_id' pointing to the users table. I would also code to an interface so that I can easily swap the billing provider. If I would have to change to PayPal I would just create another table 'paypal_billing' with the paypal's required fields, then I would create the PayPalRepository that inplements the billing interface and I shoud be good to go. What else would I need to do? What do I need to careful about so that I won't have issues if/when changing the provider?

0 likes
6 replies
MikeHopley's avatar

Personally I wouldn't represent the providers with their own table in the database. Why do you want to model Stripe and Paypal (or whatever) implementation details in your DB? Isn't your DB for modelling your application?

Yes, you do need a table to record which payment method was used. But doesn't the Stripe-specific logic belong in your StripeBilling class (and any other Stripe classes, like StripeApi)?

Ultimately, your application should care whether a payment was successful -- not whether it came from Stripe or Paypal. Yes, you need to keep track of that too, but it shouldn't intrude into your core business logic.

I like to "translate" vendor-specific information -- such as a Stripe subscription webhook -- into a "normalised" format for consumption by my application. And I like to do this as soon as possible.

cristian9509's avatar

@Mike Hopley Hey Mike, thanks for your input. I have to admit that you are 100% right but I don't really know how to implement what you are saying.

First, I want to use Laravel Cashier and that basically creates a migration with fields that are hardwired to Stripe. Can I change how Cashier interacts with the DB table or I would need a different implementation for Stripe? Can I create a BillingInterface that will be (currently) linked to Cahier?

Second, how can I know that my future billing provider will be able to deal with the fields that I currently have in my DB. Should I just add more depending on the need?

Third, how can I design the table in which I record which payment method was used?

MikeHopley's avatar

First, I want to use Laravel Cashier and that basically creates a migration with fields that are hardwired to Stripe.

Oh that's interesting, I didn't know that (having not used Cashier)! I think that's just more evidence that Cashier is not suitable for your requirements.

I can tell you what I've done. I am not saying it's the best solution, and please don't mistake me for an expert -- this is just what made sense to me for my own website!

Context: I wanted to use Stripe, but knew I would also want to add "new" Paypal (REST API) as a payment option later on, and I also had legacy subscriptions to maintain from Worldpay and "old" Paypal (website payments standard). At the moment I have new subscriptions working in Stripe, and old subscriptions being maintained fine -- but I haven't yet implemented the new Paypal option.

As for my solution, I've written about it elsewhere on these forums. Have a read of that and let me know what you think.

As you're starting from scratch, you have a great opportunity to design a sane database structure. :-) The nastiest part for me was migrating all the old customer data from my original, horrendous DB schema.

cristian9509's avatar

@Mike Hopley I got a great idea from your other post. I don't understand all your tables there but I guess if I put it on a schema model I might start to get the idea.

Can you explain me one thing? Why do you use the tables cards and vendor_cards?

MikeHopley's avatar

Can you explain me one thing? Why do you use the tables cards and vendor_cards?

My cards table contains data about the card: the last four digits, the expiry, and the type (e.g. Visa). I use this to display information to the user. I consider this part of my application.

My vendor_cards table only contains the vendor's card reference, like card_2lkasdf73kjsdfhlkasdrh. This is not part of my application really, it's just a hook for communicating with Stripe.

I've chosen to separate these things, but you don't have to. You could store the reference on the main cards table if you prefer. The same goes for things like subscriptions and vendor_subscriptions.

My way is slightly more "pure", because the database schema is more normalised, and the separation between application logic and vendor-specific details is cleaner. The downside is that my way involves extra tables / models / relationships, so it's more complex.

You could definitely make a good argument that the simpler option is better. It's up to you. :-)

martinbean's avatar

@cristian9509 If you want it to be easy to change provider then you’ll need to create an interface and code to that. Then create a concrete implementation for Stripe. In order to “change provider”, all you need to do is create a new concrete implementation.

In terms of the interface methods, thing of the actions you want a billing provider to implement. I’m just spit-balling here, but maybe something like:

interface SubscriptionProvider
{
    public function bill(Subscriber $subscriber, $amount);
    public function cancel(Subscriber $subscriber);
}

The Subscriber type-hint could be an interface you apply to your user model to mandate subscription-specific methods on the model (like getRenewalDate()).

The important thing is to not think of a particular provider when naming methods. Once you have your interface, you can begin writing your providers. These are just concrete classes that implement the interfaces above. So, a fictional one may look like this:

class FooProvider implements SubscriptionProvider
{
    protected $client;

    public function __construct(FooClientLibrary $client)
    {
        $this->client = $client;
    }

    public function bill(Subscriber $subscriber, $amount)
    {
        return $this->client->chargeAmount($subscriber->getIdentifier(), $amount);
    }

    public function cancel(Subscriber $subscriber)
    {
        return $this->client->cancelSubscription($subscriber->getSubscription());
    }
}

As you can see, the concrete class is where you put provider-specific stuff, using that provider’s client library or SDK. Hope this points you in the right direction.

Please or to participate in this conversation.