laravel / cashier-paddle

Cashier Paddle provides an expressive, fluent interface to Paddle's subscription billing services.

Home Page:https://laravel.com/docs/cashier-paddle

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

createAsCustomerIfNotExists

haszzam opened this issue · comments

Hi, I don't know if this feature would make sense in production apps or not, But I ran through the following issue while developing and I believe others have too.

Problem:

While developing we usually create test subscriptions and each subscription creates a customer inside Paddle dashboard.

But when for example you delete the laravel database user or run php artisan migrate:fresh and delete all the users the customers still remains inside Paddle dashboard and you cannot delete it manually, You can archive it but you cannot delete.

And when you try to run createAsCustomer() on a user with an email that already exists as a Paddle customer you get an exeption like so

image

I ran into this issue while developing, I don't think it's likely anyone is gonna have this issue in a production environment since you tepically would not delete the user and try to sign it up again and make it as customer, but this is very common in development.

Solution:

I created a new method createAsCustomerIfNotExists() which performs the same actions as createAsCustomer() but first it checks to see if the user's email address already exists as a customer inside Paddle and if so it will user the existing customer details instead of attempting to create a new one which will fail and through the above exception.

I added the following method to the Billable model, in my case the User model.

public function createAsCustomerIfNotExists(array $options = [])
{
    // If the customer already exists in the database just return the customer
    if ($this->customer) {
        return $this->customer;
    }

    // In the Paddle Docs you should be able to pass a comma separated list for customer status
    // But for some reason this doesn't work.
    // So I had to make two separate requests to check for both 'active' and 'archived' status
    // I tried to contact Paddle support but I didn't get a response, Maybe you will have more luck!

    // Check if Paddle customer already exist with an 'active' status
    $paddleCustomer = Cashier::api('GET', 'customers', [
        'status' => 'active',
        'search' => $this->email
    ])['data'][0] ?? null;
    
    // If customer does not exists:
    // Check if Paddle customer already exist with an 'archived' status
    if (!$paddleCustomer) {
        $paddleCustomer = Cashier::api('GET', 'customers', [
            'status' => 'archived',
            'search' => $this->email
        ])['data'][0] ?? null;
    }

    // Use the existing customer information if it exists
    // else
    // Create a new Paddle customer
    if ($paddleCustomer) {
        $customer = $this->customer()->make();

        $customer->paddle_id = $paddleCustomer['id'];
        $customer->name = $paddleCustomer['name'];
        $customer->email = $paddleCustomer['email'];
        $customer->trial_ends_at = $options['trial_ends_at'] ?? null;
        $customer->save();
        $this->refresh();
    } else {
        $customer = $this->createAsCustomer($options);
    }

    return $customer;
}

This was just a quick fix I created, and as I mentioned in the code comments, I had to send two separate requests to Paddle to check if the customer exists, One request for each status active and archived.
Idealy it should only be one request by passing the status as a comma separated list like so 'status' => 'active,archived' as stated in the Paddle Docs, But this doesn't work for now and I tried contacting them but I got no response, Maybe you can reach them.

Thanks!

Thanks @haszzam. Would maybe not filtering on status be the solution here to fetch all customers?

Hi @driesvints

I checked the other discussion and it's the same issue I'm having here.

In your reply here #218 (comment)
You're assuming the customer already exists in the database, and if it does, then ya the createAsCustomer() checks to see if the billable model has a customer attached to it in the daatabse and if so it simply returns this customer instance.

The issue arise when you delete the customer from the database and leave the billable and then try to run createAsCustomer() again on the same billable instance.

The best way to understand this issue is to reproduce it locally on your end with the following steps:

  1. Install a fresh laravel app
  2. Install and configure Cashier (Paddle)
  3. Run the migration
  4. Add the following code to DatabaseSeeder.php file
public function run(): void
{
    $user = \App\Models\User::factory()->create([
        'name'              =>  'John Doe',
        'email'             =>  'johndoe@example.com',
        'password'          =>  bcrypt('password'),
        'email_verified_at' =>  now(),
    ]);

    $user->createAsCustomer();
}
  1. Run php artisan db:seed to create the demo user above
  2. Run php artisan migrate:fresh to clear the database
  3. Run php artisan db:seed to re-create the user again

The last command should fail, because the a customer with this email johndoe@example.com already exists inside paddle.

So the whole point of this method createAsCustomerIfNotExists() is to prevent this failure by checking first if a customer with the same email exists inside paddle before attempting to create a new one.

Again, It's unlikely anyone gonna have this issue in a production environment, It's mostly gonna happen in development where you clear the database for whatever reason.

So This method would only make development a little easier, I don't know if it would make sense to add it to the library but I don't think it would hurt anyway to have available as an option.

Thanks @haszzam. Paddle has fixed the filter issue. I've sent in a fix for this now here: #220

Please note that this still will only re-add the customer record. Any previous subscription and transaction records aren't re-added.

Hi @driesvints.
Ya I understand it will only create an empty customer instance.

Thanks!

Hey @haszzam , I just recently deleted a users from my prod db directly ( my account ) and i was getting this error due to the same as you messaged here. What can i do delete customer from Paddle ??

Hi @bluzeey

As far as I know you cannot delete a customer from Paddle, You can only archive it but it would still exist in Paddle and you cannot use the same email to create a new user.

But the problem you're having was solved after I posted this issue, Now Cashier checks if the customer exists first before attempting to create a new one, and if it exists it uses the existing customer and just link it to the user in your database.

If you're using an older version of Paddle Cashier, try updating the composer package and you shouldn't get this error again.