416 | @foreach ($user->orders as $order)
417 | | {{ $order->ordered_at->toFormattedDateString() }} |
418 | {{ $order->polar_id }} |
419 | {{ $order->amount }} |
420 | {{ $order->tax_amount }} |
421 | {{ $order->refunded_amount }} |
422 | {{ $order->refunded_tax_amount }} |
423 | {{ $order->currency }} |
424 |
425 | @endforeach
426 |
427 | ```
428 |
429 | #### Check order status
430 |
431 | You can check the status of an order by using the `status` attribute:
432 |
433 | ```php
434 | $order->status;
435 | ```
436 |
437 | Or you can use some of the helper methods offers by the `Order` model:
438 |
439 | ```php
440 | $order->paid();
441 | ```
442 |
443 | Aside from that, you can run two other checks: refunded, and partially refunded. If the order is refunded, you can utilize the refunded_at timestamp:
444 |
445 | ```blade
446 | @if ($order->refunded())
447 | Order {{ $order->polar_id }} was refunded on {{ $order->refunded_at->toFormattedDateString() }}
448 | @endif
449 | ```
450 |
451 | You may also see if an order was for a certain product:
452 |
453 | ```php
454 | if ($order->hasProduct('product_id_123')) {
455 | // ...
456 | }
457 | ```
458 |
459 | Furthermore, you can check if a consumer has purchased a specific product:
460 |
461 | ```php
462 | if ($user->hasPurchasedProduct('product_id_123')) {
463 | // ...
464 | }
465 | ```
466 |
467 | ### Subscriptions
468 |
469 | #### Creating Subscriptions
470 |
471 | Starting a subscription is simple. For this, we require our product's variant id. Copy the product id and start a new subscription checkout using your billable model:
472 |
473 | ```php
474 | use Illuminate\Http\Request;
475 |
476 | Route::get('/subscribe', function (Request $request) {
477 | return $request->user()->subscribe('product_id_123');
478 | });
479 | ```
480 |
481 | When a customer completes their checkout, the incoming `SubscriptionCreated` event webhook connects it to your billable model in the database. You may then get the subscription from your billable model:
482 |
483 | ```php
484 | $subscription = $user->subscription();
485 | ```
486 |
487 | #### Checking Subscription Status
488 |
489 | Once a consumer has subscribed to your services, you can use a variety of methods to check on the status of their subscription. The most basic example is to check if a customer has a valid subscription.
490 |
491 | ```php
492 | if ($user->subscribed()) {
493 | // ...
494 | }
495 | ```
496 |
497 | You can utilize this in a variety of locations in your app, such as middleware, rules, and so on, to provide services. To determine whether an individual subscription is valid, you can use the `valid` method:
498 |
499 | ```php
500 | if ($user->subscription()->valid()) {
501 | // ...
502 | }
503 | ```
504 |
505 | This method, like the subscribed method, returns true if your membership is active, on trial, past due, or cancelled during its grace period.
506 |
507 | You may also check if a subscription is for a certain product:
508 |
509 | ```php
510 | if ($user->subscription()->hasProduct('product_id_123')) {
511 | // ...
512 | }
513 | ```
514 |
515 | If you wish to check if a subscription is on a specific product while being valid, you can use:
516 |
517 | ```php
518 | if ($user->subscribedToProduct('product_id_123')) {
519 | // ...
520 | }
521 | ```
522 |
523 | Alternatively, if you use different [subscription types](#multiple-subscriptions), you can pass a type as an additional parameter:
524 |
525 | ```php
526 | if ($user->subscribed('swimming')) {
527 | // ...
528 | }
529 |
530 | if ($user->subscribedToProduct('product_id_123', 'swimming')) {
531 | // ...
532 | }
533 | ```
534 |
535 | #### Cancelled Status
536 |
537 | To see if a user has cancelled their subscription, you can use the cancelled method:
538 |
539 | ```php
540 | if ($user->subscription()->cancelled()) {
541 | // ...
542 | }
543 | ```
544 |
545 | When they are in their grace period, you can utilize the `onGracePeriod` check.
546 |
547 | ```php
548 | if ($user->subscription()->onGracePeriod()) {
549 | // ...
550 | }
551 | ```
552 |
553 | #### Past Due Status
554 |
555 | If a recurring payment fails, the subscription will become past due. This indicates that the subscription is still valid, but your customer's payments will be retried in two weeks.
556 |
557 | ```php
558 | if ($user->subscription()->pastDue()) {
559 | // ...
560 | }
561 | ```
562 |
563 | #### Subscription Scopes
564 |
565 | There are several subscription scopes available for querying subscriptions in specific states:
566 |
567 | ```php
568 | // Get all active subscriptions...
569 | $subscriptions = Subscription::query()->active()->get();
570 |
571 | // Get all of the cancelled subscriptions for a specific user...
572 | $subscriptions = $user->subscriptions()->cancelled()->get();
573 | ```
574 |
575 | Here's all available scopes:
576 |
577 | ```php
578 | Subscription::query()->incomplete();
579 | Subscription::query()->incompleteExpired();
580 | Subscription::query()->onTrial();
581 | Subscription::query()->active();
582 | Subscription::query()->pastDue();
583 | Subscription::query()->unpaid();
584 | Subscription::query()->cancelled();
585 | ```
586 |
587 | #### Changing Plans
588 |
589 | When a consumer is on a monthly plan, they may desire to upgrade to a better plan, alter their payments to an annual plan, or drop to a lower-cost plan. In these cases, you can allow them to swap plans by giving a different product id to the `swap` method:
590 |
591 | ```php
592 | use App\Models\User;
593 |
594 | $user = User::find(1);
595 |
596 | $user->subscription()->swap('product_id_123');
597 | ```
598 |
599 | This will change the customer's subscription plan, however billing will not occur until the next payment cycle. If you want to immediately invoice the customer, you can use the `swapAndInvoice` method instead.
600 |
601 | ```php
602 | $user = User::find(1);
603 |
604 | $user->subscription()->swapAndInvoice('product_id_123');
605 | ```
606 |
607 | #### Multiple Subscriptions
608 |
609 | In certain situations, you may wish to allow your consumer to subscribe to numerous subscription kinds. For example, a gym may provide a swimming and weight lifting subscription. You can let your customers subscribe to one or both.
610 |
611 | To handle the various subscriptions, you can offer a type of subscription as the second argument when creating a new one:
612 |
613 | ```php
614 | $user = User::find(1);
615 |
616 | $checkout = $user->subscribe('product_id_123', 'swimming');
617 | ```
618 |
619 | You can now always refer to this specific subscription type by passing the type argument when getting it:
620 |
621 | ```php
622 | $user = User::find(1);
623 |
624 | // Retrieve the swimming subscription type...
625 | $subscription = $user->subscription('swimming');
626 |
627 | // Swap plans for the gym subscription type...
628 | $user->subscription('gym')->swap('product_id_123');
629 |
630 | // Cancel the swimming subscription...
631 | $user->subscription('swimming')->cancel();
632 | ```
633 |
634 | #### Cancelling Subscriptions
635 |
636 | To cancel a subscription, call the `cancel` method.
637 |
638 | ```php
639 | $user = User::find(1);
640 |
641 | $user->subscription()->cancel();
642 | ```
643 |
644 | This will cause your subscription to be cancelled. If you cancel your subscription in the middle of the cycle, it will enter a grace period, and the ends_at column will be updated. The customer will continue to have access to the services offered for the duration of the period. You may check the grace period by calling the `onGracePeriod` method:
645 |
646 | ```php
647 | if ($user->subscription()->onGracePeriod()) {
648 | // ...
649 | }
650 | ```
651 |
652 | Polar does not offer immediate cancellation. To resume a subscription while it is still in its grace period, use the resume method.
653 |
654 | ```php
655 | $user->subscription()->resume();
656 | ```
657 |
658 | When a cancelled subscription approaches the end of its grace period, it becomes expired and cannot be resumed.
659 |
660 | #### Subscription Trials
661 |
662 | > [!NOTE]
663 | > Coming soon.
664 |
665 | ### Benefits
666 |
667 | Benefits are automated features that are granted to customers when they purchase your products. You can manage benefits using both the `LaravelPolar` facade (for create/update/delete operations) and methods on your billable model (for listing and retrieving benefits).
668 |
669 | #### Creating Benefits
670 |
671 | Create benefits programmatically using the `LaravelPolar` facade:
672 |
673 | ```php
674 | use Danestves\LaravelPolar\LaravelPolar;
675 | use Polar\Models\Components;
676 |
677 | $benefit = LaravelPolar::createBenefit(
678 | new Components\BenefitCustomCreate(
679 | description: 'Premium Support',
680 | organizationId: 'your-org-id',
681 | properties: new Components\BenefitCustomCreateProperties(),
682 | )
683 | );
684 | ```
685 |
686 | #### Listing Benefits
687 |
688 | List all benefits for an organization using your billable model:
689 |
690 | ```php
691 | $benefits = $user->listBenefits('your-org-id');
692 | ```
693 |
694 | #### Getting a Specific Benefit
695 |
696 | Retrieve a specific benefit by ID using your billable model:
697 |
698 | ```php
699 | $benefit = $user->getBenefit('benefit-id-123');
700 | ```
701 |
702 | #### Listing Benefit Grants
703 |
704 | Get all grants for a specific benefit using your billable model:
705 |
706 | ```php
707 | $grants = $user->listBenefitGrants('benefit-id-123');
708 | ```
709 |
710 | #### Updating Benefits
711 |
712 | Update an existing benefit using the `LaravelPolar` facade:
713 |
714 | ```php
715 | use Danestves\LaravelPolar\LaravelPolar;
716 | use Polar\Models\Components;
717 |
718 | $benefit = LaravelPolar::updateBenefit(
719 | 'benefit-id-123',
720 | new Components\BenefitCustomUpdate(
721 | description: 'Updated Premium Support',
722 | properties: new Components\BenefitCustomUpdateProperties(),
723 | )
724 | );
725 | ```
726 |
727 | #### Deleting Benefits
728 |
729 | Delete a benefit using the `LaravelPolar` facade:
730 |
731 | ```php
732 | LaravelPolar::deleteBenefit('benefit-id-123');
733 | ```
734 |
735 | ### Usage-Based Billing
736 |
737 | Track customer usage events for metered billing. This allows you to charge customers based on their actual usage of your service.
738 |
739 | #### Tracking Usage Events
740 |
741 | Track a single usage event for a customer:
742 |
743 | ```php
744 | $user->ingestUsageEvent('api_request', [
745 | 'endpoint' => '/api/v1/data',
746 | 'method' => 'GET',
747 | 'duration_ms' => 145,
748 | ]);
749 | ```
750 |
751 | #### Batch Event Ingestion
752 |
753 | For usage-based billing, you can track multiple events at once:
754 |
755 | ```php
756 | $user->ingestUsageEvents([
757 | [
758 | 'eventName' => 'api_request',
759 | 'properties' => [
760 | 'endpoint' => '/api/v1/data',
761 | 'method' => 'GET',
762 | ],
763 | ],
764 | [
765 | 'eventName' => 'storage_used',
766 | 'properties' => [
767 | 'bytes' => 1048576,
768 | ],
769 | 'timestamp' => time(),
770 | ],
771 | ]);
772 | ```
773 |
774 | #### Listing Customer Meters
775 |
776 | List all meters for a customer:
777 |
778 | ```php
779 | $meters = $user->listCustomerMeters();
780 | ```
781 |
782 | #### Getting a Specific Customer Meter
783 |
784 | Retrieve a specific customer meter by ID using the `LaravelPolar` facade:
785 |
786 | ```php
787 | use Danestves\LaravelPolar\LaravelPolar;
788 |
789 | $meter = LaravelPolar::getCustomerMeter('meter-id-123');
790 | ```
791 |
792 | > [!NOTE]
793 | > Usage events are sent to Polar for processing. They are not stored locally in your database. Use Polar's dashboard or API to view processed usage data.
794 |
795 | ### Handling Webhooks
796 |
797 | Polar can send webhooks to your app, allowing you to react. By default, this package handles the majority of the work for you. If you have properly configured webhooks, it will listen for incoming events and update your database accordingly. We recommend activating all event kinds so you may easily upgrade in the future.
798 |
799 | #### Webhook Events
800 |
801 | The package dispatches the following webhook events:
802 |
803 | **Order Events:**
804 | - `Danestves\LaravelPolar\Events\OrderCreated`
805 | - `Danestves\LaravelPolar\Events\OrderUpdated`
806 |
807 | **Subscription Events:**
808 | - `Danestves\LaravelPolar\Events\SubscriptionCreated`
809 | - `Danestves\LaravelPolar\Events\SubscriptionUpdated`
810 | - `Danestves\LaravelPolar\Events\SubscriptionActive`
811 | - `Danestves\LaravelPolar\Events\SubscriptionCanceled`
812 | - `Danestves\LaravelPolar\Events\SubscriptionRevoked`
813 |
814 | **Benefit Grant Events:**
815 | - `Danestves\LaravelPolar\Events\BenefitGrantCreated`
816 | - `Danestves\LaravelPolar\Events\BenefitGrantUpdated`
817 | - `Danestves\LaravelPolar\Events\BenefitGrantRevoked`
818 |
819 | **Checkout Events:**
820 | - `Danestves\LaravelPolar\Events\CheckoutCreated`
821 | - `Danestves\LaravelPolar\Events\CheckoutUpdated`
822 |
823 | **Customer Events:**
824 | - `Danestves\LaravelPolar\Events\CustomerCreated`
825 | - `Danestves\LaravelPolar\Events\CustomerUpdated`
826 | - `Danestves\LaravelPolar\Events\CustomerDeleted`
827 | - `Danestves\LaravelPolar\Events\CustomerStateChanged`
828 |
829 | **Product Events:**
830 | - `Danestves\LaravelPolar\Events\ProductCreated`
831 | - `Danestves\LaravelPolar\Events\ProductUpdated`
832 |
833 | **Benefit Events:**
834 | - `Danestves\LaravelPolar\Events\BenefitCreated`
835 | - `Danestves\LaravelPolar\Events\BenefitUpdated`
836 |
837 | Each of these events has a `$payload` property containing the webhook payload. Some events also expose convenience properties for direct access to related models:
838 |
839 | **Events with Convenience Properties:**
840 |
841 | | Event | Convenience Properties |
842 | |-------|----------------------|
843 | | `OrderCreated`, `OrderUpdated` | `$billable`, `$order` |
844 | | `SubscriptionCreated`, `SubscriptionUpdated`, `SubscriptionActive`, `SubscriptionCanceled`, `SubscriptionRevoked` | `$billable`, `$subscription` |
845 | | `BenefitGrantCreated`, `BenefitGrantUpdated`, `BenefitGrantRevoked` | `$billable` |
846 |
847 | **Events with Only `$payload`:**
848 |
849 | | Event | Access Pattern |
850 | |-------|----------------|
851 | | `CheckoutCreated`, `CheckoutUpdated` | `$event->payload->checkout` |
852 | | `CustomerCreated`, `CustomerUpdated`, `CustomerDeleted`, `CustomerStateChanged` | `$event->payload->customer` |
853 | | `ProductCreated`, `ProductUpdated` | `$event->payload->product` |
854 | | `BenefitCreated`, `BenefitUpdated` | `$event->payload->benefit` |
855 |
856 | **Example Usage:**
857 |
858 | ```php
859 | // Events with convenience properties
860 | public function handle(OrderCreated $event): void
861 | {
862 | $order = $event->order; // Direct access
863 | $billable = $event->billable; // Direct access
864 | }
865 |
866 | // Events with only payload
867 | public function handle(CheckoutCreated $event): void
868 | {
869 | $checkout = $event->payload->checkout; // Access via payload
870 | }
871 | ```
872 |
873 | If you wish to respond to these events, you must establish listeners for them. You can create separate listener classes for each event type, or use a single listener class with multiple methods.
874 |
875 | #### Using Separate Listener Classes
876 |
877 | Create individual listener classes for each event:
878 |
879 | ```php
880 | payload->checkout;
891 | // Handle checkout creation...
892 | }
893 | }
894 | ```
895 |
896 | ```php
897 | subscription;
908 | // Handle subscription update...
909 | }
910 | }
911 | ```
912 |
913 | #### Using a Single Listener Class
914 |
915 | Alternatively, you can use a single listener class with multiple methods. For this approach, you'll need to register the listener as an event subscriber:
916 |
917 | ```php
918 | payload['type'] === 'subscription.updated') {
935 | // Handle the incoming event...
936 | }
937 | }
938 |
939 | /**
940 | * Handle checkout created events.
941 | */
942 | public function handleCheckoutCreated(CheckoutCreated $event): void
943 | {
944 | $checkout = $event->payload->checkout;
945 | // Handle checkout creation...
946 | }
947 |
948 | /**
949 | * Handle subscription updated events.
950 | */
951 | public function handleSubscriptionUpdated(SubscriptionUpdated $event): void
952 | {
953 | $subscription = $event->subscription;
954 | // Handle subscription update...
955 | }
956 |
957 | /**
958 | * Register the listeners for the subscriber.
959 | */
960 | public function subscribe(Dispatcher $events): void
961 | {
962 | $events->listen(
963 | WebhookHandled::class,
964 | [self::class, 'handleWebhookHandled']
965 | );
966 |
967 | $events->listen(
968 | CheckoutCreated::class,
969 | [self::class, 'handleCheckoutCreated']
970 | );
971 |
972 | $events->listen(
973 | SubscriptionUpdated::class,
974 | [self::class, 'handleSubscriptionUpdated']
975 | );
976 | }
977 | }
978 | ```
979 |
980 | The [Polar documentation](https://docs.polar.sh/integrate/webhooks/events) includes an example payload.
981 |
982 | #### Registering Listeners
983 |
984 | **For separate listener classes**, register them in your `EventServiceProvider`:
985 |
986 | ```php
987 | [
1002 | // Add your listeners here
1003 | ],
1004 | CheckoutCreated::class => [
1005 | HandleCheckoutCreated::class,
1006 | ],
1007 | SubscriptionUpdated::class => [
1008 | HandleSubscriptionUpdated::class,
1009 | ],
1010 | ];
1011 | }
1012 | ```
1013 |
1014 | **For event subscribers**, register the subscriber in your `EventServiceProvider`:
1015 |
1016 | ```php
1017 |