Add Mollie payment terminal

This commit is contained in:
2023-09-14 01:35:27 +02:00
parent 06018aa4ed
commit 1a332f6d57
4 changed files with 264 additions and 55 deletions

View File

@@ -3,20 +3,28 @@ import { onMount } from "svelte";
import Euro from "../util/Euro.svelte";
import { formatDate } from "../util/Formatting.svelte";
import LoadingIndicator from "../util/LoadingIndicator.svelte";
import MollieDeposit from "./MollieDeposit.svelte";
let loading = false
let credit_amount = 10
const checkout = async (network) => {
const checkout = async (network = "", amount = 0, country = "") => {
loading = true
if (amount < 10) {
alert("Amount needs to be at least €10")
return
}
const form = new FormData()
form.set("amount", credit_amount*1e6)
form.set("amount", amount*1e6)
form.set("network", network)
form.set("country", country)
try {
const resp = await fetch(
window.api_endpoint+"/btcpay/deposit",
{ method: "POST", body: form },
window.api_endpoint+"/user/invoice",
{method: "POST", body: form},
)
if(resp.status >= 400) {
let json = await resp.json()
@@ -35,7 +43,7 @@ let invoices = []
const load_invoices = async () => {
loading = true
try {
const resp = await fetch(window.api_endpoint+"/btcpay/invoice")
const resp = await fetch(window.api_endpoint+"/user/invoice")
if(resp.status >= 400) {
throw new Error((await resp.json()).message)
}
@@ -66,19 +74,19 @@ onMount(() => {
<section>
<h2>Deposit credits</h2>
<p>
To deposit funds on your pixeldrain account please contact
<a href="mailto:sales@pixeldrain.com">sales@pixeldrain.com</a>. We will
prepare an invoice for you. We accept wire transfers and PayPal. As we
are located in the Netherlands SEPA payments should be free and instant.
Pixeldrain credits can be used for our prepaid plans, which are
different from the Patreon plans. With prepaid you only pay for what you
use, at a rate of €4 per TB per month for storage and €2 per TB for data
transfer.
</p>
<p>
Pixeldrain credits can be used for our prepaid plans, which are
different from the Patreon plans. Because preparing invoices requires
some manual labour we only accept deposits of €100 or more. The prepaid
plans are meant for people who require a lot of bandwidth or storage.
Paying only for what you use means you can save money compared to a
fixed monthly price.
Use the form below to deposit credit on your pixeldrain account using
Mollie. The minimum deposit is €10. <b>Mollie payments are currently
only available in Europe</b>.
</p>
<MollieDeposit on:checkout={e => checkout("mollie", e.detail.amount, e.detail.country)}/>
<h3>Crypto payments</h3>
<p>
Alternatively you can use Bitcoin, Lightning network (<a
@@ -94,16 +102,16 @@ onMount(() => {
</p>
<div class="highlight_border">
Deposit amount €
<input type="number" bind:value={credit_amount} min="1"/>
<input type="number" bind:value={credit_amount} min="10"/>
<br/>
Choose payment method:<br/>
<button on:click={() => {checkout("btc")}}>
<button on:click={() => {checkout("btc", credit_amount)}}>
<span class="icon_unicode"></span> Bitcoin
</button>
<button on:click={() => {checkout("btc_lightning")}}>
<button on:click={() => {checkout("btc_lightning", credit_amount)}}>
<i class="icon">bolt</i> Lightning network
</button>
<button on:click={() => {checkout("doge")}}>
<button on:click={() => {checkout("doge", credit_amount)}}>
<span class="icon_unicode">Ð</span> Dogecoin
</button>
</div>
@@ -118,6 +126,9 @@ onMount(() => {
<tr>
<td>Created</td>
<td>Amount</td>
<td>VAT</td>
<td>Country</td>
<td>Method</td>
<td>Status</td>
<td></td>
</tr>
@@ -126,23 +137,28 @@ onMount(() => {
{#each invoices as row (row.id)}
<tr>
<td>{formatDate(row.time, true, true, false)}</td>
<td><Euro amount={row.amount}></Euro></td>
<td><Euro amount={row.amount}/></td>
<td><Euro amount={row.vat}/></td>
<td>{row.country}</td>
<td>{row.payment_method}</td>
<td>
{#if row.status === "InvoiceCreated"}
New (waiting for payment)
{#if row.status === "InvoiceCreated" || row.status === "open"}
Waiting for payment
{:else if row.status === "InvoiceProcessing"}
Payment received, waiting for confirmations
{:else if row.status === "InvoiceSettled"}
{:else if row.status === "InvoiceSettled" || row.status === "paid"}
Paid
{:else if row.status === "InvoiceExpired"}
Expired
{:else if row.status === "canceled"}
Canceled
{:else}
{row.status}
{/if}
</td>
<td>
{#if row.status === "New" || row.status === "InvoiceCreated"}
<a href={row.checkout_url} class="button button_highlight">
{#if row.status === "New" || row.status === "InvoiceCreated" || row.status === "open"}
<a href="/api/user/pay_invoice/{row.id}" class="button button_highlight">
<i class="icon">paid</i> Pay
</a>
{/if}

View File

@@ -0,0 +1,148 @@
<script>
import { createEventDispatcher } from 'svelte';
import {
At, Be, Bg, Hr, Cy, Cz, Dk, Ee, Fi, Fr, De, Gr, Hu, Ie, It, Lv, Lt, Lu, Mt,
Nl, Pl, Pt, Ro, Sk, Si, Es, Se,
} from 'svelte-flag-icons';
import Euro from '../util/Euro.svelte';
let dispatch = createEventDispatcher()
let credit_amount = 10
$: credit_micro = credit_amount*1e6
$: vat_micro = country === null ? 0 : (credit_amount*1e6)*(country.vat/100)
let country = null
let countries = [
{name: "Austria", code: At, vat: 20},
{name: "Belgium", code: Be, vat: 21},
{name: "Bulgaria", code: Bg, vat: 20},
{name: "Croatia", code: Hr, vat: 25},
{name: "Cyprus", code: Cy, vat: 19},
{name: "Czechia", code: Cz, vat: 21},
{name: "Denmark", code: Dk, vat: 25},
{name: "Estonia", code: Ee, vat: 20},
{name: "Finland", code: Fi, vat: 24},
{name: "France", code: Fr, vat: 20},
{name: "Germany", code: De, vat: 19},
{name: "Greece", code: Gr, vat: 24},
{name: "Hungary", code: Hu, vat: 27},
{name: "Ireland", code: Ie, vat: 23},
{name: "Italy", code: It, vat: 22},
{name: "Latvia", code: Lv, vat: 21},
{name: "Lithuania", code: Lt, vat: 21},
{name: "Luxembourg", code: Lu, vat: 16},
{name: "Malta", code: Mt, vat: 18},
{name: "Netherlands", code: Nl, vat: 21},
{name: "Poland", code: Pl, vat: 23},
{name: "Portugal", code: Pt, vat: 23},
{name: "Romania", code: Ro, vat: 19},
{name: "Slovakia", code: Sk, vat: 20},
{name: "Slovenia", code: Si, vat: 22},
{name: "Spain", code: Es, vat: 21},
{name: "Sweden", code: Se, vat: 25},
]
let amounts = [10, 20, 50, 100, 200, 500, 1000, 2000, 5000]
const checkout = () => {
dispatch("checkout", {country: country.name, amount: credit_amount})
}
</script>
<div class="highlight_border">
{#if country === null}
<div>Please pick your country of residence</div>
<div class="countries">
{#each countries as c}
<button on:click={() => country = c}>
<svelte:component this={c.code} size="1.5em" />
<span>{c.name}</span>
</button>
{/each}
</div>
{:else}
<div style="display: flex;">
<button on:click={() => country = null} style="flex: 0 0 auto;">
<i class="icon">chevron_left</i>
Back
</button>
<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>
<svelte:component this={country.code} size="1.5em" />
<span>{country.name} ({country.vat}% VAT)</span>
</div>
</div>
<form class="amount_grid" on:submit|preventDefault={checkout}>
<div class="span3">Please choose an amount</div>
{#each amounts as a}
<button on:click|preventDefault={() => credit_amount = a} style="font-size: 1.1em;" class:button_highlight={credit_amount === a}>
<div style="padding: 6px;">{a}</div>
</button>
{/each}
<div class="span3 mollie_checkout">
<div>Custom amount €</div>
<input type="number" bind:value={credit_amount} min="10"/>
</div>
<div class="span2" style="text-align: initial;">
Total including VAT:
<Euro amount={credit_micro + vat_micro}/>
</div>
<button type="submit" class="button_highlight">
<i class="icon">paid</i> Checkout
</button>
</form>
{/if}
</div>
<style>
.countries {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
}
.countries > button {
display: flex;
flex-direction: row;
text-align: left;
}
.countries > button > span {
margin-left: 0.5em;
}
.amount_grid {
max-width: 500px;
gap: 4px;
display: inline-grid;
align-items: center;
grid-template-columns: 1fr 1fr 1fr;
}
.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;
}
</style>

View File

@@ -1,4 +1,5 @@
<script>
import { onMount } from "svelte";
import Euro from "../util/Euro.svelte"
import LoadingIndicator from "../util/LoadingIndicator.svelte";
import SuccessMessage from "../util/SuccessMessage.svelte";
@@ -30,21 +31,38 @@ const update = async () => {
loading = false
}
}
let checkout_success = false
onMount(() => {
if (window.location.hash === "#checkout_complete") {
checkout_success = true
window.location.hash = ""
}
})
</script>
<LoadingIndicator loading={loading}/>
<section>
{#if checkout_success}
<div class="highlight_green">
<h2>Payment successful!</h2>
<p>
The credit has been added to your account balance. Activate a
subscription plan below to finish upgrading your account.
</p>
</div>
{/if}
<h2>Manage subscription</h2>
<p>
Current account balance: <Euro amount={window.user.balance_micro_eur}></Euro>
</p>
<p>
When your prepaid subscription is active you will be charged daily based
on usage. The prepaid subscription will stay active for as long as you
have credit on your account. When you reach negative balance the
subscription will automatically end. You will need a positive balance to
activate the subscription again.
Prepaid subscriptions are charged daily based on usage. When you reach
negative balance the subscription will automatically end. You will need
a positive balance to activate the subscription again.
</p>
<h3>Prepaid plans</h3>
@@ -65,16 +83,28 @@ const update = async () => {
</div>
<div class="feat_normal round_tr" class:feat_highlight={subscription === "prepaid"}>
<ul>
<li>Base price of €1 per month</li>
<li>Base price of €2 per month</li>
<li>€4 per TB per month for storage</li>
<li>€2 per TB for data transfer</li>
<li>
€2 per TB for data transfer (with <a
href="user/sharing/bandwidth">bandwidth sharing</a>
enabled)
</li>
<li>Files never expire as long as subscription is active</li>
<li>
Download page <a href="/user/sharing/branding">branding
options</a>
</li>
<li>
File <a href="/user/sharing/embedding">embedding
control</a> options
</li>
</ul>
</div>
</div>
<div>
<div class="feat_label" class:feat_highlight={subscription === "prepaid_temp_storage_120d"}>
120 days storage<br/>
240 days storage<br/>
{#if subscription === "prepaid_temp_storage_120d"}
Currently active
{:else}
@@ -88,8 +118,20 @@ const update = async () => {
<ul>
<li>Base price of €1 per month</li>
<li>€0.50 per TB per month for storage</li>
<li>€2 per TB for data transfer</li>
<li>Files expire 120 days after the last time they're viewed</li>
<li>
€2 per TB for data transfer (with <a
href="user/sharing/bandwidth">bandwidth sharing</a>
enabled)
</li>
<li>Files never expire as long as subscription is active</li>
<li>
Download page <a href="/user/sharing/branding">branding
options</a>
</li>
<li>
File <a href="/user/sharing/embedding">embedding
control</a> options
</li>
</ul>
</div>
</div>
@@ -108,6 +150,7 @@ const update = async () => {
<div class="feat_normal round_br" class:feat_highlight={subscription === ""}>
<ul>
<li>Standard free plan, files expire after 60 days.</li>
<li>Download limit of 10 GB per day</li>
</ul>
</div>
</div>

View File

@@ -147,32 +147,34 @@ onMount(() => {
<td>Time</td>
<td>Balance</td>
<td>Subscription</td>
<td colspan="2">Storage</td>
<td colspan="2">Bandwidth</td>
<td>Storage</td>
<td>Bandwidth</td>
<td>Deposit</td>
</tr>
<tr>
<td></td>
<td></td>
<td>Charge</td>
<td>Charge</td>
<td>Usage</td>
<td>Charge</td>
<td>Usage</td>
<td></td>
</tr>
</thead>
<tbody>
{#each transactions.rows as row}
<tr>
<td>{formatDate(row.time, true, true, false)}</td>
<td><Euro amount={row.new_balance}></Euro></td>
<td><Euro amount={row.subscription_charge} precision="6"></Euro></td>
<td><Euro amount={row.storage_charge} precision="6"></Euro></td>
<td>{formatDataVolume(row.storage_used, 3)}</td>
<td><Euro amount={row.bandwidth_charge} precision="6"></Euro></td>
<td>{formatDataVolume(row.bandwidth_used, 3)}</td>
<td><Euro amount={row.deposit_amount}></Euro></td>
<td>
{formatDate(row.time, true, true, false)}
</td>
<td>
<Euro amount={row.new_balance}></Euro>
</td>
<td>
<Euro amount={row.subscription_charge} precision="6"/>
</td>
<td>
<Euro amount={row.storage_charge} precision="6"/>
({formatDataVolume(row.storage_used, 3)})
</td>
<td>
<Euro amount={row.bandwidth_charge} precision="6"/>
({formatDataVolume(row.bandwidth_used, 3)})
</td>
<td>
<Euro amount={row.deposit_amount}/>
</td>
</tr>
{/each}
</tbody>