Add paypal checkout

This commit is contained in:
2025-04-16 17:12:33 +02:00
parent bc112b13c9
commit 392c61e827
13 changed files with 339 additions and 84 deletions

View File

@@ -0,0 +1,7 @@
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="64" width="64" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
<g transform="translate(0.00630876,-0.00301984)">
<path fill="#f7931a" d="m63.033,39.744c-4.274,17.143-21.637,27.576-38.782,23.301-17.138-4.274-27.571-21.638-23.295-38.78,4.272-17.145,21.635-27.579,38.775-23.305,17.144,4.274,27.576,21.64,23.302,38.784z"/>
<path fill="#FFF" d="m46.103,27.444c0.637-4.258-2.605-6.547-7.038-8.074l1.438-5.768-3.511-0.875-1.4,5.616c-0.923-0.23-1.871-0.447-2.813-0.662l1.41-5.653-3.509-0.875-1.439,5.766c-0.764-0.174-1.514-0.346-2.242-0.527l0.004-0.018-4.842-1.209-0.934,3.75s2.605,0.597,2.55,0.634c1.422,0.355,1.679,1.296,1.636,2.042l-1.638,6.571c0.098,0.025,0.225,0.061,0.365,0.117-0.117-0.029-0.242-0.061-0.371-0.092l-2.296,9.205c-0.174,0.432-0.615,1.08-1.609,0.834,0.035,0.051-2.552-0.637-2.552-0.637l-1.743,4.019,4.569,1.139c0.85,0.213,1.683,0.436,2.503,0.646l-1.453,5.834,3.507,0.875,1.439-5.772c0.958,0.26,1.888,0.5,2.798,0.726l-1.434,5.745,3.511,0.875,1.453-5.823c5.987,1.133,10.489,0.676,12.384-4.739,1.527-4.36-0.076-6.875-3.226-8.515,2.294-0.529,4.022-2.038,4.483-5.155zm-8.022,11.249c-1.085,4.36-8.426,2.003-10.806,1.412l1.928-7.729c2.38,0.594,10.012,1.77,8.878,6.317zm1.086-11.312c-0.99,3.966-7.1,1.951-9.082,1.457l1.748-7.01c1.982,0.494,8.365,1.416,7.334,5.553z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 56 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 320 94" style="enable-background:new 0 0 320 94;" xml:space="preserve">
<style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
</style>
<path class="st0" d="M289.3,44.3c6.9,0,13.2,4.5,15.4,11h-30.7C276.1,48.9,282.3,44.3,289.3,44.3z M320,60.9c0-8-3.1-15.6-8.8-21.4
c-5.7-5.8-13.3-9-21.3-9h-0.4c-8.3,0.1-16.2,3.4-22.1,9.3c-5.9,5.9-9.2,13.7-9.3,22c-0.1,8.5,3.2,16.5,9.2,22.6
c6.1,6.1,14.1,9.5,22.6,9.5h0c11.2,0,21.7-6,27.4-15.6l0.7-1.2l-12.6-6.2l-0.6,1c-3.1,5.2-8.6,8.2-14.7,8.2
c-7.7,0-14.4-5.1-16.5-12.5H320V60.9z M241.2,19.8c-5.5,0-9.9-4.4-9.9-9.9c0-5.5,4.4-9.9,9.9-9.9s9.9,4.4,9.9,9.9
C251.2,15.3,246.7,19.8,241.2,19.8z M233.6,92.7h15.2V31.8h-15.2V92.7z M204.5,1.3h15.2v91.5h-15.2V1.3z M175.4,92.7h15.2V1.3h-15.2
V92.7z M135.3,79c-9.2,0-16.8-7.5-16.8-16.7c0-9.2,7.5-16.7,16.8-16.7s16.8,7.5,16.8,16.7C152.1,71.5,144.6,79,135.3,79z
M135.3,30.5c-17.6,0-31.8,14.2-31.8,31.7S117.8,94,135.3,94c17.5,0,31.8-14.2,31.8-31.7S152.9,30.5,135.3,30.5z M70.4,30.6
c-0.8-0.1-1.6-0.1-2.4-0.1c-7.7,0-15,3.1-20.2,8.7c-5.2-5.5-12.5-8.7-20.1-8.7C12.4,30.5,0,42.9,0,58v34.7h14.9V58.5
c0-6.3,5.2-12.1,11.3-12.7c0.4,0,0.9-0.1,1.3-0.1c6.9,0,12.5,5.6,12.5,12.5v34.6h15.2V58.4c0-6.3,5.2-12.1,11.3-12.7
c0.4,0,0.9-0.1,1.3-0.1c6.9,0,12.5,5.6,12.6,12.4v34.7h15.2V58.5c0-7-2.6-13.6-7.2-18.8C83.7,34.4,77.3,31.2,70.4,30.6z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3756.09 3756.49"><title>monero</title><path d="M4128,2249.81C4128,3287,3287.26,4127.86,2250,4127.86S372,3287,372,2249.81,1212.76,371.75,2250,371.75,4128,1212.54,4128,2249.81Z" transform="translate(-371.96 -371.75)" style="fill:#fff"/><path id="_149931032" data-name=" 149931032" d="M2250,371.75c-1036.89,0-1879.12,842.06-1877.8,1878,0.26,207.26,33.31,406.63,95.34,593.12h561.88V1263L2250,2483.57,3470.52,1263v1579.9h562c62.12-186.48,95-385.85,95.37-593.12C4129.66,1212.76,3287,372,2250,372Z" transform="translate(-371.96 -371.75)" style="fill:#f26822"/><path id="_149931160" data-name=" 149931160" d="M1969.3,2764.17l-532.67-532.7v994.14H1029.38l-384.29.07c329.63,540.8,925.35,902.56,1604.91,902.56S3525.31,3766.4,3855,3225.6H3063.25V2231.47l-532.7,532.7-280.61,280.61-280.62-280.61h0Z" transform="translate(-371.96 -371.75)" style="fill:#4d4d4d"/></svg>

