142 lines
5.0 KiB
TypeScript
142 lines
5.0 KiB
TypeScript
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
|
||
},
|
||
)
|