Skip to content

Commit

Permalink
[Diagnostics] Add apple_purchase_attempt event (#4253)
Browse files Browse the repository at this point in the history
  • Loading branch information
vegaro authored and nyeu committed Oct 1, 2024
1 parent b1d6f78 commit 22b617e
Show file tree
Hide file tree
Showing 15 changed files with 505 additions and 46 deletions.
4 changes: 4 additions & 0 deletions RevenueCat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@
353FDC0D2CA41CB20055F328 /* SubscriptionPeriod+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 353FDC0C2CA41CA60055F328 /* SubscriptionPeriod+Extensions.swift */; };
353FDC0F2CA446FA0055F328 /* StoreProductDiscount+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 353FDC0E2CA446F50055F328 /* StoreProductDiscount+Extensions.swift */; };
353FDC112CA4472B0055F328 /* StoreProduct+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 353FDC102CA447270055F328 /* StoreProduct+Extensions.swift */; };
353FDAEC2CA1866A0055F328 /* StoreKitErrorHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 353FDAEB2CA1866A0055F328 /* StoreKitErrorHelper.swift */; };
3543913626F90D6A00E669DF /* TrialOrIntroPriceEligibilityCheckerSK1Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35E1CE1F26E022C20008560A /* TrialOrIntroPriceEligibilityCheckerSK1Tests.swift */; };
3543913826F90FE100E669DF /* MockIntroEligibilityCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 351B515B26D44B7900BD2BD7 /* MockIntroEligibilityCalculator.swift */; };
3543913926F90FFB00E669DF /* MockBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 351B514026D4498F00BD2BD7 /* MockBackend.swift */; };
Expand Down Expand Up @@ -1389,6 +1390,7 @@
353FDC0C2CA41CA60055F328 /* SubscriptionPeriod+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SubscriptionPeriod+Extensions.swift"; sourceTree = "<group>"; };
353FDC0E2CA446F50055F328 /* StoreProductDiscount+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StoreProductDiscount+Extensions.swift"; sourceTree = "<group>"; };
353FDC102CA447270055F328 /* StoreProduct+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StoreProduct+Extensions.swift"; sourceTree = "<group>"; };
353FDAEB2CA1866A0055F328 /* StoreKitErrorHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKitErrorHelper.swift; sourceTree = "<group>"; };
3544DA692C2C848E00704E9D /* CustomerCenterViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomerCenterViewModelTests.swift; sourceTree = "<group>"; };
3544DA6A2C2C848E00704E9D /* ManageSubscriptionsViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManageSubscriptionsViewModelTests.swift; sourceTree = "<group>"; };
3546355A2C391F38001D7E85 /* FeedbackSurveyViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbackSurveyViewModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4329,6 +4331,7 @@
B324DC482720C15300103EE9 /* Error Handling */ = {
isa = PBXGroup;
children = (
353FDAEB2CA1866A0055F328 /* StoreKitErrorHelper.swift */,
5733B18D27FF586A00EC2045 /* BackendError.swift */,
B3B5FBB3269CED4B00104A0C /* BackendErrorCode.swift */,
B3022071272B3DDC008F1A0D /* DescribableError.swift */,
Expand Down Expand Up @@ -5336,6 +5339,7 @@
5753ED8E294A662400CBAB54 /* DateFormatter+Extensions.swift in Sources */,
4F174F472B07EA7E00FE538E /* StorefrontProvider.swift in Sources */,
B3AA6238268B926F00894871 /* SystemInfo.swift in Sources */,
353FDAEC2CA1866A0055F328 /* StoreKitErrorHelper.swift in Sources */,
5746508E275949F20053AB09 /* DispatchTimeInterval+Extensions.swift in Sources */,
35109DB92BC8143E001030C8 /* DiagnosticsEventsRequest.swift in Sources */,
2D294E5C26DECFD500B8FE4F /* StoreKit2TransactionListener.swift in Sources */,
Expand Down
5 changes: 5 additions & 0 deletions Sources/Diagnostics/DiagnosticsEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ extension DiagnosticsEvent {
case httpRequestPerformed
case customerInfoVerificationResult
case maxEventsStoredLimitReached
case applePurchaseAttempt

}

Expand All @@ -41,9 +42,13 @@ extension DiagnosticsEvent {
case verificationResultKey
case endpointNameKey
case responseTimeMillisKey
case storeKitVersion
case successfulKey
case responseCodeKey
case backendErrorCodeKey
case errorMessageKey
case errorCodeKey
case skErrorDescriptionKey
case eTagHitKey

}
Expand Down
25 changes: 25 additions & 0 deletions Sources/Diagnostics/DiagnosticsTracker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ protocol DiagnosticsTrackerType {
resultOrigin: HTTPResponseOrigin?,
verificationResult: VerificationResult) async

@available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *)
func trackPurchaseRequest(wasSuccessful: Bool,
storeKitVersion: StoreKitVersion,
errorMessage: String?,
errorCode: Int?,
storeKitErrorDescription: String?) async

}

