Polls for order status & displays loading while status=initiated

This is the page sent to after the payment is verified clientside at
/checkout. While status is only initiated and not updated from stripe
webhook we display spinner.

TODO should still timeout to content message
This commit is contained in:
2022-12-29 23:10:11 +01:00
parent e86f22fbe2
commit 9c549b72f0
7 changed files with 145 additions and 63 deletions

View File

@@ -1,10 +1,33 @@
import validOrderId from '$lib/utils/validOrderId';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = ({ params }) => {
export const load: PageServerLoad = async ({ fetch, params, url }) => {
const { id } = params;
const email = url.searchParams.get('email');
let order = null;
try {
const res = await fetch(`/api/v1/order/${id}`);
if (res?.status === 404) {
return {
id,
email,
order: null
};
}
const orderResponse = await res.json();
if (orderResponse?.order && orderResponse?.order?.lineItems?.length > 0) {
order = orderResponse?.order;
}
} catch (error) {
console.error('unable to parse order response');
throw error;
}
return {
id,
isValidReceipt: validOrderId(id)
email,
order
};
};

View File

@@ -1,14 +1,27 @@
<script lang="ts">
import PoolInitiatedOrder from './PoolInitiatedOrder.svelte';
import ReceiptNotFound from './ReceiptNotFound.svelte';
import BadgeType from '$lib/interfaces/BadgeType';
import type { PageData } from './$types';
import PageMeta from '$lib/components/PageMeta.svelte';
export let data: PageData;
const isValidReceipt = data.isValidReceipt as boolean;
const id = data.id as string;
let orderStatus: BadgeType = BadgeType.NOT_FOUND;
if (data?.order) {
orderStatus = data.order?.status;
}
const id = data?.id as string;
</script>
{#if isValidReceipt}
<slot />
{:else}
<PageMeta title="Kvittering" description="Søk og finn din ordre kvittering" />
{#if orderStatus === BadgeType.NOT_FOUND}
<ReceiptNotFound id="{id}" />
{:else if orderStatus === BadgeType.INITIATED}
<PoolInitiatedOrder />
{:else}
<slot />
{/if}

View File

@@ -1,17 +1,6 @@
import { redirect } from '@sveltejs/kit';
import validOrderId from '$lib/utils/validOrderId';
import type { Actions, PageServerLoad } from './$types';
export const load: PageServerLoad = ({ params, url }) => {
const { id } = params;
const email = url.searchParams.get('email');
return {
id,
email
};
};
export const actions: Actions = {
default: async ({ request }) => {
const data = await request.formData();
@@ -19,12 +8,8 @@ export const actions: Actions = {
const orderId = data.get('order-id');
const email = data.get('order-email');
// TODO replace with posting form (json) to backend to check??
// also return statusCode from the backend directly.
if (validOrderId(String(orderId)) && email) {
const receiptUrl = `/receipt/${orderId}?email=${email}`;
throw redirect(303, receiptUrl);
}
const receiptUrl = `/receipt/${orderId}?email=${email}`;
throw redirect(303, receiptUrl);
return { success: false };
}

View File

@@ -1,8 +1,11 @@
<script lang="ts">
import CircleCheckmark from '$lib/icons/CircleCheckmark.svelte';
import { page } from '$app/stores';
import CircleCheckmark from '$lib/components/loading/CircleCheckmark.svelte';
import CircleError from '$lib/components/loading/CircleError.svelte';
import type { PageServerData } from './$types';
import type { ILineItem, IOrder } from '$lib/interfaces/IOrder';
import CircleWarning from '$lib/components/loading/CircleWarning.svelte';
function subTotal(lineItems: Array<ILineItem> = []) {
let total = 0;
@@ -10,20 +13,32 @@
return total;
}
export let data: PageServerData;
const id = data.id as string;
const email = data.email as string;
const order = data.order as IOrder;
let id: string;
let email: string;
let order: IOrder;
// export let currentRoute;
// const id = currentRoute?.namedParams?.id;
// const email = currentRoute?.queryParams?.email;
const { data } = $page;
if (data) {
id = data.id as string;
email = data.email || (data?.order?.customer?.email as string);
order = data.order as IOrder;
}
</script>
<section class="order-confirmation">
<CircleCheckmark />
{#if order.status === 'SUCCESS' || order.status === 'CONFIRMED'}
<CircleCheckmark />
{:else if order.status === 'CANCELLED' || order.status === 'REJECTED'}
<CircleError />
{:else}
<CircleWarning />
{/if}
<h1>Takk for din bestilling!</h1>
{#if order.status === 'SUCCESS' || order.status === 'CONFIRMED'}
<h1>Takk for din bestilling!</h1>
{:else}
<h1>Bestilling ikke gjennomført!</h1>
{/if}
<div class="order-description">
<p>
@@ -42,7 +57,7 @@
{/each}
<p>
<code>Shipping</code>
<code>NOK 79</code>
<code>NOK 75</code>
</p>
<p>
@@ -55,6 +70,10 @@
<style lang="scss">
@import './styles-receipt-page.scss';
.order-description .underline {
text-decoration: underline;
}
.order-receipt {
background-color: #f7f7f7;
max-width: 500px;

View File

@@ -0,0 +1,60 @@
<script lang="ts">
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import { onMount } from 'svelte';
import CircleLoading from '$lib/components/loading/CircleLoading.svelte';
import { buildApiUrl } from '$lib/utils/apiUrl';
import type { PageServerData } from './$types';
const { data } = $page;
const id = data?.id as string;
let maxPoolTime: Date = new Date();
maxPoolTime.setSeconds(maxPoolTime.getSeconds() + 15);
if (!id) {
console.log('no id found after all:(');
}
function redirect() {
const url = `/receipt/${id}`;
window.location.href = url;
goto(url);
}
function checkOrder() {
const url = buildApiUrl(`/api/v1/order/${id}`);
return fetch(url)
.then((resp) => resp.json())
.then((response) => {
response?.order?.status !== 'INITIATED' ? redirect() : null;
});
}
function pool() {
if (new Date() < maxPoolTime) {
setTimeout(() => checkOrder().then(() => pool()), 1500);
return;
}
}
onMount(pool);
</script>
<section class="order-confirmation">
<CircleLoading />
<h1>Bestillingen din behandles</h1>
<div class="order-description">
<p>Vent noen sekunder mens betalingen din blir godkjent</p>
</div>
</section>
<style lang="scss">
@import './styles-receipt-page.scss';
h1 {
text-align: center;
}
</style>

View File

@@ -1,33 +1,16 @@
<script lang="ts">
import Input from '$lib/components/Input.svelte';
import PlanetButton from '$lib/components/Button.svelte';
import CircleError from '$lib/icons/CircleError.svelte';
import CircleWarning from '$lib/icons/CircleWarning.svelte';
const CircleComponent = Math.random() > 0.5 ? CircleWarning : CircleError;
import CircleWarning from '$lib/components/loading/CircleWarning.svelte';
export let id: string;
// async function handleSubmit(event) {
// const data = new FormData(this);
// console.log('formdata::', data);
// const orderId = data.get('order-id');
// const orderEmail = data.get('order-email');
// console.log('orderId:', orderId)
// console.log('orderEmail:', orderEmail)
// const url = `/receipt/${orderId}?email=${orderEmail}`;
// goto(url);
// }
let searchOrderNumber: string;
let searchOrderEmail: string;
</script>
<section class="order-confirmation">
<svelte:component this="{CircleComponent}" />
<CircleWarning />
<h1>Fant ikke din bestilling!</h1>
@@ -40,7 +23,8 @@
<form class="order-search" method="POST">
<span>Du kan forsøke søke opp din ordre her:</span>
<Input name="order-id" label="Ordre id (hex)" bind:value="{searchOrderNumber}" />
<Input name="order-id" label="Ordre id " bind:value="{searchOrderNumber}" />
<br />
<Input name="order-email" label="Epost adresse" bind:value="{searchOrderEmail}" />
<PlanetButton text="Søk" />
@@ -62,6 +46,10 @@
}
}
.underline {
text-decoration: underline;
}
:global(.order-search button) {
margin-top: 1rem;
}

View File

@@ -1,7 +1,7 @@
@import '../../../styles/media-queries.scss';
.order-confirmation {
margin: 6rem auto 0;
margin: 10rem auto 0;
display: grid;
place-items: center;
padding: 0 0.5rem;
@@ -10,8 +10,6 @@
margin-top: 3rem;
}
// @include pageMargin;
@include tablet {
padding: 0 1rem;
}
@@ -31,8 +29,4 @@
padding: 1rem;
margin: 1rem 0;
text-align: center;
.underline {
text-decoration: underline;
}
}