@invonetwork/web-sdk 1.0.0 → 1.2.0

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/dist/server.js CHANGED
@@ -1,5 +1,5 @@
1
- import { InvoError, assertSecureBaseUrl, Http } from './chunk-D3XBTH4C.js';
2
- export { InvoError } from './chunk-D3XBTH4C.js';
1
+ import { InvoError, assertSecureBaseUrl, Http, toDestinationsResult } from './chunk-P65XQ6VF.js';
2
+ export { InvoError } from './chunk-P65XQ6VF.js';
3
3
  import { createHmac } from 'crypto';
4
4
 
5
5
  var DEFAULT_TOLERANCE_SEC = 300;
@@ -161,7 +161,7 @@ async function hmacHexSubtle(secret, message) {
161
161
  }
162
162
 
163
163
  // src/server.ts
164
- var DEFAULT_UA = "invonetwork-web-sdk/1.0.0 (+https://invo.network)";
164
+ var DEFAULT_UA = "invonetwork-web-sdk/1.2.0 (+https://invo.network)";
165
165
  var MAX_USD_AMOUNT = 999.99;
166
166
  var MAX_ITEM_PRICE = 999999.99;
167
167
  function invalidInput(label, value, why) {
@@ -223,6 +223,75 @@ function toInboundPendingItem(row) {
223
223
  raw: row
224
224
  };
225
225
  }