After

Width:  |  Height:  |  Size: 940 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="772.875" height="720" viewBox="0 0 204.49 190.5" xmlns:v="https://vecta.io/nano"><g transform="matrix(.852554 0 0 .852554 680.18141 153.25756)"><path clip-path="none" d="M-788.334-20.913a1.42 1.42 0 0 0-1.399 1.197l-8.067 51.163a1.31 1.31 0 0 0 1.292 1.514h9.569a1.42 1.42 0 0 0 1.399-1.195l2.368-15.025a1.42 1.42 0 0 1 1.399-1.195h8.697c10.165 0 18.792-7.415 20.368-17.465 1.59-10.133-6.325-18.973-17.547-18.994zm9.301 11.422h6.96c5.73 0 7.594 3.38 7.004 7.119-.59 3.747-3.485 6.507-9.029 6.507h-7.084zm45.787 3.48c-2.416.009-5.197.503-8.318 1.803-7.158 2.983-10.597 9.151-12.056 13.647 0 0-4.647 13.717 5.852 21.254 0 0 9.736 7.253 20.698-.449l-.188 1.203a1.31 1.31 0 0 0 1.292 1.514h9.083c.696 0 1.288-.508 1.397-1.197l5.526-35.037a1.31 1.31 0 0 0-1.292-1.514h-9.083c-.698 0-1.29.508-1.399 1.195l-.296 1.885h-.002s-3.966-4.331-11.215-4.304zm.297 11.067c1.043 0 1.997.142 2.852.418 3.918 1.259 6.142 5.021 5.498 9.103-.793 5.026-4.914 8.728-10.198 8.728-1.043 0-1.999-.143-2.854-.418-3.918-1.259-6.155-5.023-5.51-9.105.793-5.024 4.928-8.726 10.212-8.726z" fill="#003087"/><path clip-path="none" d="M-657.536-20.913c-.696 0-1.288.508-1.397 1.197l-8.069 51.163a1.31 1.31 0 0 0 1.294 1.514h9.569c.696 0 1.288-.507 1.397-1.195l2.37-15.025a1.42 1.42 0 0 1 1.399-1.195h8.697c10.165 0 18.792-7.415 20.368-17.465 1.59-10.133-6.327-18.973-17.549-18.994zm9.301 11.422h6.96c5.73 0 7.596 3.38 7.006 7.119-.59 3.747-3.486 6.507-9.031 6.507h-7.084zm45.789 3.48c-2.416.009-5.197.503-8.318 1.803-7.158 2.983-10.599 9.151-12.058 13.647 0 0-4.646 13.717 5.854 21.254 0 0 9.736 7.253 20.698-.449l-.19 1.203a1.31 1.31 0 0 0 1.294 1.514h9.081a1.42 1.42 0 0 0 1.399-1.197l5.526-35.037a1.31 1.31 0 0 0-1.294-1.514h-9.081c-.698 0-1.29.508-1.399 1.195l-.297 1.885s-3.966-4.331-11.215-4.304zm.295 11.067c1.043 0 1.999.142 2.854.418 3.918 1.259 6.14 5.021 5.497 9.103-.793 5.026-4.914 8.728-10.198 8.728-1.043 0-1.997-.143-2.852-.418-3.918-1.259-6.155-5.023-5.51-9.105.792-5.024 4.926-8.726 10.21-8.726z" fill="#0070e0"/><path clip-path="none" d="M-705.651-4.787a1.06 1.06 0 0 0-1.01 1.387l9.956 30.9-9.004 14.564c-.436.707.071 1.618.903 1.618h10.639a1.77 1.77 0 0 0 1.515-.853l27.807-46.007c.428-.707-.081-1.611-.907-1.611h-10.639a1.77 1.77 0 0 0-1.523.867l-10.946 18.483-5.557-18.345c-.182-.597-.731-1.004-1.356-1.004z" fill="#003087"/><path clip-path="none" d="M-568.839-20.913c-.696 0-1.288.508-1.397 1.197l-8.069 51.163a1.31 1.31 0 0 0 1.294 1.514h9.567c.696 0 1.288-.507 1.397-1.195l8.071-51.165a1.31 1.31 0 0 0-1.296-1.514z" fill="#0070e0"/><path clip-path="none" d="M-687.026-152.855c-1.907 0-3.532 1.387-3.829 3.272l-6.365 40.358-5.848 37.085-.004.031.007-.031 5.848-37.085c.297-1.885 1.92-3.272 3.828-3.272h18.646c18.765 0 34.693-13.691 37.601-32.243a30.77 30.77 0 0 0 .367-4.183v-.002h-.002c-4.769-2.502-10.369-3.931-16.505-3.931z" fill="#001c64"/><path clip-path="none" d="M-636.776-148.925c-.026 1.378-.146 2.775-.367 4.185-2.909 18.552-18.836 32.243-37.601 32.243h-18.646c-1.907 0-3.53 1.387-3.828 3.272l-5.848 37.085-3.67 23.267a3.15 3.15 0 0 0 3.111 3.639h20.239c1.909 0 3.532-1.387 3.83-3.272l5.331-33.813a3.88 3.88 0 0 1 3.829-3.274h11.918c18.765 0 34.691-13.69 37.599-32.241 2.065-13.17-4.562-25.15-15.897-31.091z" fill="#0070e0"/><path clip-path="none" d="M-710.02-179.763a3.88 3.88 0 0 0-3.829 3.274l-15.882 100.708a3.15 3.15 0 0 0 3.111 3.641h23.552l5.848-37.085 6.365-40.358c.297-1.885 1.922-3.272 3.829-3.272h33.743c6.137 0 11.737 1.43 16.507 3.929.325-16.89-13.612-30.836-32.775-30.836z" fill="#003087"/></g></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,166 @@
<script lang="ts">
import { onMount } from "svelte";
import { formatDate } from "util/Formatting";
import LoadingIndicator from "util/LoadingIndicator.svelte";
import Euro from "util/Euro.svelte";
import { get_endpoint } from "lib/PixeldrainAPI";
type Invoice = {
id: string
time: string
amount: number
vat: number
country: string
payment_method: string
status: string
}
let loading = true
let invoices: Invoice[] = []
let year = 0
let month = 0
let month_str = ""
type Total = {
count: number
amount: number
vat: number
}
let totals: { [id: string]: Total } = {}
const add_total = (i: Invoice) => {
if (totals[i.payment_method] === undefined) {
totals[i.payment_method] = {count: 0, amount: 0, vat: 0}
}
totals[i.payment_method].count++
totals[i.payment_method].amount += i.amount
totals[i.payment_method].vat += i.vat
}
const get_invoices = async () => {
loading = true;
month_str = year + "-" + ("00"+(month)).slice(-2)
try {
const resp = await fetch(get_endpoint()+"/admin/invoices/"+month_str);
if(resp.status >= 400) {
throw new Error(await resp.text());
}
let resp_json = await resp.json() as Invoice[];
resp_json.sort((a, b) => {
if (a.status !== b.status) {
return a.status.localeCompare(b.status)
}
const date_a = new Date(a.time)
const date_b = new Date(b.time)
return date_a.getTime() - date_b.getTime()
})
totals = {}
resp_json.forEach(row => {
if (row.status === "paid") {
add_total(row)
}
if (row.status === "chargeback") {
alert(row.vat)
}
});
invoices = resp_json
} catch (err) {
alert(err);
} finally {
loading = false;
}
};
const last_month = () => {
month--
if (month === 0) {
month = 12
year--
}
get_invoices()
}
const next_month = () => {
month++
if (month === 13) {
month = 1
year++
}
get_invoices()
}
onMount(() => {
let now = new Date()
year = now.getFullYear()
month = now.getMonth()+1
get_invoices()
})
</script>
<LoadingIndicator loading={loading}/>
<section>
<h3>{month_str}</h3>
<div class="toolbar">
<button on:click={last_month}>
<i class="icon">chevron_left</i>
Previous month
</button>
<div class="toolbar_spacer"></div>
<button on:click={next_month}>
Next month
<i class="icon">chevron_right</i>
</button>
</div>
{#each Object.entries(totals) as [key, tot]}
{key} ({tot.count})<br/>
Amount:<Euro amount={tot.amount}/><br/>
VAT: <Euro amount={tot.vat}/><br/>
{/each}
<div class="table_scroll">
<table>
<thead>
<tr>
<td>Time</td>
<td>Amount</td>
<td>VAT</td>
<td>Country</td>
<td>Method</td>
<td>Status</td>
</tr>
</thead>
<tbody>
{#each invoices as row (row.id)}
<tr>
<td>{formatDate(row.time)}</td>
<td><Euro amount={row.amount}/></td>
<td><Euro amount={row.vat}/></td>
<td>{row.country}</td>
<td>{row.payment_method}</td>
<td>{row.status}</td>
</tr>
{/each}
</tbody>
</table>
</div>
</section>
<style>
.toolbar {
display: flex;
flex-direction: row;
width: 100%;
}
.toolbar > * { flex: 0 0 auto; }
.toolbar_spacer { flex: 1 1 auto; }
</style>

View File

@@ -9,6 +9,7 @@ import EmailReporters from "./EmailReporters.svelte";
import MollieSettlements from "./MollieSettlements.svelte";
import PayPalTaxes from "./PayPalTaxes.svelte";
import UserBans from "./user_bans/UserBans.svelte";
import InvoiceVat from "./InvoiceVAT.svelte";
let pages = [
{
@@ -62,6 +63,11 @@ let pages = [
title: "Paypal Taxes",
icon: "paypal",
component: PayPalTaxes,
}, {
path: "/admin/invoices",
title: "Invoices",
icon: "receipt",
component: InvoiceVat,
},
],
},

View File

@@ -1,6 +1,6 @@
<script>
import CreditDeposit from "layout/CreditDeposit.svelte";
import LoginRegister from "login/LoginRegister.svelte";
import MollieDeposit from "user_home/MollieDeposit.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>
<MollieDeposit/>
<CreditDeposit/>
{:else}
<p>
You are currently logged in as '{window.user.username}'. Use the
form below to activate prepaid on this account.
</p>
<MollieDeposit/>
<CreditDeposit/>
{/if}
</section>
</div>

View File

@@ -4,11 +4,13 @@ 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]
@@ -19,8 +21,30 @@ onMount(() => {
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"},
@@ -69,6 +93,13 @@ const set_country = async (e?: Event) => {
}
}
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
@@ -79,7 +110,7 @@ const checkout = async () => {
const form = new FormData()
form.set("amount", String(amount*1e6))
form.set("network", "mollie")
form.set("network", provider.name)
form.set("country", country.alpha2)
try {
@@ -161,21 +192,32 @@ const format_country = (c: typeof countries.all[0]) => {
{/each}
</div>
{:else}
{:else if provider === null}
<CreditDepositNav bind:country={country} bind:provider={provider} bind:vat={vat}/>
<div style="display: flex;">
<button on:click={() => country = null} style="flex: 0 0 auto;">
<i class="icon">chevron_left</i>
Change country
</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>
<span style="font-size: 1.5em; line-height: 1em;">{country.emoji}</span>
<span>{country.name} ({vat}% VAT)</span>
</div>
<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}/>
<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>
<form class="amount_grid" on:submit|preventDefault={checkout}>
<div class="span3">Please choose an amount</div>
{#each amounts as a}
@@ -237,6 +279,19 @@ const format_country = (c: typeof countries.all[0]) => {
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;

View File

@@ -0,0 +1,29 @@
<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

@@ -1,13 +1,12 @@
<script>
import CreditDeposit from "layout/CreditDeposit.svelte";
import { onMount } from "svelte";
import Euro from "util/Euro.svelte";
import { formatDate } from "util/Formatting";
import LoadingIndicator from "util/LoadingIndicator.svelte";
import MollieDeposit from "./MollieDeposit.svelte";
let loading = false
let credit_amount = 10
let tab = "mollie"
const checkout = async (network = "", amount = 0, country = "") => {
loading = true
@@ -55,7 +54,7 @@ const load_invoices = async () => {
invoices_tmp.forEach(row => {
row.time = new Date(row.time)
if (row.payment_method === "mollie" && row.status === "open") {
if (row.status === "open") {
unpaid_invoice = true
}
})
@@ -89,61 +88,18 @@ onMount(() => {
amount={window.user.balance_micro_eur}/>
</p>
<div class="tab_bar">
<button on:click={() => tab = "mollie"} class:button_highlight={tab === "mollie"}>
<i class="icon">euro</i>
Mollie
</button>
<button on:click={() => tab = "btcpay"} class:button_highlight={tab === "btcpay"}>
<i class="icon">currency_bitcoin</i>
Crypto
</button>
</div>
{#if tab === "mollie"}
{#if unpaid_invoice}
<div class="highlight_yellow">
<p>
You still have an unpaid invoice open. Please pay that one
before requesting a new invoice. You can find unpaid
invoices at the bottom of this page. You can cancel an
invoice by clicking Pay, and then clicking the Back link at
the bottom of the page.
</p>
</div>
{:else}
<MollieDeposit/>
{/if}
{:else if tab === "btcpay"}
<div class="highlight_border">
<p style="text-align: initial">
Alternatively you can use Bitcoin, Lightning network (<a
href="https://btcpay.pixeldrain.com/embed/uS2mbWjXUuaAqMh8XLjkjwi8oehFuxeBZxekMxv68LN/BTC/ln"
target="_blank" rel="noreferrer">node info</a>) and Dogecoin to deposit
credits on your pixeldrain account. You must pay the full amount as
stated on the invoice, else your payment will fail.
{#if unpaid_invoice}
<div class="highlight_yellow">
<p>
You still have an unpaid invoice open. Please pay that one
before requesting a new invoice. You can find unpaid
invoices at the bottom of this page. You can cancel an
invoice by clicking Pay, and then clicking the Back link at
the bottom of the page.
</p>
<p style="text-align: initial">
Do note that it is not possible to withdraw coins from your
pixeldrain account. It's not a wallet. Any amount of money you
deposit has to be used up.
</p>
Deposit amount €
<input type="number" bind:value={credit_amount} min="10"/>
<br/>
Choose payment method:<br/>
<button on:click={() => {checkout("btc", credit_amount)}}>
<i class="icon">currency_bitcoin</i> Bitcoin
</button>
<button on:click={() => {checkout("btc_lightning", credit_amount)}}>
<i class="icon">bolt</i> Lightning network
</button>
<button on:click={() => {checkout("doge", credit_amount)}}>
<span class="icon_unicode">Ð</span> Dogecoin
</button>
<button on:click={() => {checkout("xmr", credit_amount)}}>
<span class="icon_unicode">M</span> Monero
</button>
</div>
{:else}
<CreditDeposit/>
{/if}
<h3 id="invoices">Past invoices</h3>
@@ -174,7 +130,7 @@ onMount(() => {
<td>{row.country}</td>
<td>{row.payment_method}</td>
<td>
{#if row.status === "InvoiceCreated" || row.status === "open"}
{#if row.status === "InvoiceCreated" || row.status === "open" || row.status === "CREATED" || row.status === "PAYER_ACTION_REQUIRED"}
Waiting for payment
{:else if row.status === "InvoiceProcessing"}
Payment received, waiting for confirmations
@@ -189,7 +145,12 @@ onMount(() => {
{/if}
</td>
<td>
{#if row.status === "New" || row.status === "InvoiceCreated" || row.status === "open"}
{#if row.status === "New" ||
row.status === "InvoiceCreated" ||
row.status === "open" ||
row.status === "CREATED" ||
row.status === "PAYER_ACTION_REQUIRED"
}
<a href="/api/user/pay_invoice/{row.id}" class="button button_highlight">
<i class="icon">paid</i> Pay
</a>

View File

@@ -1,5 +1,4 @@
<script>
import { onMount } from "svelte";
import Euro from "util/Euro.svelte"
import LoadingIndicator from "util/LoadingIndicator.svelte";
import SuccessMessage from "util/SuccessMessage.svelte";
@@ -47,20 +46,12 @@ const update = async (plan) => {
loading = false
}
}
let checkout_success = false
onMount(() => {
if (window.location.hash === "#checkout_complete") {
checkout_success = true
}
})
</script>
<LoadingIndicator loading={loading}/>
<section>
{#if checkout_success}
{#if window.location.hash === "#checkout_complete"}
<div class="highlight_green">
<h2>Payment successful!</h2>
<p>
@@ -78,6 +69,24 @@ onMount(() => {
support@pixeldrain.com.
</p>
</div>
{:else if window.location.hash === "#order_expired"}
<div class="highlight_yellow">
<h2>Order expired</h2>
<p>
This order has expired. Please create a new order on the <a
href="/user/prepaid/deposit">credit deposit page</a>.
</p>
</div>
{:else if window.location.hash === "#order_canceled"}
<div class="highlight_yellow">
<h2>Order canceled</h2>
<p>
You have chosen to cancel the order. If you still want to
proceed with the order you can initiate payment again from the
<a href="/user/prepaid/deposit">deposit page</a>. If not then
you can let the invoice expire.
</p>
</div>
{/if}
<PatreonActivationResult/>

View File

@@ -176,6 +176,7 @@ func New(r *httprouter.Router, prefix string, conf Config) (wc *WebController) {
{GET, "admin/user_management" /* */, wc.serveTemplate("admin", handlerOpts{Auth: true})},
{GET, "admin/mollie_settlements" /**/, wc.serveTemplate("admin", handlerOpts{Auth: true})},
{GET, "admin/paypal_taxes" /* */, wc.serveTemplate("admin", handlerOpts{Auth: true})},
{GET, "admin/invoices" /* */, wc.serveTemplate("admin", handlerOpts{Auth: true})},
{GET, "admin/globals" /* */, wc.serveForm(wc.adminGlobalsForm, handlerOpts{Auth: true})},
{PST, "admin/globals" /* */, wc.serveForm(wc.adminGlobalsForm, handlerOpts{Auth: true})},