Add Business tier and Paystack prepaid billing for POS.
Deploy Ladill POS / deploy (push) Successful in 35s
Deploy Ladill POS / deploy (push) Successful in 35s
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -21,7 +21,12 @@ class SubscriptionService
|
||||
|
||||
public function priceMinor(): int
|
||||
{
|
||||
return (int) config('pos.pro.price_minor', 7900);
|
||||
return (int) config('pos.plans.pro.price_minor', config('pos.pro.price_minor', 7900));
|
||||
}
|
||||
|
||||
public function enterprisePriceMinor(): int
|
||||
{
|
||||
return (int) config('pos.plans.enterprise.price_minor', 24900);
|
||||
}
|
||||
|
||||
public function subscriptionFor(User $user): ?ProSubscription
|
||||
@@ -29,7 +34,23 @@ class SubscriptionService
|
||||
return ProSubscription::where('user_id', $user->id)->first();
|
||||
}
|
||||
|
||||
public function isPro(User $user): bool
|
||||
public function planKey(User $user): string
|
||||
{
|
||||
if (! $this->gatingActive()) {
|
||||
return ProSubscription::PLAN_PRO;
|
||||
}
|
||||
|
||||
$sub = $this->subscriptionFor($user);
|
||||
if (! $sub || ! $sub->entitled()) {
|
||||
return 'free';
|
||||
}
|
||||
|
||||
return $sub->plan === ProSubscription::PLAN_ENTERPRISE
|
||||
? ProSubscription::PLAN_ENTERPRISE
|
||||
: ProSubscription::PLAN_PRO;
|
||||
}
|
||||
|
||||
public function hasPaidPlan(User $user): bool
|
||||
{
|
||||
if (! $this->gatingActive()) {
|
||||
return true;
|
||||
@@ -38,6 +59,16 @@ class SubscriptionService
|
||||
return (bool) $this->subscriptionFor($user)?->entitled();
|
||||
}
|
||||
|
||||
public function isEnterprise(User $user): bool
|
||||
{
|
||||
return $this->planKey($user) === ProSubscription::PLAN_ENTERPRISE;
|
||||
}
|
||||
|
||||
public function isPro(User $user): bool
|
||||
{
|
||||
return $this->hasPaidPlan($user);
|
||||
}
|
||||
|
||||
public function productCount(string $ownerRef, bool $restaurant): int
|
||||
{
|
||||
if ($restaurant) {
|
||||
@@ -84,41 +115,56 @@ class SubscriptionService
|
||||
{
|
||||
$existing = $this->subscriptionFor($user);
|
||||
if ($existing && $existing->entitled() && $existing->status === ProSubscription::STATUS_ACTIVE) {
|
||||
return [true, 'You are already on Ladill POS Pro.'];
|
||||
return [true, 'You already have an active subscription.'];
|
||||
}
|
||||
|
||||
$price = $this->priceMinor();
|
||||
$reference = 'pos-pro-'.$user->id.'-'.now()->format('YmdHis').'-'.Str::random(6);
|
||||
$result = $this->billing->charge($user, $price, $reference, 'Ladill POS Pro — monthly subscription');
|
||||
|
||||
if (! $result['ok']) {
|
||||
if ($result['insufficient']) {
|
||||
$bal = number_format(((int) $result['balance_minor']) / 100, 2);
|
||||
|
||||
return [false, "Your wallet balance (GHS {$bal}) is too low. Top up, then subscribe."];
|
||||
}
|
||||
|
||||
return [false, $result['error'] ?? 'Could not start your subscription. Please try again.'];
|
||||
if ($existing?->plan === ProSubscription::PLAN_ENTERPRISE) {
|
||||
return $this->subscribeEnterprise($user);
|
||||
}
|
||||
|
||||
$periodEnd = Carbon::now()->addDays((int) config('pos.pro.period_days', 30));
|
||||
return $this->activateWalletPlan($user, ProSubscription::PLAN_PRO, $this->priceMinor(), 'Ladill POS Pro — monthly subscription', 'Welcome to Ladill POS Pro!');
|
||||
}
|
||||
|
||||
/** @return array{0:bool,1:string} */
|
||||
public function subscribeEnterprise(User $user): array
|
||||
{
|
||||
$existing = $this->subscriptionFor($user);
|
||||
if ($existing && $existing->entitled() && $existing->status === ProSubscription::STATUS_ACTIVE) {
|
||||
return [true, 'You already have an active subscription.'];
|
||||
}
|
||||
|
||||
return $this->activateWalletPlan(
|
||||
$user,
|
||||
ProSubscription::PLAN_ENTERPRISE,
|
||||
$this->enterprisePriceMinor(),
|
||||
'Ladill POS Business — monthly subscription',
|
||||
'Welcome to Ladill POS Business!',
|
||||
);
|
||||
}
|
||||
|
||||
public function activatePrepaid(User $user, string $plan, int $months): void
|
||||
{
|
||||
$existing = $this->subscriptionFor($user);
|
||||
$price = $plan === ProSubscription::PLAN_ENTERPRISE
|
||||
? $this->enterprisePriceMinor()
|
||||
: $this->priceMinor();
|
||||
|
||||
ProSubscription::updateOrCreate(
|
||||
['user_id' => $user->id],
|
||||
[
|
||||
'plan' => $plan,
|
||||
'status' => ProSubscription::STATUS_ACTIVE,
|
||||
'price_minor' => $price,
|
||||
'currency' => (string) config('pos.pro.currency', 'GHS'),
|
||||
'auto_renew' => true,
|
||||
'auto_renew' => false,
|
||||
'started_at' => $existing?->started_at ?? now(),
|
||||
'current_period_end' => $periodEnd,
|
||||
'current_period_end' => Carbon::now()->addMonths($months),
|
||||
'last_charged_at' => now(),
|
||||
'canceled_at' => null,
|
||||
'last_reference' => $reference,
|
||||
'last_reference' => null,
|
||||
'last_error' => null,
|
||||
],
|
||||
);
|
||||
|
||||
return [true, 'Welcome to Ladill POS Pro! Your subscription is active.'];
|
||||
}
|
||||
|
||||
/** @return array{0:bool,1:string} */
|
||||
@@ -137,7 +183,7 @@ class SubscriptionService
|
||||
|
||||
$until = optional($sub->current_period_end)->format('d M Y');
|
||||
|
||||
return [true, "Auto-renew is off. You keep Pro until {$until}."];
|
||||
return [true, "Auto-renew is off. You keep access until {$until}."];
|
||||
}
|
||||
|
||||
public function renewIfDue(ProSubscription $sub): void
|
||||
@@ -154,8 +200,9 @@ class SubscriptionService
|
||||
return;
|
||||
}
|
||||
|
||||
$reference = 'pos-pro-'.$user->id.'-'.now()->format('YmdHis').'-'.Str::random(6);
|
||||
$result = $this->billing->charge($user, $sub->price_minor, $reference, 'Ladill POS Pro — renewal');
|
||||
$planLabel = $sub->plan === ProSubscription::PLAN_ENTERPRISE ? 'Business' : 'Pro';
|
||||
$reference = 'pos-'.$sub->plan.'-'.$user->id.'-'.now()->format('YmdHis').'-'.Str::random(6);
|
||||
$result = $this->billing->charge($user, $sub->price_minor, $reference, "Ladill POS {$planLabel} — renewal");
|
||||
|
||||
if ($result['ok']) {
|
||||
$sub->forceFill([
|
||||
@@ -175,4 +222,42 @@ class SubscriptionService
|
||||
$sub->forceFill(['last_error' => $result['error']])->save();
|
||||
}
|
||||
}
|
||||
|
||||
/** @return array{0:bool,1:string} */
|
||||
private function activateWalletPlan(User $user, string $plan, int $price, string $description, string $successMessage): array
|
||||
{
|
||||
$existing = $this->subscriptionFor($user);
|
||||
$reference = 'pos-'.$plan.'-'.$user->id.'-'.now()->format('YmdHis').'-'.Str::random(6);
|
||||
$result = $this->billing->charge($user, $price, $reference, $description);
|
||||
|
||||
if (! $result['ok']) {
|
||||
if ($result['insufficient']) {
|
||||
$bal = number_format(((int) $result['balance_minor']) / 100, 2);
|
||||
|
||||
return [false, "Your wallet balance (GHS {$bal}) is too low. Top up, then subscribe."];
|
||||
}
|
||||
|
||||
return [false, $result['error'] ?? 'Could not start your subscription. Please try again.'];
|
||||
}
|
||||
|
||||
$periodEnd = Carbon::now()->addDays((int) config('pos.pro.period_days', 30));
|
||||
ProSubscription::updateOrCreate(
|
||||
['user_id' => $user->id],
|
||||
[
|
||||
'plan' => $plan,
|
||||
'status' => ProSubscription::STATUS_ACTIVE,
|
||||
'price_minor' => $price,
|
||||
'currency' => (string) config('pos.pro.currency', 'GHS'),
|
||||
'auto_renew' => true,
|
||||
'started_at' => $existing?->started_at ?? now(),
|
||||
'current_period_end' => $periodEnd,
|
||||
'last_charged_at' => now(),
|
||||
'canceled_at' => null,
|
||||
'last_reference' => $reference,
|
||||
'last_error' => null,
|
||||
],
|
||||
);
|
||||
|
||||
return [true, $successMessage.' Your subscription is active.'];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user