Fixed issue with one-time payments; Updated packages

This commit is contained in:
giancarlo
2024-05-18 22:56:57 +07:00
parent 5c1e1d5cd0
commit 3cf3c263bc
25 changed files with 541 additions and 604 deletions

View File

@@ -28,7 +28,7 @@
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:^",
"@supabase/supabase-js": "^2.43.2",
"@types/react": "^18.3.1",
"@types/react": "^18.3.2",
"date-fns": "^3.6.0",
"lucide-react": "^0.378.0",
"next": "14.2.3",

View File

@@ -48,6 +48,8 @@ export function PlanPicker(
pending?: boolean;
}>,
) {
const { t } = useTranslation(`billing`);
const intervals = useMemo(
() => getPlanIntervals(props.config),
[props.config],
@@ -61,7 +63,7 @@ export function PlanPicker(
.object({
planId: z.string(),
productId: z.string(),
interval: z.string(),
interval: z.string().optional(),
})
.refine(
(data) => {
@@ -76,7 +78,22 @@ export function PlanPicker(
return false;
}
},
{ message: `Please pick a plan to continue`, path: ['planId'] },
{ message: t('noPlanChosen'), path: ['planId'] },
)
.refine(
(data) => {
try {
const { plan } = getProductPlanPair(props.config, data.planId);
return !(plan.paymentType === 'recurring' && !data.interval);
} catch {
return false;
}
},
{
message: t('noIntervalPlanChosen'),
path: ['interval'],
},
),
),
defaultValues: {
@@ -100,8 +117,6 @@ export function PlanPicker(
}
}, [props.config, planId]);
const { t } = useTranslation(`billing`);
// display the period picker if the selected plan is recurring or if no plan is selected
const isRecurringPlan =
selectedPlan?.paymentType === 'recurring' || !selectedPlan;
@@ -117,82 +132,88 @@ export function PlanPicker(
className={'flex w-full max-w-xl flex-col space-y-4'}
onSubmit={form.handleSubmit(props.onSubmit)}
>
<div
className={cn('transition-all', {
['pointer-events-none opacity-50']: !isRecurringPlan,
})}
>
<FormField
name={'interval'}
render={({ field }) => {
return (
<FormItem className={'rounded-md border p-4'}>
<FormLabel htmlFor={'plan-picker-id'}>
<Trans i18nKey={'common:billingInterval.label'} />
</FormLabel>
<If condition={intervals.length}>
<div
className={cn('transition-all', {
['pointer-events-none opacity-50']: !isRecurringPlan,
})}
>
<FormField
name={'interval'}
render={({ field }) => {
return (
<FormItem className={'rounded-md border p-4'}>
<FormLabel htmlFor={'plan-picker-id'}>
<Trans i18nKey={'common:billingInterval.label'} />
</FormLabel>
<FormControl id={'plan-picker-id'}>
<RadioGroup name={field.name} value={field.value}>
<div className={'flex space-x-2.5'}>
{intervals.map((interval) => {
const selected = field.value === interval;
<FormControl id={'plan-picker-id'}>
<RadioGroup name={field.name} value={field.value}>
<div className={'flex space-x-2.5'}>
{intervals.map((interval) => {
const selected = field.value === interval;
return (
<label
htmlFor={interval}
key={interval}
className={cn(
'flex items-center space-x-2 rounded-md border border-transparent px-4 py-2 transition-colors',
{
['border-primary']: selected,
['hover:border-primary']: !selected,
},
)}
>
<RadioGroupItem
id={interval}
value={interval}
onClick={() => {
form.setValue('interval', interval, {
shouldValidate: true,
});
if (selectedProduct) {
const plan = selectedProduct.plans.find(
(item) => item.interval === interval,
);
form.setValue('planId', plan?.id ?? '', {
shouldValidate: true,
shouldDirty: true,
shouldTouch: true,
});
}
}}
/>
<span
className={cn('text-sm', {
['cursor-pointer']: !selected,
})}
return (
<label
htmlFor={interval}
key={interval}
className={cn(
'flex items-center space-x-2 rounded-md border border-transparent px-4 py-2 transition-colors',
{
['border-primary']: selected,
['hover:border-primary']: !selected,
},
)}
>
<Trans
i18nKey={`billing:billingInterval.${interval}`}
/>
</span>
</label>
);
})}
</div>
</RadioGroup>
</FormControl>
<RadioGroupItem
id={interval}
value={interval}
onClick={() => {
form.setValue('interval', interval, {
shouldValidate: true,
});
<FormMessage />
</FormItem>
);
}}
/>
</div>
if (selectedProduct) {
const plan = selectedProduct.plans.find(
(item) => item.interval === interval,
);
form.setValue(
'planId',
plan?.id ?? '',
{
shouldValidate: true,
shouldDirty: true,
shouldTouch: true,
},
);
}
}}
/>
<span
className={cn('text-sm', {
['cursor-pointer']: !selected,
})}
>
<Trans
i18nKey={`billing:billingInterval.${interval}`}
/>
</span>
</label>
);
})}
</div>
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
</div>
</If>
<FormField
name={'planId'}

View File

@@ -14,7 +14,7 @@
"./components": "./src/components/index.ts"
},
"dependencies": {
"@lemonsqueezy/lemonsqueezy.js": "2.2.0"
"@lemonsqueezy/lemonsqueezy.js": "3.0.0"
},
"devDependencies": {
"@kit/billing": "workspace:^",
@@ -25,7 +25,7 @@
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:^",
"@types/react": "^18.3.1",
"@types/react": "^18.3.2",
"next": "14.2.3",
"react": "18.3.1",
"zod": "^3.23.8"

View File

@@ -28,7 +28,7 @@
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:^",
"@types/react": "^18.3.1",
"@types/react": "^18.3.2",
"date-fns": "^3.6.0",
"next": "14.2.3",
"react": "18.3.1",

View File

@@ -55,9 +55,10 @@ export async function createStripeCheckout(
customer_email: params.customerEmail,
};
const customerCreation = isSubscription
? ({} as Record<string, string>)
: { customer_creation: 'always' };
const customerCreation =
isSubscription || customer
? ({} as Record<string, string>)
: { customer_creation: 'always' };
const lineItems = params.plan.lineItems.map((item) => {
if (item.type === 'metered') {