Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion lib/coinpay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,21 @@ export function payUrl(id: string): string {

export type CreatedPayment = { id: string; status: string; payUrl: string };

const SETUP_AMOUNT_RE = /^(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d{1,2})?$/;

export function setupAmountUsd(value: unknown = SETUP_FEE_USD): number {
if (typeof value === "number") {
if (!Number.isFinite(value) || value <= 0) throw new Error("setup payment amount must be positive");
return Math.round(value * 100) / 100;
}

const text = String(value ?? "").trim().replace(/\s+/g, "");
if (!SETUP_AMOUNT_RE.test(text)) throw new Error("setup payment amount must be a positive dollar amount");
const amount = Number(text.replace(/,/g, ""));
if (!Number.isFinite(amount) || amount <= 0) throw new Error("setup payment amount must be positive");
return amount;
}

export async function createSetupPayment(opts: {
accountId: string;
email: string;
Expand All @@ -44,7 +59,7 @@ export async function createSetupPayment(opts: {
// module is imported even when CoinPay isn't configured (offline dev mode).
const coinpay = createCoinPayClient({ apiKey: API_KEY, baseUrl: ISSUER });
const { paymentId, payment } = await coinpay.createCheckout({
amountUsd: Number(opts.amount || SETUP_FEE_USD),
amountUsd: setupAmountUsd(opts.amount || SETUP_FEE_USD),
currency: PAY_CHAIN.toLowerCase(),
paymentMethod: "crypto",
description: `moshcoding account setup — ${opts.domain}`,
Expand Down
19 changes: 19 additions & 0 deletions tests/coinpay-amount.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import assert from "node:assert/strict";
import test from "node:test";

import { setupAmountUsd } from "../lib/coinpay.ts";

test("setupAmountUsd accepts ordinary positive dollar amounts", () => {
assert.equal(setupAmountUsd("1.00"), 1);
assert.equal(setupAmountUsd(" 1,234.56 "), 1234.56);
assert.equal(setupAmountUsd(2.345), 2.35);
});

test("setupAmountUsd rejects malformed or unsafe payment amounts", () => {
assert.throws(() => setupAmountUsd(""), /positive dollar amount/);
assert.throws(() => setupAmountUsd("1e2"), /positive dollar amount/);
assert.throws(() => setupAmountUsd("0x10"), /positive dollar amount/);
assert.throws(() => setupAmountUsd("-1"), /positive dollar amount/);
assert.throws(() => setupAmountUsd(Number.NaN), /positive/);
assert.throws(() => setupAmountUsd(Number.POSITIVE_INFINITY), /positive/);
});
Loading