Skip to content

Commit ab0e6cc

Browse files
committed
component fun
1 parent 4528f07 commit ab0e6cc

24 files changed

+440
-244
lines changed

README.md

+14
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,20 @@ php artisan db:seed
7171
php artisan app:create-super-admin
7272
```
7373

74+
## Setting up Stripe
75+
- Set stripe keys in the `.env` file.
76+
- Set the webhook in Stripe to point to your server with the path `/stripe/webhook`.
77+
- In Stripe Panel, set up to subscribe to these events:
78+
- `customer.subscription.created`
79+
- `customer.subscription.updated`
80+
- `customer.subscription.deleted`
81+
- `customer.updated`
82+
- `customer.deleted`
83+
- `payment_method.automatically_updated`
84+
- `invoice.payment_action_required`
85+
- `invoice.payment_succeeded`
86+
87+
7488
# Contributing
7589
Feel free to contribute to this project by submitting a pull request.
7690

app/Models/Plan.php

+14
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,18 @@ class Plan extends Model
1515
'stripe_id',
1616
'features',
1717
];
18+
19+
public function getFeaturesListAttribute(): array
20+
{
21+
// explode per new line
22+
$list = explode("\n", $this->features);
23+
24+
// map the list. If a line begins with a + sign, set the included key to true,else if - sign, set to false
25+
return array_map(function ($item) {
26+
return [
27+
'name' => ltrim($item, '+- '),
28+
'included' => str_starts_with($item, '+'),
29+
];
30+
}, $list);
31+
}
1832
}

bootstrap/app.php

+3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
'permission' => \Spatie\Permission\Middleware\PermissionMiddleware::class,
2222
'role_or_permission' => \Spatie\Permission\Middleware\RoleOrPermissionMiddleware::class,
2323
]);
24+
$middleware->validateCsrfTokens(except: [
25+
'stripe/*',
26+
]);
2427
})
2528
->withExceptions(function (Exceptions $exceptions) {
2629
//

lang/en/plans.php

+1
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@
1515
'created_successful' => 'Plan was created',
1616
'features' => 'Features',
1717
'features_hint' => 'Each feature on one line. Use + for included features and - for excluded features.',
18+
'signup_for' => 'Sign up for',
1819
];

resources/views/account/index.blade.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
@section('title', 'Account')
33

44
@section('content')
5-
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg border border-stone-200">
6-
<div class="p-6">
5+
<x-container>
6+
<x-h2>
77
{{ __('Account') }}
8-
</div>
9-
</div>
8+
</x-h2>
9+
</x-container>>
1010
@endsection
1111

resources/views/account/subscriptions/cancel.blade.php

+12-14
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,17 @@
33

44
@section('content')
55
@include('components.dashboard.subscription-nav')
6-
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg border border-stone-200">
7-
<div class="p-6 prose max-w-none">
8-
<h2>
9-
Cancel Subscription
10-
</h2>
11-
<p>We're sad to see you go.
12-
You can cancel your subscription at any time. You will still have access to your account until the end of your billing period.
13-
</p>
14-
<form action="{{ route('account.subscriptions.cancel.post') }}" method="post">
15-
@csrf
16-
<x-button type="submit">Cancel Subscription</x-button>
17-
</form>
18-
</div>
19-
</div>
6+
<x-container>
7+
<x-h2>
8+
Cancel Subscription
9+
</x-h2>
10+
<p>We're sad to see you go.
11+
You can cancel your subscription at any time. You will still have access to your account until the end of your billing period.
12+
</p>
13+
<form action="{{ route('account.subscriptions.cancel.post') }}" method="post">
14+
@csrf
15+
<x-button type="submit">Cancel Subscription</x-button>
16+
</form>
17+
</x-container>
2018

2119
@endsection

resources/views/account/subscriptions/card.blade.php

+13-16
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,17 @@
33

44
@section('content')
55
@include('components.dashboard.subscription-nav')
6-
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg border border-stone-200">
7-
<div class="p-6 prose max-w-none">
8-
<h2>
9-
Update Creditcard Details
10-
</h2>
11-
<p>
12-
Got a new card? Update your card details here.
13-
</p>
14-
<x-card-form :action="route('account.subscriptions.card.post')">
15-
<x-button type="submit" id="card-button" data-secret="{{ $intent->client_secret }}">
16-
Update card details
17-
</x-button>
18-
</x-card-form>
19-
</div>
20-
21-
</div>
6+
<x-container>
7+
<x-h2>
8+
Update Creditcard Details
9+
</x-h2>
10+
<p>
11+
Got a new card? Update your card details here.
12+
</p>
13+
<x-card-form :action="route('account.subscriptions.card.post')">
14+
<x-button type="submit" id="card-button" data-secret="{{ $intent->client_secret }}">
15+
Update card details
16+
</x-button>
17+
</x-card-form>
18+
</x-container>
2219
@endsection

resources/views/account/subscriptions/coupon.blade.php

+23-25
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,31 @@
33

44
@section('content')
55
@include('components.dashboard.subscription-nav')
6-
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg border border-stone-200">
7-
<div class="p-6 prose max-w-none">
8-
<h2>
9-
Apply a Coupon
10-
</h2>
11-
<p>
12-
Received a coupon?
13-
You can apply a coupon to your subscription to get a discount.
14-
</p>
15-
<form action="{{ route('account.subscriptions.coupon.post') }}" method="post">
16-
@csrf
6+
<x-container>
7+
<x-h2>
8+
Apply a Coupon
9+
</x-h2>
10+
<p>
11+
Received a coupon?
12+
You can apply a coupon to your subscription to get a discount.
13+
</p>
14+
<form action="{{ route('account.subscriptions.coupon.post') }}" method="post">
15+
@csrf
1716

18-
<div class="mb-5">
19-
<x-label for="coupon">{{ __('Coupon') }}</x-label>
20-
<x-input id="coupon" name="coupon" type="text" :value="old('coupon')" autofocus
21-
placeholder="{{ __('Coupon') }}"/>
22-
@if ($errors->first('coupon'))
23-
<p class="text-red-500 text-xs italic mt-4">{{ $errors->first('coupon') }}</p>
24-
@endif
25-
</div>
17+
<div class="mb-5">
18+
<x-label for="coupon">{{ __('Coupon') }}</x-label>
19+
<x-input id="coupon" name="coupon" type="text" :value="old('coupon')" autofocus
20+
placeholder="{{ __('Coupon') }}"/>
21+
@if ($errors->first('coupon'))
22+
<p class="text-red-500 text-xs italic mt-4">{{ $errors->first('coupon') }}</p>
23+
@endif
24+
</div>
2625

27-
<x-button type="submit" id="card-button">
28-
Apply Coupon
29-
</x-button>
26+
<x-button type="submit" id="card-button">
27+
Apply Coupon
28+
</x-button>
3029

3130

32-
</form>
33-
</div>
34-
</div>
31+
</form>
32+
</x-container>
3533
@endsection

resources/views/account/subscriptions/index.blade.php

+51-53
Original file line numberDiff line numberDiff line change
@@ -3,64 +3,62 @@
33

44
@section('content')
55
@include('components.dashboard.subscription-nav')
6-
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg border border-stone-200">
7-
<div class="p-6 prose max-w-none">
8-
<h2>
9-
Subscription overview
10-
</h2>
11-
<p>
12-
Here's a summary of your subscription.
13-
</p>
14-
@if (auth()->user()->subscribed())
15-
@if ($subscription)
16-
<ul class="list-disc pl-5">
17-
<li>
18-
{{ auth()->user()->plan->title }}
19-
({{ $subscription->amount() }}/{{ $subscription->interval() }}) - <a wire:navigate.hover href="{{ route('account.subscriptions.swap') }}">Change</a>
20-
</li>
21-
{{-- @if (auth()->user()->subscription()->cancelled())--}}
22-
{{-- <li>--}}
23-
{{-- Ends at {{ $subscription->cancelAt() }}--}}
24-
{{-- <a href="{{ route('account.subscriptions.resume') }}" class="underline">Resume--}}
25-
{{-- subscription</a>--}}
26-
{{-- </li>--}}
27-
{{-- @endif--}}
6+
<x-container>
7+
<x-h2>
8+
Subscription overview
9+
</x-h2>
10+
<p>
11+
Here's a summary of your subscription.
12+
</p>
13+
@if (auth()->user()->subscribed())
14+
@if ($subscription)
15+
<ul class="list-disc pl-5">
16+
<li>
17+
{{ auth()->user()->plan->title }}
18+
({{ $subscription->amount() }}/{{ $subscription->interval() }}) - <a wire:navigate.hover href="{{ route('account.subscriptions.swap') }}">Change</a>
19+
</li>
20+
{{-- @if (auth()->user()->subscription()->cancelled())--}}
21+
{{-- <li>--}}
22+
{{-- Ends at {{ $subscription->cancelAt() }}--}}
23+
{{-- <a href="{{ route('account.subscriptions.resume') }}" class="underline">Resume--}}
24+
{{-- subscription</a>--}}
25+
{{-- </li>--}}
26+
{{-- @endif--}}
2827

29-
@if ($invoice)
30-
<li>Next Payment {{ $invoice->amount() }} on {{ $invoice->nextPaymentAttempt() }}</li>
31-
@endif
28+
@if ($invoice)
29+
<li>Next Payment {{ $invoice->amount() }} on {{ $invoice->nextPaymentAttempt() }}</li>
30+
@endif
3231

33-
@if ($customer)
34-
<li>Balance {{ $customer->balance() }}</li>
35-
@endif
32+
@if ($customer)
33+
<li>Balance {{ $customer->balance() }}</li>
34+
@endif
3635

37-
</ul>
38-
@endif
39-
40-
<div class="mt-5">
41-
Use the menu above, or
42-
<a href="{{ auth()->user()->billingPortalUrl(route('account.subscriptions')) }}" class="underline">
43-
44-
go to the Billing Portal at Stripe
45-
46-
</a>
47-
to handle your subscription.
48-
</div>
49-
@else
50-
<div>
51-
<p>You don't have a subscription. </p>
52-
<a href="{{ route('subscriptions.plans') }}">
53-
<x-button>
36+
</ul>
37+
@endif
5438

55-
<span>Go check out our available plans</span>
56-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-right"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
57-
</x-button>
58-
</a>
59-
</div>
39+
<div class="mt-5">
40+
Use the menu above, or
41+
<a href="{{ auth()->user()->billingPortalUrl(route('account.subscriptions')) }}" class="underline">
6042

61-
@endif
43+
go to the Billing Portal at Stripe
6244

45+
</a>
46+
to handle your subscription.
47+
</div>
48+
@else
49+
<div>
50+
<p>You don't have a subscription. </p>
51+
<a href="{{ route('subscriptions.plans') }}">
52+
<x-button>
6353

64-
</div>
65-
</div>
54+
<span>Go check out our available plans</span>
55+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-right">
56+
<path d="M5 12h14"/>
57+
<path d="m12 5 7 7-7 7"/>
58+
</svg>
59+
</x-button>
60+
</a>
61+
</div>
62+
@endif
63+
</x-container>
6664
@endsection

resources/views/account/subscriptions/invoices.blade.php

+17-19
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,21 @@
33

44
@section('content')
55
@include('components.dashboard.subscription-nav')
6-
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg border border-stone-200">
7-
<div class="p-6 prose max-w-none">
8-
<h2>
9-
Invoices
10-
</h2>
11-
<p>
12-
You can download your invoices here.
13-
</p>
14-
@forelse ($invoices as $invoice)
15-
<div class="flex justify-between">
16-
<div>{{ $invoice->date()->toFormattedDateString() }}</div>
17-
<div>{{ $invoice->total() }}</div>
18-
<a href="{{ route('account.subscriptions.invoice', $invoice->id) }}">Download</a>
19-
</div>
20-
@empty
21-
No invoices
22-
@endforelse
23-
</div>
24-
</div>
6+
<x-container>
7+
<x-h2>
8+
Invoices
9+
</x-h2>
10+
<p>
11+
You can download your invoices here.
12+
</p>
13+
@forelse ($invoices as $invoice)
14+
<div class="flex justify-between">
15+
<div>{{ $invoice->date()->toFormattedDateString() }}</div>
16+
<div>{{ $invoice->total() }}</div>
17+
<a href="{{ route('account.subscriptions.invoice', $invoice->id) }}">Download</a>
18+
</div>
19+
@empty
20+
No invoices
21+
@endforelse
22+
</x-container>
2523
@endsection

resources/views/account/subscriptions/resume.blade.php

+12-14
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,16 @@
33

44
@section('content')
55
@include('components.dashboard.subscription-nav')
6-
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg border border-stone-200">
7-
<div class="p-6 prose max-w-none">
8-
<h2>
9-
Resume your subscription
10-
</h2>
11-
<p>
12-
We're glad to see you back. You can resume your subscription here.
13-
</p>
14-
<form action="{{ route('account.subscriptions.resume.post') }}" method="post">
15-
@csrf
16-
<x-button type="submit">Resume Subscription</x-button>
17-
</form>
18-
</div>
19-
</div>
6+
<x-container>
7+
<x-h2>
8+
Resume your subscription
9+
</x-h2>
10+
<p>
11+
We're glad to see you back. You can resume your subscription here.
12+
</p>
13+
<form action="{{ route('account.subscriptions.resume.post') }}" method="post">
14+
@csrf
15+
<x-button type="submit">Resume Subscription</x-button>
16+
</form>
17+
</x-container>
2018
@endsection

0 commit comments

Comments
 (0)