archtechx / tenancy

Automatic multi-tenancy for Laravel. No code changes needed.
https://tenancyforlaravel.com
MIT License
3.57k stars 423 forks source link

Cashier Integration (or even just help with relationships would do the trick) #286

Closed cameroncollector closed 4 years ago

cameroncollector commented 4 years ago

I may be overlooking something in documentation or have just spun my wheels to the point where I've mentally blocked out the solution but has anyone integrated this with cashier? I originally wanted to set the billable attribute up on a customer table (in the central app) then do a check once tenancy kicks in to verify subscription, bounce to a landing/payment page if not active. I added the custom column to the Tenants table for customer_id then tried to reference it via a relationship once I proceed into tenancy yet I'm getting a null on the relationship. I know the relationships are working fine as I can load the relation via web routes just fine. When I kicked the Billable attribute over to the Tenant model I got the following error going through the basic setting up of a Stripe customer and adding a paymentMethod

`Stripe\Exception\UnexpectedValueException {#1059 ▼

message: "Could not determine which URL to request: Stripe\PaymentMethod instance has invalid ID: "

code: 0

file: "/Applications/MAMP/htdocs/phonesaas/vendor/stripe/stripe-php/lib/ApiResource.php"

line: 99

trace: {▶} }`

Thanks in advance for any input!

stancl commented 4 years ago

Hi,

How does your Tenant model look?

cameroncollector commented 4 years ago

Hi,

How does your Tenant model look?

Originally I didn't create a separate Tenant model instead simply used the default set up, then I created the following recently in hopes it'd do the trick to no avail

`

///////Tenant.php file

namespace App;

use Illuminate\Database\Eloquent\Model; use Laravel\Cashier\Billable; class Tenant extends Model { // use Billable;

 public function domain(){
    return $this->hasOne(Domain::class);
}

 public function customer(){
    return $this->belongsTo(Customer::class);
}

}`

stancl commented 4 years ago

I have no experience with Cashier and that error message is not really helpful, so I'm not sure how I can help. Maybe sharing the full stack trace, giving more info (when does this happen, what are you doing, how are you doing it) would help.

cameroncollector commented 4 years ago

As it stands I'd like to associate one of the central app's customers with a tenant, then do a simple check via the $model->subscribed() built into cashier. However I can't retrieve the relationship between Tenant and Customer models once I'm in Tenant routes.

Below I attached an image of my routes/tenant.php file while testing both ->customer and ->customer() to try and reference the relationship (by extension once those work I can chain the Cashier functions).

Screen Shot 2020-02-04 at 1 55 24 PM
stancl commented 4 years ago

retrieve the relationship between Tenant and Customer models once I'm in Tenant routes.

You can use the Stancl\Tenancy\StorageDrivers\Database\CentralConnection trait on your models to force them to use the central DB connection even in the tenant app.

hackerESQ commented 4 years ago

Apologies for posting in a closed issue. This has come up for me though, and I think I may have found a solution to using Cashier on the central DB tenants column.

Cashier allows you to set the model that will be the "billable" model. That is usually the User model. However, since the User model should be in the tenant db (in the above described scenario), and Cashier allows you the change the model used, the billable model could theoretically be set to the Tenant model.

I've already modified the DB migration for Cashier to add the customer columns to the tenant migration. But the question is, is there some clean way to update the Tenant model to include the billable trait? Like this:

use Laravel\Cashier\Billable;

class Tenant implements ArrayAccess
{
    use Billable;
}

ETA -- would something like this work? Add a model called Tenant into the laravel App:

namespace App;

use Laravel\Cashier\Billable;

class Tenant extends \Stancl\Tenancy\Tenant
{
    use Billable;
    //
}
stancl commented 4 years ago

Yes, you need to create a custom model and no, the latter would not work, because Stancl\Tenancy\Tenant is not a model in v2.

hackerESQ commented 4 years ago

Makes complete sense. Any tips on how to create a custom model? I tried this:

namespace App;

use Laravel\Cashier\Billable;
use Illuminate\Database\Eloquent\Model;
use Stancl\Tenancy\Tenant;

class Customer extends Model
{

    protected $table = 'tenants';

    use Billable;
    use Tenant;
    //
}

and got this error message.

Symfony\Component\ErrorHandler\Error\FatalError
App\Customer cannot use Stancl\Tenancy\Tenant - it is not a trait
stancl commented 4 years ago

No need to use Tenant. As long as you're using the model only for subscription management, it should work.

Anyway, v3 will have a dedicated Tenant model for this.

hackerESQ commented 4 years ago

Yep, I have added a custom data column to the Tenant database to track customer_id's from this Customer model. Any suggestions on how to get data from the customer table from within a tenant? I imagine I'll have to switch database connections, is there some way I can do that from the Customer model?

A dedicated Tenant model sounds good. Any idea how far out v3 is?

stancl commented 4 years ago

A few weeks

stancl commented 4 years ago

And no clue how to do the customer_id stuff, haven't used Cashier yet.

hackerESQ commented 4 years ago

Awesome. Looking forward to v3. Should make it easier to integrate with Cashier.

hackerESQ commented 4 years ago

Figured I'd share for anyone that stumbles on this thread (also may help @stancl with putting a dedicated Tenant model together). Here's my Eloquent Tenant model that allows me to use Cashier:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Laravel\Cashier\Billable;
use Illuminate\Database\Eloquent\Builder;
use Stancl\Tenancy\StorageDrivers\Database\CentralConnection;

class Tenant extends Model
{ 
    use Billable;
    use CentralConnection;

    /**
     * The "booted" method of the model.
     *
     * @return void
     */
    protected static function booted()
    {
        static::addGlobalScope('id', function (Builder $builder) {
            $builder->where('id', tenant()->id);
        });
    }

    /**
     * Returns us the Eloquent model of the current tenant.
     *
     * @return Tenant $tenant
     */
    public static function current() {
        return static::first();
    }

}

The table underlying this Model is the tenant table created by the stancl/tenancy package. This model definition uses a global scope to limit only tenants with the matching tenant_id. The current() method returns the first match (there should only be one). Importantly, this Model will always use the central DB connection since it uses the stancl\tenancy CentralConnection class.

Hopefully this helps anyone struggling with the Tenant model.

techguydev commented 4 years ago

If someone is coming to this thread looking how to make it work for V3 you just need to add

    use \Illuminate\Auth\Authenticatable;

To the Tenant Model.

I hope this helps you.

stancl commented 4 years ago

Why do you need to make them authenticable? Doesn't Cashier simply require the billable trait?

techguydev commented 4 years ago

@stancl Previous to add

use \Illuminate\Auth\Authenticatable;

I was getting this error

BadMethodCallException with message 'Call to undefined method App/Tenant::getAuthIdentifier()'
stancl commented 4 years ago

I don't think billable models need to be authenticable. This seems hacky. Exception disappearing doesn't mean this is the right solution.

techguydev commented 4 years ago

You are right, how did you make it work with cashier?

hackerESQ commented 4 years ago

@techguydev with version ^2.3.X you should define your own User/Subscriber/Customer/Tenant model and add the billable trait to that. See example above.

techguydev commented 4 years ago

@hackerESQ I am using V3.