capacitor-plugin-cdv-purchase 13.17.2 → 13.19.1

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.
@@ -5,6 +5,11 @@ import StoreKit
5
5
  @available(iOS 15.0, *)
6
6
  private class SK2State {
7
7
  var products: [String: Product] = [:]
8
+ /// Transactions emitted to JS and not yet finished. Access must be
9
+ /// serialized on the main thread (same as processedTransactionIds below):
10
+ /// the detached Transaction.updates observer, emitTransactionUpdate(),
11
+ /// finish(), and clearExpiredUnfinishedTransactions() all read or
12
+ /// mutate it from concurrent async contexts.
8
13
  var unfinishedTransactions: [String: Transaction] = [:]
9
14
  var transactionObserverTask: Task<Void, Never>?
10
15
  /// Transaction IDs already emitted to JS. Prevents duplicate delivery when
@@ -46,20 +51,21 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
46
51
 
47
52
  // MARK: - Lifecycle
48
53
 
49
- override public func load() {
50
- if #available(iOS 15.0, *) {
51
- startTransactionObserver()
52
- }
53
- }
54
-
55
54
  deinit {
56
55
  if #available(iOS 15.0, *) {
57
56
  sk2.transactionObserverTask?.cancel()
58
57
  }
59
58
  }
60
59
 
60
+ /// Start listening to Transaction.updates. Called at the end of init(),
61
+ /// NOT at plugin load: Transaction.updates delivers unfinished
62
+ /// transactions as soon as iteration begins, and before JS called init()
63
+ /// no listener is attached — the events would be lost while their ids
64
+ /// still land in processedTransactionIds, hiding unfinished purchases
65
+ /// for the rest of the session.
61
66
  @available(iOS 15.0, *)
