Files
recap-relay/startos/actions/setZapriteConnection.ts
T

142 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { sdk } from '../sdk'
import { configFile } from '../file-models/config.json'
const { InputSpec, Value } = sdk
// Connects the relay to the operator's Zaprite account so users can buy a
// prepaid Pro/Max period with a CARD (the "Pay by card" rail alongside the
// Bitcoin/BTCPay rail). Leave the API key blank to disable the card rail —
// the app hides "Pay by card" and the Bitcoin rail keeps working.
//
// To get your Zaprite API key:
// 1. Zaprite → Settings → API → create a key
// 2. Paste it below (stored masked)
//
// To set up the webhook in Zaprite:
// 1. Zaprite → Settings → Webhooks → add endpoint
// 2. URL: https://<your-relay-host>/relay/zaprite/webhook
// 3. Subscribe to order paid / completed events
// (No webhook secret is needed — the relay re-fetches each order from
// Zaprite's authenticated API to confirm payment before granting.)
//
// Card prices are charged in the currency below (cents for USD). They're
// separate from the dashboard's "Set Tier Prices (USD)" accounting figure;
// these are the real amounts a card buyer pays. Default ≈ parity with the
// sat prices ($21 / $42) — raise them to add a premium for card fees.
const inputSpec = InputSpec.of({
relay_zaprite_api_key: Value.text({
name: 'Zaprite API Key',
description:
'API key from Zaprite → Settings → API. Used to create hosted card checkouts and to re-fetch orders for webhook verification. Leave blank to disable the card rail.',
required: false,
default: null,
masked: true,
minLength: 0,
maxLength: 256,
}),
relay_zaprite_base_url: Value.text({
name: 'Zaprite Base URL',
description:
'Zaprite API base URL. Leave as the default unless Zaprite tells you otherwise.',
required: false,
default: 'https://api.zaprite.com',
minLength: 0,
maxLength: 256,
patterns: [
{
regex: '^(https?://.+)?$',
description: 'Must be empty or start with http:// or https://',
},
],
}),
relay_zaprite_currency: Value.text({
name: 'Card Currency',
description:
'Fiat currency the card is charged in (ISO code, e.g. USD, EUR). The card prices below are in this currency.',
required: false,
default: 'USD',
minLength: 0,
maxLength: 8,
}),
pro_card_price: Value.number({
name: 'Pro — Card Price',
description:
'Amount a card buyer pays for one prepaid Pro period, in the card currency. Default ≈ parity with the 21,000-sat Bitcoin price.',
required: true,
default: 21,
min: 0,
max: 100_000,
integer: false,
step: 0.01,
placeholder: null,
}),
max_card_price: Value.number({
name: 'Max — Card Price',
description:
'Amount a card buyer pays for one prepaid Max period, in the card currency. Default ≈ parity with the 42,000-sat Bitcoin price.',
required: true,
default: 42,
min: 0,
max: 100_000,
integer: false,
step: 0.01,
placeholder: null,
}),
})
export const setZapriteConnection = sdk.Action.withInput(
'set-zaprite-connection',
async ({ effects }) => ({
name: 'Set Zaprite Connection (card purchases)',
description:
'Wire the relay to your Zaprite account so users can buy Pro/Max with a card. Leave the API key blank to disable the card rail — the Bitcoin rail keeps working without it. Remember to add the webhook in Zaprite pointing at https://<your-relay-host>/relay/zaprite/webhook',
warning: null,
allowedStatuses: 'any',
group: 'Tiers',
visibility: 'enabled',
}),
inputSpec,
async ({ effects }) => {
const config = await configFile.read().once()
let cents: any = {}
try {
cents = JSON.parse(config?.relay_tier_prices_fiat_cents_json || '{}')
} catch {
cents = {}
}
const toMajor = (c: any, fallback: number) =>
typeof c === 'number' && Number.isFinite(c) ? c / 100 : fallback
return {
relay_zaprite_api_key: config?.relay_zaprite_api_key || undefined,
relay_zaprite_base_url:
config?.relay_zaprite_base_url || 'https://api.zaprite.com',
relay_zaprite_currency: config?.relay_zaprite_currency || 'USD',
pro_card_price: toMajor(cents?.pro, 21),
max_card_price: toMajor(cents?.max, 42),
}
},
async ({ effects, input }) => {
// Card prices are entered in major units (dollars) but Zaprite + the
// relay charge in the currency's smallest unit (cents), so ×100.
const fiatCents = {
pro: Math.round(Number(input.pro_card_price ?? 21) * 100),
max: Math.round(Number(input.max_card_price ?? 42) * 100),
}
await configFile.merge(effects, {
relay_zaprite_api_key: (input.relay_zaprite_api_key || '').trim(),
relay_zaprite_base_url: (
input.relay_zaprite_base_url || 'https://api.zaprite.com'
).trim(),
relay_zaprite_currency: (input.relay_zaprite_currency || 'USD')
.trim()
.toUpperCase(),
relay_tier_prices_fiat_cents_json: JSON.stringify(fiatCents),
})
return null
},
)