You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Several projects require billing, be it for subscriptions, one-time payments, or donations. Leaf 4 now comes with billing support for Stripe and PayStack right out of the box. This makes integrating billing into your Leaf MVC project a breeze.
3
+
Leaf MVC’s billing system helps makers ship faster by handling payments and subscriptions seamlessly. With built-in Stripe and Paystack support, you can set up one-time payments and recurring subscriptions in minutes—so you can focus on building, not billing.
4
4
5
5
## Setting up
6
6
@@ -42,39 +42,17 @@ This is all you have to do if you're planning to bill on-the-fly. Let's take a l
42
42
43
43
## Billing on-the-fly
44
44
45
-
Billing on-the-fly is the simplest way to bill a customer. This is useful for one-time payments, donations, and charging customers for services or cart items. Leaf takes care of everything for you, all you need to do is use Leaf billing to generate a payment link. This is the same whether for Blade, React, Vue, or Sveltesince billing is done on the server.
45
+
Billing on-the-fly is the fastest way to charge customers—perfect for one-time payments, donations, or services. Just generate a payment link with Leaf Billing, and we handle the rest. Whether you're using Blade, React, Vue, or Svelte, the process stays the same since billing runs on the server. No extra setup, just fast and seamless payments.
46
46
47
47
```php:no-line-numbers [MyController.php]
48
48
...
49
49
50
50
public function handleCartPurchase($cartId) {
51
51
$cart = Cart::find($cartId);
52
52
53
-
response()->redirect(
54
-
billing()->instantLink([
55
-
'amount' => $cart->total(),
56
-
'currency' => 'USD',
57
-
'description' => 'Purchase of items in cart',
58
-
'metadata' => [
59
-
'cart_id' => $cartId,
60
-
'items' => $cart->items(),
61
-
]
62
-
])
63
-
);
64
-
}
65
-
```
66
-
67
-
While this is incredibly simple, you would usually want to store other information about the payment session in your database. For those use-cases, you can use the `instantSession()` method.
68
-
69
-
```php:no-line-numbers [MyController.php]
70
-
...
71
-
72
-
public function handleCartPurchase($cartId) {
73
-
$cart = Cart::find($cartId);
74
-
75
-
$session = billing()->instantSession([
76
-
'amount' => $cart->total(),
77
-
'currency' => 'USD',
53
+
$session = billing()->charge([
54
+
'amount' => $cart->total(), // will auto convert to lowest currency unit
55
+
'currency' => 'NGN',
78
56
'description' => 'Purchase of items in cart',
79
57
'metadata' => [
80
58
'cart_id' => $cartId,
@@ -89,11 +67,11 @@ public function handleCartPurchase($cartId) {
89
67
}
90
68
```
91
69
92
-
This way, you can store the payment session in your database and use it to track the payment session. Either way, Leaf will automatically handle the payment session and will add data like the user who made the payment (if available), any metadata you added, and the payment status.
70
+
Leaf 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.
93
71
94
72
## Billing Events/Webhooks
95
73
96
-
Once you have billed a customer, either on-the-fly or through a subscription, you would want to track the payment status, and webhook events are the best way to do this. Leaf billing comes with built-in support for webhooks. Leaf can automatically set up your webhooks for you using the `scaffold:billing-webhooks` command.
74
+
Once you've charged a customer—whether with a one-time payment or a subscription—you’ll want to track the payment status. Webhooks are the best way to do this, and Leaf Billing has built-in support.
97
75
98
76
```bash:no-line-numbers
99
77
php leaf scaffold:billing-webhooks
@@ -107,30 +85,29 @@ This will generate a `_billing_webhooks.php` file in your `routes` directory whi
107
85
public function handle() {
108
86
$event = billing()->webhook();
109
87
110
-
// Handle the event
111
-
switch ($event->type) {
112
-
case 'checkout.session.completed':
113
-
// Payment was successful and the Checkout Session is complete
114
-
// ✅ Give access to your service
115
-
// $event->user() will give you the user who made the payment (if available)
116
-
break;
117
-
case 'checkout.session.expired':
118
-
// Payment was not successful and the Checkout Session has expired
119
-
// 📧 Maybe send an abandoned checkout mail?
120
-
break;
121
-
// ... handle all necessary events
122
-
}
123
-
```
88
+
if ($event->is('checkout.session.completed')) {
89
+
// Payment was successful and the Checkout Session is complete
90
+
// ✅ Give access to your service
91
+
// $event->user() will give you the user who made the payment (if available)
92
+
return;
93
+
}
124
94
125
-
<!-- TODO: change specific events to Leaf billing events like $event->type()->paymentSuccessful() -->
95
+
if ($event->is('checkout.session.expired')) {
96
+
// Payment was not successful and the Checkout Session has expired
97
+
// 📧 Maybe send an abandoned checkout mail?
98
+
return;
99
+
}
126
100
127
-
This way, you can handle all billing events in one place, and you can easily track the payment status of your customers.
101
+
// ... handle all necessary events
102
+
}
103
+
```
128
104
129
-
Since webhooks are stateless, you can't use the `session()` helper to get the user who made the payment...so we added a `webhook()` method to the billing instance to parse all data from the webhook event and create a `BillingEvent` instance which you can use to get the user who made the payment, the payment session, and all other necessary information.
105
+
Since webhooks are stateless, you can't use the `session()` helper to retrieve the user who made the payment. To solve this, Leaf Billing provides a `webhook()` method on the billing instance which automatically parses the webhook, validates its source and generates a `BillingEvent` instance, giving you access to the user who made the payment, the payment session, and all other relevant details—effortlessly keeping your billing logic clean and efficient.
130
106
131
107
The billing event instance has the following items:
132
108
133
109
-`type`: The type of the event
110
+
-`is()`: A method to check if the event is of a specific type
134
111
-`user()`: The user who made the payment
135
112
-`session()`: The payment session
136
113
-`metadata`: The raw metadata of the payment session
@@ -143,33 +120,37 @@ For our example above, we can handle the payment event like this:
143
120
public function handle() {
144
121
$event = billing()->webhook();
145
122
146
-
// Handle the event
147
-
switch ($event->type) {
148
-
case 'checkout.session.completed':
149
-
$cart = Cart::find($event->metadata['cart_id']);
150
-
$cart->status = 'paid';
151
-
$cart->save();
152
-
break;
153
-
case 'checkout.session.expired':
154
-
$cart = Cart::find($event->metadata['cart_id']);
155
-
$cart->status = 'abandoned';
156
-
$cart->save();
157
-
break;
158
-
// ... handle all necessary events
123
+
if ($event->is('checkout.session.completed')) {
124
+
$cart = Cart::find($event->metadata['cart_id']);
125
+
$cart->status = 'paid';
126
+
$cart->save();
127
+
128
+
return;
129
+
}
130
+
131
+
if ($event->is('checkout.session.expired')) {
132
+
$cart = Cart::find($event->metadata['cart_id']);
133
+
$cart->status = 'abandoned';
134
+
$cart->save();
135
+
136
+
return;
137
+
}
138
+
139
+
// ... handle all necessary events
159
140
}
160
141
```
161
142
162
143
For more information on billing events, you can check the [Stripe](https://stripe.com/docs/api/events/types) and [PayStack](https://paystack.com/docs/payments/webhooks/#types-of-events) documentation.
163
144
164
145
## Adding billing plans
165
146
166
-
While billing on-the-fly is great for one-time payments, you may want to set up subscriptions for your customers. Leaf billing allows you to set up billing plans for your customers. For this, you will have to set up your billing plans in your billing configuration file. Leaf scaffolding makes this easy with the `scaffold:billing-plans` command.
147
+
While billing on-the-fly works for one-time payments, subscriptions need a structured setup. Leaf Billing makes this effortless—just run:
167
148
168
149
```bash:no-line-numbers
169
150
php leaf scaffold:billing-plans
170
151
```
171
152
172
-
This will generate a `config/billing.php` file with a `plans` key where you can set up your billing plans, which should look like this:
153
+
This will generate a `config/billing.php` file with a `tiers` key where you can set up your billing plans, which should look like this:
173
154
174
155
```php:no-line-numbers [billing.php]
175
156
...
@@ -214,9 +195,9 @@ You just need to add an array of tiers with the following keys:
214
195
|`discount`| The discount percentage |`true`|
215
196
|`features`| An array of features for the tier |`true`|
216
197
217
-
For the pricing, you can specify different prices for different durations. You can specify the price for `monthly`, `yearly`, `quarterly`, `weekly`, and `daily` durations. You only need to specify the price for the duration you want to use, if you want to create a one-time payment instead of a subscription, you can set the`price` instead of `price.duration`.
198
+
You can set different prices for various durations—`monthly`, `yearly`, `quarterly`, `weekly`, or even `daily`. Just define the ones you need. For one-time payments, simply set `price` instead of `price.duration`.
218
199
219
-
Besides this, you also get a pricing component based on your view engine. This component is available in Blade, React, Vue, and Svelte, and you can use it to display your billing plans.
200
+
Plus, Leaf provides a built-in pricing component for Blade, React, Vue, and Svelte, so you can display your plans effortlessly in your app.
220
201
221
202
::: code-group
222
203
@@ -256,7 +237,7 @@ import Pricing from '@/components/pricing.svelte';
256
237
257
238
## Billing Subscriptions
258
239
259
-
Once you have set up your billing plans in the billing config, your users can subscribe to these plans. The `scaffold:billing-plans` command also generates a `BillingPlans` controller with a `subscribe()` method that you can use to subscribe your users to a plan.
240
+
After setting up your billing plans in the config, users can subscribe effortlessly. The `scaffold:billing-plans` command also generates a BillingPlans controller with a `subscribe()` method, making it easy to enroll users into a plan with minimal setup.
@@ -270,31 +251,34 @@ public function subscribe($plan) {
270
251
}
271
252
```
272
253
273
-
This will redirect your users to the billing provider's checkout page where they can subscribe to the plan. Once they have subscribed, you can track their subscription status using the webhook events. The `scaffold:billing-plans` command also updates the `BillingWebhooks` controller to handle subscription events.
254
+
This redirects users to the billing provider’s checkout page to subscribe. Once subscribed, you can track their status via webhook events. The `scaffold:billing-plans` command also updates the BillingWebhooks controller to handle subscription events seamlessly.
`$event->user()` returns an instance of Leaf's auth user, which automatically gets the `HasBilling` trait once Leaf billing is installed. This trait allows you to easily check the user's subscription status, plan, and other billing information directly on the user. `$event->user()` automatically attaches the billing tier information to the user object, which is why we can call `newSubscription()` and `cancelSubscription()` without any arguments in the example above.
281
+
`$event->user()` returns an instance of Leaf's auth user, enhanced with the `HasBilling` trait when Leaf Billing is installed. This lets you effortlessly check a user's subscription status, plan, and billing details. Since the billing tier is automatically attached, methods like `newSubscription()` and `cancelSubscription()`work without extra arguments.
298
282
299
283
## Checking billing status
300
284
@@ -552,15 +536,22 @@ As with all payment systems, you need to ensure that your billing system is secu
552
536
- In your [Developers], copy your public & private keys and add them to `BILLING_SECRET_KEY` & `BILLING_PUBLISHABLE_KEY` in your production environment variables.
553
537
- In your [Developers], [Webhook], [Add Enpoint]. Set `<your-domain>/billing/webhook`. Select [checkout.session.completed] event (or more if needed). Copy the signing secret and add it to `BILLING_WEBHOOK_SECRET` in your production environment variables.
554
538
555
-
::: warning CSRF Protection
556
-
If you are using the default CSRF config, then your `/billing/webhook` route is already excluded from CSRF protection, however, if you maintain your own CSRF config in `config/csrf.php`, you should exclude the `/billing/webhook` route from CSRF protection.
539
+
## Security
557
540
558
-
```php:no-line-numbers [csrf.php]
559
-
...
560
-
'except' => [
561
-
'/billing/webhook',
562
-
],
563
-
...
564
-
```
541
+
When using Leaf billing, you need to ensure that your billing system is secure. Here are a few things to check:
565
542
566
-
:::
543
+
- Billing Webhooks
544
+
545
+
Always make sure that your `BILLING_WEBHOOK_SECRET` is set in your `.env` file. This secret is used to verify that the webhook is coming from your billing provider. You can get this secret from your billing provider's dashboard. Once you set this secret, Leaf billing will automatically verify that the webhook is coming from your billing provider.
546
+
547
+
- CSRF Protection
548
+
549
+
If you are using the default CSRF config, then your `/billing/webhook` route is already excluded from CSRF protection, however, if you maintain your own CSRF config in `config/csrf.php`, you should exclude the `/billing/webhook` route from CSRF protection.
0 commit comments