lucasdotvin/laravel-soulbscription

Could this package be used together with laravel/cashier-paddle?

stefanzweifel opened this issue · 3 comments

This isn't an issue, but more a question/discussion (discussions tab is disabled):

I've stumbled upon this great package while looking for a "usage-engine" to replace my home-grown one in a side project. I love how flexible soulbscription is.

  • quote features
  • tickets
  • different periodicity_type

My problem: The project already uses laravel/cashier-paddle to handle billing and subscriptions.

cashier-paddle already exposes a subscription-method on the Model, that would use HasSubscriptions. So I run into a FatalError when adding the trait to the User-model for example.

The table name clash with my existing app, but that can be solved by overrding the $table-property and using my own version of the Subscription-model.

So long story short:

  • Do you have any guidance on how the package could be used together with laravel/cashier-paddle/laravel/cashier?
  • Is there a way to "just" use the feature consumption part of the package?

Haven't invested much time yet, so maybe this can all be solved in "userland". But maybe someone here already has the perfect solution for this.

Thanks again for publishing this package and sharing how a usage-engine can be built. ❤️

Quick update: I got the package to work with laravel/cashier-paddle by overriding some methods, renaming a table and bringing my own model.
A quick dump of all the changes made.

Change the name of the to be migrated table.

- Schema::create('subscriptions', function (Blueprint $table) {
+ Schema::create('soulbscription_subscriptions', function (Blueprint $table) {

Create a SoulbscriptionSubscription-model that extends the default Soulbscription Subscription-model and update the used table name.

namespace App\Models;

use Illuminate\Datbase\Eloquent\Model;
use LucasDotVin\Soulbscription\Models\Subscription;

class SoulbscriptionSubscription extends Subscription
{
    protected $table = 'soulbscription_subscriptions';
}

Use the new model in config/soulbscription.php.

use App\Models\SoulbscriptionSubscription;

return [
    'models' => [
        'subscription' => SoulbscriptionSubscription::class,
    ],
];

The tricky part was adding HasSubscriptions to my User/Billable model.

Through Trait Conflict Resolution I've renamed the subscription-method from the HasSubscriptions-trait to something different.
I've then overriden the subscribeTo, consumeNotQuotaFeature and loadSubscriptionFeatures-method to use soulbscriptionSubscription instead of just subscription.

 class User extends Authenticatable implements MustVerifyEmail
 {
     use Billable;
+    use HasSubscriptions {
+        Billable::subscription insteadof HasSubscriptions;
+        HasSubscriptions::subscription as soulbscriptionSubscription;
+    }
 
     protected function consumeNotQuotaFeature(Feature $feature, ?float  $consumption = null)
     {
         $consumptionExpiration = $feature->consumable
+            ? $feature->calculateNextRecurrenceEnd($this->soulbscriptionSubscription->started_at)
             : null;
 
         $featureConsumption = $this->featureConsumptions()
             ->make([
                 'consumption' => $consumption,
                 'expired_at' => $consumptionExpiration,
             ])
             ->feature()
             ->associate($feature);
 
         $featureConsumption->save();
 
         return $featureConsumption;
     }
 
     public function subscribeTo(Plan $plan, $expiration = null, $startDate = null)
     {
         $expiration = $expiration ?? $plan->calculateNextRecurrenceEnd($startDate);
 
         $graceDaysEnd = $plan->hasGraceDays
             ? $plan->calculateGraceDaysEnd($expiration)
             : null;
 
+        return $this->soulbscriptionSubscription()
             ->make([
                 'expired_at' => $expiration,
                 'grace_days_ended_at' => $graceDaysEnd,
             ])
             ->plan()
             ->associate($plan)
             ->start($startDate);
     }
 
     protected function loadSubscriptionFeatures(): Collection
     {
         if (! is_null($this->loadedSubscriptionFeatures)) {
             return $this->loadedSubscriptionFeatures;
         }
 
+        $this->loadMissing('soulbscriptionSubscription.plan.features');
 
+        return $this->loadedSubscriptionFeatures = $this->soulbscriptionSubscription->plan->features ?? Collection::empty();
     } 

Overriding package methods is obviously not the best solution.
Maybe the package could expose a getSubscriptionsRelationshipName-method to allow package consumers to change the used relationship name without overriding core methods.

I might give this a try in a fork and propose a PR if this is something you would be interested in. Will also do some research how other package authors solve this problem.

Hey! Hello, @stefanzweifel! Thanks for the comments! I'm flattered you're enjoying using this package. 😅 You've made very good points here, perhaps Soulbscription needs some updates to be more modularized (so you could use only the feature consumption handling, for instance) and to be compatible with already existing solutions from the Laravel ecosystem. I'm gonna start looking for a viable way to do it, but every contribution is totally welcome! Once more, thanks, man! 🚀

Hey @lucasdotvin,

Thanks so much for this fantastic package! 🫶

I'm using Laravel Spark and encountering the same issues that @stefanzweifel mentioned. Is there any workaround for this? Overriding package methods isn't an option for me, unfortunately. 😅

Best,
Alex