{"id":28711168,"url":"https://github.com/leafsphp/billing","last_synced_at":"2026-02-27T07:03:39.791Z","repository":{"id":273792976,"uuid":"909976326","full_name":"leafsphp/billing","owner":"leafsphp","description":"💵 Billing adapter for Leaf (Stripe, Paystack, LemonSqueezy)","archived":false,"fork":false,"pushed_at":"2025-05-12T14:11:56.000Z","size":143,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-09T15:08:20.347Z","etag":null,"topics":["billing","paystack","saas","stripe"],"latest_commit_sha":null,"homepage":"https://leafphp.dev/docs/utils/billing.html","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/leafsphp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"open_collective":"leaf","github":"leafsphp"}},"created_at":"2024-12-30T07:35:07.000Z","updated_at":"2025-05-12T14:12:00.000Z","dependencies_parsed_at":"2025-01-23T01:34:50.304Z","dependency_job_id":"155d22be-07ed-409e-92d1-b6a461f98c2f","html_url":"https://github.com/leafsphp/billing","commit_stats":null,"previous_names":["leafsphp/billing"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/leafsphp/billing","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leafsphp%2Fbilling","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leafsphp%2Fbilling/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leafsphp%2Fbilling/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leafsphp%2Fbilling/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/leafsphp","download_url":"https://codeload.github.com/leafsphp/billing/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leafsphp%2Fbilling/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274462532,"owners_count":25290109,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-10T02:00:12.551Z","response_time":83,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["billing","paystack","saas","stripe"],"created_at":"2025-06-14T21:39:42.870Z","updated_at":"2026-02-27T07:03:39.763Z","avatar_url":"https://github.com/leafsphp.png","language":"PHP","readme":"\u003c!-- markdownlint-disable no-inline-html --\u003e\n\u003cp align=\"center\"\u003e\n  \u003cbr\u003e\u003cbr\u003e\n  \u003cimg src=\"https://leafphp.dev/logo-circle.png\" height=\"100\"/\u003e\n  \u003cbr\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eLeaf Billing (Beta)\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://packagist.org/packages/leafs/billing\"\n    \u003e\u003cimg\n      src=\"https://poser.pugx.org/leafs/billing/v/stable\"\n      alt=\"Latest Stable Version\"\n  /\u003e\u003c/a\u003e\n  \u003ca href=\"https://packagist.org/packages/leafs/billing\"\n    \u003e\u003cimg\n      src=\"https://poser.pugx.org/leafs/billing/downloads\"\n      alt=\"Total Downloads\"\n  /\u003e\u003c/a\u003e\n  \u003ca href=\"https://packagist.org/packages/leafs/billing\"\n    \u003e\u003cimg\n      src=\"https://poser.pugx.org/leafs/billing/license\"\n      alt=\"License\"\n  /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\u003cbr /\u003e\n\u003cbr /\u003e\n\nLeaf's billing system helps makers move faster by handling payments and subscriptions out of the box. With built-in Stripe support—and more providers like Paystack coming soon—you can set up one-time payments or recurring subscriptions in just a few minutes. That means less time worrying about billing and more time building.\n\n## Setting up\n\nTo get started, create a Stripe account and grab your API keys. Then, drop them into your `.env` file:\n\n```env [Stripe]\nBILLING_PROVIDER=stripe\nSTRIPE_API_KEY=sk_test_XXXX\nSTRIPE_PUBLISHABLE_KEY=pk_test_XXXX\nSTRIPE_WEBHOOK_SECRET=whsec_XXXX # only if you are using webhooks\n```\n\nYou then have to install the Stripe module for Leaf:\n\n```bash\nleaf install stripe\n```\n\n## Billing on-the-fly\n\nBilling on-the-fly is the fastest way to charge customers—ideal for one-time payments, donations, or services. Just generate a payment link with Leaf Billing, and we’ll handle the rest. You can do this using the `billing()` helper in your controller.\n\n```php\n...\n\npublic function handleCartPurchase($cartId) {\n    $cart = Cart::find($cartId);\n\n    $session = billing()-\u003echarge([\n        'currency' =\u003e 'USD',\n        'description' =\u003e 'Purchase of items in cart',\n        'metadata' =\u003e [\n            'cart_id' =\u003e $cartId,\n            'items' =\u003e $cart-\u003eitems(),\n        ]\n    ]);\n\n    $cart-\u003epayment_session = $session-\u003eid;\n    $cart-\u003esave();\n\n    response()-\u003eredirect($session-\u003eurl);\n}\n```\n\nLeaf takes care of the entire payment session for you—automatically tracking the user (if available), any metadata you provide, and the payment status, keeping your code clean and focused on your app.\n\n## Billing Callbacks\n\nBy default, Leaf Billing redirects users to `/billing/callback` after a payment is completed or canceled. You can customize this behavior by setting `BILLING_SUCCESS_URL` and `BILLING_CANCEL_URL` in your `.env` file, or by passing custom URLs directly to the `charge()` method.\n\n```php [CallbacksController.php]\n\u003c?php\n\nnamespace App\\Controllers\\Billing;\n\n/**\n * Billing Callback\n * ---\n * Handles the redirect from the billing provider after payment.\n * This is a stateful controller, so sessions and auth are available.\n */\nclass CallbacksController extends Controller\n{\n    public function handle()\n    {\n        $billingSession = billing()-\u003ecallback();\n\n        if (!$billingSession-\u003eisSuccessful()) {\n            return response()-\u003ejson(['message' =\u003e 'Payment failed']);\n        }\n\n        return response()-\u003ejson(['message' =\u003e 'Payment successful']);\n    }\n}\n```\n\n`billing()-\u003ecallback()` parses and validates the callback, returning a BillingSession with full payment details. Use `isSuccessful()` to determine the outcome. This is ideal for one-time payments—no subscription logic needed.\n\n## Billing with subscriptions\n\nUnlike one-time payments, subscriptions require a more structured setup—but Leaf Billing makes it effortless. Just run the `scaffold:subscriptions` command to instantly generate everything you need: billing config, controllers, routes, and views. You'll be up and running with subscriptions in minutes.\n\n```bash\nphp leaf scaffold:subscriptions\n```\n\nYou then need to update the generated `config/billing.php` file with your subscription tiers under the `tiers` key:\n\n```php\n...\n    'tiers' =\u003e [\n        [\n            'name' =\u003e 'Starter',\n            'description' =\u003e 'For individuals and small teams',\n            'trialDays' =\u003e 5,\n            'price.monthly' =\u003e 100,\n            'price.yearly' =\u003e 1000,\n            'discount' =\u003e 25,\n            'features' =\u003e [\n                [\n                    'title' =\u003e 'Something 1',\n                    'description' =\u003e\n                        'Expertly crafted functionality including auth, mailing, billing, blogs, e-commerce, dashboards, and more.',\n                ],\n                [\n                    'title' =\u003e 'Another thing 1',\n                    'description' =\u003e\n                        'Beautiful templates and page sections built with Blade, Alpine.js, and Tailwind CSS to skip the boilerplate and build faster.',\n                ],\n                [\n                    'title' =\u003e 'Something else 1',\n                    'description' =\u003e\n                        'Get instant access to everything we have today, plus any new functionality and Leaf Zero templates we add in the future.',\n                ],\n            ],\n        ],\n        [\n            'name' =\u003e 'Pro',\n            'description' =\u003e 'For larger teams and companies',\n            'trialDays' =\u003e 10,\n            'price.monthly' =\u003e 200,\n            'price.yearly' =\u003e 2000,\n            'discount' =\u003e 50,\n            'features' =\u003e [\n                [\n                    'title' =\u003e 'Something 2',\n                    'description' =\u003e\n                        'Expertly crafted functionality including auth, mailing, billing, blogs, e-commerce, dashboards, and more.',\n                ],\n                [\n                    'title' =\u003e 'Another thing 2',\n                    'description' =\u003e\n                        'Beautiful templates and page sections built with Blade, Alpine.js, and Tailwind CSS to skip the boilerplate and build faster.',\n                ],\n                [\n                    'title' =\u003e 'Something else 2',\n                    'description' =\u003e\n                        'Get instant access to everything we have today, plus any new functionality and Leaf Zero templates we add in the future.',\n                ],\n            ],\n        ],\n    ]\n];\n```\n\n## Displaying your plans\n\nThe `scaffold:subscriptions` command also generates a pricing component tailored to your chosen view engine—Blade, React, Vue, or Svelte. You can display your plans with just one line of code. The component is fully customizable, so you can tweak the design to match your app’s look and feel seamlessly.\n\n```blade [Blade]\n@component('components.billing.pricing')\n```\n\n```jsx [React]\nimport Pricing from '@/components/billing/pricing';\n\n...\n\n\u003cPricing /\u003e\n```\n\n```vue [Vue]\n\u003cscript setup\u003e\nimport Pricing from '@/components/billing/pricing.vue';\n\n...\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cPricing /\u003e\n\u003c/template\u003e\n```\n\n```svelte [Svelte]\n\u003cscript\u003e\nimport Pricing from '@/components/billing/pricing.svelte';\n\u003c/script\u003e\n\n\u003cPricing /\u003e\n```\n\n## Billing Events/Webhooks\n\nOnce you’ve charged a customer—especially for a subscription—you’ll want to track their payment status. The best way to do this is through webhooks. When you run the `scaffold:subscriptions` command, Leaf Billing automatically generates a webhook controller that listens for events from your billing provider and handles them for you.\n\n```php [WebhooksController.php]\n\u003c?php\n\nnamespace App\\Controllers\\Billing;\n\n/**\n * Webhooks Controller\n * ----------\n * This controller processes all webhooks from the billing provider.\n * Since webhooks are stateless, sessions, authentication, and other\n * stateful data aren't available. However, Leaf automatically parses the webhook payload,\n * giving you direct access to the current user or subscription from the event data.\n */\nclass WebhooksController extends Controller\n{\n    public function handle()\n    {\n        $event = billing()-\u003ewebhook();\n\n        /**\n         * $event-\u003etype() - to get the event type\n         * $event-\u003eis() - to check if the event is a specific type\n         * $event-\u003etier() - to get the subscription tier (if available)\n         * $event-\u003esubscription() - to get the current subscription (if available)\n         * $event-\u003euser() - to get the current user (returns auth()-\u003euser() if available)\n         * $event-\u003epreviousSubscriptionTier() - to get the previous subscription tier (if available)\n         * $event-\u003ecancelSubscription() - to cancel the subscription in webhook request (if available)\n         * $event-\u003eactivateSubscription() - to activate the new subscription in webhook (if available)\n         */\n\n        if ($event-\u003eis('invoice.payment_succeeded')) {\n            // Payment was successful\n\n            if ($event-\u003edata()['object']['billing_reason'] === 'subscription_cycle') {\n                // Subscription renewed/charged after trial/cycle\n                // ✅ Give access to your service\n            }\n\n            // Other payment succeeded events\n            // ✅ Give access to your service\n\n            return;\n        }\n\n        if ($event-\u003eis('customer.subscription.updated')) {\n            if ($event-\u003eactivateSubscription()) {\n                response()-\u003ejson([\n                    'status' =\u003e 'success',\n                ]);\n            } else {\n                // Subscription was not activated\n                // ❌ Retry or handle manually\n                response()-\u003ejson([\n                    'status' =\u003e 'failed',\n                ], 500);\n            }\n\n            return;\n        }\n\n        if ($event-\u003eis('customer.subscription.deleted')) {\n            if ($event-\u003ecancelSubscription()) {\n                response()-\u003ejson([\n                    'status' =\u003e 'success',\n                ]);\n            } else {\n                // Subscription was not cancelled\n                // ❌ Retry or handle manually\n                response()-\u003ejson([\n                    'status' =\u003e 'failed',\n                ], 500);\n            }\n\n            return;\n        }\n\n        if ($event-\u003eis('customer.subscription.trial_will_end')) {\n            // Trial will end soon\n            // 📧 Maybe send a trial ending mail?\n            return;\n        }\n\n        if ($event-\u003eis('customer.subscription.paused')) {\n            // Subscription was paused\n            // ❌ Remove access to your service\n            return;\n        }\n\n        if ($event-\u003eis('customer.subscription.resumed')) {\n            // Subscription was resumed\n            // ✅ Give access to your service\n            return;\n        }\n\n        // ... handle all other necessary events\n    }\n}\n```\n\nSince webhooks are stateless, you can't use the `session()` or `auth()` helpers to retrieve the user who made the payment. This is a common issue with webhooks, as they are designed to be stateless and don't have access to the session or authentication data. However, Leaf Billing automatically parses the webhook payload and provides you with a `BillingEvent` instance, which gives you access to the user who made the payment, the subscription, and all other relevant details.\n","funding_links":["https://opencollective.com/leaf","https://github.com/sponsors/leafsphp"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleafsphp%2Fbilling","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleafsphp%2Fbilling","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleafsphp%2Fbilling/lists"}