62
67
  private func startTransactionObserver() {
68
+ guard sk2.transactionObserverTask == nil else { return }
63
69
  sk2.transactionObserverTask = Task.detached { [weak self] in
64
70
  for await result in Transaction.updates {
65
71
  guard let self = self else { return }
@@ -73,22 +79,18 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
73
79
  let jwsRepresentation = result.jwsRepresentation
74
80
  switch result {
75
81
  case .verified(let transaction):
76
- // Serialize access to processedTransactionIds on the main thread
77
- // since this method runs from a detached Task while init()/purchase()
78
- // also mutate the set.
79
- let shouldEmit: Bool = await MainActor.run {
80
- if sk2.processedTransactionIds.contains(transaction.id) {
81
- debugLog("Transaction.updates: skipping duplicate id=\(transaction.id)")
82
- return false
83
- }
84
- sk2.processedTransactionIds.insert(transaction.id)
85
- return true
82
+ guard await markEmittedToJs(transaction.id) else {
83
+ debugLog("Transaction.updates: skipping duplicate id=\(transaction.id)")
84
+ return
86
85
  }
87
- guard shouldEmit else { return }
88
86
  await emitTransactionUpdate(transaction, state: "PaymentTransactionStatePurchased",
89
87
  jwsRepresentation: jwsRepresentation)
90
88
  case .unverified(let transaction, _):
91
- // Emit as Purchased — let JS layer handle verification
89
+ // Emit as Purchased — let JS layer handle verification.
90
+ // Deliberately NOT deduped via markEmittedToJs: marking an
91
+ // unverified id would suppress the transaction for the rest of
92
+ // the session even if a later delivery verifies. A double-emit of
93
+ // the same JWS is harmless — JS re-validates it.
92
94
  await emitTransactionUpdate(transaction, state: "PaymentTransactionStatePurchased",
93
95
  jwsRepresentation: jwsRepresentation)
94
96
  }
@@ -104,29 +106,55 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
104
106
  debugEnabled = call.getBool("debug", false)
105
107
  debugLog("init")
106
108
 
107
- // Load current entitlements and emit to JS so existing subscriptions
108
- // are visible immediately on app launch (without requiring a manual restore).
109
+ // Load current entitlements and unfinished transactions, and emit them
110
+ // to JS so existing purchases are visible immediately on app launch
111
+ // (without requiring a manual restore).
109
112
  Task {
110
- for await result in Transaction.currentEntitlements {
113
+ let (entitlements, unfinishedIds) = await self.entitlementsWithUnfinishedIds()
114
+ for result in entitlements {
111
115
  switch result {
112
116
  case .verified(let transaction):
113
117
  if transaction.isUpgraded {
114
118
  debugLog("init: skipping upgraded entitlement id=\(transaction.id) product=\(transaction.productID)")
115
119
  continue
116
120
  }
117
- if self.sk2.processedTransactionIds.contains(transaction.id) {
118
- debugLog("init: skipping already-processed entitlement id=\(transaction.id)")
121
+ guard await self.markEmittedToJs(transaction.id) else {
122
+ debugLog("init: skipping already-emitted entitlement id=\(transaction.id)")
119
123
  continue
120
124
  }
121
- self.sk2.processedTransactionIds.insert(transaction.id)
122
- self.sk2.unfinishedTransactions[String(transaction.id)] = transaction
123
125
  await self.emitTransactionUpdate(transaction,
124
126
  state: "PaymentTransactionStateRestored",
125
- jwsRepresentation: result.jwsRepresentation)
127
+ jwsRepresentation: result.jwsRepresentation,
128
+ isFinished: !unfinishedIds.contains(transaction.id))
126
129
  case .unverified(let transaction, let error):
127
130
  debugLog("init: unverified entitlement id=\(transaction.id) product=\(transaction.productID) error=\(error)")
128
131
  }
129
132
  }
133
+ // Emit every transaction the app hasn't finished yet. This is the
134
+ // only reliable source for unfinished consumables: they never
135
+ // appear in currentEntitlements, so without this pass an
136
+ // unfinished consumable would hang in limbo for the session.
137
+ for await result in Transaction.unfinished {
138
+ switch result {
139
+ case .verified(let transaction):
140
+ if transaction.isUpgraded {
141
+ debugLog("init: skipping upgraded unfinished transaction id=\(transaction.id) product=\(transaction.productID)")
142
+ continue
143
+ }
144
+ guard await self.markEmittedToJs(transaction.id) else { continue }
145
+ debugLog("init: emitting unfinished transaction id=\(transaction.id) product=\(transaction.productID)")
146
+ await self.emitTransactionUpdate(transaction,
147
+ state: "PaymentTransactionStateRestored",
148
+ jwsRepresentation: result.jwsRepresentation,
149
+ isFinished: false)
150
+ case .unverified(let transaction, let error):
151
+ debugLog("init: unverified unfinished transaction id=\(transaction.id) product=\(transaction.productID) error=\(error)")
152
+ }
153
+ }
154
+ // Only now start observing Transaction.updates — see the comment
155
+ // on startTransactionObserver() for why starting earlier loses
156
+ // unfinished transactions.
157
+ self.startTransactionObserver()
130
158
  call.resolve()
131
159
  }
132
160
  }
@@ -206,12 +234,13 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
206
234
  let jwsRepresentation = verification.jwsRepresentation
207
235
  switch verification {
208
236
  case .verified(let transaction):
209
- sk2.processedTransactionIds.insert(transaction.id)
237
+ _ = await markEmittedToJs(transaction.id)
210
238
  await emitTransactionUpdate(transaction,
211
239
  state: "PaymentTransactionStatePurchased",
212
240
  jwsRepresentation: jwsRepresentation)
213
241
  case .unverified(let transaction, _):
214
- // Emit as Purchased — let JS handle verification
242
+ // Emit as Purchased — let JS handle verification.
243
+ // Deliberately not deduped — see handleTransactionUpdate.
215
244
  await emitTransactionUpdate(transaction,
216
245
  state: "PaymentTransactionStatePurchased",
217
246
  jwsRepresentation: jwsRepresentation)
@@ -254,9 +283,17 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
254
283
  debugLog("finish: \(transactionId)")
255
284
 
256
285
  Task {
257
- if let transaction = sk2.unfinishedTransactions[transactionId] {
286
+ // Atomically claim the transaction on the main thread: read and
287
+ // removal must be one step, or two concurrent finish() calls for
288
+ // the same id could both proceed. Serialized because the detached
289
+ // Transaction.updates observer also mutates the dictionary.
290
+ let transaction: Transaction? = await MainActor.run {
291
+ guard let t = sk2.unfinishedTransactions[transactionId] else { return nil }
292
+ _ = sk2.unfinishedTransactions.removeValue(forKey: transactionId)
293
+ return t
294
+ }
295
+ if let transaction = transaction {
258
296
  await transaction.finish()
259
- sk2.unfinishedTransactions.removeValue(forKey: transactionId)
260
297
  // Note: do NOT remove from processedTransactionIds here.
261
298
  // Once a transaction ID has been emitted to JS, it must stay in the set
262
299
  // for the lifetime of the session to prevent Transaction.updates from
@@ -289,17 +326,21 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
289
326
  Task {
290
327
  do {
291
328
  try await AppStore.sync()
292
- for await result in Transaction.currentEntitlements {
329
+ let (entitlements, unfinishedIds) = await entitlementsWithUnfinishedIds()
330
+ for result in entitlements {
293
331
  switch result {
294
332
  case .verified(let transaction):
295
333
  if transaction.isUpgraded {
296
334
  debugLog("restore: skipping upgraded entitlement id=\(transaction.id) product=\(transaction.productID)")
297
335
  continue
298
336
  }
299
- sk2.processedTransactionIds.insert(transaction.id)
337
+ // Restore deliberately re-emits transactions already
338
+ // delivered this session — mark, but ignore the result.
339
+ _ = await markEmittedToJs(transaction.id)
300
340
  await emitTransactionUpdate(transaction,
301
341
  state: "PaymentTransactionStateRestored",
302
- jwsRepresentation: result.jwsRepresentation)
342
+ jwsRepresentation: result.jwsRepresentation,
343
+ isFinished: !unfinishedIds.contains(transaction.id))
303
344
  case .unverified(let transaction, let error):
304
345
  debugLog("restore: unverified entitlement id=\(transaction.id) product=\(transaction.productID) error=\(error)")
305
346
  }
@@ -407,19 +448,63 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
407
448
  if let expirationDate = transaction.expirationDate, expirationDate < Date() {
408
449
  debugLog("clearExpired: finishing expired transaction id=\(transaction.id) product=\(transaction.productID) expired=\(expirationDate)")
409
450
  await transaction.finish()
410
- sk2.unfinishedTransactions.removeValue(forKey: String(transaction.id))
451
+ await MainActor.run {
452
+ _ = sk2.unfinishedTransactions.removeValue(forKey: String(transaction.id))
453
+ }
411
454
  // Note: do NOT remove from processedTransactionIds.
412
455
  // The set must grow monotonically to prevent re-delivery via Transaction.updates.
413
456
  }
414
457
  }
415
458
  }
416
459
 
460
+ /// Current entitlements, plus the ids of every transaction the app has
461
+ /// not finished yet. Used to tell finished entitlements apart from
462
+ /// unfinished ones: finished non-consumables and non-renewing
463
+ /// subscriptions stay in Transaction.currentEntitlements and are
464
+ /// re-emitted at every launch.
465
+ ///
466
+ /// The unfinished snapshot is taken AFTER listing entitlements, so an
467
+ /// entitlement is never wrongly reported as finished: anything listed
468
+ /// that is still unfinished is in the snapshot. The only possible error
469
+ /// is the safe one (finished in between, reported as unfinished).
470
+ @available(iOS 15.0, *)
471
+ private func entitlementsWithUnfinishedIds() async -> ([VerificationResult<Transaction>], Set<UInt64>) {
472
+ var entitlements: [VerificationResult<Transaction>] = []
473
+ for await result in Transaction.currentEntitlements {
474
+ entitlements.append(result)
475
+ }
476
+ var ids = Set<UInt64>()
477
+ for await result in Transaction.unfinished {
478
+ ids.insert(result.unsafePayloadValue.id)
479
+ }
480
+ return (entitlements, ids)
481
+ }
482
+
483
+ /// Atomically check-and-mark a transaction id as emitted to the JS layer.
484
+ /// Returns false when it was already emitted this session. "Emitted" is
485
+ /// unrelated to Apple's finished state — it only prevents duplicate
486
+ /// delivery of the same transaction within one app session. Serialized on
487
+ /// the main thread because the Transaction.updates observer runs in a
488
+ /// detached Task while init()/restore()/purchase() run in their own Tasks.
489
+ @available(iOS 15.0, *)
490
+ private func markEmittedToJs(_ id: UInt64) async -> Bool {
491
+ return await MainActor.run {
492
+ if sk2.processedTransactionIds.contains(id) { return false }
493
+ sk2.processedTransactionIds.insert(id)
494
+ return true
495
+ }
496
+ }
497
+
417
498
  @available(iOS 15.0, *)
418
499
  private func emitTransactionUpdate(_ transaction: Transaction, state: String,
419
500
  errorCode: Int? = nil, errorText: String? = nil,
420
- jwsRepresentation: String? = nil) async {
501
+ jwsRepresentation: String? = nil,
502
+ isFinished: Bool? = nil) async {
421
503
  let transactionId = String(transaction.id)
422
- sk2.unfinishedTransactions[transactionId] = transaction
504
+ // Serialized on the main thread — see SK2State.unfinishedTransactions.
505
+ await MainActor.run {
506
+ sk2.unfinishedTransactions[transactionId] = transaction
507
+ }
423
508
 
424
509
  var data: [String: Any] = [
425
510
  "state": state,
@@ -443,6 +528,11 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
443
528
  if let jws = jwsRepresentation {
444
529
  data["jwsRepresentation"] = jws
445
530
  }
531
+ // Whether the app already called finish() on this transaction. Only
532
+ // set when known (Restored emissions), so JS can skip re-delivery.
533
+ if let isFinished = isFinished {
534
+ data["isFinished"] = isFinished
535
+ }
446
536
 
447
537
  notifyListeners("transactionUpdated", data: data)
448
538
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-plugin-cdv-purchase",
3
- "version": "13.17.2",
3
+ "version": "13.19.1",
4
4
  "description": "In-App Purchases for Capacitor — iOS (StoreKit 2) and Android (Google Play Billing)",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -38,6 +38,7 @@
38
38
  "dist/",
39
39
  "types/",
40
40
  "www/",
41
+ "src/ts/platforms/iaptic-js/iaptic-js-types.d.ts",
41
42
  "android/",
42
43
  "!android/build",
43
44
  "ios/",
@@ -0,0 +1,326 @@
1
+ declare namespace ModuleIapticJS {
2
+ /**
3
+ * Type of product that can be purchased
4
+ */
5
+ export type ProductType = 'subscription' | 'consumable' | 'non consumable' | 'paid subscription';
6
+
7
+ /**
8
+ * Represents a pricing phase for an offer
9
+ */
10
+ export interface PricingPhase {
11
+ /** Price in micros (1/1,000,000 of the currency unit). For example, $1.99 = 1990000 micros */
12
+ priceMicros: number;
13
+ /** ISO 4217 currency code (e.g., 'USD', 'EUR', 'JPY') */
14
+ currency: string;
15
+ /**
16
+ * ISO 8601 duration string representing the billing period
17
+ * Examples: 'P1M' = 1 month, 'P1Y' = 1 year, 'P7D' = 7 days
18
+ */
19
+ billingPeriod: string;
20
+ /**
21
+ * Defines how the subscription recurs
22
+ */
23
+ recurrenceMode: 'INFINITE_RECURRING' | 'NON_RECURRING' | 'FINITE_RECURRING';
24
+ /**
25
+ * Defines when payment is collected
26
+ */
27
+ paymentMode: 'PayAsYouGo' | 'PayUpFront';
28
+ }
29
+
30
+ /**
31
+ * Represents a purchase offer for a product
32
+ */
33
+ export interface Offer {
34
+ /** Unique identifier for the offer in the format 'platform:id' */
35
+ id: string;
36
+ /** Payment platform identifier (e.g., 'stripe', 'google', 'apple') */
37
+ platform: string;
38
+ /** Type of the offer */
39
+ offerType: 'Subscription' | 'Default';
40
+ /** Array of pricing phases that make up this offer's payment schedule */
41
+ pricingPhases: PricingPhase[];
42
+ }
43
+
44
+ /**
45
+ * Represents a product that can be purchased
46
+ */
47
+ export interface Product {
48
+ /** Type of the product (subscription, consumable, etc.) */
49
+ type: ProductType;
50
+ /** Unique identifier for the product */
51
+ id: string;
52
+ /** Display name of the product shown to customers */
53
+ title: string;
54
+ /** Optional detailed description of the product */
55
+ description?: string;
56
+ /** Available purchase offers for this product */
57
+ offers: Offer[];
58
+ /**
59
+ * Optional additional data associated with the product
60
+ */
61
+ metadata?: Record<string, any>;
62
+ /** Optional platform identifier (e.g., 'stripe') */
63
+ platform?: string;
64
+ }
65
+
66
+ /**
67
+ * Represents a purchased product
68
+ */
69
+ export interface Purchase {
70
+ /** Unique identifier for the purchase */
71
+ purchaseId: string;
72
+ /** Unique identifier for the transaction */
73
+ transactionId: string;
74
+ /** Unique identifier for the product */
75
+ productId: string;
76
+ /** Platform identifier (e.g., 'stripe') */
77
+ platform: 'stripe';
78
+ /** Date of the purchase */
79
+ purchaseDate: string;
80
+ /** Date of the last renewal */
81
+ lastRenewalDate: string;
82
+ /** Date of the expiration */
83
+ expirationDate: string;
84
+ /** Intent of the renewal */
85
+ renewalIntent: 'Renew' | 'Cancel';
86
+ /** Whether the purchase is a trial period */
87
+ isTrialPeriod: boolean;
88
+ /** Price in micros (1/1,000,000 of the currency unit) */
89
+ amountMicros: number;
90
+ /** Currency code (e.g., 'USD', 'EUR') */
91
+ currency: string;
92
+ }
93
+
94
+ /**
95
+ * Represents an order for a product
96
+ */
97
+ export interface Order {
98
+ /** Unique identifier for the offer */
99
+ offerId: string;
100
+ /** Username in the application */
101
+ applicationUsername: string;
102
+ /** URL to redirect to on success */
103
+ successUrl: string;
104
+ /** URL to redirect to on cancel */
105
+ cancelUrl: string;
106
+ /** Optional access token for the user */
107
+ accessToken?: string;
108
+ }
109
+
110
+ /** Plan change request */
111
+ export interface PlanChange {
112
+ /** Unique identifier for the purchase to replace (optional) */
113
+ purchaseId: string;
114
+ /** New offer to subscribe to */
115
+ offerId: string;
116
+ /** Optional access token for the user */
117
+ accessToken?: string;
118
+ }
119
+
120
+ /**
121
+ * Configuration options for creating an Iaptic adapter
122
+ */
123
+ export interface Config {
124
+ /** Adapter type (currently only 'stripe' is supported) */
125
+ type: 'stripe';
126
+ /** Stripe publishable key */
127
+ stripePublicKey: string;
128
+ /** Your application name as configured on iaptic.com */
129
+ appName: string;
130
+ /** Your Iaptic public API key */
131
+ apiKey: string;
132
+ /** Optional custom Iaptic URL for private deployments */
133
+ customIapticUrl?: string;
134
+ }
135
+
136
+ /**
137
+ * Response from Iaptic's /stripe/products API
138
+ */
139
+ export interface GetProductsResponse {
140
+ /** Whether the request was successful */
141
+ ok: boolean;
142
+ /** Array of available products */
143
+ products: Product[];
144
+ }
145
+
146
+ /**
147
+ * Response from Iaptic's /stripe/checkout API
148
+ */
149
+ export interface PostCheckoutSessionResponse {
150
+ /** Whether the request was successful */
151
+ ok: boolean;
152
+ /** Stripe Checkout URL where the customer will be redirected */
153
+ url: string;
154
+ /** Access token for the customer */
155
+ accessToken: string;
156
+ }
157
+
158
+ /**
159
+ * Response from Iaptic's /stripe/purchases API
160
+ */
161
+ export interface GetPurchasesResponse {
162
+ /** Whether the request was successful */
163
+ ok: boolean;
164
+ /** Array of customer's purchases */
165
+ purchases: Purchase[];
166
+ /** Optional new access token */
167
+ newAccessToken?: string;
168
+ }
169
+
170
+ /**
171
+ * Response from Iaptic's /stripe/change-plan API
172
+ */
173
+ export interface ChangePlanResponse {
174
+ /** Whether the request was successful */
175
+ ok: boolean;
176
+ /** Updated purchase details */
177
+ purchase: Purchase;
178
+ /** Optional new access token */
179
+ newAccessToken?: string;
180
+ }
181
+
182
+ /**
183
+ * Represents a scheduled refresh for a subscription
184
+ */
185
+ export interface ScheduledRefresh {
186
+ /** Unique identifier for the scheduled refresh */
187
+ id: string;
188
+ /** Unique identifier for the subscription */
189
+ subscriptionId: string;
190
+ /** Date and time of the scheduled refresh */
191
+ scheduledAt: number;
192
+ /** Whether the refresh has been completed */
193
+ completed: boolean;
194
+ /** Whether the refresh is currently in progress */
195
+ inProgress: boolean;
196
+ /** Reason for the scheduled refresh */
197
+ reason: string;
198
+ }
199
+
200
+ /**
201
+ * RefreshScheduler class for managing subscription refreshes
202
+ */
203
+ export class RefreshScheduler {
204
+ schedules: ScheduledRefresh[];
205
+ constructor(iapticStripe: IapticStripe);
206
+ scheduleRefresh(subscriptionId: string, date: Date, reason: string): void;
207
+ schedulePurchaseRefreshes(purchase: Purchase): void;
208
+ clearSchedules(): void;
209
+ }
210
+
211
+ /**
212
+ * Main class for interacting with Iaptic's Stripe integration
213
+ */
214
+ export class IapticStripe {
215
+ /**
216
+ * Creates a new IapticStripe instance
217
+ * @param config - Configuration options for the Stripe integration
218
+ */
219
+ constructor(config: Config);
220
+
221
+ /**
222
+ * Gets a list of available products and their pricing
223
+ * @returns Promise resolving to an array of products
224
+ */
225
+ getProducts(): Promise<Product[]>;
226
+
227
+ /**
228
+ * Forces a refresh of the products list from the server
229
+ * @returns Promise resolving to an array of products
230
+ */
231
+ refreshProducts(): Promise<Product[]>;
232
+
233
+ /**
234
+ * Gets the current access token if one exists
235
+ * @returns The current access token or undefined if none exists
236
+ */
237
+ getAccessToken(): string | undefined;
238
+
239
+ /**
240
+ * Creates a new order and redirects to Stripe Checkout
241
+ * @param params - Order parameters including product and URLs
242
+ */
243
+ order(params: Order): Promise<void>;
244
+
245
+ /**
246
+ * Gets the customer's purchase history
247
+ * @param accessToken - Optional access token (uses stored token if not provided)
248
+ * @returns Promise resolving to an array of purchases
249
+ */
250
+ getPurchases(accessToken?: string): Promise<Purchase[]>;
251
+
252
+ /**
253
+ * Redirects to Stripe Customer Portal for subscription management
254
+ * @param params - Portal parameters including return URL
255
+ */
256
+ redirectToCustomerPortal(params: {
257
+ returnUrl?: string;
258
+ accessToken?: string;
259
+ }): Promise<void>;
260
+
261
+ /**
262
+ * Changes a subscription plan
263
+ * @param planChange - Plan change parameters
264
+ * @returns Promise resolving to the updated purchase details
265
+ */
266
+ changePlan(planChange: PlanChange): Promise<Purchase>;
267
+
268
+ /**
269
+ * Clears all stored data including access tokens and cached products
270
+ */
271
+ clearStoredData(): void;
272
+ }
273
+
274
+ /**
275
+ * Utility functions for the Iaptic library
276
+ */
277
+ export class Utils {
278
+ /**
279
+ * Format a currency amount from micros with proper localization
280
+ * @param amountMicros - Amount in micros (1/1,000,000 of currency unit)
281
+ * @param currency - ISO 4217 currency code (e.g., 'USD', 'EUR')
282
+ * @returns Formatted currency string
283
+ */
284
+ static formatCurrency(amountMicros: number, currency: string): string;
285
+
286
+ /**
287
+ * Format an ISO 8601 period string into human-readable English
288
+ * @param period - ISO 8601 duration string
289
+ * @returns Human-readable period string
290
+ */
291
+ static formatBillingPeriodEN(period: string): string;
292
+
293
+ /**
294
+ * Base64 encode a string with fallbacks for different environments
295
+ */
296
+ static base64Encode(str: string): string;
297
+
298
+ /**
299
+ * Build a URL with query parameters
300
+ */
301
+ static buildUrl(baseUrl: string, params: Record<string, string>): string;
302
+ }
303
+
304
+ /**
305
+ * Creates an Iaptic adapter for the specified payment platform
306
+ * @param config - Configuration options for the adapter
307
+ * @returns An initialized adapter instance
308
+ */
309
+ export function createAdapter(config: Config): IapticStripe;
310
+ }
311
+
312
+ /**
313
+ * Global IapticJS object available in browser environments
314
+ */
315
+ interface Window {
316
+ IapticJS: {
317
+ /** Current version of the IapticJS library */
318
+ version: string;
319
+ /** Function to create a new Iaptic adapter */
320
+ createAdapter: typeof ModuleIapticJS.createAdapter;
321
+ /** Stripe adapter class */
322
+ IapticStripe: typeof ModuleIapticJS.IapticStripe;
323
+ /** Utility functions */
324
+ Utils: typeof ModuleIapticJS.Utils;
325
+ };
326
+ }