Update prepaid checkout to allow all countries

This commit is contained in:
2025-04-09 09:47:15 +02:00
parent 4b297fec46
commit 5382d53513
43 changed files with 534 additions and 257 deletions

View File

@@ -10,6 +10,7 @@
"dependencies": {
"behave-js": "^1.5.0",
"chart.js": "^4.4.6",
"country-data-list": "^1.4.0",
"pure-color": "^1.3.0",
"rollup-plugin-includepaths": "^0.2.4",
"svelte-preprocess": "^6.0.3",
@@ -2302,9 +2303,9 @@
"license": "MIT"
},
"node_modules/caniuse-lite": {
"version": "1.0.30001677",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz",
"integrity": "sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==",
"version": "1.0.30001712",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001712.tgz",
"integrity": "sha512-MBqPpGYYdQ7/hfKiet9SCI+nmN5/hp4ZzveOJubl5DTAMa5oggjAuoi0Z4onBpKPFI2ePGnQuQIzF3VxDjDJig==",
"devOptional": true,
"funding": [
{
@@ -2436,6 +2437,12 @@
"url": "https://opencollective.com/core-js"
}
},
"node_modules/country-data-list": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/country-data-list/-/country-data-list-1.4.0.tgz",
"integrity": "sha512-3i1Q1onE9MaqYPdtTUzZRZJY7lBXWpud3eWXrw3ckJyvOzXcsFALiy0Z4Giksy5ECpq9ajYijMPxfRZvVP65UA==",
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",

View File

@@ -23,6 +23,7 @@
"dependencies": {
"behave-js": "^1.5.0",
"chart.js": "^4.4.6",
"country-data-list": "^1.4.0",
"pure-color": "^1.3.0",
"rollup-plugin-includepaths": "^0.2.4",
"svelte-preprocess": "^6.0.3",

View File

@@ -159,7 +159,8 @@ const report_description = () => {
<p>
If you think this file violates pixeldrain's
<a href="/abuse">content policy</a> you can report it for moderation
with this form. Please submit copyright infringement notices through our
with this form. For copyright infringement notices or urgent matters
please use our
<a href="/abuse#toc_2">abuse e-mail address</a>.
</p>
<form on:submit={submit} style="width: 100%" class="report_form">

View File

@@ -32,7 +32,7 @@ $: transfer_left = $stats.limits.transfer_limit - $stats.limits.transfer_limit_u
<p>
<strong>
<a href="/#pro" target="_blank" class="button button_highlight" rel="noreferrer">
<a href="/user/prepaid/deposit" target="_blank" class="button button_highlight" rel="noreferrer">
<i class="icon">bolt</i> Upgrade your account
</a>
to disable the transfer limit

View File

@@ -71,7 +71,7 @@ let file = {
<button on:click={() => {dispatch("download")}}>
<i class="icon">download</i> Download
</button>
<a href="/#pro" target="_blank" class="button button_highlight">
<a href="/user/prepaid/deposit" target="_blank" class="button button_highlight">
<i class="icon">bolt</i> Upgrade your account
</a>
</IconBlock>

View File

@@ -37,8 +37,8 @@ export let icon_href = ""
least {formatDuration((file_size/($stats.limits.speed_limit))*1000, 0)}
</li>
<li>
<a href="/#pro" target="_blank" class="button button_highlight">
<i class="icon">bolt</i> Upgrade your account
<a href="/user/prepaid/deposit" target="_blank" class="button button_highlight">
<i class="icon">bolt</i> Get Premium
</a>
and earn my eternal gratitude
{#if !window.user_authenticated}

View File

@@ -114,3 +114,14 @@ export const put_user = async (data: Object) => {
((window as any).user as User)[key] = data[key]
}
}
export type VATRate = {
name: string,
vat: number,
alpha2: string,
alpha3: string,
}
export const get_misc_vat_rate = async (country_code: string) => {
return await check_response(await fetch(get_endpoint() + "/misc/vat_rate/" + country_code)) as VATRate
}

View File

@@ -1,62 +1,74 @@
<script>
<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";
let loading = false
let amount = 20
let country = null
let country: typeof countries.all[0] = null
let country_input = ""
let vat = 0
$: credit_micro = amount*1e6
$: vat_micro = country === null ? 0 : (amount*1e6)*(country.vat/100)
let countries = [
{name: "Austria", flag: "🇦🇹", vat: 20},
{name: "Belgium", flag: "🇧🇪", vat: 21},
{name: "Bulgaria", flag: "🇧🇬", vat: 20},
{name: "Croatia", flag: "🇭🇷", vat: 25},
{name: "Cyprus", flag: "🇨🇾", vat: 19},
{name: "Czechia", flag: "🇨🇿", vat: 21},
{name: "Denmark", flag: "🇩🇰", vat: 25},
{name: "Estonia", flag: "🇪🇪", vat: 20},
{name: "Finland", flag: "🇫🇮", vat: 24},
{name: "France", flag: "🇫🇷", vat: 20},
{name: "Germany", flag: "🇩🇪", vat: 19},
{name: "Greece", flag: "🇬🇷", vat: 24},
{name: "Hungary", flag: "🇭🇺", vat: 27},
{name: "Ireland", flag: "🇮🇪", vat: 23},
{name: "Italy", flag: "🇮🇹", vat: 22},
{name: "Latvia", flag: "🇱🇻", vat: 21},
{name: "Lithuania", flag: "🇱🇹", vat: 21},
{name: "Luxembourg", flag: "🇱🇺", vat: 16},
{name: "Malta", flag: "🇲🇹", vat: 18},
{name: "Netherlands", flag: "🇳🇱", vat: 21},
{name: "Poland", flag: "🇵🇱", vat: 23},
{name: "Portugal", flag: "🇵🇹", vat: 23},
{name: "Romania", flag: "🇷🇴", vat: 19},
{name: "Slovakia", flag: "🇸🇰", vat: 20},
{name: "Slovenia", flag: "🇸🇮", vat: 22},
{name: "Spain", flag: "🇪🇸", vat: 21},
{name: "Sweden", flag: "🇸🇪", vat: 25},
{name: "Other", flag: "🌐", vat: 0},
]
let amounts = [10, 20, 50, 100, 200, 500, 1000, 2000, 5000]
const set_country = (c) => {
country = c
window.localStorage.setItem("checkout_country", c.name)
}
const amounts = [10, 20, 50, 100, 200, 500, 1000, 2000, 5000]
onMount(() => {
const country_name = window.localStorage.getItem("checkout_country")
for (let i = 0; i < countries.length; i++) {
if (countries[i].name === country_name) {
country = countries[i]
}
const checkout_country = window.localStorage.getItem("checkout_country")
if (countries[checkout_country] !== undefined) {
country_input = checkout_country
set_country()
}
})
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 checkout = async () => {
loading = true
@@ -66,13 +78,13 @@ const checkout = async () => {
}
const form = new FormData()
form.set("amount", amount*1e6)
form.set("amount", String(amount*1e6))
form.set("network", "mollie")
form.set("country", country.name)
form.set("country", country.alpha2)
try {
const resp = await fetch(
window.api_endpoint+"/user/invoice",
get_endpoint()+"/user/invoice",
{method: "POST", body: form},
)
if(resp.status >= 400) {
@@ -87,6 +99,19 @@ const checkout = async () => {
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">
@@ -97,62 +122,43 @@ const checkout = async () => {
<div>
Please pick your country of residence
</div>
<div class="countries">
{#each countries as c}
<button on:click={() => set_country(c)}>
<span class="icon_unicode">{c.flag}</span>
<span>{c.name}</span>
</button>
{/each}
<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">
<div>
<img class="bankicon" src="/res/img/payment_providers/paypal.svg" alt="PayPal" title="PayPal"/>
PayPal
</div>
<div>
<img class="bankicon" src="/res/img/payment_providers/ideal.svg" alt="iDEAL" title="iDEAL"/>
iDEAL
</div>
<div>
<img class="bankicon" src="/res/img/payment_providers/klarna.svg" alt="Klarna" title="Klarna"/>
Klarna
</div>
<div>
<img class="bankicon" src="/res/img/payment_providers/bancontact.svg" alt="Bancontact" title="Bancontact"/>
Bancontact
</div>
<div>
<img class="bankicon" src="/res/img/payment_providers/banktransfer.svg" alt="SEPA" title="SEPA"/>
SEPA
</div>
<div>
<img class="bankicon" src="/res/img/payment_providers/sofort.svg" alt="SOFORT" title="SOFORT"/>
SOFORT
</div>
<div>
<img class="bankicon" src="/res/img/payment_providers/kbc.svg" alt="KBC/CBC" title="CBC"/>
KBC/CBC
</div>
<div>
<img class="bankicon" src="/res/img/payment_providers/belfius.svg" alt="Belfius" title="Belfius"/>
Belfius
</div>
<div>
<img class="bankicon" src="/res/img/payment_providers/giropay.svg" alt="Giropay" title="Giropay"/>
Giropay
</div>
<div>
<img class="bankicon" src="/res/img/payment_providers/eps.svg" alt="EPS" title="EPS"/>
EPS
</div>
<div>
<img class="bankicon" src="/res/img/payment_providers/przelewy24.svg" alt="Przelewy24" title="Przelewy24"/>
Przelewy24
</div>
{#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}
@@ -165,8 +171,8 @@ const checkout = async () => {
<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.flag}</span>
<span>{country.name} ({country.vat}% VAT)</span>
<span style="font-size: 1.5em; line-height: 1em;">{country.emoji}</span>
<span>{country.name} ({vat}% VAT)</span>
</div>
</div>
@@ -189,25 +195,40 @@ const checkout = async () => {
<div class="span2" style="text-align: initial;">
Total including VAT:
<Euro amount={credit_micro + vat_micro}/>
<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>
.countries {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
.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(140px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
.processors > div {
display: flex;