rampkit-expo-dev 0.0.66 → 0.0.68
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/build/RampKitNative.d.ts +19 -0
- package/build/RampKitNative.js +37 -0
- package/ios/RampKitModule.swift +258 -56
- package/package.json +1 -1
package/build/RampKitNative.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ interface RampKitNativeModule {
|
|
|
18
18
|
getNotificationPermissions(): Promise<NotificationPermissionResult>;
|
|
19
19
|
startTransactionObserver(appId: string): Promise<void>;
|
|
20
20
|
stopTransactionObserver(): Promise<void>;
|
|
21
|
+
trackPurchaseCompleted(productId: string, transactionId?: string, originalTransactionId?: string): Promise<void>;
|
|
22
|
+
trackPurchaseFromProduct(productId: string): Promise<void>;
|
|
21
23
|
}
|
|
22
24
|
export interface NativeDeviceInfo {
|
|
23
25
|
appUserId: string;
|
|
@@ -162,4 +164,21 @@ export declare const TransactionObserver: {
|
|
|
162
164
|
* Stop listening for purchase transactions
|
|
163
165
|
*/
|
|
164
166
|
stop(): Promise<void>;
|
|
167
|
+
/**
|
|
168
|
+
* Manually track a purchase completion
|
|
169
|
+
* Use this when Superwall/RevenueCat reports a purchase but the automatic
|
|
170
|
+
* observer doesn't catch it (they finish transactions before we see them)
|
|
171
|
+
*
|
|
172
|
+
* @param productId - The product ID (e.g., "com.app.yearly")
|
|
173
|
+
* @param transactionId - Optional transaction ID if available
|
|
174
|
+
* @param originalTransactionId - Optional original transaction ID (for renewals)
|
|
175
|
+
*/
|
|
176
|
+
trackPurchase(productId: string, transactionId?: string, originalTransactionId?: string): Promise<void>;
|
|
177
|
+
/**
|
|
178
|
+
* Track a purchase by looking up the product's latest transaction
|
|
179
|
+
* Use this when you only have the productId (common with Superwall)
|
|
180
|
+
*
|
|
181
|
+
* @param productId - The product ID to look up and track
|
|
182
|
+
*/
|
|
183
|
+
trackPurchaseByProductId(productId: string): Promise<void>;
|
|
165
184
|
};
|
package/build/RampKitNative.js
CHANGED
|
@@ -62,6 +62,8 @@ function createFallbackModule() {
|
|
|
62
62
|
},
|
|
63
63
|
async startTransactionObserver(_appId) { },
|
|
64
64
|
async stopTransactionObserver() { },
|
|
65
|
+
async trackPurchaseCompleted(_productId, _transactionId, _originalTransactionId) { },
|
|
66
|
+
async trackPurchaseFromProduct(_productId) { },
|
|
65
67
|
};
|
|
66
68
|
}
|
|
67
69
|
function generateFallbackUserId() {
|
|
@@ -285,4 +287,39 @@ exports.TransactionObserver = {
|
|
|
285
287
|
console.warn("[RampKit] Failed to stop transaction observer:", e);
|
|
286
288
|
}
|
|
287
289
|
},
|
|
290
|
+
/**
|
|
291
|
+
* Manually track a purchase completion
|
|
292
|
+
* Use this when Superwall/RevenueCat reports a purchase but the automatic
|
|
293
|
+
* observer doesn't catch it (they finish transactions before we see them)
|
|
294
|
+
*
|
|
295
|
+
* @param productId - The product ID (e.g., "com.app.yearly")
|
|
296
|
+
* @param transactionId - Optional transaction ID if available
|
|
297
|
+
* @param originalTransactionId - Optional original transaction ID (for renewals)
|
|
298
|
+
*/
|
|
299
|
+
async trackPurchase(productId, transactionId, originalTransactionId) {
|
|
300
|
+
try {
|
|
301
|
+
console.log("[RampKit] Manually tracking purchase:", productId);
|
|
302
|
+
await RampKitNativeModule.trackPurchaseCompleted(productId, transactionId, originalTransactionId);
|
|
303
|
+
console.log("[RampKit] Purchase tracked successfully:", productId);
|
|
304
|
+
}
|
|
305
|
+
catch (e) {
|
|
306
|
+
console.warn("[RampKit] Failed to track purchase:", e);
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
/**
|
|
310
|
+
* Track a purchase by looking up the product's latest transaction
|
|
311
|
+
* Use this when you only have the productId (common with Superwall)
|
|
312
|
+
*
|
|
313
|
+
* @param productId - The product ID to look up and track
|
|
314
|
+
*/
|
|
315
|
+
async trackPurchaseByProductId(productId) {
|
|
316
|
+
try {
|
|
317
|
+
console.log("[RampKit] Looking up and tracking purchase for:", productId);
|
|
318
|
+
await RampKitNativeModule.trackPurchaseFromProduct(productId);
|
|
319
|
+
console.log("[RampKit] Purchase lookup and tracking complete:", productId);
|
|
320
|
+
}
|
|
321
|
+
catch (e) {
|
|
322
|
+
console.warn("[RampKit] Failed to track purchase by product:", e);
|
|
323
|
+
}
|
|
324
|
+
},
|
|
288
325
|
};
|
package/ios/RampKitModule.swift
CHANGED
|
@@ -9,14 +9,27 @@ public class RampKitModule: Module {
|
|
|
9
9
|
private let installDateKey = "rk_install_date"
|
|
10
10
|
private let launchCountKey = "rk_launch_count"
|
|
11
11
|
private let lastLaunchKey = "rk_last_launch"
|
|
12
|
-
|
|
12
|
+
private let trackedTransactionsKey = "rk_tracked_transactions"
|
|
13
|
+
|
|
13
14
|
// Transaction observer task
|
|
14
15
|
private var transactionObserverTask: Task<Void, Never>?
|
|
15
16
|
private var appId: String?
|
|
16
17
|
private var userId: String?
|
|
17
|
-
|
|
18
|
+
private var isConfigured = false
|
|
19
|
+
|
|
20
|
+
// Set of already-tracked originalTransactionIds to prevent duplicates
|
|
21
|
+
private var trackedTransactionIds: Set<String> = []
|
|
22
|
+
|
|
18
23
|
public func definition() -> ModuleDefinition {
|
|
19
24
|
Name("RampKit")
|
|
25
|
+
|
|
26
|
+
// OnCreate runs when JS first requires the module - may be too late for some transactions
|
|
27
|
+
OnCreate {
|
|
28
|
+
print("[RampKit] ⚡ OnCreate called - module being initialized")
|
|
29
|
+
self.loadTrackedTransactions()
|
|
30
|
+
self.userId = self.getOrCreateUserId()
|
|
31
|
+
print("[RampKit] ⚡ OnCreate complete, userId: \(self.userId ?? "nil")")
|
|
32
|
+
}
|
|
20
33
|
|
|
21
34
|
// ============================================================================
|
|
22
35
|
// Device Info
|
|
@@ -89,14 +102,30 @@ public class RampKitModule: Module {
|
|
|
89
102
|
// ============================================================================
|
|
90
103
|
// Transaction Observer (StoreKit 2)
|
|
91
104
|
// ============================================================================
|
|
92
|
-
|
|
105
|
+
|
|
93
106
|
AsyncFunction("startTransactionObserver") { (appId: String) in
|
|
94
107
|
self.startTransactionObserver(appId: appId)
|
|
95
108
|
}
|
|
96
|
-
|
|
109
|
+
|
|
97
110
|
AsyncFunction("stopTransactionObserver") { () in
|
|
98
111
|
self.stopTransactionObserver()
|
|
99
112
|
}
|
|
113
|
+
|
|
114
|
+
// ============================================================================
|
|
115
|
+
// Manual Purchase Tracking (Fallback for Superwall/RevenueCat)
|
|
116
|
+
// ============================================================================
|
|
117
|
+
|
|
118
|
+
AsyncFunction("trackPurchaseCompleted") { (productId: String, transactionId: String?, originalTransactionId: String?) in
|
|
119
|
+
await self.trackPurchaseCompletedManually(
|
|
120
|
+
productId: productId,
|
|
121
|
+
transactionId: transactionId,
|
|
122
|
+
originalTransactionId: originalTransactionId
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
AsyncFunction("trackPurchaseFromProduct") { (productId: String) in
|
|
127
|
+
await self.trackPurchaseFromProductId(productId: productId)
|
|
128
|
+
}
|
|
100
129
|
}
|
|
101
130
|
|
|
102
131
|
// MARK: - Device Info Collection
|
|
@@ -224,8 +253,23 @@ public class RampKitModule: Module {
|
|
|
224
253
|
SecItemAdd(addQuery as CFDictionary, nil)
|
|
225
254
|
}
|
|
226
255
|
|
|
256
|
+
// MARK: - Tracked Transactions (Deduplication)
|
|
257
|
+
|
|
258
|
+
private func loadTrackedTransactions() {
|
|
259
|
+
if let stored = UserDefaults.standard.array(forKey: trackedTransactionsKey) as? [String] {
|
|
260
|
+
trackedTransactionIds = Set(stored)
|
|
261
|
+
print("[RampKit] 📚 Loaded \(trackedTransactionIds.count) tracked transaction IDs")
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private func saveTrackedTransactions() {
|
|
266
|
+
let array = Array(trackedTransactionIds)
|
|
267
|
+
UserDefaults.standard.set(array, forKey: trackedTransactionsKey)
|
|
268
|
+
print("[RampKit] 💾 Saved \(trackedTransactionIds.count) tracked transaction IDs")
|
|
269
|
+
}
|
|
270
|
+
|
|
227
271
|
// MARK: - Launch Tracking
|
|
228
|
-
|
|
272
|
+
|
|
229
273
|
private func getLaunchTrackingData() -> [String: Any?] {
|
|
230
274
|
let defaults = UserDefaults.standard
|
|
231
275
|
let now = ISO8601DateFormatter().string(from: Date())
|
|
@@ -386,78 +430,137 @@ public class RampKitModule: Module {
|
|
|
386
430
|
}
|
|
387
431
|
|
|
388
432
|
// MARK: - StoreKit 2 Transaction Observer
|
|
389
|
-
|
|
433
|
+
|
|
434
|
+
/// Called from JavaScript - sets appId and IMMEDIATELY checks for purchases we may have missed
|
|
390
435
|
private func startTransactionObserver(appId: String) {
|
|
391
436
|
self.appId = appId
|
|
392
437
|
self.userId = getOrCreateUserId()
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
438
|
+
self.isConfigured = true
|
|
439
|
+
|
|
440
|
+
print("[RampKit] ✅ Transaction observer configured with appId: \(appId)")
|
|
441
|
+
print("[RampKit] 📊 Already tracked \(trackedTransactionIds.count) transactions")
|
|
442
|
+
|
|
398
443
|
if #available(iOS 15.0, *) {
|
|
399
|
-
|
|
400
|
-
|
|
444
|
+
// CRITICAL: Check current entitlements for any purchases we missed
|
|
445
|
+
// This is the KEY mechanism for catching Superwall/RevenueCat purchases
|
|
446
|
+
Task {
|
|
447
|
+
await self.checkAndTrackCurrentEntitlements()
|
|
401
448
|
}
|
|
402
|
-
|
|
403
|
-
// Also
|
|
449
|
+
|
|
450
|
+
// Also start listening for future transactions
|
|
404
451
|
Task {
|
|
405
|
-
await self.
|
|
452
|
+
await self.startTransactionUpdatesListener()
|
|
406
453
|
}
|
|
407
454
|
}
|
|
408
|
-
|
|
409
|
-
print("[RampKit] Transaction observer started")
|
|
410
455
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
transactionObserverTask = nil
|
|
415
|
-
print("[RampKit] Transaction observer stopped")
|
|
416
|
-
}
|
|
417
|
-
|
|
456
|
+
|
|
457
|
+
/// Check all current entitlements and track any we haven't seen before
|
|
458
|
+
/// This catches purchases made by Superwall/RevenueCat before our observer started
|
|
418
459
|
@available(iOS 15.0, *)
|
|
419
|
-
private func
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
460
|
+
private func checkAndTrackCurrentEntitlements() async {
|
|
461
|
+
print("[RampKit] 🔍 Checking current entitlements for missed purchases...")
|
|
462
|
+
|
|
463
|
+
var foundCount = 0
|
|
464
|
+
var trackedCount = 0
|
|
465
|
+
var newCount = 0
|
|
466
|
+
|
|
467
|
+
for await result in Transaction.currentEntitlements {
|
|
468
|
+
foundCount += 1
|
|
469
|
+
|
|
470
|
+
guard case .verified(let transaction) = result else {
|
|
471
|
+
print("[RampKit] ⚠️ Unverified entitlement skipped")
|
|
472
|
+
continue
|
|
428
473
|
}
|
|
474
|
+
|
|
475
|
+
let originalId = String(transaction.originalID)
|
|
476
|
+
print("[RampKit] 📦 Found entitlement: \(transaction.productID), originalID: \(originalId)")
|
|
477
|
+
|
|
478
|
+
// Check if we've already tracked this transaction
|
|
479
|
+
if trackedTransactionIds.contains(originalId) {
|
|
480
|
+
trackedCount += 1
|
|
481
|
+
print("[RampKit] ✓ Already tracked: \(transaction.productID)")
|
|
482
|
+
continue
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// NEW transaction we haven't seen!
|
|
486
|
+
newCount += 1
|
|
487
|
+
print("[RampKit] 🆕 NEW purchase detected: \(transaction.productID)")
|
|
488
|
+
|
|
489
|
+
// Track it
|
|
490
|
+
await self.handleTransaction(transaction)
|
|
491
|
+
|
|
492
|
+
// Mark as tracked
|
|
493
|
+
trackedTransactionIds.insert(originalId)
|
|
494
|
+
saveTrackedTransactions()
|
|
429
495
|
}
|
|
496
|
+
|
|
497
|
+
print("[RampKit] 🔍 Entitlement check complete:")
|
|
498
|
+
print("[RampKit] - Total found: \(foundCount)")
|
|
499
|
+
print("[RampKit] - Already tracked: \(trackedCount)")
|
|
500
|
+
print("[RampKit] - NEW (sent events): \(newCount)")
|
|
430
501
|
}
|
|
431
|
-
|
|
502
|
+
|
|
503
|
+
/// Start listening for Transaction.updates (for future purchases)
|
|
432
504
|
@available(iOS 15.0, *)
|
|
433
|
-
private func
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
505
|
+
private func startTransactionUpdatesListener() async {
|
|
506
|
+
guard transactionObserverTask == nil else {
|
|
507
|
+
print("[RampKit] Transaction updates listener already running")
|
|
508
|
+
return
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
print("[RampKit] 👂 Starting Transaction.updates listener...")
|
|
512
|
+
|
|
513
|
+
transactionObserverTask = Task {
|
|
514
|
+
for await result in Transaction.updates {
|
|
515
|
+
print("[RampKit] 🎉 Transaction.updates received!")
|
|
516
|
+
|
|
517
|
+
guard case .verified(let transaction) = result else {
|
|
518
|
+
print("[RampKit] ⚠️ Unverified transaction update")
|
|
519
|
+
continue
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
let originalId = String(transaction.originalID)
|
|
523
|
+
|
|
524
|
+
// Skip if already tracked
|
|
525
|
+
if self.trackedTransactionIds.contains(originalId) {
|
|
526
|
+
print("[RampKit] ✓ Transaction.updates: Already tracked \(transaction.productID)")
|
|
527
|
+
await transaction.finish()
|
|
528
|
+
continue
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
print("[RampKit] 🆕 Transaction.updates: NEW purchase \(transaction.productID)")
|
|
532
|
+
|
|
533
|
+
// Track it
|
|
438
534
|
await self.handleTransaction(transaction)
|
|
535
|
+
|
|
536
|
+
// Mark as tracked
|
|
537
|
+
self.trackedTransactionIds.insert(originalId)
|
|
538
|
+
self.saveTrackedTransactions()
|
|
539
|
+
|
|
540
|
+
// Finish the transaction
|
|
439
541
|
await transaction.finish()
|
|
440
|
-
} catch {
|
|
441
|
-
print("[RampKit] Unfinished transaction verification failed: \(error)")
|
|
442
542
|
}
|
|
443
543
|
}
|
|
444
544
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
case .verified(let safe):
|
|
452
|
-
return safe
|
|
453
|
-
@unknown default:
|
|
454
|
-
fatalError("Unknown VerificationResult case")
|
|
455
|
-
}
|
|
545
|
+
|
|
546
|
+
private func stopTransactionObserver() {
|
|
547
|
+
transactionObserverTask?.cancel()
|
|
548
|
+
transactionObserverTask = nil
|
|
549
|
+
isConfigured = false
|
|
550
|
+
print("[RampKit] Transaction observer stopped")
|
|
456
551
|
}
|
|
457
|
-
|
|
552
|
+
|
|
458
553
|
@available(iOS 15.0, *)
|
|
459
554
|
private func handleTransaction(_ transaction: Transaction) async {
|
|
460
|
-
|
|
555
|
+
print("[RampKit] 🔄 handleTransaction called for: \(transaction.productID)")
|
|
556
|
+
print("[RampKit] - id: \(transaction.id)")
|
|
557
|
+
print("[RampKit] - originalID: \(transaction.originalID)")
|
|
558
|
+
print("[RampKit] - purchaseDate: \(transaction.purchaseDate)")
|
|
559
|
+
|
|
560
|
+
guard let appId = self.appId, let userId = self.userId else {
|
|
561
|
+
print("[RampKit] ❌ handleTransaction failed: appId=\(self.appId ?? "nil"), userId=\(self.userId ?? "nil")")
|
|
562
|
+
return
|
|
563
|
+
}
|
|
461
564
|
|
|
462
565
|
let formatter = ISO8601DateFormatter()
|
|
463
566
|
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
@@ -474,6 +577,8 @@ public class RampKitModule: Module {
|
|
|
474
577
|
return
|
|
475
578
|
}
|
|
476
579
|
|
|
580
|
+
print("[RampKit] ✅ Transaction is a new purchase, will send event")
|
|
581
|
+
|
|
477
582
|
// All new purchases (including trials) are tracked as purchase_completed
|
|
478
583
|
// The isTrial and offerType properties indicate trial status
|
|
479
584
|
let eventName = "purchase_completed"
|
|
@@ -636,7 +741,104 @@ public class RampKitModule: Module {
|
|
|
636
741
|
print("[RampKit] Failed to send purchase event: \(error)")
|
|
637
742
|
}
|
|
638
743
|
}
|
|
639
|
-
|
|
744
|
+
|
|
745
|
+
// MARK: - Manual Purchase Tracking (Fallback for Superwall/RevenueCat)
|
|
746
|
+
|
|
747
|
+
/// Track a purchase manually when you have the transaction IDs
|
|
748
|
+
/// Use this when Superwall or RevenueCat provides the transaction info
|
|
749
|
+
@available(iOS 15.0, *)
|
|
750
|
+
private func trackPurchaseCompletedManually(
|
|
751
|
+
productId: String,
|
|
752
|
+
transactionId: String?,
|
|
753
|
+
originalTransactionId: String?
|
|
754
|
+
) async {
|
|
755
|
+
print("[RampKit] 📲 Manual purchase tracking called for: \(productId)")
|
|
756
|
+
|
|
757
|
+
guard let appId = self.appId, let userId = self.userId else {
|
|
758
|
+
print("[RampKit] ❌ Manual tracking failed: SDK not initialized (appId or userId missing)")
|
|
759
|
+
return
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
let formatter = ISO8601DateFormatter()
|
|
763
|
+
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
764
|
+
|
|
765
|
+
var properties: [String: Any] = [
|
|
766
|
+
"productId": productId,
|
|
767
|
+
"purchaseDate": formatter.string(from: Date()),
|
|
768
|
+
"source": "manual" // Mark as manually tracked
|
|
769
|
+
]
|
|
770
|
+
|
|
771
|
+
// Add transaction IDs if available
|
|
772
|
+
if let transactionId = transactionId {
|
|
773
|
+
properties["transactionId"] = transactionId
|
|
774
|
+
}
|
|
775
|
+
if let originalTransactionId = originalTransactionId {
|
|
776
|
+
properties["originalTransactionId"] = originalTransactionId
|
|
777
|
+
} else if let transactionId = transactionId {
|
|
778
|
+
// Use transactionId as originalTransactionId if not provided (first purchase)
|
|
779
|
+
properties["originalTransactionId"] = transactionId
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Try to get product info from StoreKit
|
|
783
|
+
if let product = await getProduct(for: productId) {
|
|
784
|
+
properties["amount"] = product.price
|
|
785
|
+
properties["currency"] = product.priceFormatStyle.currencyCode
|
|
786
|
+
properties["priceFormatted"] = product.displayPrice
|
|
787
|
+
properties["productType"] = mapProductType(product.type)
|
|
788
|
+
|
|
789
|
+
if let subscription = product.subscription {
|
|
790
|
+
properties["subscriptionPeriod"] = formatSubscriptionPeriod(subscription.subscriptionPeriod)
|
|
791
|
+
properties["subscriptionGroupId"] = subscription.subscriptionGroupID
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Get storefront
|
|
796
|
+
if let storefront = await Storefront.current {
|
|
797
|
+
properties["storefront"] = storefront.countryCode
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
print("[RampKit] 📤 Sending manual purchase_completed event...")
|
|
801
|
+
await sendPurchaseEvent(
|
|
802
|
+
appId: appId,
|
|
803
|
+
userId: userId,
|
|
804
|
+
eventName: "purchase_completed",
|
|
805
|
+
properties: properties
|
|
806
|
+
)
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/// Track a purchase by looking up the latest transaction for a product
|
|
810
|
+
/// Use this when you only know the productId (Superwall doesn't always provide transaction IDs)
|
|
811
|
+
@available(iOS 15.0, *)
|
|
812
|
+
private func trackPurchaseFromProductId(productId: String) async {
|
|
813
|
+
print("[RampKit] 🔍 Looking up transaction for product: \(productId)")
|
|
814
|
+
|
|
815
|
+
// Try to find the latest transaction for this product
|
|
816
|
+
var latestTransaction: Transaction?
|
|
817
|
+
|
|
818
|
+
for await result in Transaction.currentEntitlements {
|
|
819
|
+
if case .verified(let transaction) = result {
|
|
820
|
+
if transaction.productID == productId {
|
|
821
|
+
if latestTransaction == nil || transaction.purchaseDate > latestTransaction!.purchaseDate {
|
|
822
|
+
latestTransaction = transaction
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
if let transaction = latestTransaction {
|
|
829
|
+
print("[RampKit] ✅ Found transaction for product: \(productId)")
|
|
830
|
+
await handleTransaction(transaction)
|
|
831
|
+
} else {
|
|
832
|
+
print("[RampKit] ⚠️ No transaction found for product, tracking manually: \(productId)")
|
|
833
|
+
// Fall back to manual tracking without transaction IDs
|
|
834
|
+
await trackPurchaseCompletedManually(
|
|
835
|
+
productId: productId,
|
|
836
|
+
transactionId: nil,
|
|
837
|
+
originalTransactionId: nil
|
|
838
|
+
)
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
640
842
|
// MARK: - Device Helpers
|
|
641
843
|
|
|
642
844
|
private func getDeviceModelIdentifier() -> String {
|
package/package.json
CHANGED