@available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *)
Expand Down Expand Up @@ -91,6 +98,24 @@ final class DiagnosticsTracker: DiagnosticsTrackerType {
)
}

func trackPurchaseRequest(wasSuccessful: Bool,
storeKitVersion: StoreKitVersion,
errorMessage: String?,
errorCode: Int?,
storeKitErrorDescription: String?) async {
await track(
DiagnosticsEvent(eventType: .applePurchaseAttempt,
properties: [
.successfulKey: AnyEncodable(wasSuccessful),
.storeKitVersion: AnyEncodable("store_kit_\(storeKitVersion.debugDescription)"),
.errorMessageKey: AnyEncodable(errorMessage),
.errorCodeKey: AnyEncodable(errorCode),
.skErrorDescriptionKey: AnyEncodable(storeKitErrorDescription)
],
timestamp: self.dateProvider.now())
)
}

}

@available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *)
Expand Down
9 changes: 9 additions & 0 deletions Sources/Diagnostics/Networking/DiagnosticsEventsRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ private extension DiagnosticsEvent.EventType {
case .httpRequestPerformed: return "http_request_performed"
case .customerInfoVerificationResult: return "customer_info_verification_result"
case .maxEventsStoredLimitReached: return "max_events_stored_limit_reached"
case .applePurchaseAttempt: return "apple_purchase_attempt"
}

}
Expand All @@ -80,12 +81,20 @@ private extension DiagnosticsEvent.DiagnosticsPropertyKey {
return "endpoint_name"
case .responseTimeMillisKey:
return "response_time_millis"
case .storeKitVersion:
return "store_kit_version"
case .successfulKey:
return "successful"
case .responseCodeKey:
return "response_code"
case .backendErrorCodeKey:
return "backend_error_code"
case .errorMessageKey:
return "error_message"
case .errorCodeKey:
return "error_code"
case .skErrorDescriptionKey:
return "sk_error_description"
case .eTagHitKey:
return "etag_hit"
}
Expand Down
53 changes: 53 additions & 0 deletions Sources/Error Handling/SKError+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,59 @@ extension SKError: PurchasesErrorConvertible {

}

