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.
- package/README.md +32 -0
- package/dist/index.js +370 -15
- package/dist/index.js.map +1 -1
- package/ios/Sources/PurchasePlugin/PurchasePlugin.swift +126 -36
- package/package.json +2 -1
- package/src/ts/platforms/iaptic-js/iaptic-js-types.d.ts +326 -0
- package/www/store.d.ts +213 -5
- package/www/store.js +370 -15
|
@@ -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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
108
|
-
// are visible immediately on app launch
|
|
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
|
-
|
|
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
|
-
|
|
118
|
-
debugLog("init: skipping already-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
501
|
+
jwsRepresentation: String? = nil,
|
|
502
|
+
isFinished: Bool? = nil) async {
|
|
421
503
|
let transactionId = String(transaction.id)
|
|
422
|
-
|
|
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.
|
|
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
|
+
}
|