Skip to content

Commit

Permalink
feat: add an order confirmation page when purchasing a subscription.
Browse files Browse the repository at this point in the history
  • Loading branch information
zicklag committed Dec 14, 2024
1 parent 59d8606 commit 4b50525
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 4 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@sveltejs/vite-plugin-svelte": "4.0.0-next.6",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@types/canvas-confetti": "^1.6.4",
"@types/cookie": "^0.6.0",
"@types/dns-packet": "^5.6.5",
"@types/eslint": "^8.56.12",
Expand All @@ -43,6 +44,7 @@
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"autoprefixer": "^10.4.20",
"canvas-confetti": "^1.9.3",
"codemirror": "^6.0.1",
"cookie": "^0.6.0",
"date-fns": "^3.6.0",
Expand Down
16 changes: 16 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ export const reroute: Reroute = ({ url }) => {
return url.pathname;
}

// If this is a lemonsqueezy webhook.
if (url.host == env.PUBLIC_POLAR_WEBHOOK_DOMAIN) {
// If this is a polar webhook.
if (
url.host == env.PUBLIC_POLAR_WEBHOOK_DOMAIN &&
url.pathname == '/__internal__/polar-webhook'
) {
return '/__internal__/polar-webhook';
}

Expand Down
15 changes: 14 additions & 1 deletion src/lib/billing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class BillingEngine {
},
allowDiscountCodes: true,
discountId: env.POLAR_AUTO_DISCOUNT_ID,
successUrl: pubenv.PUBLIC_URL + '/my-profile'
successUrl: pubenv.PUBLIC_URL + `/order-confirmation`
});

return checkout.url;
Expand Down Expand Up @@ -197,6 +197,19 @@ class BillingEngine {
const subscriptionInfo = await this.getSubscriptionInfo(rauthyId);
await updateUserSubscriptionBenefits(rauthyId, subscriptionInfo.benefits);
}

/** Checks whether or not the subscription associated to a checkout has been received over the
* webhook. */
async checkoutSubscriptionIsReady(checkoutId: string): Promise<boolean> {
const checkout = await this.polar.checkouts.custom.get({ id: checkoutId });

const rauthyId = checkout.metadata.rauthyId;
if (typeof rauthyId != 'string') return false;

const info = await this.getSubscriptionInfo(rauthyId);

return !!info.subscriptions.find((s) => s.checkoutId == checkoutId);
}
}

export const billing = new BillingEngine();
2 changes: 1 addition & 1 deletion src/routes/(app)/[username]/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
});
</script>

<div class="flex flex-row flex-wrap-reverse sm:flex-nowrap">
<div class="flex flex-row flex-wrap-reverse justify-center sm:flex-nowrap">
{#if data.profileMatchesUserSession}
<aside
class="card sticky top-8 mx-4 my-8 flex w-full min-w-[15em] flex-col p-5 sm:h-[85vh] sm:w-auto"
Expand Down
75 changes: 75 additions & 0 deletions src/routes/(app)/order-confirmation/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<script lang="ts">
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { ProgressRadial } from '@skeletonlabs/skeleton';
import { fade } from 'svelte/transition';
import confetti from 'canvas-confetti';
function fireworks() {
var duration = 3 * 1000;
var animationEnd = Date.now() + duration;
var defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 0 };
function randomInRange(min: number, max: number) {
return Math.random() * (max - min) + min;
}
var interval = setInterval(function () {
var timeLeft = animationEnd - Date.now();
if (timeLeft <= 0) {
return clearInterval(interval);
}
var particleCount = 50 * (timeLeft / duration);
// since particles fall down, start a bit higher than random
confetti({
...defaults,
particleCount,
origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 }
});
confetti({
...defaults,
particleCount,
origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 }
});
}, 400);
}
let loading = $state(true);
onMount(() => {
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) fireworks();
const interval = setInterval(async () => {
const resp = await fetch($page.url.href + '/check');
if (resp.status == 200) {
loading = false;
clearInterval(interval);
}
}, 1000);
});
</script>

<main class="relative mt-8 flex flex-col items-center gap-4 px-2">
<h1 class="text-4xl font-bold">Order Confirmed 🎉</h1>

<p class="px-3 text-center">
Thank you for your subscription! We are now processing your order and setting you up.
</p>

{#if loading}
<div transition:fade>
<ProgressRadial width="w-[5em] absolute top-[6em]" />
</div>
{/if}

{#if !loading}
<button
transition:fade
class="variant-ghost btn absolute top-[13em]"
onclick={() => (window.location.href = '/my-profile')}
disabled={loading}>Go to Profile</button
>
{/if}
</main>
14 changes: 14 additions & 0 deletions src/routes/(app)/order-confirmation/check/+server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { billing } from '$lib/billing';
import { getSession } from '$lib/rauthy/server';
import type { RequestHandler } from '@sveltejs/kit';

export const GET: RequestHandler = async ({ request, fetch }) => {
const { sessionInfo } = await getSession(fetch, request);
if (!sessionInfo) return new Response(undefined, { status: 400 });

if ((await billing.getSubscriptionInfo(sessionInfo.user_id)).isSubscribed) {
return new Response(undefined, { status: 200 });
} else {
return new Response(undefined, { status: 202 });
}
};

0 comments on commit 4b50525

Please sign in to comment.