Created
June 29, 2022 17:26
-
-
Save wilsonowilson/16ffa7a238df1fb5e3827038530e972e to your computer and use it in GitHub Desktop.
Paddle Stripe-Style Checkout
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script context="module" lang="ts"> | |
import { getSubscriptionPlan } from '$lib/billing/api'; | |
import { Account, getInitialUser, SubscriptionPlan } from '$lib/core/api'; | |
export const router = false; | |
import type { Load } from '@sveltejs/kit'; | |
import { onMount } from 'svelte'; | |
export const load: Load = async ({ params }) => { | |
const id = params.planId; | |
const result = await getSubscriptionPlan(id); | |
if (result.isErr()) { | |
return { | |
status: 500, | |
error: result.error.message | |
}; | |
} | |
if (!result.value) { | |
return { status: 404 }; | |
} | |
return { | |
props: { | |
plan: result.value | |
} | |
}; | |
}; | |
</script> | |
<script lang="ts"> | |
import Icon from '$lib/core/components/Icon.svelte'; | |
import Logo from '$lib/core/components/Logo.svelte'; | |
import { getUserAccount } from '$lib/onboarding/api'; | |
import { goto } from '$app/navigation'; | |
import { page } from '$app/stores'; | |
import { account as accountStore } from '$lib/core/stores/accountStore'; | |
import { fade } from 'svelte/transition'; | |
import LoadingIcon from '$lib/core/components/LoadingIcon.svelte'; | |
export let plan: SubscriptionPlan; | |
let currency = 'USD'; | |
$: currencySymbol = currency == 'USD' ? '$' : ''; | |
let subtotal = ''; | |
let tax = ''; | |
let total = ''; | |
let taxPercent = ''; | |
let loading = true; | |
onMount(async () => { | |
const user = await getInitialUser(); | |
if (!user) { | |
goto('/signin'); | |
return; | |
} | |
let account: Account = $accountStore; | |
if (!account) { | |
const result = await getUserAccount(user.uid); | |
if (result.isErr()) { | |
return; | |
} else { | |
account = result.value; | |
} | |
} | |
// @ts-ignore | |
await Paddle.Setup({ | |
vendor: 140287, | |
eventCallback: function (eventData) { | |
updatePrices(eventData); | |
} | |
}); | |
// @ts-ignore | |
await Paddle.Checkout.open({ | |
method: 'inline', | |
product: plan.paddle_id, | |
allowQuantity: false, | |
successCallback: () => { | |
window.location.href = `/checkout/${$page.params.planId}/success`; | |
}, | |
frameTarget: 'checkout-container', | |
email: user.email, | |
passthrough: JSON.stringify({ | |
account_id: account.id | |
}), | |
frameStyle: 'width:100%; min-width:280px; background-color: transparent; border: none;' | |
}); | |
}); | |
function updatePrices(data) { | |
var _subtotal = | |
data.eventData.checkout.prices.customer.total - | |
data.eventData.checkout.prices.customer.total_tax; | |
subtotal = _subtotal.toFixed(2); | |
currency = data.eventData.checkout.prices.customer.currency; | |
tax = data.eventData.checkout.prices.customer.total_tax; | |
total = data.eventData.checkout.prices.customer.total; | |
taxPercent = ((parseFloat(tax) / parseFloat(subtotal)) * 100).toFixed(0).toString(); | |
loading = false; | |
} | |
</script> | |
<div class="flex flex-col h-screen"> | |
<div class="duration-100 flex-grow overflow-y-auto"> | |
<div class=" lg:grid lg:grid-cols-2 min-h-screen"> | |
<div | |
class=" {loading | |
? 'bg-zinc-200 text-black' | |
: 'bg-primary-base text-white'} duration-100 flex justify-center lg:justify-end py-12 lg:py-20 pr-20 w-full" | |
> | |
<div class="max-w-sm lg:max-w-[26rem] mx-auto lg:mx-0 w-full lg:float-right"> | |
<div class="w-full px-8 lg:px-12"> | |
<div | |
class="flex items-center gap-2 md:translate-x-0 -translate-x-6 text-zinc-300 mb-8 lg:-ml-8" | |
> | |
<a href="/pricing" class="hover:bg-white/20 duration-75 p-1 rounded-full"> | |
<Icon name="arrow-right" class="rotate-180" size="20px" /> | |
</a> | |
<div | |
class="p-1.5 {loading | |
? 'opacity-30' | |
: 'opacity-100'} duration-100 bg-white shadow-md rounded-full" | |
> | |
<Logo size="20px" /> | |
</div> | |
</div> | |
{#if loading} | |
<div class="w-44 h-6 plc" /> | |
{:else} | |
<div class="sm:text-lg whitespace-nowrap"> | |
Subscribe to {plan.name} | |
</div> | |
{/if} | |
<div class="flex items-end mt-2"> | |
{#if loading} | |
<div class="w-40 sm:w-64 h-8 sm:h-12 plc" /> | |
{:else} | |
<div class="font-bold text-2xl sm:text-4xl"> | |
{currencySymbol}{subtotal} | |
</div> | |
<div class="mb-1 ml-1 flex flex-none"> | |
<div> | |
/ | |
{#if plan.cycle == 'monthly'} | |
month | |
{:else if plan.cycle == 'yearly'} | |
year | |
{/if} | |
</div> | |
</div> | |
{/if} | |
</div> | |
{#if loading} | |
<div class="w-40 h-6 mt-2 plc lg:hidden" /> | |
{:else} | |
<p class="text-white/70 mt-2 lg:hidden">Includes {currencySymbol}{tax} VAT</p> | |
{/if} | |
<div class="hidden lg:block"> | |
<div class="flex justify-between mt-8"> | |
<div> | |
{#if loading} | |
<div class="w-24 h-9 plc" /> | |
{:else} | |
<div>{plan.name}</div> | |
<div class="text-sm text-opacity-70 text-white"> | |
{#if plan.cycle == 'monthly'} | |
Billed Monthly | |
{:else if plan.cycle == 'yearly'} | |
Billed Yearly | |
{:else if plan.cycle == 'lifetime'} | |
Lifetime | |
{/if} | |
</div> | |
{/if} | |
</div> | |
{#if loading} | |
<div class="w-16 h-5 plc" /> | |
{:else} | |
<div class="">{currencySymbol}{subtotal}</div> | |
{/if} | |
</div> | |
<div class="flex justify-between mt-8"> | |
<div> | |
{#if loading} | |
<div class="w-20 h-6 plc" /> | |
{:else} | |
<div>VAT ({taxPercent}%)</div> | |
{/if} | |
</div> | |
{#if loading} | |
<div class="w-16 h-5 plc" /> | |
{:else} | |
<div class="">{currencySymbol}{tax}</div> | |
{/if} | |
</div> | |
<hr class="border-white/20 mt-8" /> | |
<div class="flex justify-between mt-8"> | |
<div> | |
{#if loading} | |
<div class="w-28 h-6 plc" /> | |
{:else} | |
<div>Total due today</div> | |
{/if} | |
</div> | |
{#if loading} | |
<div class="w-16 h-5 plc" /> | |
{:else} | |
<div class="">{currencySymbol}{total}</div> | |
{/if} | |
</div> | |
{#if loading} | |
<div class="w-52 mt-28 h-5 plc" /> | |
{:else} | |
<div in:fade={{ duration: 200 }} class="mt-28 flex text-sm items-center gap-1"> | |
Powered by | |
<img | |
src="https://theme.zdassets.com/theme_assets/1561536/c0497b032cd66c6f8aa15ad713cf7bba60ad83ad.png" | |
alt="" | |
class="h-4" | |
/> | |
<div class="w-[1px] mx-2 bg-white/30 h-4" /> | |
<div class="flex items-center gap-2 text-sm text-white/80"> | |
<a href="https://paddle.com/legal/checkout-buyer-terms/" target="_blank" | |
>Terms</a | |
> | |
<a href="https://paddle.com/legal/privacy/" target="_blank">Privacy</a> | |
</div> | |
</div> | |
{/if} | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="lg:shadow-xl bg-white px-0 sm:px-8 py-12 lg:py-20 lg:pl-20 lg:pt-20 w-full"> | |
<div class="px-8 mx-auto lg:mx-0 max-w-md"> | |
{#if loading} | |
<div class="w-44 h-8 plc" /> | |
{:else} | |
<h3 class="text-xl font-bold">Payment Details</h3> | |
{/if} | |
{#if loading} | |
<div class="w-full h-14 mt-2 plc" /> | |
{:else} | |
<p class="text-sm text-gray-600 mt-2"> | |
Complete your purchase by providing your payment information. | |
</p> | |
{/if} | |
</div> | |
<div class="max-w-md px-4 w-full mt-1 mx-auto lg:mx-0"> | |
{#if loading} | |
<div class="mt-8 flex justify-center"> | |
<LoadingIcon size="28px" scale={1.4} /> | |
</div> | |
{/if} | |
<div class="checkout-container {loading ? 'opacity-0' : 'opacity-100'}" /> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<style> | |
.plc { | |
@apply bg-gradient-to-r from-black/5 to-black/5 rounded-md animate-pulse; | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment