Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Paywalls: initial configuration types #2780

Merged
merged 7 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions RevenueCat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@
37E35C8515C5E2D01B0AF5C1 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E3507939634ED5A9280544 /* Strings.swift */; };
42F1DF385E3C1F9903A07FBF /* ProductsFetcherSK1.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFB3CBAA73855779FE828CE2 /* ProductsFetcherSK1.swift */; };
4F0201C42A13C85500091612 /* Assertions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F0201C32A13C85500091612 /* Assertions.swift */; };
4F05876F2A5DE03F00E9A834 /* PaywallDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F05876E2A5DE03F00E9A834 /* PaywallDataTests.swift */; };
4F0BBA812A1D0524000E75AB /* DefaultDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F0BBA802A1D0524000E75AB /* DefaultDecodable.swift */; };
4F0BBAAC2A1D253D000E75AB /* OfflineCustomerInfoCreatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F0BBAAB2A1D253D000E75AB /* OfflineCustomerInfoCreatorTests.swift */; };
4F0CE2BD2A215CE600561895 /* TransactionPosterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F0CE2BC2A215CE600561895 /* TransactionPosterTests.swift */; };
Expand Down Expand Up @@ -250,6 +251,8 @@
4F83F6B92A5DB805003F90A5 /* TestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57554CC0282AE1E3009A7E58 /* TestCase.swift */; };
4F83F6BA2A5DB807003F90A5 /* CurrentTestCaseTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 575A17AA2773A59300AA6F22 /* CurrentTestCaseTracker.swift */; };
4F83F6BB2A5DB80B003F90A5 /* OSVersionEquivalent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57DE80AD28075D77008D6C6F /* OSVersionEquivalent.swift */; };
4F87610F2A5C9E490006FA14 /* PaywallData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F87610E2A5C9E490006FA14 /* PaywallData.swift */; };
4F87612C2A5CAB980006FA14 /* PaywallTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F87612B2A5CAB980006FA14 /* PaywallTemplate.swift */; };
4F8A58172A16EE3500EF97AD /* MockOfflineCustomerInfoCreator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F8A58162A16EE3500EF97AD /* MockOfflineCustomerInfoCreator.swift */; };
4F8A58182A16EE3500EF97AD /* MockOfflineCustomerInfoCreator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F8A58162A16EE3500EF97AD /* MockOfflineCustomerInfoCreator.swift */; };
4F90AFCB2A3915340047E63F /* TestMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F90AFCA2A3915340047E63F /* TestMessage.swift */; };
Expand Down Expand Up @@ -935,6 +938,7 @@
37E35F783903362B65FB7AF3 /* MockProductsRequestFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockProductsRequestFactory.swift; sourceTree = "<group>"; };
37E35FDA0A44EA03EA12DAA2 /* DateFormatter+ExtensionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DateFormatter+ExtensionsTests.swift"; sourceTree = "<group>"; };
4F0201C32A13C85500091612 /* Assertions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Assertions.swift; sourceTree = "<group>"; };
4F05876E2A5DE03F00E9A834 /* PaywallDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallDataTests.swift; sourceTree = "<group>"; };
4F0BBA802A1D0524000E75AB /* DefaultDecodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultDecodable.swift; sourceTree = "<group>"; };
4F0BBAAB2A1D253D000E75AB /* OfflineCustomerInfoCreatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfflineCustomerInfoCreatorTests.swift; sourceTree = "<group>"; };
4F0CE2BC2A215CE600561895 /* TransactionPosterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionPosterTests.swift; sourceTree = "<group>"; };
Expand All @@ -960,6 +964,8 @@
4F6EEBD82A38ED76007FD783 /* FakeSigning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeSigning.swift; sourceTree = "<group>"; };
4F7DBFBC2A1E986C00A2F511 /* StoreKit2TransactionFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKit2TransactionFetcher.swift; sourceTree = "<group>"; };
4F8038322A1EA7C300D21039 /* TransactionPoster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionPoster.swift; sourceTree = "<group>"; };
4F87610E2A5C9E490006FA14 /* PaywallData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallData.swift; sourceTree = "<group>"; };
4F87612B2A5CAB980006FA14 /* PaywallTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallTemplate.swift; sourceTree = "<group>"; };
4F8A58162A16EE3500EF97AD /* MockOfflineCustomerInfoCreator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockOfflineCustomerInfoCreator.swift; sourceTree = "<group>"; };
4F90AFCA2A3915340047E63F /* TestMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestMessage.swift; sourceTree = "<group>"; };
4F98E9D22A465A4400DB6EAB /* TestStoreProduct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestStoreProduct.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1600,6 +1606,7 @@
2DDA3E4524DB0B4500EDFE5B /* Misc */,
35D832CB262A5B3400E60AC5 /* Networking */,
57488B7D29CB70DA0000EE7E /* OfflineEntitlements */,
4F87610D2A5C9E330006FA14 /* Paywalls */,
354235D524C11138008C84EE /* Purchasing */,
57F3C0C929B7A04D0004FD7E /* Security */,
354895D0267AE32D001DC5B1 /* SubscriberAttributes */,
Expand Down Expand Up @@ -2193,6 +2200,15 @@
path = DebugUI;
sourceTree = "<group>";
};
4F87610D2A5C9E330006FA14 /* Paywalls */ = {
isa = PBXGroup;
children = (
4F87610E2A5C9E490006FA14 /* PaywallData.swift */,
4F87612B2A5CAB980006FA14 /* PaywallTemplate.swift */,
);
path = Paywalls;
sourceTree = "<group>";
};
4F90AFC92A39152A0047E63F /* Helpers */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -2336,6 +2352,7 @@
574A2F3E282D75E300150D40 /* OfferingsDecodingTests.swift */,
574A2F4E282D7B9E00150D40 /* PostOfferDecodingTests.swift */,
5766C61F282DA3D50067D886 /* GetIntroEligibilityDecodingTests.swift */,
4F05876E2A5DE03F00E9A834 /* PaywallDataTests.swift */,
57045B3729C514A8001A5417 /* ProductEntitlementMappingDecodingTests.swift */,
);
path = Responses;
Expand Down Expand Up @@ -3209,6 +3226,7 @@
57DC9F4627CC2E4900DA6AF9 /* HTTPRequest.swift in Sources */,
2DC5623024EC63730031F69B /* OperationDispatcher.swift in Sources */,
575642B62910116900719219 /* EligibilityStrings.swift in Sources */,
4F87610F2A5C9E490006FA14 /* PaywallData.swift in Sources */,
57A0FBF22749CF66009E2FC3 /* SynchronizedUserDefaults.swift in Sources */,
F5714EE526DC2F1D00635477 /* CodableStrings.swift in Sources */,
57488BE829CB7FB60000EE7E /* OfflineEntitlementsStrings.swift in Sources */,
Expand Down Expand Up @@ -3318,6 +3336,7 @@
2DDF41AD24F6F37C005BC22D /* ASN1ObjectIdentifier.swift in Sources */,
35549323269E298B005F9AE9 /* OfferingsFactory.swift in Sources */,
57536A28278522B400E2AE7F /* SK2StoreTransaction.swift in Sources */,
4F87612C2A5CAB980006FA14 /* PaywallTemplate.swift in Sources */,
2D9C7BB326D838FC006838BE /* UIApplication+RCExtensions.swift in Sources */,
F56E2E7727622B5E009FED5B /* TransactionsManager.swift in Sources */,
B34605CC279A6E380031CA74 /* LogInOperation.swift in Sources */,
Expand Down Expand Up @@ -3488,6 +3507,7 @@
B380D69B27726AB500984578 /* DNSCheckerTests.swift in Sources */,
5774F9C12805EA3000997128 /* BaseHTTPResponseTest.swift in Sources */,
351B51B526D450E800BD2BD7 /* ProductsFetcherSK1Tests.swift in Sources */,
4F05876F2A5DE03F00E9A834 /* PaywallDataTests.swift in Sources */,
2DDF41CC24F6F4C3005BC22D /* AppleReceiptBuilderTests.swift in Sources */,
575A8EE52922C9F300936709 /* MockStoreKit2TransactionListenerDelegate.swift in Sources */,
57BF87592967880C00424254 /* MockCachingTrialOrIntroPriceEligibilityChecker.swift in Sources */,
Expand Down
2 changes: 2 additions & 0 deletions Sources/Networking/Responses/OfferingsResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ struct OfferingsResponse {
let identifier: String
let description: String
let packages: [Package]
@IgnoreDecodeErrors<PaywallData?>
var paywall: PaywallData?
Comment on lines +32 to +33
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important that we don't fail to load the offering if this fails. The error will get logged with #2778

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1000. At least for now

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should fail at the Paywalls SDK level though

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what I'm thinking:

if let paywall = offering.paywall {
  // The paywall is required
  PaywallView(offering: offering, paywall: paywall)
} else {
  // You gotta handle it and do something yourself.
}

With the public PaywallData constructor, users could to this:

} else {
  // This is hardcoded in the app
  let simplePaywall = PaywallData(
      template: .simple
      config: ...
  )
  PaywallView(offering: offering, paywall: simplePaywall)
}

