feat: taxes
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing

ref: LNVPS/api#18
This commit is contained in:
2025-03-11 15:59:32 +00:00
parent c1312d97f1
commit 26d36adbeb
9 changed files with 149 additions and 116 deletions

View File

@ -1 +1,2 @@
#VITE_API_URL="http://localhost:8000"
VITE_API_URL="http://localhost:8000"
VITE_REVOLUT_MODE="sandbox"

View File

@ -20,6 +20,7 @@
"@xterm/addon-webgl": "^0.18.0",
"@xterm/xterm": "^5.5.0",
"classnames": "^2.5.1",
"iso-3166-1": "^2.1.1",
"marked": "^15.0.7",
"qr-code-styling": "^1.8.4",
"react": "^18.3.1",

View File

@ -24,6 +24,7 @@ export interface AccountDetail {
email?: string;
contact_nip17: boolean;
contact_email: boolean;
country_code: string;
}
export interface VmCostPlan {
@ -141,6 +142,7 @@ export interface VmPayment {
created: string;
expires: string;
amount: number;
tax: number;
is_paid: boolean;
data: {
lightning?: string;

View File

@ -1,10 +1,12 @@
import RevolutCheckout, { Mode } from "@revolut/checkout";
import { useEffect, useRef } from "react";
import { VmCostPlan } from "../api";
interface RevolutProps {
amount: {
currency: string;
amount: VmCostPlan | {
amount: number;
currency: string;
tax?: number;
};
pubkey: string;
loadOrder: () => Promise<string>;
@ -25,12 +27,11 @@ export function RevolutPayWidget({
async function load(pubkey: string, ref: HTMLDivElement) {
const { revolutPay } = await RevolutCheckout.payments({
locale: "auto",
mode: (mode ?? "sandbox") as Mode,
mode: (mode ?? "prod") as Mode,
publicToken: pubkey,
});
ref.innerHTML = "";
revolutPay.mount(ref, {
sessionToken: "",
currency: amount.currency,
totalAmount: amount.amount,
createOrder: async () => {

View File

@ -48,7 +48,12 @@ export default function VpsPayment({
avatar="/logo.jpg"
className="cursor-pointer rounded-xl overflow-hidden"
/>
{(payment.amount / 1000).toLocaleString()} sats
<div className="flex flex-col items-center">
<div>{((payment.amount + payment.tax) / 1000).toLocaleString()} sats</div>
{payment.tax > 0 && <div className="text-xs">
including {(payment.tax / 1000).toLocaleString()} sats tax
</div>}
</div>
<div className="monospace select-all break-all text-center text-sm">
{invoice}
</div>

View File

@ -3,6 +3,7 @@ import { useEffect, useState } from "react";
import { AccountDetail } from "../api";
import { AsyncButton } from "../components/button";
import { Icon } from "../components/icon";
import { default as iso } from "iso-3166-1";
export function AccountSettings() {
const login = useLogin();
@ -13,9 +14,23 @@ export function AccountSettings() {
login?.api.getAccount().then(setAcc);
}, [login]);
function notifications() {
return (
<>
if (!acc) return;
return <div className="flex flex-col gap-4">
<h3>
Account Settings
</h3>
<div className="flex gap-2 items-center">
<h4>Country</h4>
<select value={acc?.country_code}
onChange={(e) =>
setAcc((s) => (s ? { ...s, country_code: e.target.value } : undefined))
}
>
{iso.all().map(c => <option value={c.alpha3}>{c.country}</option>)}
</select>
</div>
<h3>Notification Settings</h3>
<div className="flex gap-2 items-center">
<input
@ -67,9 +82,5 @@ export function AccountSettings() {
Save
</AsyncButton>
</div>
</>
);
}
return <>{notifications()}</>;
</div>
}

View File

@ -34,8 +34,8 @@ export default function HomePage() {
{offers?.custom_template && (
<VpsCustomOrder templates={offers.custom_template} />
)}
<small className="text-neutral-400">
All VPS come with 1x IPv4 and 1x IPv6 address and unmetered traffic
<small className="text-neutral-400 text-center">
All VPS come with 1x IPv4 and 1x IPv6 address and unmetered traffic, all prices are excluding taxes.
</small>
<div className="flex flex-col gap-6">
<div className="text-center">

View File

@ -67,6 +67,7 @@ export function VmBillingPage() {
</div>
{state && (
<RevolutPayWidget
mode={import.meta.env.VITE_REVOLUT_MODE}
pubkey={pkey}
amount={state.template.cost_plan}
onPaid={() => {
@ -122,7 +123,10 @@ export function VmBillingPage() {
</Link>
<div className="text-xl bg-neutral-900 rounded-xl px-3 py-4 flex justify-between items-center">
<div>Renewal for #{state.id}</div>
<div>
<CostLabel cost={state.template.cost_plan} />
<span className="text-sm">ex. tax</span>
</div>
</div>
{days > 0 && (
<div>

View File

@ -2451,6 +2451,13 @@ __metadata:
languageName: node
linkType: hard
"iso-3166-1@npm:^2.1.1":
version: 2.1.1
resolution: "iso-3166-1@npm:2.1.1"
checksum: 10c0/65daf0283d22b2848d733a50ba6116a01df3e4d77970bb31d0df0696b4b386bdad0946dea53c9bb1e748e5e5e74608c07b6f4fe044908a5cffbf46f315385400
languageName: node
linkType: hard
"isomorphic-ws@npm:^5.0.0":
version: 5.0.0
resolution: "isomorphic-ws@npm:5.0.0"
@ -2618,6 +2625,7 @@ __metadata:
eslint-plugin-react-hooks: "npm:^5.1.0-rc.0"
eslint-plugin-react-refresh: "npm:^0.4.9"
globals: "npm:^15.9.0"
iso-3166-1: "npm:^2.1.1"
marked: "npm:^15.0.7"
postcss: "npm:^8.4.41"
prettier: "npm:^3.3.3"