@ophelio/sdk 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -66,10 +66,12 @@ GETs are retried automatically on network errors / 429 / 502 / 503 / 504
66
66
  (exponential backoff with jitter, `Retry-After` honoured; configure with
67
67
  `maxRetries`, default 2). Plain mutations are **never** retried.
68
68
 
69
- `entitlements.redeem` and `entitlements.recordUsage` are idempotency-keyed:
70
- the SDK generates a key per call and reuses it across its own retries, so
71
- they're safely retryable. If you retry at the application level (offline
72
- gate queues), pass your own key:
69
+ The idempotency-keyed mutations — `memberships.create`, `memberships.renew`,
70
+ `memberships.createTransaction`, `entitlements.redeem`, and
71
+ `entitlements.recordUsage` — each generate a key per call and reuse it across
72
+ the SDK's own retries, so they're safely retried on transient failures. If you
73
+ retry at the application level (offline gate queues, at-least-once job runners),
74
+ pass your own key so replays are recognised across processes:
73
75
 
74
76
  ```ts
75
77
  await ophelio.entitlements.redeem(
@@ -151,26 +153,27 @@ collection is written to while you iterate. Paginated methods are marked
151
153
 
152
154
  Everything reachable with an API key is covered:
153
155
 
154
- | Namespace | Methods |
155
- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
156
- | `admit` | `check({ card })` |
157
- | `members` | `sync({ since? })`, `get(id)`, `listEntitlements(id, { type? })`, `reissueCard(id, { note? })` |
158
- | `memberships` | `list(params?)`, `get(id)`, `create(...)`, `update(id, ...)`, `cancel(id, ...)`, `uncancel(id)`, `renew(id, ...)`, `upgrade(id, ...)`, `downgrade(id, ...)`, `listMembers(id)`, `listScheduledChanges(id, params?)`, `cancelScheduledChange(id, changeId)`, `listTransactions(id, params?)`, `listTermTransactions(id, termId, params?)`, `createTransaction(id, ...)` |
159
- | `customers` | `list(params?)`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)`, `listMemberships(id, params?)` |
160
- | `entitlements` | `redeem(...)`, `recordUsage(...)`, `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
161
- | `plans` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)`, `listBillingOptions(id)`, `createBillingOption(id, ...)`, `listEntitlements(id)`, `createEntitlement(id, ...)`, `listPlanLinks(id)`, `createPlanLink(id, ...)`, `listPricingGroupMappings(id)`, `createPricingGroupMapping(id, ...)` |
162
- | `billingOptions` | `list()`, `get(id)`, `update(id, ...)`, `delete(id)`, `listSalesChannels(id)`, `createSalesChannelLink(id, ...)` |
163
- | `memberRoles` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
164
- | `pricingGroups` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
165
- | `salesChannels` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
166
- | `planEntitlements` | `get(id)`, `update(id, ...)`, `delete(id)` |
167
- | `planLinks` | `delete(id)` |
168
- | `planPricingGroupMappings` | `delete(id)` |
169
- | `billingOptionSalesChannels` | `delete(id)` |
170
- | `webhookSubscriptions` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)`, `listDeliveries(id, params?)` |
156
+ | Namespace | Methods |
157
+ | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
158
+ | `admit` | `check({ card })` |
159
+ | `members` | `sync({ since? })`, `get(id)`, `listEntitlements(id, { type? })`, `update(id, ...)`, `remove(id)`, `setPrimary(id)`, `reissueCard(id, { note? })` |
160
+ | `memberships` | `list(params?)`, `get(id)`, `create(...)`, `update(id, ...)`, `cancel(id, ...)`, `uncancel(id)`, `renew(id, ...)`, `upgrade(id, ...)`, `downgrade(id, ...)`, `listMembers(id)`, `addMember(id, ...)`, `listScheduledChanges(id, params?)`, `cancelScheduledChange(id, changeId)`, `listTransactions(id, params?)`, `listTermTransactions(id, termId, params?)`, `createTransaction(id, ...)` |
161
+ | `customers` | `list(params?)`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)`, `listMemberships(id, params?)` |
162
+ | `entitlements` | `redeem(...)`, `recordUsage(...)`, `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
163
+ | `plans` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)`, `listBillingOptions(id)`, `createBillingOption(id, ...)`, `listEntitlements(id)`, `createEntitlement(id, ...)`, `listPlanLinks(id)`, `createPlanLink(id, ...)`, `listPricingGroupMappings(id)`, `createPricingGroupMapping(id, ...)` |
164
+ | `billingOptions` | `list()`, `get(id)`, `update(id, ...)`, `delete(id)`, `listSalesChannels(id)`, `createSalesChannelLink(id, ...)` |
165
+ | `memberRoles` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
166
+ | `pricingGroups` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
167
+ | `salesChannels` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
168
+ | `planEntitlements` | `get(id)`, `update(id, ...)`, `delete(id)` |
169
+ | `planLinks` | `delete(id)` |
170
+ | `planPricingGroupMappings` | `delete(id)` |
171
+ | `billingOptionSalesChannels` | `delete(id)` |
172
+ | `webhookSubscriptions` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)`, `listDeliveries(id, params?)` |
171
173
 
172
174
  Every method accepts a trailing `options` argument: `{ signal?, headers? }`
173
- (plus `idempotencyKey?` on `redeem` / `recordUsage`).
175
+ (plus `idempotencyKey?` on the idempotency-keyed mutations: `memberships.create`
176
+ / `renew` / `createTransaction` and `entitlements.redeem` / `recordUsage`).
174
177
 
175
178
  Endpoints requiring a user session (API key management, organisation and
176
179
  project admin) are intentionally not in the SDK — API keys cannot call them.
package/dist/index.cjs CHANGED
@@ -117,6 +117,15 @@ function sleep(ms) {
117
117
  const DEFAULT_BASE_URL = "https://api.ophel.io";
118
118
  const DEFAULT_TIMEOUT_MS = 3e4;
119
119
  const DEFAULT_MAX_RETRIES = 2;
120
+ /**
121
+ * Resolves the caller-supplied idempotency key to the value sent on the wire.
122
+ * Trims it and falls back to a generated key when it is absent, empty, or
123
+ * whitespace-only — a blank key must never leave an idempotency-keyed mutation
124
+ * retryable but unkeyed, or a retried write could duplicate.
125
+ */
126
+ function resolveIdempotencyKey(idempotencyKey) {
127
+ return idempotencyKey?.trim() || crypto.randomUUID();
128
+ }
120
129
  var Transport = class {
121
130
  apiKey;
122
131
  baseUrl;
@@ -384,7 +393,7 @@ var EntitlementsResource = class extends APIResource {
384
393
  method: "POST",
385
394
  path: "/api/v1/entitlements/redeem",
386
395
  body: params,
387
- idempotencyKey: idempotencyKey ?? crypto.randomUUID(),
396
+ idempotencyKey: resolveIdempotencyKey(idempotencyKey),
388
397
  retryable: true,
389
398
  options: requestOptions
390
399
  });
@@ -399,7 +408,7 @@ var EntitlementsResource = class extends APIResource {
399
408
  method: "POST",
400
409
  path: "/api/v1/entitlements/usage",
401
410
  body: params,
402
- idempotencyKey: idempotencyKey ?? crypto.randomUUID(),
411
+ idempotencyKey: resolveIdempotencyKey(idempotencyKey),
403
412
  retryable: true,
404
413
  options: requestOptions
405
414
  })).usage;
@@ -526,6 +535,44 @@ var MembersResource = class extends APIResource {
526
535
  options
527
536
  })).entitlements;
528
537
  }
538
+ /**
539
+ * Updates a member's details. Accepts `name`, `date_of_birth` and `role`
540
+ * (a member-role code). To reassign the primary member use `setPrimary`;
541
+ * to rotate a card use `reissueCard`.
542
+ */
543
+ update(id, params, options) {
544
+ return this.transport.request({
545
+ method: "PATCH",
546
+ path: `/api/v1/members/${encodeURIComponent(id)}`,
547
+ body: params,
548
+ retryable: false,
549
+ options
550
+ });
551
+ }
552
+ /**
553
+ * Remove a member from its membership. Rejected if it is the last member
554
+ * or the sole primary — reassign the primary first with `setPrimary`.
555
+ */
556
+ remove(id, options) {
557
+ return this.transport.request({
558
+ method: "DELETE",
559
+ path: `/api/v1/members/${encodeURIComponent(id)}`,
560
+ retryable: false,
561
+ options
562
+ });
563
+ }
564
+ /**
565
+ * Make this member the primary of its membership, demoting the current
566
+ * primary atomically. A no-op if it is already primary.
567
+ */
568
+ setPrimary(id, options) {
569
+ return this.transport.request({
570
+ method: "POST",
571
+ path: `/api/v1/members/${encodeURIComponent(id)}/set-primary`,
572
+ retryable: false,
573
+ options
574
+ });
575
+ }
529
576
  /** Issue a fresh card id, invalidating the old card at the gate. */
530
577
  reissueCard(id, params = {}, options) {
531
578
  return this.transport.request({
@@ -562,13 +609,22 @@ var MembershipsResource = class extends APIResource {
562
609
  options
563
610
  });
564
611
  }
565
- create(params, options) {
612
+ /**
613
+ * Create a membership atomically (record, members, first term, optional
614
+ * payment). Idempotent: the Idempotency-Key is generated once per call (and
615
+ * reused across the SDK's internal retries), so a retried create returns the
616
+ * original membership instead of creating a duplicate. Supply your own key to
617
+ * dedupe application-level retries across processes.
618
+ */
619
+ create(params, options = {}) {
620
+ const { idempotencyKey, ...requestOptions } = options;
566
621
  return this.transport.request({
567
622
  method: "POST",
568
623
  path: "/api/v1/memberships",
569
624
  body: params,
570
- retryable: false,
571
- options
625
+ idempotencyKey: resolveIdempotencyKey(idempotencyKey),
626
+ retryable: true,
627
+ options: requestOptions
572
628
  });
573
629
  }
574
630
  update(id, params, options) {
@@ -615,14 +671,19 @@ var MembershipsResource = class extends APIResource {
615
671
  /**
616
672
  * Report a payment outcome. Ophel.io never processes payments — your
617
673
  * payment provider does; this records the result and drives dunning.
674
+ * Idempotent: the Idempotency-Key is generated once per call (and reused
675
+ * across the SDK's internal retries), so a retried report records the
676
+ * transaction once and does not advance dunning twice.
618
677
  */
619
- createTransaction(id, params, options) {
678
+ createTransaction(id, params, options = {}) {
679
+ const { idempotencyKey, ...requestOptions } = options;
620
680
  return this.transport.request({
621
681
  method: "POST",
622
682
  path: `/api/v1/memberships/${encodeURIComponent(id)}/transactions`,
623
683
  body: params,
624
- retryable: false,
625
- options
684
+ idempotencyKey: resolveIdempotencyKey(idempotencyKey),
685
+ retryable: true,
686
+ options: requestOptions
626
687
  });
627
688
  }
628
689
  async listMembers(id, options) {
@@ -633,17 +694,38 @@ var MembershipsResource = class extends APIResource {
633
694
  options
634
695
  })).members;
635
696
  }
636
- /** Open the next term. Rejected (409) while cancelled or cancellation-pending. */
637
- renew(id, params, options) {
697
+ /**
698
+ * Add a member to an existing membership, up to the plan's max party size.
699
+ * `role` is a member-role code; an optional `email` links the member to a
700
+ * customer record. Rejected on cancelled or expired memberships.
701
+ */
702
+ addMember(id, params, options) {
638
703
  return this.transport.request({
639
704
  method: "POST",
640
- path: `/api/v1/memberships/${encodeURIComponent(id)}/renew`,
705
+ path: `/api/v1/memberships/${encodeURIComponent(id)}/members`,
641
706
  body: params,
642
707
  retryable: false,
643
708
  options
644
709
  });
645
710
  }
646
711
  /**
712
+ * Open the next term. Rejected (409) while cancelled or cancellation-pending.
713
+ * Idempotent: the Idempotency-Key is generated once per call (and reused
714
+ * across the SDK's internal retries), so a retried renewal returns the
715
+ * original result instead of opening a second term.
716
+ */
717
+ renew(id, params, options = {}) {
718
+ const { idempotencyKey, ...requestOptions } = options;
719
+ return this.transport.request({
720
+ method: "POST",
721
+ path: `/api/v1/memberships/${encodeURIComponent(id)}/renew`,
722
+ body: params,
723
+ idempotencyKey: resolveIdempotencyKey(idempotencyKey),
724
+ retryable: true,
725
+ options: requestOptions
726
+ });
727
+ }
728
+ /**
647
729
  * Move to a linked higher plan. Applies immediately or schedules at term
648
730
  * end — `scheduled_change` is non-null in the deferred case.
649
731
  */
@@ -1188,7 +1270,9 @@ const WEBHOOK_EVENT_CODES = [
1188
1270
  "membership.downgraded",
1189
1271
  "scheduled_change.failed",
1190
1272
  "entitlement.used",
1273
+ "member.added",
1191
1274
  "member.card_issued",
1275
+ "member.updated",
1192
1276
  "member.removed",
1193
1277
  "plan.updated"
1194
1278
  ];