extension SKError.Code {

var trackingDescription: String {
switch self {
case .unknown:
return "unknown"
case .clientInvalid:
return "client_invalid"
case .paymentCancelled:
return "payment_cancelled"
case .paymentInvalid:
return "payment_invalid"
case .paymentNotAllowed:
return "payment_not_allowed"
case .storeProductNotAvailable:
return "store_product_not_available"
case .cloudServicePermissionDenied:
return "cloud_service_permission_denied"
case .cloudServiceNetworkConnectionFailed:
return "cloud_service_network_connection_failed"
case .cloudServiceRevoked:
return "cloud_service_revoked"
case .privacyAcknowledgementRequired:
return "privacy_acknowledgement_required"
case .unauthorizedRequestData:
return "unauthorized_request_data"
case .invalidOfferIdentifier:
return "invalid_offer_identifier"
case .invalidSignature:
return "invalid_signature"
case .missingOfferParams:
return "missing_offer_parameters"
case .invalidOfferPrice:
return "invalid_offer_price"
case .overlayCancelled:
return "overlay_cancelled"
case .overlayInvalidConfiguration:
return "overlay_invalid_configuration"
case .overlayTimeout:
return "overlay_timeout"
case .ineligibleForOffer:
return "ineligible_for_offer"
case .unsupportedPlatform:
return "unsupported_platform"
case .overlayPresentedInBackgroundScene:
return "overlay_presented_in_background_scene"
@unknown default:
return "unknown_store_kit_error"
}
}

}

private extension SKError {

enum UndocumentedCode: Int {
Expand Down
42 changes: 42 additions & 0 deletions Sources/Error Handling/StoreKitError+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,25 @@ extension StoreKitError: PurchasesErrorConvertible {
}
}

var trackingDescription: String {
switch self {
case .unknown:
return "unknown"
case .userCancelled:
return "user_cancelled"
case .networkError(let urlError):
return "network_error_\(urlError.code.rawValue)"
case .systemError(let error):
return "system_error_\(String(describing: error))"
case .notAvailableInStorefront:
return "not_available_in_storefront"
case .notEntitled:
return "not_entitled"
@unknown default:
return "unknown_store_kit_error"
}
}

}

@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
Expand Down Expand Up @@ -78,4 +97,27 @@ extension Product.PurchaseError: PurchasesErrorConvertible {
}
}

var trackingDescription: String {
switch self {
case .invalidQuantity:
return "invalid_quantity"
case .productUnavailable:
return "product_unavailable"
case .purchaseNotAllowed:
return "purchase_not_allowed"
case .ineligibleForOffer:
return "ineligible_for_offer"
case .invalidOfferIdentifier:
return "invalid_offer_identifier"
case .invalidOfferPrice:
return "invalid_offer_price"
case .invalidOfferSignature:
return "invalid_offer_signature"
case .missingOfferParameters:
return "missing_offer_parameters"
@unknown default:
return "unknown_store_kit_error"
}
}

}
35 changes: 35 additions & 0 deletions Sources/Error Handling/StoreKitErrorHelper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// Copyright RevenueCat Inc. All Rights Reserved.
//
// Licensed under the MIT License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://opensource.org/licenses/MIT
//
// StoreKitErrorHelper.swift
//
// Created by Cesar de la Vega on 19/9/24.

import StoreKit

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
enum StoreKitErrorUtils {

static func extractStoreKitErrorDescription(from error: Error?) -> String? {
guard let underlyingError = (error as NSError?)?.userInfo[NSUnderlyingErrorKey] as? Error else {
return nil
}

if let skError = underlyingError as? SKError {
return skError.code.trackingDescription
} else if let storeKitError = underlyingError as? StoreKitError {
return storeKitError.trackingDescription
} else if let storeKitError = underlyingError as? StoreKit.Product.PurchaseError {
return storeKitError.trackingDescription
}

return nil
}

}
3 changes: 2 additions & 1 deletion Sources/Purchasing/Purchases/Purchases.swift
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,8 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void
storeKit2StorefrontListener: StoreKit2StorefrontListener(delegate: nil),
storeKit2ObserverModePurchaseDetector: storeKit2ObserverModePurchaseDetector,
storeMessagesHelper: storeMessagesHelper,
diagnosticsSynchronizer: diagnosticsSynchronizer
diagnosticsSynchronizer: diagnosticsSynchronizer,
diagnosticsTracker: diagnosticsTracker
)
} else {
return .init(
Expand Down
Loading

0 comments on commit 22b617e

Please sign in to comment.