Split checkout widget into separate components

This commit is contained in:
2025-05-15 17:00:20 +02:00
parent ec87ae59fc
commit 732fdb8687
23 changed files with 884 additions and 644 deletions

View File

@@ -12,6 +12,7 @@ type Invoice = {
vat: number
processing_fee: number
country: string
payment_gateway: string
payment_method: string
status: string
}
@@ -197,6 +198,7 @@ onMount(() => {
<td>VAT</td>
<td>Fee</td>
<td>Country</td>
<td>Gateway</td>
<td>Method</td>
<td>Status</td>
</tr>
@@ -210,6 +212,7 @@ onMount(() => {
<td><Euro amount={row.vat}/></td>
<td><Euro amount={row.processing_fee}/></td>
<td>{row.country}</td>
<td>{row.payment_gateway}</td>
<td>{row.payment_method}</td>
<td>{row.status}</td>
</tr>

View File

@@ -1,5 +1,5 @@
<script>
import CreditDeposit from "layout/CreditDeposit.svelte";
import Checkout from "layout/checkout/Checkout.svelte";
import LoginRegister from "login/LoginRegister.svelte";
import Euro from "util/Euro.svelte";
</script>
@@ -19,13 +19,13 @@ import Euro from "util/Euro.svelte";
balance is <Euro amount={window.user.balance_micro_eur}/>. Use
the form below to top up your balance.
</p>
<CreditDeposit/>
<Checkout/>
{:else}
<p>
You are currently logged in as '{window.user.username}'. Use the
form below to activate prepaid on this account.
</p>
<CreditDeposit/>
<Checkout/>
{/if}
</section>
</div>

View File

@@ -1,334 +0,0 @@
<script lang="ts">
import { onMount } from "svelte";
import Euro from "util/Euro.svelte";
import LoadingIndicator from "util/LoadingIndicator.svelte";
import { countries } from "country-data-list";
import { get_endpoint, get_misc_vat_rate } from "lib/PixeldrainAPI";
import CreditDepositNav from "./CreditDepositNav.svelte";
let loading = false
let amount = 20
let country: typeof countries.all[0] = null
let country_input = ""
let provider: PaymentProvider = null
let vat = 0
const amounts = [10, 20, 50, 100, 200, 500, 1000, 2000, 5000]
onMount(() => {
const checkout_country = window.localStorage.getItem("checkout_country")
if (countries[checkout_country] !== undefined) {
country_input = checkout_country
set_country()
}
const checkout_provider = window.localStorage.getItem("checkout_provider")
for (const p of providers) {
if (p.name === checkout_provider) {
set_provider(p)
break
}
}
})
type PaymentProvider = {
icon: string,
name: string,
label: string,
crypto?: boolean,
};
const providers: PaymentProvider[] = [
{icon: "paypal_full", name: "paypal", label: "PayPal"},
{icon: "mollie", name: "mollie", label: "Mollie"},
{icon: "bitcoin", name: "btc", label: "Bitcoin", crypto: true},
{icon: "dogecoin", name: "doge", label: "Dogecoin", crypto: true},
{icon: "monero", name: "xmr", label: "Monero", crypto: true},
]
const payment_providers = [
{icon: "paypal", name: "PayPal"},
{icon: "creditcard", name: "Credit/debit"},
{icon: "apple_pay", name: "Apple Pay"},
{icon: "google_pay", name: "Google Pay"},
{icon: "bancomat", name: "Bancomat"},
{icon: "bancontact", name: "Bancontact"},
{icon: "belfius", name: "Belfius"},
{icon: "blik", name: "Blik"},
{icon: "eps", name: "EPS"},
{icon: "ideal", name: "iDEAL"},
{icon: "ideal_in3", name: "iDeal in3"},
{icon: "kbc", name: "KBC"},
{icon: "mb_way", name: "MB Way"},
{icon: "multibanco", name: "Multibanco"},
{icon: "p24", name: "Przelewy24"},
{icon: "riverty", name: "Riverty"},
{icon: "satispay", name: "Satispay"},
{icon: "sepa", name: "SEPA Transfer"},
{icon: "twint", name: "Twint"},
]
const set_country = async (e?: Event) => {
loading = true
if (e !== undefined) {
e.preventDefault()
}
if (countries[country_input] === undefined) {
alert("Please enter a valid country code")
return
}
const c = countries[country_input]
// Cache the value for next checkout
window.localStorage.setItem("checkout_country", c.alpha3)
try {
const vat_rate = await get_misc_vat_rate(c.alpha3)
vat = vat_rate.vat*100
country = c
} catch (err) {
alert(err)
} finally {
loading = false
}
}
const set_provider = (p: PaymentProvider) => {
provider = p
// Cache the value for next checkout
window.localStorage.setItem("checkout_provider", p.name)
}
const checkout = async () => {
loading = true
if (amount < 10) {
alert("Amount needs to be at least €10")
return
}
const form = new FormData()
form.set("amount", String(amount*1e6))
form.set("network", provider.name)
form.set("country", country.alpha2)
try {
const resp = await fetch(
get_endpoint()+"/user/invoice",
{method: "POST", body: form},
)
if(resp.status >= 400) {
let json = await resp.json()
throw json.message
}
window.location = (await resp.json()).checkout_url
} catch (err) {
alert(err)
} finally {
loading = false
}
}
const format_country = (c: typeof countries.all[0]) => {
let str = ""
if (c.emoji !== undefined) {
str += c.emoji + " "
} else {
str += "🌐 "
}
str += c.name+" "
str += "("+c.alpha2+", "
str += c.alpha3+")"
return str
}
</script>
<div class="highlight_border">
<LoadingIndicator loading={loading}/>
{#if country === null}
<div>
Please pick your country of residence
</div>
<div>
<form on:submit|preventDefault={set_country} class="country_form">
<div class="country_search">
<datalist id="country_picker">
{#each countries.all.filter(c => c.status === "assigned") as c}
<option value={c.alpha2}>{format_country(c)}</option>
{/each}
</datalist>
<input
bind:value={country_input}
type="text"
list="country_picker"
placeholder="Search for country"
style="flex: 1 1 auto;">
<button type="submit" class="button_highlight" style="flex: 0 0 auto;">
<i class="icon">send</i>
Continue
</button>
</div>
<select bind:value={country_input} on:dblclick={set_country} style="padding: 0;" size="10">
{#each countries.all.filter(c => c.status === "assigned") as c}
<option value={c.alpha2}>{format_country(c)}</option>
{/each}
</select>
</form>
</div>
<hr/>
<div>
We support the following payment processors
</div>
<div class="processors">
{#each payment_providers as p (p.name)}
<div>
<img class="bankicon" src="/res/img/payment_providers/{p.icon}.svg" alt={p.name} title={p.name}/>
{p.name}
</div>
{/each}
</div>
{:else if provider === null}
<CreditDepositNav bind:country={country} bind:provider={provider} bind:vat={vat}/>
<h2>Please select a payment provider</h2>
<div class="providers">
{#each providers as p (p.name)}
<button on:click={() => set_provider(p)}>
<img src="/res/img/payment_providers/{p.icon}.svg" alt={p.label} title={p.label}/>
{p.label}
</button>
{/each}
</div>
{:else}
<CreditDepositNav bind:country={country} bind:provider={provider} bind:vat={vat}/>
{#if provider.crypto === true}
<p style="text-align: initial;" class="highlight_blue">
When paying with cryptocurrencies it is important that you pay the
<b>exact amount</b> stated on the order. If you pay too little, the
order fails. If you pay too much then the remaining credit will not
be added to your account. Pay close attention when sending a payment
from an online exchange, sometimes they will subtract the fees from
the amount sent which will cause the payment to fail.
</p>
{/if}
<form class="amount_grid" on:submit|preventDefault={checkout}>
<div class="span3">Please choose an amount</div>
{#each amounts as a}
<button
on:click|preventDefault={() => amount = a}
class="amount_button"
class:button_highlight={amount === a}
>
{a}
</button>
{/each}
<div class="span3 mollie_checkout">
<div>Custom amount €</div>
<input type="number" bind:value={amount} min="10"/>
</div>
<div class="span2" style="text-align: initial;">
Total including VAT:
<Euro amount={(amount*1e6) + (amount*1e6)*(vat/100)}/>
</div>
<button type="submit" class="button_highlight">
<i class="icon">paid</i> Checkout
</button>
</form>
<hr/>
<p style="text-align: initial;">
This Pixeldrain premium plan costs €1 per month, but due to
processing fees we can't accept payments less than €10. So your
deposit will give roughly 10 months of premium service depending on
usage. You can track your spending on the <a
href="/user/prepaid/transactions">transactions page</a>.
</p>
{/if}
</div>
<style>
.country_form {
display: flex;
flex-direction: column;
margin: auto;
width: 600px;
max-width: 100%;
}
.country_search {
display: flex;
flex-direction: row;
}
.processors {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
.processors > div {
display: flex;
align-items: center;
gap: 5px;
margin: 3px;
}
.providers {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(10em, 1fr));
}
.providers > button {
flex-direction: column;
justify-content: space-around;
}
.providers > button > img {
max-width: 3em;
max-height: 3em;
}
.amount_grid {
max-width: 500px;
gap: 4px;
display: inline-grid;
align-items: center;
grid-template-columns: 1fr 1fr 1fr;
}
.amount_button {
font-size: 1.1em;
line-height: 1.6em;
justify-content: center;
}
.span2 {
grid-column: span 2;
}
.span3 {
grid-column: span 3;
}
.mollie_checkout {
display: flex;
flex-direction: row;
}
.mollie_checkout > div {
display: flex;
flex: 0 0 auto;
align-items: center;
}
.mollie_checkout > input[type="number"] {
flex: 1 1 auto;
}
.bankicon {
width: 40px;
height: 30px;
}
</style>

View File

@@ -1,29 +0,0 @@
<script lang="ts">
export let country: {name?: string, emoji?: string} = null
export let provider: {label: string} = null
export let vat = 0
</script>
<div style="display: flex;">
{#if provider !== null}
<button on:click={() => provider = null} style="flex: 0 0 auto;">
<i class="icon">chevron_left</i>
Change provider
</button>
{:else if country !== null}
<button on:click={() => country = null} style="flex: 0 0 auto;">
<i class="icon">chevron_left</i>
Change country
</button>
{/if}
<div style="flex: 1 1 auto;"></div>
<div style="flex: 0 0 auto; display: flex; gap: 0.25em; align-items: center;">
<span>Paying from</span>
<span style="font-size: 1.5em; line-height: 1em;">{country.emoji}</span>
<span>
{country.name}
({vat}% VAT)
{#if provider !== null}with {provider.label}{/if}
</span>
</div>
</div>

View File

@@ -0,0 +1,100 @@
<script lang="ts">
import { onMount } from "svelte";
import LoadingIndicator from "util/LoadingIndicator.svelte";
import PickAmount from "./PickAmount.svelte";
import PickCountry from "./PickCountry.svelte";
import { payment_providers, type CheckoutState } from "./CheckoutLib";
import PickName from "./PickName.svelte";
import { get_misc_vat_rate, get_user } from "lib/PixeldrainAPI";
import { countries } from "country-data-list";
import PickProvider from "./PickProvider.svelte";
let loading = false
let state: CheckoutState = {country: null, provider: null, amount: 0, vat: 0, name: ""}
onMount(async () => {
try {
const user = await get_user()
// Get the country from the user profile
if (user.checkout_country !== "" && countries[user.checkout_country] !== undefined) {
const vat_rate = await get_misc_vat_rate(user.checkout_country)
state.vat = vat_rate.vat*100
state.country = countries[user.checkout_country]
}
// Get the provider from the user profile
for (const p of payment_providers) {
if (p.name === user.checkout_provider) {
state.provider = p
break
}
}
// Get the checkout name from the user profile
if (user.checkout_name !== "") {
state.name = user.checkout_name
}
}catch (err) {
alert("Failed to get user:"+err)
}
})
</script>
<div class="highlight_border">
<LoadingIndicator loading={loading}/>
{#if state.country !== null}
<div class="navbar">
{#if state.country !== null}
<button on:click={() => state.country = null}>
Country:
<span style="font-size: 1.5em; line-height: 1em;">{state.country.emoji}</span>
{state.country.name}
</button>
{/if}
{#if state.provider !== null}
<button on:click={() => state.provider = null}>
Provider:
<img
class="provider_icon"
src="/res/img/payment_providers/{state.provider.icon}.svg"
alt={state.provider.label}
title={state.provider.label}/>
{state.provider.label}
</button>
{/if}
{#if state.name !== ""}
<button on:click={() => state.name = ""}>
Name: {state.name}
</button>
{/if}
</div>
<hr/>
{/if}
{#if state.country === null}
<PickCountry bind:state bind:loading={loading}/>
{:else if state.provider === null}
<PickProvider bind:state/>
{:else if state.provider.need_name === true && state.name === ""}
<PickName bind:state/>
{:else}
<PickAmount bind:state bind:loading/>
{/if}
</div>
<style>
.navbar {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
gap: 0.5em;
}
.provider_icon {
height: 1.5em;
}
</style>

View File

@@ -0,0 +1,106 @@
import type { countries } from "country-data-list";
import { get_endpoint } from "lib/PixeldrainAPI";
export type CheckoutState = {
country: typeof countries.all[0]
provider: PaymentProvider
amount: number
vat: number
name: string
}
export type PaymentProvider = {
icon: string
name: string
label: string
crypto?: boolean
need_name?: boolean
country_filter?: string[]
};
export const payment_providers: PaymentProvider[] = [
{
icon: "paypal",
name: "paypal",
label: "PayPal",
}, {
icon: "bancontact",
name: "bancontact",
label: "Bancontact",
need_name: true,
country_filter: ["BE"],
}, {
icon: "eps",
name: "eps",
label: "EPS",
need_name: true,
country_filter: ["AT"],
}, {
icon: "ideal",
name: "ideal",
label: "iDEAL",
need_name: true,
country_filter: ["NL"],
}, {
icon: "p24",
name: "p24",
label: "Przelewy24",
need_name: true,
country_filter: ["PL"],
}, {
// icon: "trustly",
// name: "trustly",
// label: "Trustly",
// need_name: true,
// country_filter: ["AT", "DE", "DK", "EE", "ES", "FI", "GB", "LT", "LV", "NL", "NO", "SE"]
// }, {
icon: "bitcoin",
name: "btc",
label: "Bitcoin",
crypto: true,
}, {
icon: "dogecoin",
name: "doge",
label: "Dogecoin",
crypto: true,
}, {
icon: "monero",
name: "xmr",
label: "Monero",
crypto: true,
},
]
export const checkout = async (state: CheckoutState) => {
if (state.amount < 10) {
alert("Amount needs to be at least €10")
return
} else if (state.provider.need_name && !state.name) {
alert("Name is required for this provider")
return
}
const form = new FormData()
form.set("amount", String(state.amount * 1e6))
form.set("network", state.provider.name)
form.set("country", state.country.alpha2)
if (state.provider.need_name) {
form.set("name", state.name)
}
try {
const resp = await fetch(
get_endpoint() + "/user/invoice",
{ method: "POST", body: form },
)
if (resp.status >= 400) {
let json = await resp.json()
throw json.message
}
window.location = (await resp.json()).checkout_url
} catch (err) {
alert(err)
}
}

View File

@@ -0,0 +1,97 @@
<script lang="ts">
import Euro from "util/Euro.svelte";
import { checkout, type CheckoutState } from "./CheckoutLib";
export let state: CheckoutState
export let loading: boolean
const amounts = [10, 20, 50, 100, 200, 500, 1000, 2000, 5000]
let amount = 20
const submit = async (e: SubmitEvent) => {
e.preventDefault()
loading = true
state.amount = amount
await checkout(state)
loading = false
}
</script>
{#if state.provider.crypto === true}
<p style="text-align: initial;" class="highlight_blue">
When paying with cryptocurrencies it is important that you pay
the <b>exact amount</b> stated on the order. If you pay too
little, the order fails. If you pay too much then the remaining
credit will not be added to your account. Pay close attention
when sending a payment from an online exchange, sometimes they
will subtract the fees from the amount sent which will cause the
payment to fail.
</p>
{/if}
<form class="amount_grid" on:submit={submit}>
<div class="span3">Please choose an amount</div>
{#each amounts as a}
<button
on:click|preventDefault={() => amount = a}
class="amount_button"
class:button_highlight={amount === a}
>
{a}
</button>
{/each}
<div class="span3 custom_amount">
<div>Custom amount €</div>
<input type="number" bind:value={amount} min="10"/>
</div>
<div class="span2" style="text-align: initial;">
Total including VAT:
<Euro amount={(amount*1e6) + (amount*1e6)*(state.vat/100)}/>
</div>
<button type="submit" class="button_highlight">
<i class="icon">paid</i> Checkout
</button>
</form>
<hr/>
<p style="text-align: initial;">
This Pixeldrain premium plan costs €1 per month, but due to
processing fees we can't accept payments less than €10. So your
deposit will give roughly 10 months of premium service depending on
usage. You can track your spending on the <a
href="/user/prepaid/transactions">transactions page</a>.
</p>
<style>
.amount_grid {
max-width: 500px;
gap: 4px;
display: inline-grid;
align-items: center;
grid-template-columns: 1fr 1fr 1fr;
}
.amount_button {
font-size: 1.1em;
line-height: 1.6em;
justify-content: center;
}
.span2 { grid-column: span 2; }
.span3 { grid-column: span 3; }
.custom_amount {
display: flex;
flex-direction: row;
}
.custom_amount > div {
display: flex;
flex: 0 0 auto;
align-items: center;
}
.custom_amount > input {
flex: 1 1 auto;
}
</style>

View File

@@ -0,0 +1,122 @@
<script lang="ts">
import { countries } from "country-data-list";
import { get_misc_vat_rate, put_user } from "lib/PixeldrainAPI";
import { payment_providers, type CheckoutState } from "./CheckoutLib";
export let state: CheckoutState
export let loading: boolean
let country_input = ""
const set_country = async (e?: Event) => {
loading = true
if (e !== undefined) {
e.preventDefault()
}
if (countries[country_input] === undefined) {
alert("Please enter a valid country code")
return
}
const c = countries[country_input]
// We always clear the provider field after picking a country, as providers
// vary per country. If we did not clear the provider then we may end up
// with an invalid combination
state.provider = null
try {
// Save the user's country for later use
await put_user({checkout_country: c.alpha3})
const vat_rate = await get_misc_vat_rate(c.alpha3)
state.vat = vat_rate.vat*100
state.country = c
} catch (err) {
alert(err)
} finally {
loading = false
}
}
const format_country = (c: typeof countries.all[0]) => {
let str = ""
if (c.emoji !== undefined) {
str += c.emoji + " "
} else {
str += "🌐 "
}
str += c.name+" "
str += "("+c.alpha2+", "
str += c.alpha3+")"
return str
}
</script>
<div>
Please pick your country of residence
</div>
<div>
<form on:submit|preventDefault={set_country} class="country_form">
<div class="country_search">
<datalist id="country_picker">
{#each countries.all.filter(c => c.status === "assigned") as c}
<option value={c.alpha2}>{format_country(c)}</option>
{/each}
</datalist>
<input
bind:value={country_input}
type="text"
list="country_picker"
placeholder="Search for country"
style="flex: 1 1 auto;">
<button type="submit" class="button_highlight" style="flex: 0 0 auto;">
<i class="icon">send</i>
Continue
</button>
</div>
<select bind:value={country_input} on:dblclick={set_country} style="padding: 0;" size="10">
{#each countries.all.filter(c => c.status === "assigned") as c}
<option value={c.alpha2}>{format_country(c)}</option>
{/each}
</select>
</form>
</div>
<hr/>
<div>
We support the following payment processors
</div>
<div class="processors">
{#each payment_providers as p (p.name)}
<div>
<img class="bankicon" src="/res/img/payment_providers/{p.icon}.svg" alt={p.label} title={p.label}/>
{p.label}
</div>
{/each}
</div>
<style>
.country_form {
display: flex;
flex-direction: column;
margin: auto;
width: 600px;
max-width: 100%;
}
.country_search {
display: flex;
flex-direction: row;
}
.processors {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
.processors > div {
display: flex;
align-items: center;
gap: 5px;
margin: 3px;
}
.bankicon {
width: 2em;
}
</style>

View File

@@ -0,0 +1,50 @@
<script lang="ts">
import { put_user } from "lib/PixeldrainAPI";
import { type CheckoutState } from "./CheckoutLib";
export let state: CheckoutState
let name: string
const submit = async (e: SubmitEvent) => {
e.preventDefault()
try {
await put_user({checkout_name: name})
} catch(err) {
alert("Failed to update user:"+ err)
}
state.name = name
}
</script>
<form class="name_form" on:submit={submit}>
<div>
This payment provider requires a name for checkout. Please enter your
full name below
</div>
<input bind:value={name} type="text" autocomplete="name"/>
<button type="submit" class="button_highlight" style="align-self: end;">
Continue
<i class="icon">chevron_right</i>
</button>
</form>
<hr/>
<p style="text-align: initial;">
This Pixeldrain premium plan costs €1 per month, but due to
processing fees we can't accept payments less than €10. So your
deposit will give roughly 10 months of premium service depending on
usage. You can track your spending on the <a
href="/user/prepaid/transactions">transactions page</a>.
</p>
<style>
.name_form {
display: flex;
flex-direction: column;
margin: auto;
width: 500px;
max-width: 100%;
}
</style>

View File

@@ -0,0 +1,44 @@
<script lang="ts">
import { put_user } from "lib/PixeldrainAPI";
import { payment_providers, type CheckoutState, type PaymentProvider } from "./CheckoutLib";
export let state: CheckoutState
const set_provider = async (p: PaymentProvider) => {
try {
await put_user({checkout_provider: p.name})
} catch(err) {
alert("Failed to update user:"+ err)
}
state.provider = p
}
</script>
<span>Please select a payment provider</span>
<div class="providers">
{#each payment_providers as p (p.name)}
{#if p.country_filter === undefined || p.country_filter.includes(state.country.alpha2)}
<button on:click={() => set_provider(p)}>
<img src="/res/img/payment_providers/{p.icon}.svg" alt={p.label} title={p.label}/>
{p.label}
</button>
{/if}
{/each}
</div>
<style>
.providers {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(8em, 1fr));
}
.providers > button {
flex-direction: column;
justify-content: space-around;
}
.providers > button > img {
max-width: 3em;
max-height: 3em;
}
</style>

View File

@@ -24,6 +24,9 @@ export type User = {
file_embed_domains: string,
skip_file_viewer: boolean,
affiliate_user_name: string,
checkout_country: string,
checkout_name: string,
checkout_provider: string,
}
export type Subscription = {

View File

@@ -4,6 +4,7 @@ import CopyButton from "layout/CopyButton.svelte";
import Form from "util/Form.svelte";
import Button from "layout/Button.svelte";
import OtpSetup from "./OTPSetup.svelte";
import { put_user } from "lib/PixeldrainAPI";
let affiliate_link = window.location.protocol+"//"+window.location.host + "?ref=" + encodeURIComponent(window.user.username)
let affiliate_deny = false
@@ -52,6 +53,22 @@ let account_settings = {
log in. If you forget your username you can still log in using
your e-mail address if you have one configured`,
separator: true,
}, {
name: "checkout_country",
label: "Country code used at checkout",
type: "text",
default_value: window.user.checkout_country,
}, {
name: "checkout_provider",
label: "Default payment provider used at checkout",
type: "text",
default_value: window.user.checkout_provider,
}, {
name: "checkout_name",
label: "Full name used at checkout",
type: "text",
default_value: window.user.checkout_name,
separator: true,
},
],
submit_label: `<i class="icon">save</i> Save`,
@@ -67,11 +84,23 @@ let account_settings = {
form.append("email", fields.email)
form.append("password_new", fields.password_new1)
form.append("username", fields.username)
form.append("checkout_country", fields.checkout_country)
form.append("checkout_provider", fields.checkout_provider)
form.append("checkout_name", fields.checkout_name)
const resp = await fetch(window.api_endpoint+"/user", { method: "PUT", body: form });
if(resp.status >= 400) {
return {error_json: await resp.json()}
try {
await put_user({
email: fields.email,
password_new: fields.password_new1,
username: fields.username,
checkout_country: fields.checkout_country,
checkout_provider: fields.checkout_provider,
checkout_name: fields.checkout_name,
})
} catch (err) {
return {error_json: err}
}
return {success: true, message: "Success! Your changes have been saved"}
},
}

View File

@@ -1,5 +1,5 @@
<script>
import CreditDeposit from "layout/CreditDeposit.svelte";
import Checkout from "layout/checkout/Checkout.svelte";
import { onMount } from "svelte";
import Euro from "util/Euro.svelte";
import { formatDate } from "util/Formatting";
@@ -99,7 +99,7 @@ onMount(() => {
</p>
</div>
{:else}
<CreditDeposit/>
<Checkout/>
{/if}
<h3 id="invoices">Past invoices</h3>