pay-rails / pay

Payments for Ruby on Rails apps
https://github.com/pay-rails/pay
MIT License
1.98k stars 316 forks source link

feat: find customer in paddle when one already exists #1043

Closed jebentier closed 3 months ago

jebentier commented 3 months ago

Pull Request

Summary: There are times when creating the Customer object in the Rails, that the customer does not exist in our local system, but already exists in PaddleBilling. This situation can occur for many different reasons. In this case, instead of attempting to create a new Customer in Paddle, we should look up the existing Paddle customer and attach it to the current User.

Description: This change was motivated by the fact I use the same Paddle account for multiple products, which often share customers across them. Due to this situation, the Customer often already exists in Paddle's system before the Rails code of a specific product knows about it.

Testing: I have been running with this patch in my projects for a while now, and they have allowed me to share customers across various products without needing to manually port data across the applications.

Screenshots (if applicable):

Checklist:

Additional Notes:

deanpcmad commented 3 months ago

Good idea 👍

excid3 commented 3 months ago

This won't work as expected because it'll attempt to create duplicate Pay::Customers with the same IDs.

That's not allowed because then we can't process webhooks appropriately since there would be duplicates. This is a limitation of Paddle & LemonSqueezy since they don't allow multiple customers with the same email address like Stripe does.

This code should live in your application if you're backfilling Pay::Customer IDs by email address.

jebentier commented 3 months ago

@excid3 The Pay::Customer model already has uniqueness enforcement on the processor id, so duplicate entries being created still has the same user experience (it fails on a duplicate entry error). With this added, it allows (if user email is being used as a unique key as well) to automatically connect to existing customers in the Paddle system, which is very beneficial.

I don't really see why this needs to exist outside of pay when the duplicate case is still handled the same, just the duplicate entry error is more accurately coming from within the Rails application modelling rather than the API call out to Paddle.

Also I think you might have accidentally added the Lemon Squeezy label to the PR.

excid3 commented 3 months ago

Lemon Squeezy has the same issue, so it'd be nice to come up with a good solution.

Sorry it's a bit of a hard problem to describe. We have this issue with Jumpstart Pro and similar apps where a User may own multiple Accounts and Billing is handled separately for each account. We've gotta use a unique email address for each account to keep billing separate. If we default to the User's email for each, then this will raise an error for the second one as it attempts to create a duplicate Pay::Customer.

I can see this making sense in simpler scenarios but my concern is that in more complex scenarios it might be confusing.

jebentier commented 3 months ago

Oh, I think I'm starting to understand what you're getting at now. I'm happy to work on this with you to evolve the solution to one that works in this simpler scenario where re-using customer accounts is useful, and like you have with Jumpstart Pro where re-using is actually the wrong solutions and want to force multiple billing accounts per product.

A couple options that jump out could be:

  1. Make the behavior configurable within the Pay::Configuration
  2. Update Pay::Customer to not only delegate email to owner, but to wrap the accessor in a way that allows the attached account to specify a unique email address, maybe allowing for something like try(:billing_email) || email
excid3 commented 3 months ago

I just did a huge refactor over the weekend, so sorry I broke this.

Couldn't think of any better solutions, so we may end up implementing this with LemonSqueezy and PaddleBilling since they both have this one customer per email rule. As long as we raise an error for the duplicate Pay::Customer that describes the issue well and how they can address it in their application, I think we'll be fine.

Unfortunately, I think with Jumpstart Pro and other apps like it, we probably have to require a unique billing email for each account.