{"_id":"@cashly-billing/sdk","name":"@cashly-billing/sdk","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@cashly-billing/sdk","version":"0.1.0","description":"Cashly SDK — server-side TypeScript/Node client for the Cashly billing API","license":"MIT","author":{"name":"Cashly"},"homepage":"https://cashlybilling.com","repository":{"type":"git","url":"git+https://github.com/batler-saas/cashly.git","directory":"packages/sdk"},"bugs":{"url":"https://github.com/batler-saas/cashly/issues"},"type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./webhooks":{"import":{"types":"./dist/webhooks.d.ts","default":"./dist/webhooks.js"},"require":{"types":"./dist/webhooks.d.cts","default":"./dist/webhooks.cjs"}}},"scripts":{"build":"tsup","dev":"tsup --watch","lint":"echo 'sdk: no lint configured yet'","test":"vitest run","test:watch":"vitest","type-check":"tsc --noEmit","prepublishOnly":"pnpm run type-check && pnpm run test && pnpm run build"},"engines":{"node":">=20"},"devDependencies":{"@types/node":"20.17.10","tsup":"8.3.5","typescript":"5.6.3","vitest":"2.1.8"},"publishConfig":{"access":"public"},"keywords":["cashly","billing","subscriptions","saas","stripe-alternative","israel"],"_id":"@cashly-billing/sdk@0.1.0","gitHead":"571b93067f3664112175aafd3815fd8b26465961","_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-GFbG6eVv3U05QVyAPInzZsuvxdwBUEUZwXIZngrEsCdeoQsVJ6uslRseXOXcPgucyWxjzOOITFvgahhY66r3uA==","shasum":"5e15d06c2fe6f3061f07988fb89e74a4041a69d1","tarball":"https://registry.npmjs.org/@cashly-billing/sdk/-/sdk-0.1.0.tgz","fileCount":18,"unpackedSize":205548,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIDTxliiY5k3r872Rexdkt9jlJqHWMvAw83z6pYye9pcXAiEAhApNqEUUfBSOix2pJNZta+Vlr1szY6PhL6MTS8JwoLE="}]},"_npmUser":{"name":"cashlybilling","email":"support@batler.co.il"},"directories":{},"maintainers":[{"name":"cashlybilling","email":"support@batler.co.il"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sdk_0.1.0_1779657359499_0.22514383745892763"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-24T21:15:59.010Z","0.1.0":"2026-05-24T21:15:59.640Z","modified":"2026-05-24T21:15:59.850Z"},"maintainers":[{"name":"cashlybilling","email":"support@batler.co.il"}],"description":"Cashly SDK — server-side TypeScript/Node client for the Cashly billing API","homepage":"https://cashlybilling.com","keywords":["cashly","billing","subscriptions","saas","stripe-alternative","israel"],"repository":{"type":"git","url":"git+https://github.com/batler-saas/cashly.git","directory":"packages/sdk"},"author":{"name":"Cashly"},"bugs":{"url":"https://github.com/batler-saas/cashly/issues"},"license":"MIT","readme":"# @cashly-billing/sdk\n\nServer-side TypeScript/Node SDK for the Cashly billing API.\n\n```bash\nnpm install @cashly-billing/sdk\n# or\npnpm add @cashly-billing/sdk\n```\n\nRequires Node ≥ 20 (uses `globalThis.fetch`).\n\n## Quickstart\n\n```ts\nimport { Cashly } from '@cashly-billing/sdk';\n\nconst cashly = new Cashly({\n  secretKey: process.env.CASHLY_SECRET_KEY!, // sk_test_... or sk_live_...\n});\n\n// When a user signs up in your app\nconst customer = await cashly.customers.upsertByExternalId({\n  externalId: user.id,\n  email: user.email,\n  name: user.fullName,\n});\n\n// Before a gated action\nconst check = await cashly.entitlements.check({\n  customerExternalId: user.id,\n  feature: 'max_contacts',\n  quantity: 1,\n});\nif (!check.allowed) {\n  throw new Error('Upgrade required');\n}\n\n// After the action\nawait cashly.entitlements.track({\n  customerExternalId: user.id,\n  feature: 'sms_quota',\n  quantity: 1,\n  idempotencyKey: `sms-${message.id}`,\n});\n```\n\n## Configuration\n\n```ts\nnew Cashly({\n  secretKey: 'sk_live_...',  // required\n  baseUrl: 'https://api.cashlybilling.com', // override for self-hosted / staging\n  timeout: 30_000,           // per-request timeout in ms\n  maxRetries: 2,             // transient failures (network + 5xx + 429)\n  appName: 'my-saas',        // appears in User-Agent for our logs\n});\n```\n\nThe SDK auto-retries network errors, 5xx responses, and 429 rate-limits with\nexponential backoff (250ms → 500ms → 1s → 2s → 4s). It never retries 4xx\nclient errors.\n\n## Resources\n\n### `cashly.customers`\n\nManage the end-customers of your SaaS.\n\n```ts\n// Create\nawait cashly.customers.create({ externalId: 'user_1', email: 'a@b.co' });\n\n// Upsert — safe to call on every sign-in\nawait cashly.customers.upsertByExternalId({\n  externalId: user.id,\n  email: user.email,\n});\n\n// Look up (returns null when not found)\nconst existing = await cashly.customers.lookupByExternalId('user_1');\n\n// Read\nawait cashly.customers.get(id);\nawait cashly.customers.list({ search: 'acme', pageSize: 50 });\n\n// Update / soft delete\nawait cashly.customers.update(id, { name: 'Acme Inc' });\nawait cashly.customers.delete(id);\n```\n\n### `cashly.subscriptions`\n\n```ts\n// Create — uses plan's trialDays unless overridden\nawait cashly.subscriptions.create({ customerId, planId, trialDays: 14 });\n\n// Cancel (default: end of current period)\nawait cashly.subscriptions.cancel(id, { when: 'immediate', reason: 'switched_tools' });\n\n// Revert a scheduled cancellation\nawait cashly.subscriptions.reactivate(id);\n\n// Change plan\nawait cashly.subscriptions.changePlan(id, { planId: 'plan_pro', effective: 'now' });\n```\n\n### `cashly.entitlements`\n\nThe hot path. Use `check` before gated actions, `track` after.\n\n```ts\n// BOOLEAN — allowed iff value === 'true'\nawait cashly.entitlements.check({ customerExternalId, feature: 'custom_domain' });\n\n// LIMIT — allowed iff quantity ≤ limit (stateless)\nawait cashly.entitlements.check({ customerExternalId, feature: 'max_users', quantity: 12 });\n\n// QUOTA — allowed iff used + quantity ≤ limit (stateful, resets per period)\nconst r = await cashly.entitlements.check({ customerExternalId, feature: 'sms', quantity: 1 });\n// r.used, r.remaining, r.resetAt are populated\n\n// METERED — always allowed; track is what matters\nawait cashly.entitlements.track({\n  customerExternalId,\n  feature: 'api_calls',\n  quantity: 1,\n  idempotencyKey: `api-${request.id}`, // dedupe-safe key\n});\n\n// Full snapshot — useful for billing dashboards\nconst snap = await cashly.entitlements.snapshot({ customerExternalId });\n```\n\n`idempotencyKey` is required for `track`. Send the same key on retries and\nusage will never double-count.\n\n### `cashly.portal`\n\nGenerate Customer Portal session URLs. Wire to a \"Manage subscription\" button\nin your app.\n\n```ts\napp.post('/billing-portal', requireAuth, async (req, res) => {\n  const { url } = await cashly.portal.createSession({\n    customerExternalId: req.user.id,\n    returnUrl: `${process.env.APP_URL}/dashboard`,\n  });\n  res.redirect(url);\n});\n```\n\nThe URL is valid for 5 minutes and consumed on first visit.\n\n### `cashly.plans` / `cashly.invoices` / `cashly.paymentMethods`\n\nRead-only. Pricing/catalog mutations happen in the Cashly dashboard.\n\n```ts\nconst plans = await cashly.plans.list({ publicOnly: true });\nconst invoices = await cashly.invoices.list({ customerId });\nconst cards = await cashly.paymentMethods.list({ customerId });\n```\n\nTo download an invoice PDF, use `invoice.pdfUrl`.\n\n## Webhooks\n\nVerify incoming events with HMAC. Use raw body — do NOT `JSON.parse` first.\n\n### Express\n\n```ts\nimport express from 'express';\n\napp.post(\n  '/cashly-webhook',\n  express.raw({ type: 'application/json' }),\n  (req, res) => {\n    try {\n      const event = cashly.webhooks.verify({\n        rawBody: req.body, // Buffer\n        signature: req.headers['cashly-signature'],\n        secret: process.env.CASHLY_WEBHOOK_SECRET!,\n      });\n\n      switch (event.type) {\n        case 'subscription.canceled':\n          // event.data is fully typed as Subscription\n          break;\n        case 'charge.failed':\n          break;\n        case 'invoice.paid':\n          break;\n      }\n\n      res.send('ok');\n    } catch (err) {\n      res.status(400).send('invalid signature');\n    }\n  },\n);\n```\n\n### Next.js App Router\n\n```ts\n// app/api/cashly-webhook/route.ts\nimport { NextRequest, NextResponse } from 'next/server';\nimport { cashly } from '@/lib/cashly';\n\nexport async function POST(req: NextRequest) {\n  const rawBody = await req.text();\n  try {\n    const event = cashly.webhooks.verify({\n      rawBody,\n      signature: req.headers.get('cashly-signature') ?? undefined,\n      secret: process.env.CASHLY_WEBHOOK_SECRET!,\n    });\n    // ...handle event...\n    return NextResponse.json({ ok: true });\n  } catch {\n    return new NextResponse('invalid signature', { status: 400 });\n  }\n}\n```\n\nSubscribe to events at `Dashboard → Developers → Webhooks`. The available\nevent types are:\n\n- `customer.created` / `updated` / `deleted`\n- `subscription.created` / `updated` / `canceled` / `trial_ending` / `trial_ended` / `past_due` / `reactivated`\n- `charge.succeeded` / `failed` / `refunded`\n- `invoice.created` / `paid` / `payment_failed`\n\n## Error handling\n\nEvery SDK error extends `CashlyError`. Check the subclass to react.\n\n```ts\nimport {\n  CashlyApiError,\n  CashlyNetworkError,\n  CashlyValidationError,\n  CashlyWebhookVerificationError,\n} from '@cashly-billing/sdk';\n\ntry {\n  await cashly.entitlements.check({ customerExternalId, feature: 'foo' });\n} catch (err) {\n  if (err instanceof CashlyApiError) {\n    // err.status, err.code, err.requestId, err.body\n    if (err.status === 404) { /* feature or customer not found */ }\n    if (err.status === 429) { /* should never happen — SDK retries 429 */ }\n  } else if (err instanceof CashlyNetworkError) {\n    // Connectivity issue, all retries exhausted\n  } else if (err instanceof CashlyValidationError) {\n    // Invalid argument before the request left the SDK\n  }\n  throw err;\n}\n```\n\nWhen opening a support ticket, include `err.requestId` — it correlates with\nour server logs.\n\n## Security\n\n- The secret key never leaves your server. Don't ship `@cashly-billing/sdk` to a\n  browser bundle — it would expose the key to any client.\n- Webhook signatures protect against forged events. Always verify them\n  before trusting `event.data`.\n- For card capture (tokenization), use the Customer Portal — never collect\n  card details on your own forms. This keeps you out of PCI scope.\n\n## License\n\nMIT — see [LICENSE](./LICENSE).\n","readmeFilename":"README.md","_rev":"1-455cd345d5ee4dc12024668c0df7dcfb"}