Skip to content

Commit c0604de

Browse files
committed
Add discounted pricing option
1 parent 265b002 commit c0604de

File tree

9 files changed

+414
-183
lines changed

9 files changed

+414
-183
lines changed

app/Enums/Subscription.php

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ public static function fromStripePriceId(string $priceId): self
2929
config('subscriptions.plans.mini.stripe_price_id'),
3030
config('subscriptions.plans.mini.stripe_price_id_eap') => self::Mini,
3131
config('subscriptions.plans.pro.stripe_price_id'),
32+
config('subscriptions.plans.pro.stripe_price_id_discounted'),
3233
config('subscriptions.plans.pro.stripe_price_id_eap') => self::Pro,
3334
config('subscriptions.plans.max.stripe_price_id'),
35+
config('subscriptions.plans.max.stripe_price_id_discounted'),
3436
config('subscriptions.plans.max.stripe_price_id_eap') => self::Max,
3537
default => throw new RuntimeException("Unknown Stripe price id: {$priceId}"),
3638
};
@@ -53,12 +55,18 @@ public function name(): string
5355
return config("subscriptions.plans.{$this->value}.name");
5456
}
5557

56-
public function stripePriceId(bool $forceEap = false): string
58+
public function stripePriceId(bool $forceEap = false, bool $discounted = false): string
5759
{
5860
// EAP ends June 1st at midnight UTC
59-
return now()->isBefore('2025-06-01 00:00:00') || $forceEap
60-
? config("subscriptions.plans.{$this->value}.stripe_price_id_eap")
61-
: config("subscriptions.plans.{$this->value}.stripe_price_id");
61+
if (now()->isBefore('2025-06-01 00:00:00') || $forceEap) {
62+
return config("subscriptions.plans.{$this->value}.stripe_price_id_eap");
63+
}
64+
65+
if ($discounted) {
66+
return config("subscriptions.plans.{$this->value}.stripe_price_id_discounted");
67+
}
68+
69+
return config("subscriptions.plans.{$this->value}.stripe_price_id");
6270
}
6371

6472
public function stripePaymentLink(): string

app/Livewire/MobilePricing.php

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,35 @@
99
use Illuminate\Support\Facades\Log;
1010
use Illuminate\Support\Facades\Validator;
1111
use Illuminate\Support\Str;
12+
use Livewire\Attributes\Locked;
1213
use Livewire\Component;
1314

