v0.1.0:46 — idempotent Connect BTCPay, Go SDK now part of toolchain
Closes the last T1 BTCPay UX gap from V0.2_PLAN. Connect now checks /v1/admin/btcpay/status first; if a connection exists, returns a clear "already connected" guidance message pointing the operator at Disconnect → Connect for re-authorize cases. Without this guard, re-clicking Connect spawned a new webhook subscription on BTCPay's side every time, leaving orphan webhooks BTCPay would keep trying to deliver to. The Go SDK has been written and verified — all 4 crosscheck tests pass against the shared tests/crosscheck/vector.json (the same file the Rust/TS/Python SDKs and the daemon test against). Pure stdlib, zero third-party dependencies. Hosted in its own repo at github.com/keysat-xyz/keysat-client-go (private during alpha). This release IS the 5th-language milestone: daemon + Rust + TS + Python + Go all agree byte-for-byte on the LIC1 wire format. Daemon binary unchanged — wrapper-only revision.
This commit is contained in:
@@ -33,6 +33,51 @@ export const configureBtcpay = sdk.Action.withoutInput(
|
|||||||
async () => {
|
async () => {
|
||||||
const storeData = await store.read().once()
|
const storeData = await store.read().once()
|
||||||
if (!storeData) throw new Error('Store not initialized — restart the service.')
|
if (!storeData) throw new Error('Store not initialized — restart the service.')
|
||||||
|
|
||||||
|
// Idempotency guard: if Keysat is already connected to a BTCPay
|
||||||
|
// store, re-running Connect would spawn a NEW webhook subscription
|
||||||
|
// on BTCPay's side (because the authorize flow always registers
|
||||||
|
// one). That leaves orphan webhooks pointing at this Keysat that
|
||||||
|
// BTCPay will keep trying to deliver to, and confuses
|
||||||
|
// reconciliation. Steer the operator to Disconnect first instead.
|
||||||
|
try {
|
||||||
|
const statusResp = await adminCall(
|
||||||
|
LICENSING_URL,
|
||||||
|
storeData.admin_api_key,
|
||||||
|
'/v1/admin/btcpay/status',
|
||||||
|
{ method: 'GET' },
|
||||||
|
)
|
||||||
|
if (statusResp.ok) {
|
||||||
|
const status = (await statusResp.json()) as {
|
||||||
|
connected?: boolean
|
||||||
|
store_id?: string | null
|
||||||
|
base_url?: string | null
|
||||||
|
}
|
||||||
|
if (status.connected) {
|
||||||
|
return {
|
||||||
|
version: '1',
|
||||||
|
title: 'BTCPay already connected',
|
||||||
|
message:
|
||||||
|
`Keysat is already connected to ` +
|
||||||
|
`${status.base_url ?? '(unknown URL)'} ` +
|
||||||
|
`(store ${status.store_id ?? '(unknown id)'}).\n\n` +
|
||||||
|
`To re-authorize (e.g., switch stores or rotate the API key), ` +
|
||||||
|
`run "Disconnect BTCPay" first, then re-run "Connect BTCPay". ` +
|
||||||
|
`Existing license keys, products, and policies are unaffected ` +
|
||||||
|
`by a Disconnect/Connect cycle.\n\n` +
|
||||||
|
`If you're seeing connection problems, "Check BTCPay connection" ` +
|
||||||
|
`also reports wallet / payment-method status that the connect ` +
|
||||||
|
`flow doesn't surface.`,
|
||||||
|
result: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Status check failure is non-fatal — fall through to the
|
||||||
|
// authorize flow. Same UX as before.
|
||||||
|
} catch (_) {
|
||||||
|
// Same — non-fatal.
|
||||||
|
}
|
||||||
|
|
||||||
const resp = await adminCall(
|
const resp = await adminCall(
|
||||||
LICENSING_URL,
|
LICENSING_URL,
|
||||||
storeData.admin_api_key,
|
storeData.admin_api_key,
|
||||||
|
|||||||
@@ -9,8 +9,20 @@
|
|||||||
import { VersionInfo } from '@start9labs/start-sdk'
|
import { VersionInfo } from '@start9labs/start-sdk'
|
||||||
|
|
||||||
export const v0_1_0 = VersionInfo.of({
|
export const v0_1_0 = VersionInfo.of({
|
||||||
version: '0.1.0:45',
|
version: '0.1.0:46',
|
||||||
releaseNotes: [
|
releaseNotes: [
|
||||||
|
`Alpha-iteration revision 46 of v0.1.0 — Idempotent BTCPay Connect, plus the Go SDK is now part of the published toolchain.`,
|
||||||
|
``,
|
||||||
|
`**Idempotent Connect.** "Connect BTCPay" no longer blindly initiates a fresh authorize flow when Keysat is already connected. It now checks \`/v1/admin/btcpay/status\` first; if a connection exists, it returns a clear "already connected" message that points the operator at "Disconnect BTCPay" → "Connect BTCPay" for the re-authorize case. Closes the v0.2-plan T1 item that was the last outstanding BTCPay UX gap. Without this, re-clicking Connect spawned a new webhook subscription on BTCPay's side every time, leaving orphan webhooks BTCPay would keep trying to deliver to.`,
|
||||||
|
``,
|
||||||
|
`**Go SDK lands.** A pure-Go (stdlib only, no third-party deps) implementation of the LIC1 wire format goes live alongside this release at \`github.com/keysat-xyz/keysat-client-go\`. Verified byte-for-byte against the same \`tests/crosscheck/vector.json\` the Rust, TypeScript, Python SDKs and the daemon itself test against — all four crosscheck fixtures (v1 legacy, v2 trial with entitlements, v2 perpetual unbound, plus end-to-end PEM-load → ParseAndVerify roundtrip) pass. Five independent implementations of the wire format now agree.`,
|
||||||
|
``,
|
||||||
|
`Go SDK API: \`keysat.ParseKey\`, \`keysat.Verify\`, \`keysat.ParseAndVerify\`, \`keysat.HashFingerprint\`, \`keysat.LoadPublicKeyPEM\` for offline use; \`keysat.Client.Validate\` and \`keysat.Client.PublicKey\` for online checks. Idiomatic Go method receivers on \`LicensePayload\` (\`IsTrial\`, \`IsFingerprintBound\`, \`IsExpiredAt\`, \`HasEntitlement\`).`,
|
||||||
|
``,
|
||||||
|
`**Daemon binary unchanged.** This is a wrapper-only revision — no Rust source files moved between :45 and :46. Test count remains 31 on the daemon side; the Go SDK's 4 crosscheck tests run independently against \`go test ./...\`.`,
|
||||||
|
``,
|
||||||
|
`**Upgrade path.** v0.1.0:45 → v0.1.0:46 is a straight drop-in. No new migrations, no schema changes.`,
|
||||||
|
``,
|
||||||
`Alpha-iteration revision 45 of v0.1.0 — Buyer self-service license recovery and a database-health admin endpoint. Two operator-facing additions that close real friction points the v0.2 plan called out.`,
|
`Alpha-iteration revision 45 of v0.1.0 — Buyer self-service license recovery and a database-health admin endpoint. Two operator-facing additions that close real friction points the v0.2 plan called out.`,
|
||||||
``,
|
``,
|
||||||
`**Buyer self-service recovery.** Until now, the recovery flow for "I lost my license key" was "DM the operator with your invoice id and we re-send" — operator-time scaling badly. v0.1.0:45 ships \`POST /v1/recover\` and a server-rendered \`GET /recover\` HTML form. A buyer enters their invoice id (handed to them at checkout) and the email they paid with; if both match a settled invoice in the daemon's database, the same signed license key is re-derived and returned. No support ticket, no operator involvement.`,
|
`**Buyer self-service recovery.** Until now, the recovery flow for "I lost my license key" was "DM the operator with your invoice id and we re-send" — operator-time scaling badly. v0.1.0:45 ships \`POST /v1/recover\` and a server-rendered \`GET /recover\` HTML form. A buyer enters their invoice id (handed to them at checkout) and the email they paid with; if both match a settled invoice in the daemon's database, the same signed license key is re-derived and returned. No support ticket, no operator involvement.`,
|
||||||
|
|||||||
Reference in New Issue
Block a user