@DefaultDecodable.EmptyDictionary
var metadata: [String: AnyDecodable]

Expand Down
159 changes: 159 additions & 0 deletions Sources/Paywalls/PaywallData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
//
// 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
//
// PaywallData.swift
//
// Created by Nacho Soto on 7/10/23.

import Foundation

/// The data necessary to display a paywall using the `RevenueCatUI` library.
/// They can be created and configured in the dashboard, then access from ``Offering/paywall``.
public struct PaywallData {

/// The type of template used to display this paywall.
public var template: PaywallTemplate

/// Generic configuration for any paywall.
public var config: Configuration

fileprivate var defaultLocaleIdentifier: String
fileprivate var localization: [String: LocalizedConfiguration]

}

extension PaywallData {

/// Configuration containing values for the necessary `Locale`s.
public struct LocalizedConfiguration {

/// The content of the main action button for purchasing a subscription.
public let callToAction: String
/// The title of the paywall screen.
public let title: String

/// swiftlint:disable:next missing_docs
public init(callToAction: String, title: String) {
self.callToAction = callToAction
self.title = title
}

}

/// - Returns: ``PaywallData/LocalizedConfiguration-swift.struct`` for the given `Locale`, if found.
public func config(for locale: Locale) -> LocalizedConfiguration? {
return self.localization[locale.identifier]
}

/// The default `Locale` used if `Locale.current` is not configured for this paywall.
public var defaultLocale: Locale {
return .init(identifier: self.defaultLocaleIdentifier)
}

/// - Returns: the ``PaywallData/LocalizedConfiguration-swift.struct`` associated to the current `Locale`
/// or the configuration associated to ``defaultLocale``.
public var localizedConfiguration: LocalizedConfiguration {
return self.config(for: Locale.current) ?? self.defaultLocalizedConfiguration
}

private var defaultLocalizedConfiguration: LocalizedConfiguration {
let defaultLocale = self.defaultLocale

guard let result = self.config(for: defaultLocale) else {
fatalError(
"Corrupted data. Expected to find locale \(defaultLocale.identifier) " +
"in locales: \(Set(self.localization.keys))"
)
}

return result
}

}

