rampkit-expo-dev 0.0.69 → 0.0.71
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 +53 -7
- package/build/RampKitNative.js +116 -6
- package/build/index.d.ts +1 -1
- package/ios/RampKitModule.swift +229 -24
- 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<TransactionObserverResult>;
|
|
20
20
|
stopTransactionObserver(): Promise<void>;
|
|
21
|
+
clearTrackedTransactions(): Promise<number>;
|
|
22
|
+
recheckEntitlements(): Promise<EntitlementCheckResult>;
|
|
21
23
|
trackPurchaseCompleted(productId: string, transactionId?: string, originalTransactionId?: string): Promise<void>;
|
|
22
24
|
trackPurchaseFromProduct(productId: string): Promise<void>;
|
|
23
25
|
}
|
|
@@ -86,6 +88,41 @@ export interface NotificationPermissionResult {
|
|
|
86
88
|
};
|
|
87
89
|
error?: string;
|
|
88
90
|
}
|
|
91
|
+
export interface SentEventResult {
|
|
92
|
+
productId: string;
|
|
93
|
+
transactionId: string;
|
|
94
|
+
originalTransactionId: string;
|
|
95
|
+
purchaseDate: string;
|
|
96
|
+
status: "sent" | "skipped" | "failed" | "error";
|
|
97
|
+
httpStatus?: number;
|
|
98
|
+
error?: string;
|
|
99
|
+
reason?: string;
|
|
100
|
+
amount?: string;
|
|
101
|
+
currency?: string;
|
|
102
|
+
environment?: string;
|
|
103
|
+
}
|
|
104
|
+
export interface TrackedTransactionDetail {
|
|
105
|
+
productId: string;
|
|
106
|
+
transactionId: string;
|
|
107
|
+
originalTransactionId: string;
|
|
108
|
+
purchaseDate: string;
|
|
109
|
+
expirationDate?: string;
|
|
110
|
+
environment?: string;
|
|
111
|
+
status: "already_sent" | "skipped";
|
|
112
|
+
reason?: string;
|
|
113
|
+
}
|
|
114
|
+
export interface EntitlementCheckResult {
|
|
115
|
+
totalFound: number;
|
|
116
|
+
alreadyTracked: number;
|
|
117
|
+
newPurchases: number;
|
|
118
|
+
productIds: string[];
|
|
119
|
+
newProductIds: string[];
|
|
120
|
+
sentEvents?: SentEventResult[];
|
|
121
|
+
skippedReasons?: TrackedTransactionDetail[];
|
|
122
|
+
alreadyTrackedDetails?: TrackedTransactionDetail[];
|
|
123
|
+
trackedIdsCount: number;
|
|
124
|
+
error?: string;
|
|
125
|
+
}
|
|
89
126
|
export interface TransactionObserverResult {
|
|
90
127
|
configured: boolean;
|
|
91
128
|
appId: string;
|
|
@@ -93,13 +130,7 @@ export interface TransactionObserverResult {
|
|
|
93
130
|
previouslyTrackedCount: number;
|
|
94
131
|
iOSVersion: string;
|
|
95
132
|
listenerStarted: boolean;
|
|
96
|
-
entitlementCheck?:
|
|
97
|
-
totalFound: number;
|
|
98
|
-
alreadyTracked: number;
|
|
99
|
-
newPurchases: number;
|
|
100
|
-
productIds: string[];
|
|
101
|
-
newProductIds: string[];
|
|
102
|
-
};
|
|
133
|
+
entitlementCheck?: EntitlementCheckResult;
|
|
103
134
|
error?: string;
|
|
104
135
|
}
|
|
105
136
|
export type ImpactStyle = "light" | "medium" | "heavy" | "rigid" | "soft";
|
|
@@ -198,4 +229,19 @@ export declare const TransactionObserver: {
|
|
|
198
229
|
* @param productId - The product ID to look up and track
|
|
199
230
|
*/
|
|
200
231
|
trackPurchaseByProductId(productId: string): Promise<void>;
|
|
232
|
+
/**
|
|
233
|
+
* Clear all tracked transaction IDs from storage
|
|
234
|
+
* Use this for testing to re-trigger tracking of existing purchases
|
|
235
|
+
*
|
|
236
|
+
* @returns The number of tracked transactions that were cleared
|
|
237
|
+
*/
|
|
238
|
+
clearTracked(): Promise<number>;
|
|
239
|
+
/**
|
|
240
|
+
* Re-check current entitlements for any new purchases
|
|
241
|
+
* Call this after onboarding finishes or after a paywall is shown
|
|
242
|
+
* to catch any purchases that may have been made
|
|
243
|
+
*
|
|
244
|
+
* @returns The entitlement check result with details of all transactions
|
|
245
|
+
*/
|
|
246
|
+
recheck(): Promise<EntitlementCheckResult | null>;
|
|
201
247
|
};
|
package/build/RampKitNative.js
CHANGED
|
@@ -80,6 +80,18 @@ function createFallbackModule() {
|
|
|
80
80
|
};
|
|
81
81
|
},
|
|
82
82
|
async stopTransactionObserver() { },
|
|
83
|
+
async clearTrackedTransactions() { return 0; },
|
|
84
|
+
async recheckEntitlements() {
|
|
85
|
+
return {
|
|
86
|
+
totalFound: 0,
|
|
87
|
+
alreadyTracked: 0,
|
|
88
|
+
newPurchases: 0,
|
|
89
|
+
productIds: [],
|
|
90
|
+
newProductIds: [],
|
|
91
|
+
trackedIdsCount: 0,
|
|
92
|
+
error: "Native module not available - using fallback"
|
|
93
|
+
};
|
|
94
|
+
},
|
|
83
95
|
async trackPurchaseCompleted(_productId, _transactionId, _originalTransactionId) { },
|
|
84
96
|
async trackPurchaseFromProduct(_productId) { },
|
|
85
97
|
};
|
|
@@ -278,6 +290,72 @@ exports.Notifications = {
|
|
|
278
290
|
// ============================================================================
|
|
279
291
|
// Transaction Observer API (StoreKit 2 / Google Play Billing)
|
|
280
292
|
// ============================================================================
|
|
293
|
+
/**
|
|
294
|
+
* Helper function to log entitlement check results with full details
|
|
295
|
+
*/
|
|
296
|
+
function logEntitlementCheckResult(result, context) {
|
|
297
|
+
console.log("[RampKit] ");
|
|
298
|
+
console.log("[RampKit] ═══════════════════════════════════════════════════════════");
|
|
299
|
+
console.log(`[RampKit] 📊 ENTITLEMENT CHECK RESULT (${context})`);
|
|
300
|
+
console.log("[RampKit] ═══════════════════════════════════════════════════════════");
|
|
301
|
+
console.log("[RampKit] Total entitlements found:", result.totalFound);
|
|
302
|
+
console.log("[RampKit] Already sent to backend: ", result.alreadyTracked);
|
|
303
|
+
console.log("[RampKit] New events sent: ", result.newPurchases);
|
|
304
|
+
console.log("[RampKit] Tracked IDs in storage: ", result.trackedIdsCount);
|
|
305
|
+
console.log("[RampKit] Product IDs: ", result.productIds);
|
|
306
|
+
// Log already tracked transactions with full details
|
|
307
|
+
if (result.alreadyTrackedDetails && result.alreadyTrackedDetails.length > 0) {
|
|
308
|
+
console.log("[RampKit] ");
|
|
309
|
+
console.log("[RampKit] ✅ ALREADY SENT TRANSACTIONS:");
|
|
310
|
+
for (const tx of result.alreadyTrackedDetails) {
|
|
311
|
+
console.log("[RampKit] ────────────────────────────────────────");
|
|
312
|
+
console.log("[RampKit] 📦 Product:", tx.productId);
|
|
313
|
+
console.log("[RampKit] Transaction ID:", tx.transactionId);
|
|
314
|
+
console.log("[RampKit] Original Transaction ID:", tx.originalTransactionId);
|
|
315
|
+
console.log("[RampKit] Purchase Date:", tx.purchaseDate);
|
|
316
|
+
if (tx.expirationDate) {
|
|
317
|
+
console.log("[RampKit] Expiration Date:", tx.expirationDate);
|
|
318
|
+
}
|
|
319
|
+
if (tx.environment) {
|
|
320
|
+
console.log("[RampKit] Environment:", tx.environment);
|
|
321
|
+
}
|
|
322
|
+
console.log("[RampKit] Status: ✅ ALREADY SENT TO BACKEND");
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// Log newly sent events
|
|
326
|
+
if (result.sentEvents && result.sentEvents.length > 0) {
|
|
327
|
+
console.log("[RampKit] ");
|
|
328
|
+
console.log("[RampKit] 📤 NEWLY SENT EVENTS:");
|
|
329
|
+
for (const event of result.sentEvents) {
|
|
330
|
+
console.log("[RampKit] ────────────────────────────────────────");
|
|
331
|
+
console.log("[RampKit] 📦 Product:", event.productId);
|
|
332
|
+
console.log("[RampKit] Transaction ID:", event.transactionId);
|
|
333
|
+
console.log("[RampKit] Original Transaction ID:", event.originalTransactionId);
|
|
334
|
+
console.log("[RampKit] Purchase Date:", event.purchaseDate);
|
|
335
|
+
console.log("[RampKit] Status:", event.status === "sent" ? "✅ SENT" : `❌ ${event.status.toUpperCase()}`);
|
|
336
|
+
if (event.httpStatus) {
|
|
337
|
+
console.log("[RampKit] HTTP Status:", event.httpStatus);
|
|
338
|
+
}
|
|
339
|
+
if (event.error) {
|
|
340
|
+
console.log("[RampKit] Error:", event.error);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// Log skipped transactions
|
|
345
|
+
if (result.skippedReasons && result.skippedReasons.length > 0) {
|
|
346
|
+
console.log("[RampKit] ");
|
|
347
|
+
console.log("[RampKit] ⏭️ SKIPPED TRANSACTIONS:");
|
|
348
|
+
for (const skipped of result.skippedReasons) {
|
|
349
|
+
console.log("[RampKit] - Product:", skipped.productId, "| Reason:", skipped.reason);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (result.error) {
|
|
353
|
+
console.log("[RampKit] ");
|
|
354
|
+
console.log("[RampKit] ⚠️ Error:", result.error);
|
|
355
|
+
}
|
|
356
|
+
console.log("[RampKit] ═══════════════════════════════════════════════════════════");
|
|
357
|
+
console.log("[RampKit] ");
|
|
358
|
+
}
|
|
281
359
|
exports.TransactionObserver = {
|
|
282
360
|
/**
|
|
283
361
|
* Start listening for purchase transactions
|
|
@@ -299,12 +377,7 @@ exports.TransactionObserver = {
|
|
|
299
377
|
console.log("[RampKit] - previouslyTrackedCount:", result.previouslyTrackedCount);
|
|
300
378
|
console.log("[RampKit] - listenerStarted:", result.listenerStarted);
|
|
301
379
|
if (result.entitlementCheck) {
|
|
302
|
-
|
|
303
|
-
console.log("[RampKit] - totalFound:", result.entitlementCheck.totalFound);
|
|
304
|
-
console.log("[RampKit] - alreadyTracked:", result.entitlementCheck.alreadyTracked);
|
|
305
|
-
console.log("[RampKit] - newPurchases:", result.entitlementCheck.newPurchases);
|
|
306
|
-
console.log("[RampKit] - productIds:", result.entitlementCheck.productIds);
|
|
307
|
-
console.log("[RampKit] - newProductIds:", result.entitlementCheck.newProductIds);
|
|
380
|
+
logEntitlementCheckResult(result.entitlementCheck, "STARTUP");
|
|
308
381
|
}
|
|
309
382
|
if (result.error) {
|
|
310
383
|
console.warn("[RampKit] ⚠️ Error:", result.error);
|
|
@@ -363,4 +436,41 @@ exports.TransactionObserver = {
|
|
|
363
436
|
console.warn("[RampKit] Failed to track purchase by product:", e);
|
|
364
437
|
}
|
|
365
438
|
},
|
|
439
|
+
/**
|
|
440
|
+
* Clear all tracked transaction IDs from storage
|
|
441
|
+
* Use this for testing to re-trigger tracking of existing purchases
|
|
442
|
+
*
|
|
443
|
+
* @returns The number of tracked transactions that were cleared
|
|
444
|
+
*/
|
|
445
|
+
async clearTracked() {
|
|
446
|
+
try {
|
|
447
|
+
console.log("[RampKit] 🗑️ Clearing tracked transaction IDs...");
|
|
448
|
+
const count = await RampKitNativeModule.clearTrackedTransactions();
|
|
449
|
+
console.log("[RampKit] ✅ Cleared", count, "tracked transaction IDs");
|
|
450
|
+
return count;
|
|
451
|
+
}
|
|
452
|
+
catch (e) {
|
|
453
|
+
console.warn("[RampKit] ❌ Failed to clear tracked transactions:", e);
|
|
454
|
+
return 0;
|
|
455
|
+
}
|
|
456
|
+
},
|
|
457
|
+
/**
|
|
458
|
+
* Re-check current entitlements for any new purchases
|
|
459
|
+
* Call this after onboarding finishes or after a paywall is shown
|
|
460
|
+
* to catch any purchases that may have been made
|
|
461
|
+
*
|
|
462
|
+
* @returns The entitlement check result with details of all transactions
|
|
463
|
+
*/
|
|
464
|
+
async recheck() {
|
|
465
|
+
console.log("[RampKit] 🔄 Re-checking entitlements...");
|
|
466
|
+
try {
|
|
467
|
+
const result = await RampKitNativeModule.recheckEntitlements();
|
|
468
|
+
logEntitlementCheckResult(result, "RECHECK");
|
|
469
|
+
return result;
|
|
470
|
+
}
|
|
471
|
+
catch (e) {
|
|
472
|
+
console.warn("[RampKit] ❌ Failed to recheck entitlements:", e);
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
},
|
|
366
476
|
};
|
package/build/index.d.ts
CHANGED
|
@@ -10,6 +10,6 @@ export { collectDeviceInfo, getSessionDurationSeconds, getSessionStartTime, buil
|
|
|
10
10
|
export { default as RampKitNative } from "./RampKitNative";
|
|
11
11
|
export type { NativeDeviceInfo, NativeLaunchData } from "./RampKitNative";
|
|
12
12
|
export { Haptics, StoreReview, Notifications, TransactionObserver, isNativeModuleAvailable } from "./RampKitNative";
|
|
13
|
-
export type { ImpactStyle, NotificationType, NotificationOptions, NotificationPermissionResult, TransactionObserverResult } from "./RampKitNative";
|
|
13
|
+
export type { ImpactStyle, NotificationType, NotificationOptions, NotificationPermissionResult, TransactionObserverResult, SentEventResult, TrackedTransactionDetail, EntitlementCheckResult } from "./RampKitNative";
|
|
14
14
|
export type { DeviceInfo, RampKitEvent, EventDevice, EventContext, RampKitConfig, RampKitEventName, RampKitContext, RampKitDeviceContext, RampKitUserContext, NavigationData, ScreenPosition, OnboardingResponse, AppSessionStartedProperties, OnboardingStartedProperties, OnboardingCompletedProperties, OnboardingAbandonedProperties, OptionSelectedProperties, NotificationsResponseProperties, PaywallShownProperties, PurchaseStartedProperties, PurchaseCompletedProperties, PurchaseFailedProperties, PurchaseRestoredProperties, } from "./types";
|
|
15
15
|
export { SDK_VERSION, CAPABILITIES } from "./constants";
|
package/ios/RampKitModule.swift
CHANGED
|
@@ -111,6 +111,23 @@ public class RampKitModule: Module {
|
|
|
111
111
|
self.stopTransactionObserver()
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
AsyncFunction("clearTrackedTransactions") { () -> Int in
|
|
115
|
+
let count = self.trackedTransactionIds.count
|
|
116
|
+
self.trackedTransactionIds.removeAll()
|
|
117
|
+
self.saveTrackedTransactions()
|
|
118
|
+
print("[RampKit] 🗑️ Cleared \(count) tracked transaction IDs")
|
|
119
|
+
return count
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
AsyncFunction("recheckEntitlements") { () -> [String: Any] in
|
|
123
|
+
print("[RampKit] 🔄 Re-checking entitlements (called from JS)...")
|
|
124
|
+
if #available(iOS 15.0, *) {
|
|
125
|
+
return await self.checkAndTrackCurrentEntitlements()
|
|
126
|
+
} else {
|
|
127
|
+
return ["error": "iOS 15+ required"]
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
114
131
|
// ============================================================================
|
|
115
132
|
// Manual Purchase Tracking (Fallback for Superwall/RevenueCat)
|
|
116
133
|
// ============================================================================
|
|
@@ -472,56 +489,115 @@ public class RampKitModule: Module {
|
|
|
472
489
|
@available(iOS 15.0, *)
|
|
473
490
|
private func checkAndTrackCurrentEntitlements() async -> [String: Any] {
|
|
474
491
|
print("[RampKit] 🔍 Checking current entitlements for missed purchases...")
|
|
492
|
+
print("[RampKit] 📚 Currently have \(trackedTransactionIds.count) tracked transaction IDs in storage")
|
|
493
|
+
|
|
494
|
+
let formatter = ISO8601DateFormatter()
|
|
495
|
+
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
475
496
|
|
|
476
497
|
var foundCount = 0
|
|
477
498
|
var trackedCount = 0
|
|
478
499
|
var newCount = 0
|
|
479
500
|
var productIds: [String] = []
|
|
480
501
|
var newProductIds: [String] = []
|
|
502
|
+
var sentEvents: [[String: Any]] = []
|
|
503
|
+
var skippedReasons: [[String: Any]] = []
|
|
504
|
+
var alreadyTrackedDetails: [[String: Any]] = [] // NEW: Details of already-tracked transactions
|
|
481
505
|
|
|
482
506
|
for await result in Transaction.currentEntitlements {
|
|
483
507
|
foundCount += 1
|
|
484
508
|
|
|
485
509
|
guard case .verified(let transaction) = result else {
|
|
486
510
|
print("[RampKit] ⚠️ Unverified entitlement skipped")
|
|
511
|
+
skippedReasons.append(["productId": "unknown", "reason": "unverified"])
|
|
487
512
|
continue
|
|
488
513
|
}
|
|
489
514
|
|
|
490
515
|
let originalId = String(transaction.originalID)
|
|
516
|
+
let transactionId = String(transaction.id)
|
|
491
517
|
productIds.append(transaction.productID)
|
|
492
|
-
|
|
518
|
+
|
|
519
|
+
// Build transaction details for logging
|
|
520
|
+
var txDetails: [String: Any] = [
|
|
521
|
+
"productId": transaction.productID,
|
|
522
|
+
"transactionId": transactionId,
|
|
523
|
+
"originalTransactionId": originalId,
|
|
524
|
+
"purchaseDate": formatter.string(from: transaction.purchaseDate)
|
|
525
|
+
]
|
|
526
|
+
if let expirationDate = transaction.expirationDate {
|
|
527
|
+
txDetails["expirationDate"] = formatter.string(from: expirationDate)
|
|
528
|
+
}
|
|
529
|
+
if #available(iOS 16.0, *) {
|
|
530
|
+
txDetails["environment"] = transaction.environment.rawValue
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
print("[RampKit] 📦 Found entitlement:")
|
|
534
|
+
print("[RampKit] - productId: \(transaction.productID)")
|
|
535
|
+
print("[RampKit] - transactionId: \(transactionId)")
|
|
536
|
+
print("[RampKit] - originalTransactionId: \(originalId)")
|
|
537
|
+
print("[RampKit] - purchaseDate: \(formatter.string(from: transaction.purchaseDate))")
|
|
493
538
|
|
|
494
539
|
// Check if we've already tracked this transaction
|
|
495
540
|
if trackedTransactionIds.contains(originalId) {
|
|
496
541
|
trackedCount += 1
|
|
497
|
-
|
|
542
|
+
txDetails["status"] = "already_sent"
|
|
543
|
+
alreadyTrackedDetails.append(txDetails)
|
|
544
|
+
print("[RampKit] ✅ STATUS: Already sent to backend (originalTransactionId in tracked set)")
|
|
545
|
+
continue
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Skip renewals and revocations
|
|
549
|
+
guard transaction.originalID == transaction.id,
|
|
550
|
+
transaction.revocationDate == nil else {
|
|
551
|
+
let reason = transaction.revocationDate != nil ? "revoked" : "renewal"
|
|
552
|
+
txDetails["status"] = "skipped"
|
|
553
|
+
txDetails["reason"] = reason
|
|
554
|
+
skippedReasons.append(txDetails)
|
|
555
|
+
print("[RampKit] ⏭️ STATUS: Skipped (\(reason))")
|
|
498
556
|
continue
|
|
499
557
|
}
|
|
500
558
|
|
|
501
559
|
// NEW transaction we haven't seen!
|
|
502
560
|
newCount += 1
|
|
503
561
|
newProductIds.append(transaction.productID)
|
|
504
|
-
print("[RampKit] 🆕 NEW purchase
|
|
505
|
-
|
|
506
|
-
// Track it
|
|
507
|
-
await self.
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
562
|
+
print("[RampKit] 🆕 STATUS: NEW purchase - will send to backend now...")
|
|
563
|
+
|
|
564
|
+
// Track it and get the result
|
|
565
|
+
let sendResult = await self.handleTransactionWithResult(transaction)
|
|
566
|
+
sentEvents.append(sendResult)
|
|
567
|
+
|
|
568
|
+
// Only mark as tracked if send succeeded
|
|
569
|
+
if let status = sendResult["status"] as? String, status == "sent" {
|
|
570
|
+
trackedTransactionIds.insert(originalId)
|
|
571
|
+
saveTrackedTransactions()
|
|
572
|
+
print("[RampKit] ✅ Event sent successfully! HTTP status: \(sendResult["httpStatus"] ?? "unknown")")
|
|
573
|
+
print("[RampKit] ✅ Marked originalTransactionId \(originalId) as tracked")
|
|
574
|
+
} else {
|
|
575
|
+
print("[RampKit] ❌ Send failed: \(sendResult["error"] ?? "unknown error")")
|
|
576
|
+
print("[RampKit] ⚠️ Will retry on next app launch")
|
|
577
|
+
}
|
|
512
578
|
}
|
|
513
579
|
|
|
514
|
-
print("[RampKit]
|
|
515
|
-
print("[RampKit]
|
|
516
|
-
print("[RampKit]
|
|
517
|
-
print("[RampKit]
|
|
580
|
+
print("[RampKit] ")
|
|
581
|
+
print("[RampKit] 🔍 ═══════════════════════════════════════════")
|
|
582
|
+
print("[RampKit] 🔍 ENTITLEMENT CHECK SUMMARY:")
|
|
583
|
+
print("[RampKit] 🔍 ═══════════════════════════════════════════")
|
|
584
|
+
print("[RampKit] Total entitlements found: \(foundCount)")
|
|
585
|
+
print("[RampKit] Already sent to backend: \(trackedCount)")
|
|
586
|
+
print("[RampKit] Skipped (renewal/revoked): \(skippedReasons.count)")
|
|
587
|
+
print("[RampKit] NEW events sent: \(newCount)")
|
|
588
|
+
print("[RampKit] Tracked IDs in storage: \(trackedTransactionIds.count)")
|
|
589
|
+
print("[RampKit] 🔍 ═══════════════════════════════════════════")
|
|
518
590
|
|
|
519
591
|
return [
|
|
520
592
|
"totalFound": foundCount,
|
|
521
593
|
"alreadyTracked": trackedCount,
|
|
522
594
|
"newPurchases": newCount,
|
|
523
595
|
"productIds": productIds,
|
|
524
|
-
"newProductIds": newProductIds
|
|
596
|
+
"newProductIds": newProductIds,
|
|
597
|
+
"sentEvents": sentEvents,
|
|
598
|
+
"skippedReasons": skippedReasons,
|
|
599
|
+
"alreadyTrackedDetails": alreadyTrackedDetails, // NEW
|
|
600
|
+
"trackedIdsCount": trackedTransactionIds.count
|
|
525
601
|
]
|
|
526
602
|
}
|
|
527
603
|
|
|
@@ -553,14 +629,33 @@ public class RampKitModule: Module {
|
|
|
553
629
|
continue
|
|
554
630
|
}
|
|
555
631
|
|
|
632
|
+
// Skip renewals - backend gets these from App Store S2S notifications
|
|
633
|
+
if transaction.originalID != transaction.id {
|
|
634
|
+
print("[RampKit] ⏭️ Transaction.updates: skipped (renewal) \(transaction.productID)")
|
|
635
|
+
await transaction.finish()
|
|
636
|
+
continue
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Skip revocations - backend gets these from S2S notifications
|
|
640
|
+
if transaction.revocationDate != nil {
|
|
641
|
+
print("[RampKit] ⏭️ Transaction.updates: skipped (revoked) \(transaction.productID)")
|
|
642
|
+
await transaction.finish()
|
|
643
|
+
continue
|
|
644
|
+
}
|
|
645
|
+
|
|
556
646
|
print("[RampKit] 🆕 Transaction.updates: NEW purchase \(transaction.productID)")
|
|
557
647
|
|
|
558
|
-
// Track it
|
|
559
|
-
await self.
|
|
648
|
+
// Track it and check result
|
|
649
|
+
let sendResult = await self.handleTransactionWithResult(transaction)
|
|
560
650
|
|
|
561
|
-
//
|
|
562
|
-
|
|
563
|
-
|
|
651
|
+
// Only mark as tracked if send succeeded
|
|
652
|
+
if let status = sendResult["status"] as? String, status == "sent" {
|
|
653
|
+
self.trackedTransactionIds.insert(originalId)
|
|
654
|
+
self.saveTrackedTransactions()
|
|
655
|
+
print("[RampKit] ✅ Transaction.updates: Sent and tracked \(transaction.productID)")
|
|
656
|
+
} else {
|
|
657
|
+
print("[RampKit] ⚠️ Transaction.updates: Send failed, will retry \(transaction.productID)")
|
|
658
|
+
}
|
|
564
659
|
|
|
565
660
|
// Finish the transaction
|
|
566
661
|
await transaction.finish()
|
|
@@ -669,6 +764,100 @@ public class RampKitModule: Module {
|
|
|
669
764
|
)
|
|
670
765
|
}
|
|
671
766
|
|
|
767
|
+
/// Handle transaction and return result for JS logging
|
|
768
|
+
@available(iOS 15.0, *)
|
|
769
|
+
private func handleTransactionWithResult(_ transaction: Transaction) async -> [String: Any] {
|
|
770
|
+
let formatter = ISO8601DateFormatter()
|
|
771
|
+
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
772
|
+
|
|
773
|
+
var result: [String: Any] = [
|
|
774
|
+
"productId": transaction.productID,
|
|
775
|
+
"transactionId": String(transaction.id),
|
|
776
|
+
"originalTransactionId": String(transaction.originalID),
|
|
777
|
+
"purchaseDate": formatter.string(from: transaction.purchaseDate)
|
|
778
|
+
]
|
|
779
|
+
|
|
780
|
+
guard let appId = self.appId, let userId = self.userId else {
|
|
781
|
+
result["status"] = "error"
|
|
782
|
+
result["error"] = "appId or userId missing"
|
|
783
|
+
return result
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Check if this is a renewal (originalID != id)
|
|
787
|
+
if transaction.originalID != transaction.id {
|
|
788
|
+
result["status"] = "skipped"
|
|
789
|
+
result["reason"] = "renewal (originalID != id)"
|
|
790
|
+
return result
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Check if revoked
|
|
794
|
+
if transaction.revocationDate != nil {
|
|
795
|
+
result["status"] = "skipped"
|
|
796
|
+
result["reason"] = "revoked"
|
|
797
|
+
return result
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// Build properties
|
|
801
|
+
var properties: [String: Any] = [:]
|
|
802
|
+
properties["productId"] = transaction.productID
|
|
803
|
+
properties["transactionId"] = String(transaction.id)
|
|
804
|
+
properties["originalTransactionId"] = String(transaction.originalID)
|
|
805
|
+
properties["purchaseDate"] = formatter.string(from: transaction.purchaseDate)
|
|
806
|
+
properties["quantity"] = transaction.purchasedQuantity
|
|
807
|
+
properties["productType"] = mapProductType(transaction.productType)
|
|
808
|
+
|
|
809
|
+
if let expirationDate = transaction.expirationDate {
|
|
810
|
+
properties["expirationDate"] = formatter.string(from: expirationDate)
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
if let offerType = transaction.offerType {
|
|
814
|
+
properties["isTrial"] = offerType == .introductory
|
|
815
|
+
properties["isIntroOffer"] = offerType == .introductory
|
|
816
|
+
properties["offerType"] = formatOfferType(offerType)
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
if let offerId = transaction.offerID {
|
|
820
|
+
properties["offerId"] = offerId
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
properties["storefront"] = transaction.storefrontCountryCode
|
|
824
|
+
|
|
825
|
+
if #available(iOS 16.0, *) {
|
|
826
|
+
properties["environment"] = transaction.environment.rawValue
|
|
827
|
+
result["environment"] = transaction.environment.rawValue
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// Get price info
|
|
831
|
+
if let product = await getProduct(for: transaction.productID) {
|
|
832
|
+
properties["amount"] = product.price
|
|
833
|
+
properties["currency"] = product.priceFormatStyle.currencyCode
|
|
834
|
+
properties["priceFormatted"] = product.displayPrice
|
|
835
|
+
result["amount"] = "\(product.price)"
|
|
836
|
+
result["currency"] = product.priceFormatStyle.currencyCode
|
|
837
|
+
|
|
838
|
+
if let subscription = product.subscription {
|
|
839
|
+
properties["subscriptionPeriod"] = formatSubscriptionPeriod(subscription.subscriptionPeriod)
|
|
840
|
+
properties["subscriptionGroupId"] = subscription.subscriptionGroupID
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Send event
|
|
845
|
+
let sendResult = await sendPurchaseEventWithResult(
|
|
846
|
+
appId: appId,
|
|
847
|
+
userId: userId,
|
|
848
|
+
eventName: "purchase_completed",
|
|
849
|
+
properties: properties
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
result["status"] = sendResult.success ? "sent" : "failed"
|
|
853
|
+
result["httpStatus"] = sendResult.statusCode
|
|
854
|
+
if let error = sendResult.error {
|
|
855
|
+
result["error"] = error
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
return result
|
|
859
|
+
}
|
|
860
|
+
|
|
672
861
|
@available(iOS 15.0, *)
|
|
673
862
|
private func formatOfferType(_ offerType: Transaction.OfferType) -> String {
|
|
674
863
|
if offerType == .introductory {
|
|
@@ -726,6 +915,16 @@ public class RampKitModule: Module {
|
|
|
726
915
|
}
|
|
727
916
|
|
|
728
917
|
private func sendPurchaseEvent(appId: String, userId: String, eventName: String, properties: [String: Any]) async {
|
|
918
|
+
let _ = await sendPurchaseEventWithResult(appId: appId, userId: userId, eventName: eventName, properties: properties)
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
private struct SendEventResult {
|
|
922
|
+
let success: Bool
|
|
923
|
+
let statusCode: Int
|
|
924
|
+
let error: String?
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
private func sendPurchaseEventWithResult(appId: String, userId: String, eventName: String, properties: [String: Any]) async -> SendEventResult {
|
|
729
928
|
let event: [String: Any] = [
|
|
730
929
|
"appId": appId,
|
|
731
930
|
"appUserId": userId,
|
|
@@ -747,23 +946,29 @@ public class RampKitModule: Module {
|
|
|
747
946
|
],
|
|
748
947
|
"properties": properties
|
|
749
948
|
]
|
|
750
|
-
|
|
751
|
-
guard let url = URL(string: "https://uustlzuvjmochxkxatfx.supabase.co/functions/v1/app-user-events") else {
|
|
752
|
-
|
|
949
|
+
|
|
950
|
+
guard let url = URL(string: "https://uustlzuvjmochxkxatfx.supabase.co/functions/v1/app-user-events") else {
|
|
951
|
+
return SendEventResult(success: false, statusCode: 0, error: "Invalid URL")
|
|
952
|
+
}
|
|
953
|
+
|
|
753
954
|
var request = URLRequest(url: url)
|
|
754
955
|
request.httpMethod = "POST"
|
|
755
956
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
756
957
|
request.setValue("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InV1c3RsenV2am1vY2h4a3hhdGZ4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjIxMDM2NTUsImV4cCI6MjA3NzY3OTY1NX0.d5XsIMGnia4n9Pou0IidipyyEfSlwpXFoeDBufMOEwE", forHTTPHeaderField: "apikey")
|
|
757
958
|
request.setValue("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InV1c3RsenV2am1vY2h4a3hhdGZ4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjIxMDM2NTUsImV4cCI6MjA3NzY3OTY1NX0.d5XsIMGnia4n9Pou0IidipyyEfSlwpXFoeDBufMOEwE", forHTTPHeaderField: "Authorization")
|
|
758
|
-
|
|
959
|
+
|
|
759
960
|
do {
|
|
760
961
|
request.httpBody = try JSONSerialization.data(withJSONObject: event)
|
|
761
962
|
let (_, response) = try await URLSession.shared.data(for: request)
|
|
762
963
|
if let httpResponse = response as? HTTPURLResponse {
|
|
763
964
|
print("[RampKit] Purchase event sent: \(eventName) - Status: \(httpResponse.statusCode)")
|
|
965
|
+
let success = httpResponse.statusCode >= 200 && httpResponse.statusCode < 300
|
|
966
|
+
return SendEventResult(success: success, statusCode: httpResponse.statusCode, error: success ? nil : "HTTP \(httpResponse.statusCode)")
|
|
764
967
|
}
|
|
968
|
+
return SendEventResult(success: false, statusCode: 0, error: "No HTTP response")
|
|
765
969
|
} catch {
|
|
766
970
|
print("[RampKit] Failed to send purchase event: \(error)")
|
|
971
|
+
return SendEventResult(success: false, statusCode: 0, error: error.localizedDescription)
|
|
767
972
|
}
|
|
768
973
|
}
|
|
769
974
|
|
package/package.json
CHANGED