226
+ function toSmsVerifyResult(raw) {
227
+ return {
228
+ status: String(raw["status"] ?? ""),
229
+ transactionId: String(raw["transaction_id"] ?? ""),
230
+ claimCode: raw["claim_code"],
231
+ newBalance: raw["new_balance"] ?? null,
232
+ orderId: raw["order_id"],
233
+ raw
234
+ };
235
+ }
236
+ function toClaimResult(raw) {
237
+ const status = String(raw["status"] ?? "");
238
+ return {
239
+ status,
240
+ transactionId: raw["transaction_id"],
241
+ newBalance: raw["new_balance"] ?? null,
242
+ currencyName: raw["currency_name"],
243
+ orderId: raw["order_id"],
244
+ needsAccountSelection: status === "needs_account_selection",
245
+ candidates: Array.isArray(raw["candidates"]) ? raw["candidates"] : void 0,
246
+ raw
247
+ };
248
+ }
249
+ function toTransactionStatus(raw) {
250
+ return {
251
+ transactionId: String(raw["transaction_id"] ?? ""),
252
+ transactionType: String(raw["transaction_type"] ?? ""),
253
+ transactionStatus: String(raw["transaction_status"] ?? ""),
254
+ verificationState: String(raw["verification_state"] ?? ""),
255
+ amount: raw["amount"] ?? null,
256
+ netAmount: raw["net_amount"] ?? null,
257
+ currencyId: raw["currency_id"] ?? "",
258
+ orderId: raw["order_id"],
259
+ toPhone: raw["to_phone"],
260
+ toIdentityId: raw["to_identity_id"] ?? null,
261
+ raw
262
+ };
263
+ }
264
+ function toGuardianApprovalStatus(raw) {
265
+ const approval = raw["approval"] ?? {};
266
+ return {
267
+ state: String(approval["state"] ?? ""),
268
+ approvalId: approval["approval_id"],
269
+ transactionId: approval["transaction_id"],
270
+ expiresAt: approval["expires_at"],
271
+ decidedAt: approval["decided_at"],
272
+ decisionSource: approval["decision_source"],
273
+ actionDescription: approval["action_description"],
274
+ raw
275
+ };
276
+ }
277
+ function toPhoneShareInitiateResult(raw) {
278
+ return {
279
+ approvalId: raw["approval_id"],
280
+ codeSent: typeof raw["code_sent"] === "boolean" ? raw["code_sent"] : void 0,
281
+ expiresAt: raw["expires_at"],
282
+ alreadyApproved: typeof raw["already_approved"] === "boolean" ? raw["already_approved"] : void 0,
283
+ raw
284
+ };
285
+ }
286
+ function toPhoneShareApproveResult(raw) {
287
+ return {
288
+ approvalId: raw["approval_id"],
289
+ approved: typeof raw["approved"] === "boolean" ? raw["approved"] : void 0,
290
+ alreadyApproved: typeof raw["already_approved"] === "boolean" ? raw["already_approved"] : void 0,
291
+ expiresAt: raw["expires_at"],
292
+ raw
293
+ };
294
+ }
226
295
  function toCurrencyBalance(row) {
227
296
  return {
228
297
  currencyId: row["currency_id"] ?? "",
@@ -669,6 +738,227 @@ var InvoServer = class {
669
738
  raw
670
739
  };
671
740
  }
741
+ /**
742
+ * Complete a TRANSFER's SMS-PIN step (un-enrolled fallback). NOT idempotent —
743
+ * each attempt is counted server-side, so this is never auto-retried. A bad PIN
744
+ * throws a 400 InvoError carrying `attempts_remaining` on `err.body`; a blocked
745
+ * recipient throws 429 (`sms_verification_blocked`).
746
+ */
747
+ async verifySmsTransfer(transactionId, smsPin, opts) {
748
+ if (typeof transactionId !== "string" || !transactionId.trim()) {
749
+ throw invalidInput("transactionId", transactionId, "is required");
750
+ }
751
+ if (typeof smsPin !== "string" || !smsPin.trim()) {
752
+ throw invalidInput("smsPin", smsPin, "is required");
753
+ }
754
+ const raw = await this.http.post(
755
+ "/api/transfers/verify-sms",
756
+ { transaction_id: transactionId, sms_pin: smsPin },
757
+ this.auth,
758
+ { signal: opts?.signal }
759
+ // NOT idempotent — attempts are counted; never auto-retried
760
+ );
761
+ return toSmsVerifyResult(raw);
762
+ }
763
+ /** Complete a SEND's SMS-PIN step (un-enrolled fallback). See verifySmsTransfer. */
764
+ async verifySmsSend(transactionId, smsPin, opts) {
765
+ if (typeof transactionId !== "string" || !transactionId.trim()) {
766
+ throw invalidInput("transactionId", transactionId, "is required");
767
+ }
768
+ if (typeof smsPin !== "string" || !smsPin.trim()) {
769
+ throw invalidInput("smsPin", smsPin, "is required");
770
+ }
771
+ const raw = await this.http.post(
772
+ "/api/currency-sends/verify-sms",
773
+ { transaction_id: transactionId, sms_pin: smsPin },
774
+ this.auth,
775
+ { signal: opts?.signal }
776
+ // NOT idempotent — attempts are counted; never auto-retried
777
+ );
778
+ return toSmsVerifyResult(raw);
779
+ }
780
+ /**
781
+ * Claim an inbound TRANSFER by its claim code, crediting one of this game's currencies.
782
+ * NOT idempotent. A 200 may come back as `needsAccountSelection` (the recipient phone
783
+ * maps to >1 of your players) — inspect `result.candidates` and re-submit with
784
+ * `targetPlayerId`. Errors surface via InvoError (404 unknown code, 403 phone mismatch,
785
+ * 400 not-claimable/expired/wrong-value, 409 `err.isPhoneShareApprovalRequired`, …).
786
+ */
787
+ async claimTransfer(input, opts) {
788
+ const body = {
789
+ claim_code: input.claimCode,
790
+ target_player_name: input.targetPlayerName,
791
+ target_player_email: input.targetPlayerEmail,
792
+ target_player_phone: input.targetPlayerPhone,
793
+ target_currency_id: input.targetCurrencyId
794
+ };
795
+ if (input.targetPlayerId != null) body["target_player_id"] = input.targetPlayerId;
796
+ const raw = await this.http.post(
797
+ "/api/transfers/claim-transfer",
798
+ body,
799
+ this.auth,
800
+ { signal: opts?.signal }
801
+ // NOT idempotent — never auto-retried
802
+ );
803
+ return toClaimResult(raw);
804
+ }
805
+ /**
806
+ * Claim an inbound SEND by its claim code. NOT idempotent. May return
807
+ * `needsAccountSelection` (multi-account phone) — re-submit with `receiverPlayerId`.
808
+ * See claimTransfer for the error surface.
809
+ */
810
+ async claimCurrency(input, opts) {
811
+ const body = {
812
+ claim_code: input.claimCode,
813
+ receiver_player_name: input.receiverPlayerName,
814
+ receiver_player_email: input.receiverPlayerEmail,
815
+ receiver_player_phone: input.receiverPlayerPhone
816
+ };
817
+ if (input.receiverPlayerId != null) body["receiver_player_id"] = input.receiverPlayerId;
818
+ const raw = await this.http.post(
819
+ "/api/currency-sends/claim-currency",
820
+ body,
821
+ this.auth,
822
+ { signal: opts?.signal }
823
+ // NOT idempotent — never auto-retried
824
+ );
825
+ return toClaimResult(raw);
826
+ }
827
+ /** Read a TRANSFER's status (verification_state + amounts + destination). */
828
+ async getTransferStatus(transactionId, opts) {
829
+ if (typeof transactionId !== "string" || !transactionId.trim()) {
830
+ throw invalidInput("transactionId", transactionId, "is required");
831
+ }
832
+ const raw = await this.http.get(
833
+ `/api/transfers/${encodeURIComponent(transactionId)}/status`,
834
+ this.auth,
835
+ { signal: opts?.signal }
836
+ );
837
+ return toTransactionStatus(raw);
838
+ }
839
+ /** Read a SEND's status (adds fees, game names, claim_info, … on `raw`). */
840
+ async getSendStatus(transactionId, opts) {
841
+ if (typeof transactionId !== "string" || !transactionId.trim()) {
842
+ throw invalidInput("transactionId", transactionId, "is required");
843
+ }
844
+ const raw = await this.http.get(
845
+ `/api/currency-sends/${encodeURIComponent(transactionId)}/status`,
846
+ this.auth,
847
+ { signal: opts?.signal }
848
+ );
849
+ return toTransactionStatus(raw);
850
+ }
851
+ /**
852
+ * Poll a transaction's guardian approval status (minor/guardian flow). The retry
853
+ * layer already retries the 503 (`{status:"unavailable"}`) for this idempotent GET;
854
+ * a missing approval throws a 404 InvoError (`{status:"not_found"}`).
855
+ */
856
+ async getGuardianApprovalStatus(transactionId, opts) {
857
+ if (typeof transactionId !== "string" || !transactionId.trim()) {
858
+ throw invalidInput("transactionId", transactionId, "is required");
859
+ }
860
+ const raw = await this.http.get(
861
+ `/api/transactions/${encodeURIComponent(transactionId)}/approval-status`,
862
+ this.auth,
863
+ { signal: opts?.signal }
864
+ );
865
+ return toGuardianApprovalStatus(raw);
866
+ }
867
+ /**
868
+ * List the games/tenants a player can send/transfer to FROM `sourceGameId`, with
869
+ * display metadata inline (name, icon, currency, min/max limits) — one call, no
870
+ * per-game lookup. SERVER variant (game-secret) of the browser `InvoClient.getDestinations`;
871
+ * the source game is passed explicitly rather than inferred from a player token. Rows
872
+ * share the browser shape. Idempotent (a POST read — safe to retry).
873
+ */
874
+ async getDestinations(query, opts) {
875
+ if (query.sourceGameId == null || String(query.sourceGameId).trim() === "") {
876
+ throw invalidInput("sourceGameId", query.sourceGameId, "is required");
877
+ }
878
+ const direction = query.direction ?? "transfer";
879
+ const path = direction === "send" ? "/api/currency-sends/available-destinations" : "/api/transfers/available-destinations";
880
+ const raw = await this.http.post(
881
+ path,
882
+ { source_game_id: query.sourceGameId },
883
+ this.auth,
884
+ { idempotent: true, signal: opts?.signal }
885
+ // a POST read — no side effect, safe to retry
886
+ );
887
+ return toDestinationsResult(raw, direction);
888
+ }
889
+ /**
890
+ * Phone-share OTP handshake — resolves a 409 `PHONE_SHARE_APPROVAL_REQUIRED`.
891
+ *
892
+ * These three endpoints are UNAUTHENTICATED (phone-owner-driven: no game secret,
893
+ * no player token) — a partner backend relays the handshake. The triggering 409
894
+ * carries `approval_id, expires_at, code_sent, existing_account_hints[], next_endpoint`.
895
+ *
896
+ * `phoneShareInitiate` is the fallback OTP send (use when the 409 didn't already
897
+ * dispatch a code). NOT idempotent — never auto-retried. OTP TTL ~10 min.
898
+ */
899
+ async phoneShareInitiate(input, opts) {
900
+ if (typeof input.phone !== "string" || !input.phone.trim()) {
901
+ throw invalidInput("phone", input.phone, "is required");
902
+ }
903
+ if (typeof input.email !== "string" || !input.email.trim()) {
904
+ throw invalidInput("email", input.email, "is required");
905
+ }
906
+ const raw = await this.http.post(
907
+ "/api/wallet/phone-share/initiate",
908
+ { phone: input.phone, email: input.email },
909
+ { kind: "none" },
910
+ // UNAUTHENTICATED — phone-owner-driven; send no auth header
911
+ { signal: opts?.signal }
912
+ // NOT idempotent — sends an OTP; never auto-retried
913
+ );
914
+ return toPhoneShareInitiateResult(raw);
915
+ }
916
+ /**
917
+ * Approve a phone-share with the OTP the phone owner received. On success the grant
918
+ * is stored server-side, keyed by (phone, requesting_email) and auto-consumed — the
919
+ * caller simply RE-ISSUES the identical original request. UNAUTHENTICATED. NOT
920
+ * idempotent (attempts are counted) — never auto-retried.
921
+ */
922
+ async phoneShareApprove(input, opts) {
923
+ if (typeof input.approvalId !== "string" || !input.approvalId.trim()) {
924
+ throw invalidInput("approvalId", input.approvalId, "is required");
925
+ }
926
+ if (typeof input.otp !== "string" || !input.otp.trim()) {
927
+ throw invalidInput("otp", input.otp, "is required");
928
+ }
929
+ const raw = await this.http.post(
930
+ "/api/wallet/phone-share/approve",
931
+ { approval_id: input.approvalId, otp: input.otp },
932
+ { kind: "none" },
933
+ // UNAUTHENTICATED — phone-owner-driven; send no auth header
934
+ { signal: opts?.signal }
935
+ // NOT idempotent — OTP attempts are counted; never auto-retried
936
+ );
937
+ return toPhoneShareApproveResult(raw);
938
+ }
939
+ /**
940
+ * Poll whether a phone-share (phone, requesting_email) pair is approved yet.
941
+ * UNAUTHENTICATED, idempotent GET. `approved:true` means the original request can
942
+ * be re-issued.
943
+ */
944
+ async phoneShareStatus(input, opts) {
945
+ if (typeof input.phone !== "string" || !input.phone.trim()) {
946
+ throw invalidInput("phone", input.phone, "is required");
947
+ }
948
+ if (typeof input.email !== "string" || !input.email.trim()) {
949
+ throw invalidInput("email", input.email, "is required");
950
+ }
951
+ const q = new URLSearchParams();
952
+ q.set("phone", input.phone);
953
+ q.set("email", input.email);
954
+ const raw = await this.http.get(
955
+ `/api/wallet/phone-share/status?${q.toString()}`,
956
+ { kind: "none" },
957
+ // UNAUTHENTICATED — phone-owner-driven; send no auth header
958
+ { signal: opts?.signal }
959
+ );
960
+ return { approved: raw["approved"] === true, raw };
961
+ }
672
962
  toInitiateResult(raw) {
673
963
  const vm = raw["verification_method"];
674
964
  const guardian = raw["guardian_approval"];
@@ -37,6 +37,10 @@ declare class InvoError extends Error {
37
37
  * is a rate-limit, not a top-up condition) is NOT misclassified as this.
38
38
  */
39
39
  get isInsufficientBalance(): boolean;
40
+ /** True if a claim needs phone-share approval before it can complete (claim → 409). */
41
+ get isPhoneShareApprovalRequired(): boolean;
42
+ /** True if the phone-share (phone, requesting_email) pair was already approved. */
43
+ get isPhoneShareAlreadyApproved(): boolean;
40
44
  /** True if an idempotency-keyed request was a duplicate (item purchase → 409). */
41
45
  get isDuplicateRequest(): boolean;
42
46
  /** Seconds to wait before retrying, when the backend throttled the call (429 `retry_after`). */
@@ -405,6 +409,14 @@ interface DestinationsQuery {
405
409
  /** "transfer" | "send". Defaults "transfer" (the set is identical for both today). */
406
410
  direction?: "transfer" | "send";
407
411
  }
412
+ /** Server-side (game-secret) destinations query. Unlike the browser variant, the
413
+ * source game isn't inferred from a player token — pass it explicitly. */
414
+ interface ServerDestinationsQuery {
415
+ /** The game the player is sending/transferring FROM. */
416
+ sourceGameId: string | number;
417
+ /** "transfer" | "send". Defaults "transfer" (the set is identical for both today). */
418
+ direction?: "transfer" | "send";
419
+ }
408
420
  /** A game/tenant this player can send/transfer to, with display metadata inline. */
409
421
  interface DestinationGame {
410
422
  gameId: string | number;
@@ -459,5 +471,127 @@ interface LinkDeviceResult {
459
471
  status: string;
460
472
  raw: Record<string, unknown>;
461
473
  }
474
+ interface SmsVerifyResult {
475
+ /** "success" on a completed verification. */
476
+ status: string;
477
+ transactionId: string;
478
+ /** The recipient claim code, when the completion issued one. */
479
+ claimCode?: string;
480
+ /** Canonical balance AFTER completion, when the backend returned it. */
481
+ newBalance?: string | number | null;
482
+ orderId?: string;
483
+ raw: Record<string, unknown>;
484
+ }
485
+ interface ClaimTransferInput {
486
+ claimCode: string;
487
+ targetPlayerName: string;
488
+ targetPlayerEmail: string;
489
+ targetPlayerPhone: string;
490
+ /** Which of this game's currencies to credit. */
491
+ targetCurrencyId: string | number;
492
+ /** Re-submit with this after a `needsAccountSelection` result to disambiguate a
493
+ * phone that maps to more than one of your players. */
494
+ targetPlayerId?: string | number;
495
+ }
496
+ interface ClaimCurrencyInput {
497
+ claimCode: string;
498
+ receiverPlayerName: string;
499
+ receiverPlayerEmail: string;
500
+ receiverPlayerPhone: string;
501
+ /** Re-submit with this after a `needsAccountSelection` result (multi-account phone). */
502
+ receiverPlayerId?: string | number;
503
+ }
504
+ interface ClaimResult {
505
+ /** "success" on completion, or "needs_account_selection" (see `needsAccountSelection`). */
506
+ status: string;
507
+ transactionId?: string;
508
+ newBalance?: string | number | null;
509
+ currencyName?: string;
510
+ orderId?: string;
511
+ /**
512
+ * true when a 200 came back as `status:"needs_account_selection"` (the recipient
513
+ * phone maps to more than one of your players). NOT an error — re-submit the claim
514
+ * with `targetPlayerId` (transfer) / `receiverPlayerId` (send) from `candidates`.
515
+ */
516
+ needsAccountSelection: boolean;
517
+ /** Candidate accounts to disambiguate, present on a `needsAccountSelection` result. */
518
+ candidates?: Record<string, unknown>[];
519
+ raw: Record<string, unknown>;
520
+ }
521
+ /** `verification_state` ∈ awaiting | approved | completed | expired | failed | unknown. */
522
+ interface TransactionStatusResult {
523
+ transactionId: string;
524
+ /** "transfer" | "send". */
525
+ transactionType: string;
526
+ transactionStatus: string;
527
+ verificationState: string;
528
+ amount: string | number | null;
529
+ netAmount: string | number | null;
530
+ currencyId: string | number;
531
+ orderId?: string;
532
+ toPhone?: string;
533
+ toIdentityId?: string | null;
534
+ /** Everything else (send fees, game names, claim_info, failure_reason, sender claim_code, …). */
535
+ raw: Record<string, unknown>;
536
+ }
537
+ /** `state` ∈ pending | approved | rejected | expired. */
538
+ interface GuardianApprovalStatusResult {
539
+ state: string;
540
+ approvalId?: string;
541
+ transactionId?: string;
542
+ expiresAt?: string;
543
+ decidedAt?: string;
544
+ decisionSource?: string;
545
+ actionDescription?: string;
546
+ raw: Record<string, unknown>;
547
+ }
548
+ /**
549
+ * A phone + requesting-email pair identifying a phone-share approval. Used to send
550
+ * the fallback OTP and to poll status. The server-side grant is keyed by this pair.
551
+ */
552
+ interface PhoneShareContact {
553
+ /** The phone number whose owner must approve the share. */
554
+ phone: string;
555
+ /** The email of the account requesting to use that phone (requesting_email). */
556
+ email: string;
557
+ }
558
+ /** Result of `phoneShareInitiate` (fallback OTP send). Known fields are surfaced;
559
+ * everything else stays on `raw`. */
560
+ interface PhoneShareInitiateResult {
561
+ /** The approval id to pass back to `phoneShareApprove`. */
562
+ approvalId?: string;
563
+ /** Whether the backend actually dispatched an OTP. */
564
+ codeSent?: boolean;
565
+ /** When the OTP / approval expires (ISO). OTP TTL is ~10 min. */
566
+ expiresAt?: string;
567
+ /** true when the (phone, email) pair was already approved — no OTP needed. */
568
+ alreadyApproved?: boolean;
569
+ raw: Record<string, unknown>;
570
+ }
571
+ /** Input for `phoneShareApprove`. */
572
+ interface PhoneShareApproveInput {
573
+ /** The `approval_id` from the 409 body or from `phoneShareInitiate`. */
574
+ approvalId: string;
575
+ /** The one-time code the phone owner received. */
576
+ otp: string;
577
+ }
578
+ /** Result of `phoneShareApprove`. Known fields are surfaced; the rest stays on `raw`.
579
+ * On success the grant is stored server-side, so the caller simply re-issues the
580
+ * identical original request (the grant is auto-consumed). */
581
+ interface PhoneShareApproveResult {
582
+ approvalId?: string;
583
+ /** true once the share is approved. */
584
+ approved?: boolean;
585
+ /** true when the pair was already approved before this call. */
586
+ alreadyApproved?: boolean;
587
+ expiresAt?: string;
588
+ raw: Record<string, unknown>;
589
+ }
590
+ /** Result of `phoneShareStatus` (idempotent GET). */
591
+ interface PhoneShareStatusResult {
592
+ /** true when the (phone, email) pair is approved and the original request can be re-issued. */
593
+ approved: boolean;
594
+ raw: Record<string, unknown>;
595
+ }
462
596
 
463
- export { type ApproveResult as A, type BalanceResult as B, type ClientConfig as C, type DestinationsQuery as D, type EnrollmentBeginResult as E, type PlayerBalanceResult as F, type InboundPendingQuery as G, type InboundPendingResult as H, InvoError as I, type LinkedIdentitiesQuery as J, type LinkedIdentitiesResult as K, type LinkDeviceResult as L, type CurrencyBalance as M, type InboundPendingItem as N, type OrderDetailsResult as O, type PendingCollectResult as P, type LinkedIdentityEmail as Q, type Rail as R, type ServerConfig as S, type VerificationMethod as V, type CallOptions as a, type ConfirmReceiptResult as b, type DestinationsResult as c, type EnrollmentVerifyResult as d, type BalanceRow as e, type DestinationGame as f, type InvoErrorInfo as g, type InvoHooks as h, type InvoRequestInfo as i, type InvoResponseInfo as j, type PendingCollectItem as k, type PlayerToken as l, type InitiateSendInput as m, type InitiateResult as n, type InitiateTransferInput as o, type CreateCheckoutInput as p, type CreateCheckoutResult as q, type PurchaseInput as r, type PurchaseResult as s, type ConfirmPaymentResult as t, type PurchaseItemInput as u, type PurchaseItemResult as v, type ItemHistoryQuery as w, type ItemHistoryResult as x, type ItemOrderQuery as y, type PlayerBalanceQuery as z };
597
+ export { type PhoneShareApproveResult as $, type ApproveResult as A, type BalanceResult as B, type ClientConfig as C, type DestinationsQuery as D, type EnrollmentBeginResult as E, type PlayerBalanceResult as F, type InboundPendingQuery as G, type InboundPendingResult as H, InvoError as I, type LinkedIdentitiesQuery as J, type LinkedIdentitiesResult as K, type LinkDeviceResult as L, type SmsVerifyResult as M, type ClaimTransferInput as N, type OrderDetailsResult as O, type PendingCollectResult as P, type ClaimResult as Q, type Rail as R, type ServerConfig as S, type ClaimCurrencyInput as T, type TransactionStatusResult as U, type VerificationMethod as V, type GuardianApprovalStatusResult as W, type ServerDestinationsQuery as X, type PhoneShareContact as Y, type PhoneShareInitiateResult as Z, type PhoneShareApproveInput as _, type CallOptions as a, type PhoneShareStatusResult as a0, type CurrencyBalance as a1, type InboundPendingItem as a2, type LinkedIdentityEmail as a3, type ConfirmReceiptResult as b, type DestinationsResult as c, type EnrollmentVerifyResult as d, type BalanceRow as e, type DestinationGame as f, type InvoErrorInfo as g, type InvoHooks as h, type InvoRequestInfo as i, type InvoResponseInfo as j, type PendingCollectItem as k, type PlayerToken as l, type InitiateSendInput as m, type InitiateResult as n, type InitiateTransferInput as o, type CreateCheckoutInput as p, type CreateCheckoutResult as q, type PurchaseInput as r, type PurchaseResult as s, type ConfirmPaymentResult as t, type PurchaseItemInput as u, type PurchaseItemResult as v, type ItemHistoryQuery as w, type ItemHistoryResult as x, type ItemOrderQuery as y, type PlayerBalanceQuery as z };
@@ -37,6 +37,10 @@ declare class InvoError extends Error {
37
37
  * is a rate-limit, not a top-up condition) is NOT misclassified as this.
38
38
  */
39
39
  get isInsufficientBalance(): boolean;
40
+ /** True if a claim needs phone-share approval before it can complete (claim → 409). */
41
+ get isPhoneShareApprovalRequired(): boolean;
42
+ /** True if the phone-share (phone, requesting_email) pair was already approved. */
43
+ get isPhoneShareAlreadyApproved(): boolean;
40
44
  /** True if an idempotency-keyed request was a duplicate (item purchase → 409). */
41
45
  get isDuplicateRequest(): boolean;
42
46
  /** Seconds to wait before retrying, when the backend throttled the call (429 `retry_after`). */
@@ -405,6 +409,14 @@ interface DestinationsQuery {
405
409
  /** "transfer" | "send". Defaults "transfer" (the set is identical for both today). */
406
410
  direction?: "transfer" | "send";
407
411
  }
412
+ /** Server-side (game-secret) destinations query. Unlike the browser variant, the
413
+ * source game isn't inferred from a player token — pass it explicitly. */
414
+ interface ServerDestinationsQuery {
415
+ /** The game the player is sending/transferring FROM. */
416
+ sourceGameId: string | number;
417
+ /** "transfer" | "send". Defaults "transfer" (the set is identical for both today). */
418
+ direction?: "transfer" | "send";
419
+ }
408
420
  /** A game/tenant this player can send/transfer to, with display metadata inline. */
409
421
  interface DestinationGame {
410
422
  gameId: string | number;
@@ -459,5 +471,127 @@ interface LinkDeviceResult {
459
471
  status: string;
460
472
  raw: Record<string, unknown>;
461
473
  }
474
+ interface SmsVerifyResult {
475
+ /** "success" on a completed verification. */
476
+ status: string;
477
+ transactionId: string;
478
+ /** The recipient claim code, when the completion issued one. */
479
+ claimCode?: string;
480
+ /** Canonical balance AFTER completion, when the backend returned it. */
481
+ newBalance?: string | number | null;
482
+ orderId?: string;
483
+ raw: Record<string, unknown>;
484
+ }
485
+ interface ClaimTransferInput {
486
+ claimCode: string;
487
+ targetPlayerName: string;
488
+ targetPlayerEmail: string;
489
+ targetPlayerPhone: string;
490
+ /** Which of this game's currencies to credit. */
491
+ targetCurrencyId: string | number;
492
+ /** Re-submit with this after a `needsAccountSelection` result to disambiguate a
493
+ * phone that maps to more than one of your players. */
494
+ targetPlayerId?: string | number;
495
+ }
496
+ interface ClaimCurrencyInput {
497
+ claimCode: string;
498
+ receiverPlayerName: string;
499
+ receiverPlayerEmail: string;
500
+ receiverPlayerPhone: string;
501
+ /** Re-submit with this after a `needsAccountSelection` result (multi-account phone). */
502
+ receiverPlayerId?: string | number;
503
+ }
504
+ interface ClaimResult {
505
+ /** "success" on completion, or "needs_account_selection" (see `needsAccountSelection`). */
506
+ status: string;
507
+ transactionId?: string;
508
+ newBalance?: string | number | null;
509
+ currencyName?: string;
510
+ orderId?: string;
511
+ /**
512
+ * true when a 200 came back as `status:"needs_account_selection"` (the recipient
513
+ * phone maps to more than one of your players). NOT an error — re-submit the claim
514
+ * with `targetPlayerId` (transfer) / `receiverPlayerId` (send) from `candidates`.
515
+ */
516
+ needsAccountSelection: boolean;
517
+ /** Candidate accounts to disambiguate, present on a `needsAccountSelection` result. */
518
+ candidates?: Record<string, unknown>[];
519
+ raw: Record<string, unknown>;
520
+ }
521
+ /** `verification_state` ∈ awaiting | approved | completed | expired | failed | unknown. */
522
+ interface TransactionStatusResult {
523
+ transactionId: string;
524
+ /** "transfer" | "send". */
525
+ transactionType: string;
526
+ transactionStatus: string;
527
+ verificationState: string;
528
+ amount: string | number | null;
529
+ netAmount: string | number | null;
530
+ currencyId: string | number;
531
+ orderId?: string;
532
+ toPhone?: string;
533
+ toIdentityId?: string | null;
534
+ /** Everything else (send fees, game names, claim_info, failure_reason, sender claim_code, …). */
535
+ raw: Record<string, unknown>;
536
+ }
537
+ /** `state` ∈ pending | approved | rejected | expired. */
538
+ interface GuardianApprovalStatusResult {
539
+ state: string;
540
+ approvalId?: string;
541
+ transactionId?: string;
542
+ expiresAt?: string;
543
+ decidedAt?: string;
544
+ decisionSource?: string;
545
+ actionDescription?: string;
546
+ raw: Record<string, unknown>;
547
+ }
548
+ /**
549
+ * A phone + requesting-email pair identifying a phone-share approval. Used to send
550
+ * the fallback OTP and to poll status. The server-side grant is keyed by this pair.
551
+ */
552
+ interface PhoneShareContact {
553
+ /** The phone number whose owner must approve the share. */
554
+ phone: string;
555
+ /** The email of the account requesting to use that phone (requesting_email). */
556
+ email: string;
557
+ }
558
+ /** Result of `phoneShareInitiate` (fallback OTP send). Known fields are surfaced;
559
+ * everything else stays on `raw`. */
560
+ interface PhoneShareInitiateResult {
561
+ /** The approval id to pass back to `phoneShareApprove`. */
562
+ approvalId?: string;
563
+ /** Whether the backend actually dispatched an OTP. */
564
+ codeSent?: boolean;
565
+ /** When the OTP / approval expires (ISO). OTP TTL is ~10 min. */
566
+ expiresAt?: string;
567
+ /** true when the (phone, email) pair was already approved — no OTP needed. */
568
+ alreadyApproved?: boolean;
569
+ raw: Record<string, unknown>;
570
+ }
571
+ /** Input for `phoneShareApprove`. */
572
+ interface PhoneShareApproveInput {
573
+ /** The `approval_id` from the 409 body or from `phoneShareInitiate`. */
574
+ approvalId: string;
575
+ /** The one-time code the phone owner received. */
576
+ otp: string;
577
+ }
578
+ /** Result of `phoneShareApprove`. Known fields are surfaced; the rest stays on `raw`.
579
+ * On success the grant is stored server-side, so the caller simply re-issues the
580
+ * identical original request (the grant is auto-consumed). */
581
+ interface PhoneShareApproveResult {
582
+ approvalId?: string;
583
+ /** true once the share is approved. */
584
+ approved?: boolean;
585
+ /** true when the pair was already approved before this call. */
586
+ alreadyApproved?: boolean;
587
+ expiresAt?: string;
588
+ raw: Record<string, unknown>;
589
+ }
590
+ /** Result of `phoneShareStatus` (idempotent GET). */
591
+ interface PhoneShareStatusResult {
592
+ /** true when the (phone, email) pair is approved and the original request can be re-issued. */
593
+ approved: boolean;
594
+ raw: Record<string, unknown>;
595
+ }
462
596
 
463
- export { type ApproveResult as A, type BalanceResult as B, type ClientConfig as C, type DestinationsQuery as D, type EnrollmentBeginResult as E, type PlayerBalanceResult as F, type InboundPendingQuery as G, type InboundPendingResult as H, InvoError as I, type LinkedIdentitiesQuery as J, type LinkedIdentitiesResult as K, type LinkDeviceResult as L, type CurrencyBalance as M, type InboundPendingItem as N, type OrderDetailsResult as O, type PendingCollectResult as P, type LinkedIdentityEmail as Q, type Rail as R, type ServerConfig as S, type VerificationMethod as V, type CallOptions as a, type ConfirmReceiptResult as b, type DestinationsResult as c, type EnrollmentVerifyResult as d, type BalanceRow as e, type DestinationGame as f, type InvoErrorInfo as g, type InvoHooks as h, type InvoRequestInfo as i, type InvoResponseInfo as j, type PendingCollectItem as k, type PlayerToken as l, type InitiateSendInput as m, type InitiateResult as n, type InitiateTransferInput as o, type CreateCheckoutInput as p, type CreateCheckoutResult as q, type PurchaseInput as r, type PurchaseResult as s, type ConfirmPaymentResult as t, type PurchaseItemInput as u, type PurchaseItemResult as v, type ItemHistoryQuery as w, type ItemHistoryResult as x, type ItemOrderQuery as y, type PlayerBalanceQuery as z };
597
+ export { type PhoneShareApproveResult as $, type ApproveResult as A, type BalanceResult as B, type ClientConfig as C, type DestinationsQuery as D, type EnrollmentBeginResult as E, type PlayerBalanceResult as F, type InboundPendingQuery as G, type InboundPendingResult as H, InvoError as I, type LinkedIdentitiesQuery as J, type LinkedIdentitiesResult as K, type LinkDeviceResult as L, type SmsVerifyResult as M, type ClaimTransferInput as N, type OrderDetailsResult as O, type PendingCollectResult as P, type ClaimResult as Q, type Rail as R, type ServerConfig as S, type ClaimCurrencyInput as T, type TransactionStatusResult as U, type VerificationMethod as V, type GuardianApprovalStatusResult as W, type ServerDestinationsQuery as X, type PhoneShareContact as Y, type PhoneShareInitiateResult as Z, type PhoneShareApproveInput as _, type CallOptions as a, type PhoneShareStatusResult as a0, type CurrencyBalance as a1, type InboundPendingItem as a2, type LinkedIdentityEmail as a3, type ConfirmReceiptResult as b, type DestinationsResult as c, type EnrollmentVerifyResult as d, type BalanceRow as e, type DestinationGame as f, type InvoErrorInfo as g, type InvoHooks as h, type InvoRequestInfo as i, type InvoResponseInfo as j, type PendingCollectItem as k, type PlayerToken as l, type InitiateSendInput as m, type InitiateResult as n, type InitiateTransferInput as o, type CreateCheckoutInput as p, type CreateCheckoutResult as q, type PurchaseInput as r, type PurchaseResult as s, type ConfirmPaymentResult as t, type PurchaseItemInput as u, type PurchaseItemResult as v, type ItemHistoryQuery as w, type ItemHistoryResult as x, type ItemOrderQuery as y, type PlayerBalanceQuery as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@invonetwork/web-sdk",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "INVO Web SDK — currency purchase + passkey (WebAuthn) verification for partner web platforms.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "private": false,