1415
class MobilePricing extends Component
1516
{
17+
#[Locked]
18+
public bool $discounted = false;
19+
20+
#[Locked]
21+
public $user;
22+
1623
protected $listeners = [
1724
'purchase-request-submitted' => 'handlePurchaseRequest',
1825
];
1926

27+
public function mount()
28+
{
29+
if (request()->has('email')) {
30+
$this->user = $this->findOrCreateUser(request()->query('email'));
31+
}
32+
}
33+
2034
public function handlePurchaseRequest(array $data)
2135
{
22-
$user = $this->findOrCreateUser($data['email']);
36+
if (!$this->user) {
37+
$user = $this->findOrCreateUser($data['email']);
38+
}
2339

24-
return $this->createCheckoutSession($data['plan'], $user);
40+
return $this->createCheckoutSession($data['plan'], $this->user ?? $user);
2541
}
2642

2743
public function createCheckoutSession(?string $plan, ?User $user = null)
@@ -57,7 +73,7 @@ public function createCheckoutSession(?string $plan, ?User $user = null)
5773
$user->createOrGetStripeCustomer();
5874

5975
$checkout = $user
60-
->newSubscription('default', $subscription->stripePriceId())
76+
->newSubscription('default', $subscription->stripePriceId(discounted: $this->discounted))
6177
->allowPromotionCodes()
6278
->checkout([
6379
'success_url' => $this->successUrl(),

config/subscriptions.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
'name' => 'Pro',
1515
'stripe_price_id' => env('STRIPE_PRO_PRICE_ID'),
1616
'stripe_price_id_eap' => env('STRIPE_PRO_PRICE_ID_EAP'),
17+
'stripe_price_id_discounted' => env('STRIPE_PRO_PRICE_ID_DISCOUNTED'),
1718
'stripe_payment_link' => env('STRIPE_PRO_PAYMENT_LINK'),
1819
'anystack_product_id' => env('ANYSTACK_PRODUCT_ID'),
1920
'anystack_policy_id' => env('ANYSTACK_PRO_POLICY_ID'),
@@ -22,6 +23,7 @@
2223
'name' => 'Max',
2324
'stripe_price_id' => env('STRIPE_MAX_PRICE_ID'),
2425
'stripe_price_id_eap' => env('STRIPE_MAX_PRICE_ID_EAP'),
26+
'stripe_price_id_discounted' => env('STRIPE_MAX_PRICE_ID_DISCOUNTED'),
2527
'stripe_payment_link' => env('STRIPE_MAX_PAYMENT_LINK'),
2628
'anystack_product_id' => env('ANYSTACK_PRODUCT_ID'),
2729
'anystack_policy_id' => env('ANYSTACK_MAX_POLICY_ID'),
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
<x-layout title="NativePHP for iOS and Android">
2+
{{-- Hero Section --}}
3+
<section
4+
class="mt-10 md:mt-14"
5+
aria-labelledby="hero-heading"
6+
>
7+
<header class="relative z-10 grid place-items-center text-center">
8+
{{-- Primary Heading --}}
9+
<h1
10+
id="hero-heading"
11+
x-init="
12+
() => {
13+
motion.inView($el, (element) => {
14+
motion.animate(
15+
$el,
16+
{
17+
opacity: [0, 1],
18+
y: [-10, 0],
19+
},
20+
{
21+
duration: 0.7,
22+
ease: motion.easeOut,
23+
},
24+
)
25+
})
26+
}
27+
"
28+
class="text-3xl font-extrabold sm:text-4xl"
29+
>
30+
Discounted Licenses
31+
</h1>
32+
33+
{{-- Introduction Description --}}
34+
<h2
35+
x-init="
36+
() => {
37+
motion.inView($el, (element) => {
38+
motion.animate(
39+
$el,
40+
{
41+
opacity: [0, 1],
42+
y: [10, 0],
43+
},
44+
{
45+
duration: 0.7,
46+
ease: motion.easeOut,
47+
},
48+
)
49+
})
50+
}
51+
"
52+
class="mx-auto max-w-xl pt-4 text-base/relaxed text-gray-600 sm:text-lg/relaxed dark:text-gray-400"
53+
>
54+
Thanks for supporting NativePHP and Bifrost.<br>
55+
Now go get your discounted license!
56+
</h2>
57+
</header>
58+
</section>
59+
60+
{{-- Pricing Section --}}
61+
<livewire:mobile-pricing :discounted="true" />
62+
63+
{{-- Ultra Section --}}
64+
<x-ultra-plan />
65+
66+
{{-- Testimonials Section --}}
67+
{{-- <x-testimonials /> --}}
68+
69+
{{-- FAQ Section --}}
70+
<section
71+
class="mt-24"
72+
aria-labelledby="faq-heading"
73+
>
74+
{{-- Section Heading --}}
75+
<h2
76+
id="faq-heading"
77+
x-init="
78+
() => {
79+
motion.inView($el, (element) => {
80+
motion.animate(
81+
$el,
82+
{
83+
opacity: [0, 1],
84+
y: [-10, 0],
85+
},
86+
{
87+
duration: 0.7,
88+
ease: motion.easeOut,
89+
},
90+
)
91+
})
92+
}
93+
"
94+
class="text-center text-3xl font-semibold opacity-0"
95+
>
96+
Frequently Asked Questions
97+
</h2>
98+
99+
{{-- FAQ List --}}
100+
<div
101+
x-init="
102+
() => {
103+
motion.inView($el, (element) => {
104+
motion.animate(
105+
Array.from($el.children),
106+
{
107+
x: [-50, 0],
108+
opacity: [0, 1],
109+
},
110+
{
111+
duration: 0.7,
112+
ease: motion.circOut,
113+
delay: motion.stagger(0.1),
114+
},
115+
)
116+
})
117+
}
118+
"
119+
class="mx-auto flex w-full max-w-2xl flex-col items-center gap-4 pt-10"
120+
aria-labelledby="faq-heading"
121+
>
122+
<x-faq-card question="Are these discounted licenses different somehow? What's the catch?">
123+
<p>
124+
No catch! They're the same licenses.
125+
</p>
126+
</x-faq-card>
127+
128+
<x-faq-card
129+
question="When my discounted license renews, will it renew at the same price or go up to the regular price?"
130+
>
131+
<p>
132+
It'll renew at the <em>discounted</em> price. As long as you keep up your subscription, you'll
133+
benefit from that discounted price.
134+
</p>
135+
</x-faq-card>
136+
137+
<x-faq-card
138+
question="Can I still build apps if I choose not to renew my license?"
139+
>
140+
<p>
141+
Yes. Renewing your license entitles you to receive the
142+
latest package updates but isn't required to build and
143+
release apps.
144+
</p>
145+
</x-faq-card>
146+
147+
<x-faq-card question="Can I upgrade or downgrade my license later?">
148+
<p>That's not currently possible.</p>
149+
</x-faq-card>
150+
151+
<x-faq-card question="Can I use NativePHP for commercial projects?">
152+
<p>
153+
Absolutely! You can use NativePHP for any kind of project,
154+
including commercial ones. We can't wait to see what you
155+
build!
156+
</p>
157+
</x-faq-card>
158+
159+
<x-faq-card question="Can I get an invoice?">
160+
<p>
161+
You'll get an invoice with your receipt via email and you can always retrieve past invoices
162+
in the
163+
<a
164+
href="https://billing.stripe.com/p/login/4gwaGV5VK0uU44E288"
165+
onclick="event.stopPropagation()"
166+
class="inline-block underline hover:text-violet-400"
167+
aria-label="Stripe billing portal"
168+
target="_blank"
169+
>
170+
Stripe billing portal.
171+
</a>
172+
</p>
173+
</x-faq-card>
174+
175+
<x-faq-card question="How can I manage my subscription?">
176+
<p>
177+
You can manage your subscription via the
178+
<a
179+
href="https://billing.stripe.com/p/login/4gwaGV5VK0uU44E288"
180+
onclick="event.stopPropagation()"
181+
class="inline-block underline hover:text-violet-400"
182+
aria-label="Stripe billing portal"
183+
target="_blank"
184+
>
185+
Stripe billing portal.
186+
</a>
187+
</p>
188+
</x-faq-card>
189+
</div>
190+
</section>
191+
</x-layout>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<section
2+
class="mx-auto mt-10 max-w-2xl"
3+
aria-labelledby="ultra-tier-heading"
4+
>
5+
<div
6+
x-init="
7+
() => {
8+
motion.inView($el, (element) => {
9+
motion.animate(
10+
$el,
11+
{
12+
opacity: [0, 1],
13+
y: [10, 0],
14+
},
15+
{
16+
duration: 0.7,
17+
ease: motion.easeOut,
18+
},
19+
)
20+
})
21+
}
22+
"
23+
class="dark:bg-mirage space-y-3 rounded-2xl bg-gray-100 p-7 text-center"
24+
>
25+
{{-- Plan Name --}}
26+
<h3
27+
id="ultra-tier-heading"
28+
class="text-2xl font-semibold"
29+
>
30+
Ultra
31+
</h3>
32+
33+
{{-- Price --}}
34+
{{-- <div --}}
35+
{{-- class="flex items-start gap-1.5 pt-5" --}}
36+
{{-- aria-label="Price: $10,000+ per year" --}}
37+
{{-- > --}}
38+
{{-- <div class="text-5xl font-semibold">$20,000</div> --}}
39+
{{-- <div class="self-end pb-1.5 text-zinc-500">per year</div> --}}
40+
{{-- </div> --}}
41+
42+
<p class="dark:text-gray-400">
43+
Partners get even more! <b>Ultra</b> is our Partnership Program, offering dedicated support,
44+
feature development, training, license management, marketing opportunities, and other
45+
enterprise-oriented services for teams of any size.
46+
</p>
47+
<div>
48+
<a
49+
href="{{ route('partners') }}"
50+
class="mx-auto mt-5 block w-full max-w-xs rounded-2xl bg-zinc-200 py-4 text-center text-sm font-medium transition duration-200 ease-in-out hover:bg-zinc-800 hover:text-white dark:bg-slate-700/30 dark:hover:bg-slate-700/40"
51+
>
52+
Learn more
53+
</a>
54+
</div>
55+
</div>
56+
</section>

0 commit comments

Comments
 (0)