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 () => {
|
||||
const storeData = await store.read().once()
|
||||
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(
|
||||
LICENSING_URL,
|
||||
storeData.admin_api_key,
|
||||
|
||||
Reference in New Issue
Block a user