extension PaywallData {

/// Generic configuration for any paywall.
public struct Configuration {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is empty for now.


// swiftlint:disable:next missing_docs
public init() {}

}

}

// MARK: - Constructors

extension PaywallData {

init(
template: PaywallTemplate,
config: Configuration,
defaultLocale: String,
localization: [String: LocalizedConfiguration]
) {
self.template = template
self.config = config
self.defaultLocaleIdentifier = defaultLocale
self.localization = localization
}

/// Creates a test ``PaywallData`` with one localization
public init(
template: PaywallTemplate,
config: Configuration,
localization: LocalizedConfiguration
) {
let locale = Locale.current.identifier

self.init(
template: template,
config: config,
defaultLocale: locale,
localization: [locale: localization]
)
}

}

// MARK: - Codable

extension PaywallData.LocalizedConfiguration: Codable {

private enum CodingKeys: String, CodingKey {
case callToAction = "cta"
case title
}

}
extension PaywallData.Configuration: Codable {}
extension PaywallData: Codable {

// Note: these are camel case but converted by the decoder
private enum CodingKeys: String, CodingKey {
case template = "templateName"
case defaultLocaleIdentifier = "defaultLocale"
case config
case localization = "localizedStrings"
}

}

// MARK: - Equatable

extension PaywallData.LocalizedConfiguration: Equatable {}
extension PaywallData.Configuration: Equatable {}
extension PaywallData: Equatable {}

// MARK: - Sendable

extension PaywallData.LocalizedConfiguration: Sendable {}
extension PaywallData.Configuration: Sendable {}
extension PaywallData: Sendable {}
27 changes: 27 additions & 0 deletions Sources/Paywalls/PaywallTemplate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// 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
//
// PaywallTemplate.swift
//
// Created by Nacho Soto on 7/10/23.

import Foundation

/// The type of template used to display a paywall.
public enum PaywallTemplate: String {

/// swiftlint:disable:next missing_docs
case example1 = "sample_1"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will obviously change, just a placeholder.


}

extension PaywallTemplate: Codable {}
extension PaywallTemplate: Sendable {}
extension PaywallTemplate: Equatable {}
extension PaywallTemplate: CaseIterable {}
25 changes: 24 additions & 1 deletion Sources/Purchasing/Offering.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ import Foundation
*/
@objc public var metadata: [String: Any] { self._metadata.data }

/**
Paywall configuration defined in RevenueCat dashboard.
*/
public let paywall: PaywallData?

/**
Array of ``Package`` objects available for purchase.
*/
Expand Down Expand Up @@ -118,17 +123,35 @@ import Foundation

// swiftlint:disable cyclomatic_complexity

/// Initialize an ``Offering`` given a list of ``Package``s.
@objc
public convenience init(
identifier: String,
serverDescription: String,
metadata: [String: Any] = [:],
availablePackages: [Package]
) {
self.init(
identifier: identifier,
serverDescription: serverDescription,
metadata: metadata,
paywall: nil,
availablePackages: availablePackages
)
}
/// Initialize an ``Offering`` given a list of ``Package``s.
public init(
identifier: String,
serverDescription: String,
metadata: [String: Any],
metadata: [String: Any] = [:],
paywall: PaywallData? = nil,
availablePackages: [Package]
) {
self.identifier = identifier
self.serverDescription = serverDescription
self.availablePackages = availablePackages
self._metadata = Metadata(data: metadata)
self.paywall = paywall

var foundPackages: [PackageType: Package] = [:]

Expand Down
1 change: 1 addition & 0 deletions Sources/Purchasing/OfferingsFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class OfferingsFactory {
return Offering(identifier: offering.identifier,
serverDescription: offering.description,
metadata: offering.metadata.mapValues(\.asAny),
paywall: offering.paywall,
availablePackages: availablePackages)
}

Expand Down
7 changes: 6 additions & 1 deletion Tests/APITesters/ObjCAPITester/ObjCAPITester/RCOfferingAPI.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
@implementation RCOfferingAPI

+ (void)checkAPI {
RCOffering *o = nil; // No public initializer.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We didn't update this when we exposed it for TestStoreProduct.

RCOffering *o = nil;
NSString *i = o.identifier;
NSString *sd = o.serverDescription;
NSArray<RCPackage *> *a = o.availablePackages;
Expand All @@ -34,6 +34,11 @@ + (void)checkAPI {
RCPackage *ok = [o objectForKeyedSubscript:@""];
NSDictionary<NSString *, id> *md = o.metadata;

o = [[RCOffering alloc] initWithIdentifier:@""
serverDescription:@""
metadata:@{}
availablePackages:a];

NSLog(o, i, sd, a, l, an, s, t, tm, m, w, p, ok, md);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
4F1428A22A4A11D7006CD196 /* TestStoreProductAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F1428A12A4A11D7006CD196 /* TestStoreProductAPI.swift */; };
4F1428A72A4A16C0006CD196 /* TestStoreProductDiscountAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F1428A62A4A16C0006CD196 /* TestStoreProductDiscountAPI.swift */; };
4F6BEE752A27C77C00CD9322 /* OtherAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6BEE742A27C77C00CD9322 /* OtherAPI.swift */; };
4FF58AD22A5DDA5B00451B28 /* PaywallAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF58AD12A5DDA5B00451B28 /* PaywallAPI.swift */; };
570FAF562864EE1D00D3C769 /* NonSubscriptionTransactionAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 570FAF552864EE1D00D3C769 /* NonSubscriptionTransactionAPI.swift */; };
5738F40C27866DD00096D623 /* StoreProductDiscountAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5738F40B27866DD00096D623 /* StoreProductDiscountAPI.swift */; };
5738F42127866F8F0096D623 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D614C626EBE7EA007DDB75 /* main.swift */; };
Expand Down Expand Up @@ -60,6 +61,7 @@
4F1428A12A4A11D7006CD196 /* TestStoreProductAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestStoreProductAPI.swift; sourceTree = "<group>"; };
4F1428A62A4A16C0006CD196 /* TestStoreProductDiscountAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestStoreProductDiscountAPI.swift; sourceTree = "<group>"; };
4F6BEE742A27C77C00CD9322 /* OtherAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherAPI.swift; sourceTree = "<group>"; };
4FF58AD12A5DDA5B00451B28 /* PaywallAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallAPI.swift; sourceTree = "<group>"; };
570FAF552864EE1D00D3C769 /* NonSubscriptionTransactionAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonSubscriptionTransactionAPI.swift; sourceTree = "<group>"; };
5738F40B27866DD00096D623 /* StoreProductDiscountAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreProductDiscountAPI.swift; sourceTree = "<group>"; };
5738F429278673A80096D623 /* SubscriptionPeriodAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPeriodAPI.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -146,6 +148,7 @@
A5D614C326EBE7EA007DDB75 /* OfferingsAPI.swift */,
4F6BEE742A27C77C00CD9322 /* OtherAPI.swift */,
A5D614C926EBE7EA007DDB75 /* PackageAPI.swift */,
4FF58AD12A5DDA5B00451B28 /* PaywallAPI.swift */,
B3A4C833280DE72600D4AE17 /* PromotionalOfferAPI.swift */,
A5D614CE26EBE7EA007DDB75 /* PurchasesAPI.swift */,
A513AD33272B4C0100E0C1BA /* RefundRequestStatusAPI.swift */,
Expand Down Expand Up @@ -249,6 +252,7 @@
2DD778E6270E23460079CBD4 /* PurchasesAPI.swift in Sources */,
2DD778EF270E23460079CBD4 /* EntitlementInfoAPI.swift in Sources */,
4F1428A72A4A16C0006CD196 /* TestStoreProductDiscountAPI.swift in Sources */,
4FF58AD22A5DDA5B00451B28 /* PaywallAPI.swift in Sources */,
2DD778E9270E23460079CBD4 /* CustomerInfoAPI.swift in Sources */,
5758EE4F2786493400B3B703 /* StoreProductAPI.swift in Sources */,
5753ED9D294A6F3F00CBAB54 /* PurchasesReceiptParserAPI.swift in Sources */,
Expand Down
Loading