diff --git a/Podfile b/Podfile new file mode 100644 index 0000000..0f64770 --- /dev/null +++ b/Podfile @@ -0,0 +1,11 @@ +# Uncomment the next line to define a global platform for your project +# platform :ios, '9.0' + +target 'calm-mind' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + # Pods for calm-mind + pod 'Purchases', '3.8.0' + +end diff --git a/Podfile.lock b/Podfile.lock new file mode 100644 index 0000000..db7bfb5 --- /dev/null +++ b/Podfile.lock @@ -0,0 +1,20 @@ +PODS: + - Purchases (3.8.0): + - PurchasesCoreSwift (= 3.8.0) + - PurchasesCoreSwift (3.8.0) + +DEPENDENCIES: + - Purchases (= 3.8.0) + +SPEC REPOS: + trunk: + - Purchases + - PurchasesCoreSwift + +SPEC CHECKSUMS: + Purchases: 6d5e1cdba3de4766d39b0f166bb851bdfffe179c + PurchasesCoreSwift: 638f04384b60f44c8728d0af7a10785bbd747cab + +PODFILE CHECKSUM: da09ec65fa4b69a882a7d9a00deeccaa0ab91f43 + +COCOAPODS: 1.10.0 diff --git a/Pods/Manifest.lock b/Pods/Manifest.lock new file mode 100644 index 0000000..db7bfb5 --- /dev/null +++ b/Pods/Manifest.lock @@ -0,0 +1,20 @@ +PODS: + - Purchases (3.8.0): + - PurchasesCoreSwift (= 3.8.0) + - PurchasesCoreSwift (3.8.0) + +DEPENDENCIES: + - Purchases (= 3.8.0) + +SPEC REPOS: + trunk: + - Purchases + - PurchasesCoreSwift + +SPEC CHECKSUMS: + Purchases: 6d5e1cdba3de4766d39b0f166bb851bdfffe179c + PurchasesCoreSwift: 638f04384b60f44c8728d0af7a10785bbd747cab + +PODFILE CHECKSUM: da09ec65fa4b69a882a7d9a00deeccaa0ab91f43 + +COCOAPODS: 1.10.0 diff --git a/Pods/Pods.xcodeproj/project.pbxproj b/Pods/Pods.xcodeproj/project.pbxproj new file mode 100644 index 0000000..fe5d003 --- /dev/null +++ b/Pods/Pods.xcodeproj/project.pbxproj @@ -0,0 +1,1265 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 052E739017171D657D8E2692CCA20E04 /* RCPackage.h in Headers */ = {isa = PBXBuildFile; fileRef = B18B516B38CB919E56F69CEB9F1FFAFA /* RCPackage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0653BAEADF463B2AA3DDCFA7FA45FBD0 /* RCBackend.h in Headers */ = {isa = PBXBuildFile; fileRef = B0A6A761A1022A586416C5D2FFD784FC /* RCBackend.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 080B618ABD58BA804F310D1738069665 /* TransactionsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 114910BCF10B7F8073A5D6E9A0393223 /* TransactionsFactory.swift */; }; + 08DA5DC6F3DEA5F41B46B4D9A8503576 /* IntroEligibilityCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF25A9E7FA44BFD4EC6B13767F8FFACD /* IntroEligibilityCalculator.swift */; }; + 0D0879E27EC40A0C11E5BAA90DDE4057 /* RCOfferingsFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = D9CF792C28585574974AD4308217108D /* RCOfferingsFactory.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 0D93143BB57C99571484EB3E85AB46AF /* RCReceiptFetcher.h in Headers */ = {isa = PBXBuildFile; fileRef = C36A53CB66891C484755C46C4C3E046E /* RCReceiptFetcher.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 0EA5CA5E695DD29759048E3050557004 /* RCOffering.h in Headers */ = {isa = PBXBuildFile; fileRef = B0B29788AB1EA25837D8E3DB2B49FC65 /* RCOffering.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 13CA36D487C0BC06ADFE309D4D37252D /* RCBackend.m in Sources */ = {isa = PBXBuildFile; fileRef = 10EB77477F1E2D60E0239CEDCCE32C8B /* RCBackend.m */; }; + 13CAA41D53F756C71703E697A65CD165 /* RCDeviceCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D4A311AEAB4058B2CEAD160E98CFD01 /* RCDeviceCache.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 14B1925DABCB3E22E64078867A9341AD /* NSError+RCExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = ECBAC03BAC58431A9F190F96A904049C /* NSError+RCExtensions.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 152C78F28E05148F25344309D5D8BC19 /* RCSystemInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = BE9841AA391181AB8B3B92CA23E2C671 /* RCSystemInfo.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 195024D5BD9D0B94DA711E4EE21F6E14 /* RCSubscriberAttributesManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 5FA1F6A03FF84FEA75AAC5660A33A499 /* RCSubscriberAttributesManager.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 1D4EDC4A4AE28D148570C0C47860E193 /* RCIntroEligibility+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = BE502930B1B7329B22A0DAB16E98C667 /* RCIntroEligibility+Protected.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 1F8426DB406567E4CDAC3B8AC97458B5 /* RCDateProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 124A50234EBFD5715CFC628684457F8B /* RCDateProvider.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 25CEF2FAC4E39E0E65417CE47C827E20 /* RCPurchasesErrors.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F4B2817C5F791D0FE79EC98CF2127EC /* RCPurchasesErrors.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 25FBF59814A4D17212888706FF692D40 /* RCHTTPRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 361B923A02B326398D77965EF3B3EB82 /* RCHTTPRequest.m */; }; + 268C923D5753A23AC098C97B7CFBBE5F /* RCAttributionData.m in Sources */ = {isa = PBXBuildFile; fileRef = 46B5707CBAA2A5605C84CEE0093F7FD1 /* RCAttributionData.m */; }; + 270AD49631F9FFECB3141C060983A2B9 /* NSDictionary+RCExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A80CF445038C04D5ED8C595DEE245F1 /* NSDictionary+RCExtensions.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 2737E46BE7534441D3F4C9109F319A09 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7C7CDA4363CBEC03E06A56F9FD1A9E3A /* Foundation.framework */; }; + 298207D63E4FE3C8A5AFDCE6FFAB967A /* RCIntroEligibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 49C559C85BE98795A63EB9D2B8645817 /* RCIntroEligibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2A56178D9AE4CA3D9A344827807E7124 /* ASN1ContainerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4C794EE6592B00A40C018046C38A5B8 /* ASN1ContainerBuilder.swift */; }; + 323CC37A59200E1139B68DC6F800A760 /* RCProductInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = D6818D2A4E2018FE4C31FCA0C8E233E4 /* RCProductInfo.m */; }; + 335945CDC95AAC3776B77531F1FC6EB3 /* Pods-calm-mind-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 6FE2E828245B552334F9F54A18F829DF /* Pods-calm-mind-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 33B863578764A476F05F49FBA686F7A6 /* RCStoreKitRequestFetcher.h in Headers */ = {isa = PBXBuildFile; fileRef = 37AA770232C5D89C4BDFAEF9E0DD924C /* RCStoreKitRequestFetcher.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 37A839D754E695E553DF46FAB66D3354 /* RCSubscriberAttribute.m in Sources */ = {isa = PBXBuildFile; fileRef = C748A76A4F0C79AEE1C5F9612D2A9956 /* RCSubscriberAttribute.m */; }; + 37E05321FF1723AC6DFBFEC30D8BDE67 /* RCInMemoryCachedObject+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = CCEE8055B624818B24F36BE8DC4CFCD1 /* RCInMemoryCachedObject+Protected.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 3899A105EEED58439560FCA4B0C43701 /* RCStoreKitWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 91E49EB50DABB9B321C38F5BBB45B2D9 /* RCStoreKitWrapper.m */; }; + 397093C3046EBFE4036A8D6E4C25278F /* Pods-calm-mind-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = A45955A573A5BED016455CF3CE612B77 /* Pods-calm-mind-dummy.m */; }; + 3A2FCCF03349542E73EF906625CF2339 /* InAppPurchase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7394DB39CF5B2EB6009AEA6D3571E3F3 /* InAppPurchase.swift */; }; + 3DFCA7264A1CEC49E99113565F04EA84 /* NSData+RCExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = C161B11FD1D750BD5A9D74A48BC8282A /* NSData+RCExtensions.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 3E2C1D4612C2DA8886CEF2FA4903AE93 /* RCEntitlementInfos.h in Headers */ = {isa = PBXBuildFile; fileRef = 7D825FAC3BCB363EEB74F5ACF13B84E3 /* RCEntitlementInfos.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3F682A9EEF9225584D975F0725D2639A /* NSData+RCExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = E0CE1B20D4C076BC50BE5B23DE3A7B77 /* NSData+RCExtensions.m */; }; + 40C86BE73AC9784C373B522E45C6FE8B /* RCOfferings.h in Headers */ = {isa = PBXBuildFile; fileRef = 48DEB902CA1616E7B8C596DBF11E97D5 /* RCOfferings.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 40C895B65A73DE0B1DD2568196255B52 /* RCHTTPRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 6FF1B6291AE7422ABC5C5D08DBD48512 /* RCHTTPRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4278DE079796BE3122519B09535A85BD /* RCEntitlementInfo+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = CA4D6144AACAA1226E6154C42A96C851 /* RCEntitlementInfo+Protected.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4283E4027EF567DAC64F3A445DEC7990 /* RCSubscriberAttribute.h in Headers */ = {isa = PBXBuildFile; fileRef = 555B5AD5FE59DDC25B8D9392E2C447C9 /* RCSubscriberAttribute.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 428F24D52D4333BF9D4E70CC310900A7 /* RCEntitlementInfos+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = 7D24A9C1A350D099BD5A9BD42C74BDD2 /* RCEntitlementInfos+Protected.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 452BE9C5120CA543ECE0313F9C6FDFEE /* RCAttributionNetwork.h in Headers */ = {isa = PBXBuildFile; fileRef = 9EEBD93F0C41F78359E23EDA302F2154 /* RCAttributionNetwork.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 459E0A9E40A0591B346A83402FFF4328 /* RCTransaction.h in Headers */ = {isa = PBXBuildFile; fileRef = 4C2A429ECB9957E19C8CCE61939287BB /* RCTransaction.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 45AA3B9447875657972E31005E2A227C /* RCPurchases.h in Headers */ = {isa = PBXBuildFile; fileRef = 582BC2C0929CBBDDFE1248F40DA3C6C6 /* RCPurchases.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 46F3AE65F1B549D5F211B2459AD5E549 /* RCHTTPClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 1B1FDCE4ED0ECB32004BBEE12BA098D8 /* RCHTTPClient.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 46FE7DD5DA778F124DF8465BEFD7CFF4 /* RCOffering.m in Sources */ = {isa = PBXBuildFile; fileRef = 555CB795614C8C1A21512571D4DDE054 /* RCOffering.m */; }; + 4D60B35A9692461A83C67FDC444FEBB5 /* RCPurchasesErrorUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C0E1A6A563103DDE5AB8D0F7534FFEE /* RCPurchasesErrorUtils.m */; }; + 4E318D0E488A7CD6F340A59483E11FA7 /* NSLocale+RCExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 76110519B9CDB08F38CA55FD52FCB360 /* NSLocale+RCExtensions.m */; }; + 508B969E293E9D530C35D452ECC7F2C7 /* AppleReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF77AFF86606093AF1F7E11773246218 /* AppleReceipt.swift */; }; + 5215CCABC9B7AFACB1C0B93B0A2AD672 /* RCEntitlementInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = FEC0E4283339CE2ED89CD95A9D51AA22 /* RCEntitlementInfo.m */; }; + 52E78F29B7B6E85D600B1041342109DB /* RCStoreKitWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = ECA69D894E55BFBF0FBEEBC436DA94D6 /* RCStoreKitWrapper.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 54BA9227A6734B2E20217E646C93A5B0 /* RCPurchases+SubscriberAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = 70864B89729AF520A10C907EFF84CD09 /* RCPurchases+SubscriberAttributes.m */; }; + 55FC26FB3398D6D794E287AEBA5F9BC4 /* ASN1ObjectIdentifierBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = C39202D54CEED9CDE54EF82F0FAB78B9 /* ASN1ObjectIdentifierBuilder.swift */; }; + 5AFB6B9138598A7ABC3BAF9A2A4903BB /* RCOfferings+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = 9FE91D88F08BC0028FF70FFC1BD6CE5C /* RCOfferings+Protected.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 5B10E365017D8E54A7172155139E7E32 /* PurchasesCoreSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B51404464C4EB6D4C594037AEB68841 /* PurchasesCoreSwift.framework */; }; + 5C4055F7B4015328AF63A134C16D79C8 /* RCStoreKitRequestFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = DB17BA34F6E64BA30BEE9C759507FBCC /* RCStoreKitRequestFetcher.m */; }; + 5CEDA03C5DAFC9D0975D9CF6562087A0 /* RCEntitlementInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = F376F8D502696D1F0A3B8D939C5B9F9F /* RCEntitlementInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5F3FC37844F858D7EF4AB3E61576140B /* RCPurchases+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = 52A83F1E964B29B798FF233551EBD489 /* RCPurchases+Protected.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 64AF544E5B5329574EF5037E46D5C7FA /* RCLogUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 515BF29A3F0AE7785CF4EA4FEEA058ED /* RCLogUtils.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 64DDCDEBE4C7663DE788A835067C1923 /* RCPurchases.m in Sources */ = {isa = PBXBuildFile; fileRef = 72FFED19270862243CE1F39953F3676F /* RCPurchases.m */; }; + 65028C2B3E7B787B704C42606E736371 /* UInt8+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A053C4FCA4F5CF5F08EBCB564E90B5B9 /* UInt8+Extensions.swift */; }; + 66592755E68603B4F9762B111EDA3526 /* RCDateProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E2689EE18445FF7384E7373AFC589A4 /* RCDateProvider.m */; }; + 6750743F1C6783FAC8E4161CD57F315B /* NSLocale+RCExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F7859E2965542F60D5B0B70D58A5095 /* NSLocale+RCExtensions.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 67EBC24E08D6DC0D3A53FE5AF7F6AE6F /* RCOfferingsFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = B042D1EB2F66D7986DAAAAA359896E1B /* RCOfferingsFactory.m */; }; + 686EF1EE35E0D02AE6FD9751FB3C3839 /* OperationDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85B6AD8CB509157DE4DA8C3DAEF15D29 /* OperationDispatcher.swift */; }; + 688C879CAB8FA63547436A5FC9218554 /* InAppPurchaseBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = C838FD0A18FE48AE8D1237C6A8F24FCB /* InAppPurchaseBuilder.swift */; }; + 69010EF98D54267770E13579A1D5EFC3 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E6D36D415ED94FECA27A8C1123507DB /* Strings.swift */; }; + 69321C7A5D5F3E23833EABBC41150392 /* ASN1ObjectIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311EDFA3232595897C0F1D363AD5A96B /* ASN1ObjectIdentifier.swift */; }; + 6A4BEC3FEA72DC3C0B91509A850533DA /* RCAttributionFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 95B6AE2B327C0B11B31D442A6F01B496 /* RCAttributionFetcher.m */; }; + 6C570D0F32E71D4AFBC834293A683169 /* RCProductInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = CAE433AB2A04ACCE7835951FBDCADCE3 /* RCProductInfo.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 6C9B5A0CE8BF3253E20229EDAFDAC12A /* RCHTTPClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 042476F9492D1C094E5130F52DF6E78E /* RCHTTPClient.m */; }; + 71861AAA6F7FAC798B789F4C4837A51C /* RCReceiptFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 7204ED0655F389582BA8E59DCB19190B /* RCReceiptFetcher.m */; }; + 733B1A205CCD9F93D4D6A2930E912D97 /* RCPurchases+SubscriberAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = 73DDE5281E0C43DD02C519D43263C1DE /* RCPurchases+SubscriberAttributes.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 79EB563DC883ABF028727C110F14F46C /* PurchasesCoreSwift-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 56D899CB8D73C3CFBC5EA0C6294891A2 /* PurchasesCoreSwift-dummy.m */; }; + 7AFA33F27E3FE5278AA577AABCB58726 /* RCPromotionalOffer.m in Sources */ = {isa = PBXBuildFile; fileRef = 05594E329B395E495E76774FF3069B1D /* RCPromotionalOffer.m */; }; + 7D787FBC2622A66C158F64C52978BCF1 /* PurchasesCoreSwift-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E485310A76B571F082279DB13567531 /* PurchasesCoreSwift-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 817AC795B4736545B1C77CCDFD5F6A81 /* ReceiptParsingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 428F420B70D82F82760CC4B95DF17CAD /* ReceiptParsingError.swift */; }; + 8217E352CAAE9AD355D9CD76EF76C822 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BE1000410AABAC8444B7B26CD6F6BDB0 /* StoreKit.framework */; }; + 8451B2FD631DCD15286F36DB5C964B0A /* RCSpecialSubscriberAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = 333E0056461D64D02DCD2CD8743B88F5 /* RCSpecialSubscriberAttributes.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 84E4D2CB4BAF15436064DD440F86A43E /* ProductsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E92C9EE4CF219E24F8A3A9536E5EAF2 /* ProductsManager.swift */; }; + 88EA2ED684283EB2D9B350748CC1BA74 /* RCReceiptRefreshPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = 63FDA277300CB36F21E910CC7B4B8133 /* RCReceiptRefreshPolicy.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 89682F97BA5CCB7AF043549CD4BD2789 /* ReceiptParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3123EB27C9638F33576412BF2CDE500 /* ReceiptParser.swift */; }; + 89AB6DBE4DF5C18347827AEC4899A780 /* RCInMemoryCachedObject.m in Sources */ = {isa = PBXBuildFile; fileRef = AA8436C1F141D89154A722EFDE05DB35 /* RCInMemoryCachedObject.m */; }; + 89D0FF1B40D5E981514FE669C8A8643F /* NSError+RCExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 125FD6C3511778DCDC980EA15CB92ED0 /* NSError+RCExtensions.m */; }; + 8D57263836FD33060ACBAD9F14083C9E /* DateExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58EFCD12F97032C865F401FE49D18175 /* DateExtensions.swift */; }; + 9966D70E0E9E00D82605AD97F4086716 /* RCPackage+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = F3BEB273020E4FA1D6AF1263EA04AD3E /* RCPackage+Protected.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 996F33FFFE67324A69AA5CBCA761D014 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7C7CDA4363CBEC03E06A56F9FD1A9E3A /* Foundation.framework */; }; + 9E86B1F2450339BCCEB67DB8F36EE434 /* NSDate+RCExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 122CCD37D70A959FD7C6ADC4BF3E7C75 /* NSDate+RCExtensions.m */; }; + A1FB2E1D613974FB63ABEF649DA49457 /* RCIntroEligibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 98B6790723217DCAF7A28EEE80C662B9 /* RCIntroEligibility.m */; }; + A678028AF0BFEE6861252AC39F94513B /* RCAttributionFetcher+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = 16B2C3EDE5B57E2CCE12B391F729E549 /* RCAttributionFetcher+Protected.h */; settings = {ATTRIBUTES = (Project, ); }; }; + B0B995B8CA9C6C5574AF99B67A35A661 /* RCHTTPStatusCodes.h in Headers */ = {isa = PBXBuildFile; fileRef = 11890ACA10FC7E794DB73A8CBBB8BF12 /* RCHTTPStatusCodes.h */; settings = {ATTRIBUTES = (Project, ); }; }; + B0BE61DE96E7DEAA2C0E205A9405373E /* RCAttributionFetcher.h in Headers */ = {isa = PBXBuildFile; fileRef = 1D4AC0716265C371E1211F1ACB431E80 /* RCAttributionFetcher.h */; settings = {ATTRIBUTES = (Project, ); }; }; + B20598774B6B58A3852514196429AD83 /* RCSystemInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = E8DBF669839138836F3A0C6FCFF2229C /* RCSystemInfo.m */; }; + B3D2DBB77672221B4126DCB8C11821D7 /* Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 733D005D38DF2017B18A8D090CAAD63F /* Transaction.swift */; }; + B9E2CE14F784493FB6B5AB8B6CD2749D /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BE1000410AABAC8444B7B26CD6F6BDB0 /* StoreKit.framework */; }; + BB7167CA498C8FE4C1CA3AF31F9744E2 /* ArraySlice_UInt8+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E426521AD968BA19443CC3EA3AD72A71 /* ArraySlice_UInt8+Extensions.swift */; }; + BC3809E415A4C1CF361130ACB7F3C145 /* RCPromotionalOffer.h in Headers */ = {isa = PBXBuildFile; fileRef = 37CD2284ED39FF6B6DFA67238C16131A /* RCPromotionalOffer.h */; settings = {ATTRIBUTES = (Project, ); }; }; + BE1799BE116B4C087916E28AB71F081B /* AppleReceiptBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E9965A8AB4C947148376228F54644A /* AppleReceiptBuilder.swift */; }; + BF6E0C4C5186DA1B2955634AD84E40BC /* NSDictionary+RCExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 839CA53622CA80F5B21F486CAD6D1520 /* NSDictionary+RCExtensions.m */; }; + BFD2CBC47F4B91B6DF0904EE3ECCF054 /* RCLogUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF5FCF84BA66FA266578CD949C6269F /* RCLogUtils.m */; }; + C024F901878341E0D74C3B796FBD5506 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7C7CDA4363CBEC03E06A56F9FD1A9E3A /* Foundation.framework */; }; + C11E0DAB9E71DA372242FB956E9AC209 /* RCPurchasesErrorUtils+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = A80A02A31E5CED7391D8CF2F296DF889 /* RCPurchasesErrorUtils+Protected.h */; settings = {ATTRIBUTES = (Project, ); }; }; + C1FEDDC5A620BD30471F5021411AB54F /* RCDeviceCache+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = 69C49351E43EE9FE7A932C3FCC0C3928 /* RCDeviceCache+Protected.h */; settings = {ATTRIBUTES = (Project, ); }; }; + C23235DB8FFB65AB7662D13B07F1B016 /* RCPurchasesErrorUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 9054DA2B11B1187DE2C99A18ACBB9C3B /* RCPurchasesErrorUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C32AE550265BE3734DE37379C92AB172 /* RCOffering+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = 7BA90C035390215B55F083EF6C840543 /* RCOffering+Protected.h */; settings = {ATTRIBUTES = (Project, ); }; }; + C5A1ACFBBDC55643594562B2DBB98273 /* RCPurchaserInfo+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = 659E996DFDE3DFC83E7D74C6093663AA /* RCPurchaserInfo+Protected.h */; settings = {ATTRIBUTES = (Project, ); }; }; + C5D14153A9A27A52EF745D23AC05A224 /* Purchases.h in Headers */ = {isa = PBXBuildFile; fileRef = FD26BBAE872A26DB38C2EAE001F5E5CE /* Purchases.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C7FD55F097E9884ABBB6CC54782769D1 /* ISO3601DateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C526273982E86AFBDBBDA8697EF8467E /* ISO3601DateFormatter.swift */; }; + C8678F96B402A67762933E6F7BD261F3 /* RCSubscriberAttribute+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = D72540A38557949101A58492389AA57F /* RCSubscriberAttribute+Protected.h */; settings = {ATTRIBUTES = (Project, ); }; }; + C8F7B485A358F76FD93036187080D3D4 /* AttributionStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC67BC7F7282D8655B55A4F3481A21A0 /* AttributionStrings.swift */; }; + CD7EF1412B7FC27E8F43588291EC1A03 /* RCInMemoryCachedObject.h in Headers */ = {isa = PBXBuildFile; fileRef = E8CB868C95DABC416B724CAAF864465D /* RCInMemoryCachedObject.h */; settings = {ATTRIBUTES = (Project, ); }; }; + CFC6D608887CDC187FFCD82C17BF6C98 /* RCISOPeriodFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 760FB05E050EA4677F4B73AEBEFE612A /* RCISOPeriodFormatter.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D60BF519F5703F1638570F7DBE1F7850 /* Purchases-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 51A1650DA24673A3A9727E0513CD4D89 /* Purchases-dummy.m */; }; + D99983BF93297FA586A0171683CCB407 /* RCProductInfoExtractor.h in Headers */ = {isa = PBXBuildFile; fileRef = 30A05696E1D0ABCC40278056A9956AE2 /* RCProductInfoExtractor.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D9D26CCD5543A430129CE008A01E5ECA /* RCProductInfoExtractor.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DDD384F0BD68E4D4E763AD49D4EF280 /* RCProductInfoExtractor.m */; }; + DE93F9CB669648DDD8A707EB79ACE95B /* RCAttributionData.h in Headers */ = {isa = PBXBuildFile; fileRef = B21B0F45421CC76D06121EF87F55332F /* RCAttributionData.h */; settings = {ATTRIBUTES = (Project, ); }; }; + E04092BB9BC901014BB66DF9EC50F2DF /* RCCrossPlatformSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = F0BADA16264EF9114E22FCBD946C1C56 /* RCCrossPlatformSupport.h */; settings = {ATTRIBUTES = (Project, ); }; }; + E184D53C48FE168C9977624473676A1E /* RCIdentityManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 67427849AEE43264D4F64CBCAED6915E /* RCIdentityManager.h */; settings = {ATTRIBUTES = (Project, ); }; }; + E8E9D76A9EA04553588F12A46EC5CC3E /* RCPackage.m in Sources */ = {isa = PBXBuildFile; fileRef = 87CCC165573958BD15E27C44DA71C49B /* RCPackage.m */; }; + EA8F11A20AFA442A54285C79A822E78D /* RCISOPeriodFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = C2614685CA876F5AA972FB72A0DBCED5 /* RCISOPeriodFormatter.m */; }; + EC3328778FFE688561C8176F182EBCBE /* RCOfferings.m in Sources */ = {isa = PBXBuildFile; fileRef = 952EB5DDF4708544AD1FDE79482C3E20 /* RCOfferings.m */; }; + ED29727A66C6064B17613A2153B1C450 /* NSDate+RCExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = DAACD9EE505D16DAD65EF789D1AA40C9 /* NSDate+RCExtensions.h */; settings = {ATTRIBUTES = (Project, ); }; }; + EFD468C47E81A1D3E9EE03F907EC724B /* RCPromotionalOffer+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = E41B8D9D4F09B258105451FD3FB87222 /* RCPromotionalOffer+Protected.h */; settings = {ATTRIBUTES = (Project, ); }; }; + F09515456D553C816B5A87A53EAA1FCE /* RCIdentityManager.m in Sources */ = {isa = PBXBuildFile; fileRef = D2E9326261BC43C2E3CA956EC92ACC41 /* RCIdentityManager.m */; }; + F1134D60B6B50216BD1D284AF30F8F61 /* Purchases-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D0B09DAC8910E8F46B124066C156A0B /* Purchases-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F3995BBD875D1474CA1A0F57E4ED4B42 /* RCSubscriberAttributesManager.m in Sources */ = {isa = PBXBuildFile; fileRef = FA39B7E1B1F6EA2534C96EAC11EC5714 /* RCSubscriberAttributesManager.m */; }; + F544AB89C05AAC723FC1089709E7377F /* RCEntitlementInfos.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CBF4ED12EB9A25365F994D55ACCDBE1 /* RCEntitlementInfos.m */; }; + F792580943AF69C52023DE9060E9EEC4 /* ASN1Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF9A37949EF51C553DFF415DCA9825B4 /* ASN1Container.swift */; }; + FBECA83B8E280FDB6DAFCE9048B8550F /* RCPurchaserInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 416018C6B3F7A403074FD293FF0C4BB8 /* RCPurchaserInfo.m */; }; + FCF3A8A035E869F33B56468E097C3FED /* RCPurchaserInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 749AF730CE213FF10A861F95A657834A /* RCPurchaserInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FE02197675E297AEEDB249FB33CBBD6A /* RCDeviceCache.m in Sources */ = {isa = PBXBuildFile; fileRef = A04AAA9428B695A2E42579BCE1DE6BEE /* RCDeviceCache.m */; }; + FE889AE379F62C9ED547A83C42A2A9AE /* ProductsRequestFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644D1F4D40113420225A5F981BB1D8D7 /* ProductsRequestFactory.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 740944A13D88C81B42BD9BFA75034603 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 19EF5509BA60F0CD4916B9F24A2F7886; + remoteInfo = Purchases; + }; + 9E44F9EE268BAB5681A411DAC8AF5513 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 2BC2CC6A5EB8F91429DC14E6BDC342DF; + remoteInfo = PurchasesCoreSwift; + }; + FCC298E20725D272E532AF5F65CBC491 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 2BC2CC6A5EB8F91429DC14E6BDC342DF; + remoteInfo = PurchasesCoreSwift; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 042476F9492D1C094E5130F52DF6E78E /* RCHTTPClient.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCHTTPClient.m; path = Purchases/Networking/RCHTTPClient.m; sourceTree = ""; }; + 05594E329B395E495E76774FF3069B1D /* RCPromotionalOffer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCPromotionalOffer.m; path = Purchases/Purchasing/RCPromotionalOffer.m; sourceTree = ""; }; + 087225F362E5FB6A81CC8F5C4BFD86A2 /* Purchases.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Purchases.release.xcconfig; sourceTree = ""; }; + 0C0E1A6A563103DDE5AB8D0F7534FFEE /* RCPurchasesErrorUtils.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCPurchasesErrorUtils.m; path = Purchases/Public/RCPurchasesErrorUtils.m; sourceTree = ""; }; + 0CF5FCF84BA66FA266578CD949C6269F /* RCLogUtils.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCLogUtils.m; path = Purchases/Misc/RCLogUtils.m; sourceTree = ""; }; + 0D0B09DAC8910E8F46B124066C156A0B /* Purchases-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Purchases-umbrella.h"; sourceTree = ""; }; + 0E485310A76B571F082279DB13567531 /* PurchasesCoreSwift-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "PurchasesCoreSwift-umbrella.h"; sourceTree = ""; }; + 10EB77477F1E2D60E0239CEDCCE32C8B /* RCBackend.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCBackend.m; path = Purchases/Networking/RCBackend.m; sourceTree = ""; }; + 114910BCF10B7F8073A5D6E9A0393223 /* TransactionsFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TransactionsFactory.swift; path = PurchasesCoreSwift/Purchasing/TransactionsFactory.swift; sourceTree = ""; }; + 11890ACA10FC7E794DB73A8CBBB8BF12 /* RCHTTPStatusCodes.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCHTTPStatusCodes.h; path = Purchases/Networking/RCHTTPStatusCodes.h; sourceTree = ""; }; + 122CCD37D70A959FD7C6ADC4BF3E7C75 /* NSDate+RCExtensions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSDate+RCExtensions.m"; path = "Purchases/FoundationExtensions/NSDate+RCExtensions.m"; sourceTree = ""; }; + 124A50234EBFD5715CFC628684457F8B /* RCDateProvider.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCDateProvider.h; path = Purchases/Misc/RCDateProvider.h; sourceTree = ""; }; + 125FD6C3511778DCDC980EA15CB92ED0 /* NSError+RCExtensions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSError+RCExtensions.m"; path = "Purchases/FoundationExtensions/NSError+RCExtensions.m"; sourceTree = ""; }; + 16B2C3EDE5B57E2CCE12B391F729E549 /* RCAttributionFetcher+Protected.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "RCAttributionFetcher+Protected.h"; path = "Purchases/ProtectedExtensions/RCAttributionFetcher+Protected.h"; sourceTree = ""; }; + 173403D9233DC83DD4A2D8EEED0880BF /* Purchases.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Purchases.debug.xcconfig; sourceTree = ""; }; + 1B1FDCE4ED0ECB32004BBEE12BA098D8 /* RCHTTPClient.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCHTTPClient.h; path = Purchases/Networking/RCHTTPClient.h; sourceTree = ""; }; + 1D4AC0716265C371E1211F1ACB431E80 /* RCAttributionFetcher.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCAttributionFetcher.h; path = Purchases/Purchasing/RCAttributionFetcher.h; sourceTree = ""; }; + 1F4B2817C5F791D0FE79EC98CF2127EC /* RCPurchasesErrors.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCPurchasesErrors.h; path = Purchases/Public/RCPurchasesErrors.h; sourceTree = ""; }; + 2E6D36D415ED94FECA27A8C1123507DB /* Strings.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Strings.swift; path = PurchasesCoreSwift/Logging/Strings/Strings.swift; sourceTree = ""; }; + 30A05696E1D0ABCC40278056A9956AE2 /* RCProductInfoExtractor.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCProductInfoExtractor.h; path = Purchases/Purchasing/RCProductInfoExtractor.h; sourceTree = ""; }; + 311EDFA3232595897C0F1D363AD5A96B /* ASN1ObjectIdentifier.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ASN1ObjectIdentifier.swift; path = PurchasesCoreSwift/LocalReceiptParsing/BasicTypes/ASN1ObjectIdentifier.swift; sourceTree = ""; }; + 333E0056461D64D02DCD2CD8743B88F5 /* RCSpecialSubscriberAttributes.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCSpecialSubscriberAttributes.h; path = Purchases/SubscriberAttributes/RCSpecialSubscriberAttributes.h; sourceTree = ""; }; + 361B923A02B326398D77965EF3B3EB82 /* RCHTTPRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCHTTPRequest.m; path = Purchases/Networking/RCHTTPRequest.m; sourceTree = ""; }; + 37AA770232C5D89C4BDFAEF9E0DD924C /* RCStoreKitRequestFetcher.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCStoreKitRequestFetcher.h; path = Purchases/Purchasing/RCStoreKitRequestFetcher.h; sourceTree = ""; }; + 37CD2284ED39FF6B6DFA67238C16131A /* RCPromotionalOffer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCPromotionalOffer.h; path = Purchases/Purchasing/RCPromotionalOffer.h; sourceTree = ""; }; + 3DDD384F0BD68E4D4E763AD49D4EF280 /* RCProductInfoExtractor.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCProductInfoExtractor.m; path = Purchases/Purchasing/RCProductInfoExtractor.m; sourceTree = ""; }; + 416018C6B3F7A403074FD293FF0C4BB8 /* RCPurchaserInfo.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCPurchaserInfo.m; path = Purchases/Public/RCPurchaserInfo.m; sourceTree = ""; }; + 428F420B70D82F82760CC4B95DF17CAD /* ReceiptParsingError.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ReceiptParsingError.swift; path = PurchasesCoreSwift/LocalReceiptParsing/ReceiptParsingError.swift; sourceTree = ""; }; + 43EA8BB6508F44B39158007B6FBEEAE8 /* Pods-calm-mind-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-calm-mind-acknowledgements.markdown"; sourceTree = ""; }; + 46B5707CBAA2A5605C84CEE0093F7FD1 /* RCAttributionData.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCAttributionData.m; path = Purchases/Purchasing/RCAttributionData.m; sourceTree = ""; }; + 48DEB902CA1616E7B8C596DBF11E97D5 /* RCOfferings.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCOfferings.h; path = Purchases/Public/RCOfferings.h; sourceTree = ""; }; + 49C559C85BE98795A63EB9D2B8645817 /* RCIntroEligibility.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCIntroEligibility.h; path = Purchases/Public/RCIntroEligibility.h; sourceTree = ""; }; + 4A80CF445038C04D5ED8C595DEE245F1 /* NSDictionary+RCExtensions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSDictionary+RCExtensions.h"; path = "Purchases/FoundationExtensions/NSDictionary+RCExtensions.h"; sourceTree = ""; }; + 4C2A429ECB9957E19C8CCE61939287BB /* RCTransaction.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCTransaction.h; path = Purchases/Public/RCTransaction.h; sourceTree = ""; }; + 4D4A311AEAB4058B2CEAD160E98CFD01 /* RCDeviceCache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCDeviceCache.h; path = Purchases/Caching/RCDeviceCache.h; sourceTree = ""; }; + 4DC908152C7FF585FD72F10305133610 /* Pods-calm-mind-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-calm-mind-frameworks.sh"; sourceTree = ""; }; + 5153D49110BC9C6B6321BF255DDDFD03 /* Pods-calm-mind-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-calm-mind-acknowledgements.plist"; sourceTree = ""; }; + 515BF29A3F0AE7785CF4EA4FEEA058ED /* RCLogUtils.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCLogUtils.h; path = Purchases/Misc/RCLogUtils.h; sourceTree = ""; }; + 51A1650DA24673A3A9727E0513CD4D89 /* Purchases-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Purchases-dummy.m"; sourceTree = ""; }; + 52A83F1E964B29B798FF233551EBD489 /* RCPurchases+Protected.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "RCPurchases+Protected.h"; path = "Purchases/ProtectedExtensions/RCPurchases+Protected.h"; sourceTree = ""; }; + 555B5AD5FE59DDC25B8D9392E2C447C9 /* RCSubscriberAttribute.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCSubscriberAttribute.h; path = Purchases/SubscriberAttributes/RCSubscriberAttribute.h; sourceTree = ""; }; + 555CB795614C8C1A21512571D4DDE054 /* RCOffering.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCOffering.m; path = Purchases/Public/RCOffering.m; sourceTree = ""; }; + 56D899CB8D73C3CFBC5EA0C6294891A2 /* PurchasesCoreSwift-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "PurchasesCoreSwift-dummy.m"; sourceTree = ""; }; + 582BC2C0929CBBDDFE1248F40DA3C6C6 /* RCPurchases.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCPurchases.h; path = Purchases/Public/RCPurchases.h; sourceTree = ""; }; + 58EFCD12F97032C865F401FE49D18175 /* DateExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DateExtensions.swift; path = PurchasesCoreSwift/Misc/DateExtensions.swift; sourceTree = ""; }; + 5A1F7866BEEF2AF2C8D2A88FD7944481 /* Purchases-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Purchases-Info.plist"; sourceTree = ""; }; + 5EB42D7C1DE3D1C019814488DC99F2D0 /* Pods-calm-mind.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-calm-mind.release.xcconfig"; sourceTree = ""; }; + 5FA1F6A03FF84FEA75AAC5660A33A499 /* RCSubscriberAttributesManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCSubscriberAttributesManager.h; path = Purchases/SubscriberAttributes/RCSubscriberAttributesManager.h; sourceTree = ""; }; + 63FDA277300CB36F21E910CC7B4B8133 /* RCReceiptRefreshPolicy.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCReceiptRefreshPolicy.h; path = Purchases/Purchasing/RCReceiptRefreshPolicy.h; sourceTree = ""; }; + 644D1F4D40113420225A5F981BB1D8D7 /* ProductsRequestFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ProductsRequestFactory.swift; path = PurchasesCoreSwift/Purchasing/ProductsRequestFactory.swift; sourceTree = ""; }; + 659E996DFDE3DFC83E7D74C6093663AA /* RCPurchaserInfo+Protected.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "RCPurchaserInfo+Protected.h"; path = "Purchases/ProtectedExtensions/RCPurchaserInfo+Protected.h"; sourceTree = ""; }; + 67427849AEE43264D4F64CBCAED6915E /* RCIdentityManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCIdentityManager.h; path = Purchases/Purchasing/RCIdentityManager.h; sourceTree = ""; }; + 69C49351E43EE9FE7A932C3FCC0C3928 /* RCDeviceCache+Protected.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "RCDeviceCache+Protected.h"; path = "Purchases/ProtectedExtensions/RCDeviceCache+Protected.h"; sourceTree = ""; }; + 6E2689EE18445FF7384E7373AFC589A4 /* RCDateProvider.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCDateProvider.m; path = Purchases/Misc/RCDateProvider.m; sourceTree = ""; }; + 6E92C9EE4CF219E24F8A3A9536E5EAF2 /* ProductsManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ProductsManager.swift; path = PurchasesCoreSwift/Purchasing/ProductsManager.swift; sourceTree = ""; }; + 6FE2E828245B552334F9F54A18F829DF /* Pods-calm-mind-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-calm-mind-umbrella.h"; sourceTree = ""; }; + 6FF1B6291AE7422ABC5C5D08DBD48512 /* RCHTTPRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCHTTPRequest.h; path = Purchases/Networking/RCHTTPRequest.h; sourceTree = ""; }; + 70864B89729AF520A10C907EFF84CD09 /* RCPurchases+SubscriberAttributes.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "RCPurchases+SubscriberAttributes.m"; path = "Purchases/SubscriberAttributes/RCPurchases+SubscriberAttributes.m"; sourceTree = ""; }; + 7204ED0655F389582BA8E59DCB19190B /* RCReceiptFetcher.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCReceiptFetcher.m; path = Purchases/Purchasing/RCReceiptFetcher.m; sourceTree = ""; }; + 72FFED19270862243CE1F39953F3676F /* RCPurchases.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCPurchases.m; path = Purchases/Public/RCPurchases.m; sourceTree = ""; }; + 733D005D38DF2017B18A8D090CAAD63F /* Transaction.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Transaction.swift; path = PurchasesCoreSwift/Transaction.swift; sourceTree = ""; }; + 7394DB39CF5B2EB6009AEA6D3571E3F3 /* InAppPurchase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = InAppPurchase.swift; path = PurchasesCoreSwift/LocalReceiptParsing/BasicTypes/InAppPurchase.swift; sourceTree = ""; }; + 73DDE5281E0C43DD02C519D43263C1DE /* RCPurchases+SubscriberAttributes.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "RCPurchases+SubscriberAttributes.h"; path = "Purchases/SubscriberAttributes/RCPurchases+SubscriberAttributes.h"; sourceTree = ""; }; + 749AF730CE213FF10A861F95A657834A /* RCPurchaserInfo.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCPurchaserInfo.h; path = Purchases/Public/RCPurchaserInfo.h; sourceTree = ""; }; + 760FB05E050EA4677F4B73AEBEFE612A /* RCISOPeriodFormatter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCISOPeriodFormatter.h; path = Purchases/Misc/RCISOPeriodFormatter.h; sourceTree = ""; }; + 76110519B9CDB08F38CA55FD52FCB360 /* NSLocale+RCExtensions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSLocale+RCExtensions.m"; path = "Purchases/FoundationExtensions/NSLocale+RCExtensions.m"; sourceTree = ""; }; + 7B17777921009FC8182549288BF9C4D0 /* PurchasesCoreSwift-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "PurchasesCoreSwift-prefix.pch"; sourceTree = ""; }; + 7B51404464C4EB6D4C594037AEB68841 /* PurchasesCoreSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PurchasesCoreSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7BA90C035390215B55F083EF6C840543 /* RCOffering+Protected.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "RCOffering+Protected.h"; path = "Purchases/ProtectedExtensions/RCOffering+Protected.h"; sourceTree = ""; }; + 7C7CDA4363CBEC03E06A56F9FD1A9E3A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + 7C9001E70BF6D63816D9FCC170A48BE1 /* PurchasesCoreSwift.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = PurchasesCoreSwift.modulemap; sourceTree = ""; }; + 7D24A9C1A350D099BD5A9BD42C74BDD2 /* RCEntitlementInfos+Protected.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "RCEntitlementInfos+Protected.h"; path = "Purchases/ProtectedExtensions/RCEntitlementInfos+Protected.h"; sourceTree = ""; }; + 7D825FAC3BCB363EEB74F5ACF13B84E3 /* RCEntitlementInfos.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCEntitlementInfos.h; path = Purchases/Public/RCEntitlementInfos.h; sourceTree = ""; }; + 839CA53622CA80F5B21F486CAD6D1520 /* NSDictionary+RCExtensions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSDictionary+RCExtensions.m"; path = "Purchases/FoundationExtensions/NSDictionary+RCExtensions.m"; sourceTree = ""; }; + 85B6AD8CB509157DE4DA8C3DAEF15D29 /* OperationDispatcher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = OperationDispatcher.swift; path = PurchasesCoreSwift/Misc/OperationDispatcher.swift; sourceTree = ""; }; + 87CCC165573958BD15E27C44DA71C49B /* RCPackage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCPackage.m; path = Purchases/Public/RCPackage.m; sourceTree = ""; }; + 8B1EC43D035484A9B9F6B0010A518907 /* PurchasesCoreSwift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = PurchasesCoreSwift.debug.xcconfig; sourceTree = ""; }; + 8F7859E2965542F60D5B0B70D58A5095 /* NSLocale+RCExtensions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSLocale+RCExtensions.h"; path = "Purchases/FoundationExtensions/NSLocale+RCExtensions.h"; sourceTree = ""; }; + 9054DA2B11B1187DE2C99A18ACBB9C3B /* RCPurchasesErrorUtils.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCPurchasesErrorUtils.h; path = Purchases/Public/RCPurchasesErrorUtils.h; sourceTree = ""; }; + 91E49EB50DABB9B321C38F5BBB45B2D9 /* RCStoreKitWrapper.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCStoreKitWrapper.m; path = Purchases/Purchasing/RCStoreKitWrapper.m; sourceTree = ""; }; + 952EB5DDF4708544AD1FDE79482C3E20 /* RCOfferings.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCOfferings.m; path = Purchases/Public/RCOfferings.m; sourceTree = ""; }; + 95B6AE2B327C0B11B31D442A6F01B496 /* RCAttributionFetcher.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCAttributionFetcher.m; path = Purchases/Purchasing/RCAttributionFetcher.m; sourceTree = ""; }; + 98B6790723217DCAF7A28EEE80C662B9 /* RCIntroEligibility.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCIntroEligibility.m; path = Purchases/Public/RCIntroEligibility.m; sourceTree = ""; }; + 9CBF4ED12EB9A25365F994D55ACCDBE1 /* RCEntitlementInfos.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCEntitlementInfos.m; path = Purchases/Public/RCEntitlementInfos.m; sourceTree = ""; }; + 9D2E720DEC9EC21251E5887717D984CC /* Purchases-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Purchases-prefix.pch"; sourceTree = ""; }; + 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 9EEBD93F0C41F78359E23EDA302F2154 /* RCAttributionNetwork.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCAttributionNetwork.h; path = Purchases/Public/RCAttributionNetwork.h; sourceTree = ""; }; + 9FE91D88F08BC0028FF70FFC1BD6CE5C /* RCOfferings+Protected.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "RCOfferings+Protected.h"; path = "Purchases/ProtectedExtensions/RCOfferings+Protected.h"; sourceTree = ""; }; + A04AAA9428B695A2E42579BCE1DE6BEE /* RCDeviceCache.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCDeviceCache.m; path = Purchases/Caching/RCDeviceCache.m; sourceTree = ""; }; + A053C4FCA4F5CF5F08EBCB564E90B5B9 /* UInt8+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UInt8+Extensions.swift"; path = "PurchasesCoreSwift/LocalReceiptParsing/DataConverters/UInt8+Extensions.swift"; sourceTree = ""; }; + A45955A573A5BED016455CF3CE612B77 /* Pods-calm-mind-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-calm-mind-dummy.m"; sourceTree = ""; }; + A777D9939AFEC140F0C60BDBFD6A8DD2 /* Pods-calm-mind-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-calm-mind-Info.plist"; sourceTree = ""; }; + A80A02A31E5CED7391D8CF2F296DF889 /* RCPurchasesErrorUtils+Protected.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "RCPurchasesErrorUtils+Protected.h"; path = "Purchases/ProtectedExtensions/RCPurchasesErrorUtils+Protected.h"; sourceTree = ""; }; + A8744BFCBA0EFF3EFCB22EF6E0408B42 /* PurchasesCoreSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = PurchasesCoreSwift.framework; path = PurchasesCoreSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + AA8436C1F141D89154A722EFDE05DB35 /* RCInMemoryCachedObject.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCInMemoryCachedObject.m; path = Purchases/Caching/RCInMemoryCachedObject.m; sourceTree = ""; }; + AF25A9E7FA44BFD4EC6B13767F8FFACD /* IntroEligibilityCalculator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IntroEligibilityCalculator.swift; path = PurchasesCoreSwift/IntroEligibilityCalculator.swift; sourceTree = ""; }; + B042D1EB2F66D7986DAAAAA359896E1B /* RCOfferingsFactory.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCOfferingsFactory.m; path = Purchases/Purchasing/RCOfferingsFactory.m; sourceTree = ""; }; + B0A6A761A1022A586416C5D2FFD784FC /* RCBackend.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCBackend.h; path = Purchases/Networking/RCBackend.h; sourceTree = ""; }; + B0B29788AB1EA25837D8E3DB2B49FC65 /* RCOffering.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCOffering.h; path = Purchases/Public/RCOffering.h; sourceTree = ""; }; + B18B516B38CB919E56F69CEB9F1FFAFA /* RCPackage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCPackage.h; path = Purchases/Public/RCPackage.h; sourceTree = ""; }; + B21B0F45421CC76D06121EF87F55332F /* RCAttributionData.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCAttributionData.h; path = Purchases/Purchasing/RCAttributionData.h; sourceTree = ""; }; + B4C794EE6592B00A40C018046C38A5B8 /* ASN1ContainerBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ASN1ContainerBuilder.swift; path = PurchasesCoreSwift/LocalReceiptParsing/Builders/ASN1ContainerBuilder.swift; sourceTree = ""; }; + B58117D414ADAA84452F2B1CD9F93301 /* PurchasesCoreSwift-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "PurchasesCoreSwift-Info.plist"; sourceTree = ""; }; + BE1000410AABAC8444B7B26CD6F6BDB0 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; }; + BE502930B1B7329B22A0DAB16E98C667 /* RCIntroEligibility+Protected.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "RCIntroEligibility+Protected.h"; path = "Purchases/ProtectedExtensions/RCIntroEligibility+Protected.h"; sourceTree = ""; }; + BE9841AA391181AB8B3B92CA23E2C671 /* RCSystemInfo.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCSystemInfo.h; path = Purchases/Misc/RCSystemInfo.h; sourceTree = ""; }; + C161B11FD1D750BD5A9D74A48BC8282A /* NSData+RCExtensions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSData+RCExtensions.h"; path = "Purchases/FoundationExtensions/NSData+RCExtensions.h"; sourceTree = ""; }; + C2614685CA876F5AA972FB72A0DBCED5 /* RCISOPeriodFormatter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCISOPeriodFormatter.m; path = Purchases/Misc/RCISOPeriodFormatter.m; sourceTree = ""; }; + C2B686595E51771F6F825E2789FFD5AE /* PurchasesCoreSwift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = PurchasesCoreSwift.release.xcconfig; sourceTree = ""; }; + C36A53CB66891C484755C46C4C3E046E /* RCReceiptFetcher.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCReceiptFetcher.h; path = Purchases/Purchasing/RCReceiptFetcher.h; sourceTree = ""; }; + C39202D54CEED9CDE54EF82F0FAB78B9 /* ASN1ObjectIdentifierBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ASN1ObjectIdentifierBuilder.swift; path = PurchasesCoreSwift/LocalReceiptParsing/Builders/ASN1ObjectIdentifierBuilder.swift; sourceTree = ""; }; + C526273982E86AFBDBBDA8697EF8467E /* ISO3601DateFormatter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ISO3601DateFormatter.swift; path = PurchasesCoreSwift/LocalReceiptParsing/DataConverters/ISO3601DateFormatter.swift; sourceTree = ""; }; + C748A76A4F0C79AEE1C5F9612D2A9956 /* RCSubscriberAttribute.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCSubscriberAttribute.m; path = Purchases/SubscriberAttributes/RCSubscriberAttribute.m; sourceTree = ""; }; + C838FD0A18FE48AE8D1237C6A8F24FCB /* InAppPurchaseBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = InAppPurchaseBuilder.swift; path = PurchasesCoreSwift/LocalReceiptParsing/Builders/InAppPurchaseBuilder.swift; sourceTree = ""; }; + CA4D6144AACAA1226E6154C42A96C851 /* RCEntitlementInfo+Protected.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "RCEntitlementInfo+Protected.h"; path = "Purchases/ProtectedExtensions/RCEntitlementInfo+Protected.h"; sourceTree = ""; }; + CAE433AB2A04ACCE7835951FBDCADCE3 /* RCProductInfo.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCProductInfo.h; path = Purchases/Purchasing/RCProductInfo.h; sourceTree = ""; }; + CCEE8055B624818B24F36BE8DC4CFCD1 /* RCInMemoryCachedObject+Protected.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "RCInMemoryCachedObject+Protected.h"; path = "Purchases/ProtectedExtensions/RCInMemoryCachedObject+Protected.h"; sourceTree = ""; }; + D2E9326261BC43C2E3CA956EC92ACC41 /* RCIdentityManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCIdentityManager.m; path = Purchases/Purchasing/RCIdentityManager.m; sourceTree = ""; }; + D3123EB27C9638F33576412BF2CDE500 /* ReceiptParser.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ReceiptParser.swift; path = PurchasesCoreSwift/LocalReceiptParsing/ReceiptParser.swift; sourceTree = ""; }; + D6818D2A4E2018FE4C31FCA0C8E233E4 /* RCProductInfo.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCProductInfo.m; path = Purchases/Purchasing/RCProductInfo.m; sourceTree = ""; }; + D72540A38557949101A58492389AA57F /* RCSubscriberAttribute+Protected.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "RCSubscriberAttribute+Protected.h"; path = "Purchases/ProtectedExtensions/RCSubscriberAttribute+Protected.h"; sourceTree = ""; }; + D9CF792C28585574974AD4308217108D /* RCOfferingsFactory.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCOfferingsFactory.h; path = Purchases/Purchasing/RCOfferingsFactory.h; sourceTree = ""; }; + DAACD9EE505D16DAD65EF789D1AA40C9 /* NSDate+RCExtensions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSDate+RCExtensions.h"; path = "Purchases/FoundationExtensions/NSDate+RCExtensions.h"; sourceTree = ""; }; + DB17BA34F6E64BA30BEE9C759507FBCC /* RCStoreKitRequestFetcher.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCStoreKitRequestFetcher.m; path = Purchases/Purchasing/RCStoreKitRequestFetcher.m; sourceTree = ""; }; + DF77AFF86606093AF1F7E11773246218 /* AppleReceipt.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AppleReceipt.swift; path = PurchasesCoreSwift/LocalReceiptParsing/BasicTypes/AppleReceipt.swift; sourceTree = ""; }; + DF9A37949EF51C553DFF415DCA9825B4 /* ASN1Container.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ASN1Container.swift; path = PurchasesCoreSwift/LocalReceiptParsing/BasicTypes/ASN1Container.swift; sourceTree = ""; }; + E0CE1B20D4C076BC50BE5B23DE3A7B77 /* NSData+RCExtensions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSData+RCExtensions.m"; path = "Purchases/FoundationExtensions/NSData+RCExtensions.m"; sourceTree = ""; }; + E3758240AA9C1AB477C1A5A3379A7AEE /* Pods_calm_mind.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_calm_mind.framework; path = "Pods-calm-mind.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; + E41B8D9D4F09B258105451FD3FB87222 /* RCPromotionalOffer+Protected.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "RCPromotionalOffer+Protected.h"; path = "Purchases/ProtectedExtensions/RCPromotionalOffer+Protected.h"; sourceTree = ""; }; + E426521AD968BA19443CC3EA3AD72A71 /* ArraySlice_UInt8+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ArraySlice_UInt8+Extensions.swift"; path = "PurchasesCoreSwift/LocalReceiptParsing/DataConverters/ArraySlice_UInt8+Extensions.swift"; sourceTree = ""; }; + E8CB868C95DABC416B724CAAF864465D /* RCInMemoryCachedObject.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCInMemoryCachedObject.h; path = Purchases/Caching/RCInMemoryCachedObject.h; sourceTree = ""; }; + E8DBF669839138836F3A0C6FCFF2229C /* RCSystemInfo.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCSystemInfo.m; path = Purchases/Misc/RCSystemInfo.m; sourceTree = ""; }; + E8FE76D998DEF8D11647FFB144B011C7 /* Pods-calm-mind.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-calm-mind.modulemap"; sourceTree = ""; }; + ECA69D894E55BFBF0FBEEBC436DA94D6 /* RCStoreKitWrapper.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCStoreKitWrapper.h; path = Purchases/Purchasing/RCStoreKitWrapper.h; sourceTree = ""; }; + ECBAC03BAC58431A9F190F96A904049C /* NSError+RCExtensions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSError+RCExtensions.h"; path = "Purchases/FoundationExtensions/NSError+RCExtensions.h"; sourceTree = ""; }; + F0BADA16264EF9114E22FCBD946C1C56 /* RCCrossPlatformSupport.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCCrossPlatformSupport.h; path = Purchases/Misc/RCCrossPlatformSupport.h; sourceTree = ""; }; + F376F8D502696D1F0A3B8D939C5B9F9F /* RCEntitlementInfo.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RCEntitlementInfo.h; path = Purchases/Public/RCEntitlementInfo.h; sourceTree = ""; }; + F3BEB273020E4FA1D6AF1263EA04AD3E /* RCPackage+Protected.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "RCPackage+Protected.h"; path = "Purchases/ProtectedExtensions/RCPackage+Protected.h"; sourceTree = ""; }; + F7A588C75347128A4E82225E568D4F1D /* Purchases.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = Purchases.modulemap; sourceTree = ""; }; + F9E9965A8AB4C947148376228F54644A /* AppleReceiptBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AppleReceiptBuilder.swift; path = PurchasesCoreSwift/LocalReceiptParsing/Builders/AppleReceiptBuilder.swift; sourceTree = ""; }; + FA39B7E1B1F6EA2534C96EAC11EC5714 /* RCSubscriberAttributesManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCSubscriberAttributesManager.m; path = Purchases/SubscriberAttributes/RCSubscriberAttributesManager.m; sourceTree = ""; }; + FBA7F9EF3FAEB0BC7D79BB1AFF265A06 /* Purchases.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Purchases.framework; path = Purchases.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FC67BC7F7282D8655B55A4F3481A21A0 /* AttributionStrings.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AttributionStrings.swift; path = PurchasesCoreSwift/Logging/Strings/AttributionStrings.swift; sourceTree = ""; }; + FD26BBAE872A26DB38C2EAE001F5E5CE /* Purchases.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = Purchases.h; path = Purchases/Public/Purchases.h; sourceTree = ""; }; + FD4CC8CD46EB75E1D83BB0B9297466E9 /* Pods-calm-mind.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-calm-mind.debug.xcconfig"; sourceTree = ""; }; + FEC0E4283339CE2ED89CD95A9D51AA22 /* RCEntitlementInfo.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RCEntitlementInfo.m; path = Purchases/Public/RCEntitlementInfo.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8B7BC0C8B05C89E642A2962B832A599B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2737E46BE7534441D3F4C9109F319A09 /* Foundation.framework in Frameworks */, + B9E2CE14F784493FB6B5AB8B6CD2749D /* StoreKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CE2A589D03A024565296CDCE4F55F3F8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 996F33FFFE67324A69AA5CBCA761D014 /* Foundation.framework in Frameworks */, + 5B10E365017D8E54A7172155139E7E32 /* PurchasesCoreSwift.framework in Frameworks */, + 8217E352CAAE9AD355D9CD76EF76C822 /* StoreKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DD7F3A9A3038508C13C10CF9EC20EF1F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C024F901878341E0D74C3B796FBD5506 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 3AF13BB7E34A4E57530CA3208B5A9F98 /* Purchases */ = { + isa = PBXGroup; + children = ( + C161B11FD1D750BD5A9D74A48BC8282A /* NSData+RCExtensions.h */, + E0CE1B20D4C076BC50BE5B23DE3A7B77 /* NSData+RCExtensions.m */, + DAACD9EE505D16DAD65EF789D1AA40C9 /* NSDate+RCExtensions.h */, + 122CCD37D70A959FD7C6ADC4BF3E7C75 /* NSDate+RCExtensions.m */, + 4A80CF445038C04D5ED8C595DEE245F1 /* NSDictionary+RCExtensions.h */, + 839CA53622CA80F5B21F486CAD6D1520 /* NSDictionary+RCExtensions.m */, + ECBAC03BAC58431A9F190F96A904049C /* NSError+RCExtensions.h */, + 125FD6C3511778DCDC980EA15CB92ED0 /* NSError+RCExtensions.m */, + 8F7859E2965542F60D5B0B70D58A5095 /* NSLocale+RCExtensions.h */, + 76110519B9CDB08F38CA55FD52FCB360 /* NSLocale+RCExtensions.m */, + FD26BBAE872A26DB38C2EAE001F5E5CE /* Purchases.h */, + B21B0F45421CC76D06121EF87F55332F /* RCAttributionData.h */, + 46B5707CBAA2A5605C84CEE0093F7FD1 /* RCAttributionData.m */, + 1D4AC0716265C371E1211F1ACB431E80 /* RCAttributionFetcher.h */, + 95B6AE2B327C0B11B31D442A6F01B496 /* RCAttributionFetcher.m */, + 16B2C3EDE5B57E2CCE12B391F729E549 /* RCAttributionFetcher+Protected.h */, + 9EEBD93F0C41F78359E23EDA302F2154 /* RCAttributionNetwork.h */, + B0A6A761A1022A586416C5D2FFD784FC /* RCBackend.h */, + 10EB77477F1E2D60E0239CEDCCE32C8B /* RCBackend.m */, + F0BADA16264EF9114E22FCBD946C1C56 /* RCCrossPlatformSupport.h */, + 124A50234EBFD5715CFC628684457F8B /* RCDateProvider.h */, + 6E2689EE18445FF7384E7373AFC589A4 /* RCDateProvider.m */, + 4D4A311AEAB4058B2CEAD160E98CFD01 /* RCDeviceCache.h */, + A04AAA9428B695A2E42579BCE1DE6BEE /* RCDeviceCache.m */, + 69C49351E43EE9FE7A932C3FCC0C3928 /* RCDeviceCache+Protected.h */, + F376F8D502696D1F0A3B8D939C5B9F9F /* RCEntitlementInfo.h */, + FEC0E4283339CE2ED89CD95A9D51AA22 /* RCEntitlementInfo.m */, + CA4D6144AACAA1226E6154C42A96C851 /* RCEntitlementInfo+Protected.h */, + 7D825FAC3BCB363EEB74F5ACF13B84E3 /* RCEntitlementInfos.h */, + 9CBF4ED12EB9A25365F994D55ACCDBE1 /* RCEntitlementInfos.m */, + 7D24A9C1A350D099BD5A9BD42C74BDD2 /* RCEntitlementInfos+Protected.h */, + 1B1FDCE4ED0ECB32004BBEE12BA098D8 /* RCHTTPClient.h */, + 042476F9492D1C094E5130F52DF6E78E /* RCHTTPClient.m */, + 6FF1B6291AE7422ABC5C5D08DBD48512 /* RCHTTPRequest.h */, + 361B923A02B326398D77965EF3B3EB82 /* RCHTTPRequest.m */, + 11890ACA10FC7E794DB73A8CBBB8BF12 /* RCHTTPStatusCodes.h */, + 67427849AEE43264D4F64CBCAED6915E /* RCIdentityManager.h */, + D2E9326261BC43C2E3CA956EC92ACC41 /* RCIdentityManager.m */, + E8CB868C95DABC416B724CAAF864465D /* RCInMemoryCachedObject.h */, + AA8436C1F141D89154A722EFDE05DB35 /* RCInMemoryCachedObject.m */, + CCEE8055B624818B24F36BE8DC4CFCD1 /* RCInMemoryCachedObject+Protected.h */, + 49C559C85BE98795A63EB9D2B8645817 /* RCIntroEligibility.h */, + 98B6790723217DCAF7A28EEE80C662B9 /* RCIntroEligibility.m */, + BE502930B1B7329B22A0DAB16E98C667 /* RCIntroEligibility+Protected.h */, + 760FB05E050EA4677F4B73AEBEFE612A /* RCISOPeriodFormatter.h */, + C2614685CA876F5AA972FB72A0DBCED5 /* RCISOPeriodFormatter.m */, + 515BF29A3F0AE7785CF4EA4FEEA058ED /* RCLogUtils.h */, + 0CF5FCF84BA66FA266578CD949C6269F /* RCLogUtils.m */, + B0B29788AB1EA25837D8E3DB2B49FC65 /* RCOffering.h */, + 555CB795614C8C1A21512571D4DDE054 /* RCOffering.m */, + 7BA90C035390215B55F083EF6C840543 /* RCOffering+Protected.h */, + 48DEB902CA1616E7B8C596DBF11E97D5 /* RCOfferings.h */, + 952EB5DDF4708544AD1FDE79482C3E20 /* RCOfferings.m */, + 9FE91D88F08BC0028FF70FFC1BD6CE5C /* RCOfferings+Protected.h */, + D9CF792C28585574974AD4308217108D /* RCOfferingsFactory.h */, + B042D1EB2F66D7986DAAAAA359896E1B /* RCOfferingsFactory.m */, + B18B516B38CB919E56F69CEB9F1FFAFA /* RCPackage.h */, + 87CCC165573958BD15E27C44DA71C49B /* RCPackage.m */, + F3BEB273020E4FA1D6AF1263EA04AD3E /* RCPackage+Protected.h */, + CAE433AB2A04ACCE7835951FBDCADCE3 /* RCProductInfo.h */, + D6818D2A4E2018FE4C31FCA0C8E233E4 /* RCProductInfo.m */, + 30A05696E1D0ABCC40278056A9956AE2 /* RCProductInfoExtractor.h */, + 3DDD384F0BD68E4D4E763AD49D4EF280 /* RCProductInfoExtractor.m */, + 37CD2284ED39FF6B6DFA67238C16131A /* RCPromotionalOffer.h */, + 05594E329B395E495E76774FF3069B1D /* RCPromotionalOffer.m */, + E41B8D9D4F09B258105451FD3FB87222 /* RCPromotionalOffer+Protected.h */, + 749AF730CE213FF10A861F95A657834A /* RCPurchaserInfo.h */, + 416018C6B3F7A403074FD293FF0C4BB8 /* RCPurchaserInfo.m */, + 659E996DFDE3DFC83E7D74C6093663AA /* RCPurchaserInfo+Protected.h */, + 582BC2C0929CBBDDFE1248F40DA3C6C6 /* RCPurchases.h */, + 72FFED19270862243CE1F39953F3676F /* RCPurchases.m */, + 52A83F1E964B29B798FF233551EBD489 /* RCPurchases+Protected.h */, + 73DDE5281E0C43DD02C519D43263C1DE /* RCPurchases+SubscriberAttributes.h */, + 70864B89729AF520A10C907EFF84CD09 /* RCPurchases+SubscriberAttributes.m */, + 1F4B2817C5F791D0FE79EC98CF2127EC /* RCPurchasesErrors.h */, + 9054DA2B11B1187DE2C99A18ACBB9C3B /* RCPurchasesErrorUtils.h */, + 0C0E1A6A563103DDE5AB8D0F7534FFEE /* RCPurchasesErrorUtils.m */, + A80A02A31E5CED7391D8CF2F296DF889 /* RCPurchasesErrorUtils+Protected.h */, + C36A53CB66891C484755C46C4C3E046E /* RCReceiptFetcher.h */, + 7204ED0655F389582BA8E59DCB19190B /* RCReceiptFetcher.m */, + 63FDA277300CB36F21E910CC7B4B8133 /* RCReceiptRefreshPolicy.h */, + 333E0056461D64D02DCD2CD8743B88F5 /* RCSpecialSubscriberAttributes.h */, + 37AA770232C5D89C4BDFAEF9E0DD924C /* RCStoreKitRequestFetcher.h */, + DB17BA34F6E64BA30BEE9C759507FBCC /* RCStoreKitRequestFetcher.m */, + ECA69D894E55BFBF0FBEEBC436DA94D6 /* RCStoreKitWrapper.h */, + 91E49EB50DABB9B321C38F5BBB45B2D9 /* RCStoreKitWrapper.m */, + 555B5AD5FE59DDC25B8D9392E2C447C9 /* RCSubscriberAttribute.h */, + C748A76A4F0C79AEE1C5F9612D2A9956 /* RCSubscriberAttribute.m */, + D72540A38557949101A58492389AA57F /* RCSubscriberAttribute+Protected.h */, + 5FA1F6A03FF84FEA75AAC5660A33A499 /* RCSubscriberAttributesManager.h */, + FA39B7E1B1F6EA2534C96EAC11EC5714 /* RCSubscriberAttributesManager.m */, + BE9841AA391181AB8B3B92CA23E2C671 /* RCSystemInfo.h */, + E8DBF669839138836F3A0C6FCFF2229C /* RCSystemInfo.m */, + 4C2A429ECB9957E19C8CCE61939287BB /* RCTransaction.h */, + 9306B6970893A583397278C12F6425F1 /* Support Files */, + ); + name = Purchases; + path = Purchases; + sourceTree = ""; + }; + 615502CC4B88CFAE6D4E77F46BCC2F18 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 7B51404464C4EB6D4C594037AEB68841 /* PurchasesCoreSwift.framework */, + DE86B01FC114C3FA41B4914EBC7CC7E1 /* iOS */, + ); + name = Frameworks; + sourceTree = ""; + }; + 70F5EE37CF01DC8BEF961DE92F8D6590 /* Support Files */ = { + isa = PBXGroup; + children = ( + 7C9001E70BF6D63816D9FCC170A48BE1 /* PurchasesCoreSwift.modulemap */, + 56D899CB8D73C3CFBC5EA0C6294891A2 /* PurchasesCoreSwift-dummy.m */, + B58117D414ADAA84452F2B1CD9F93301 /* PurchasesCoreSwift-Info.plist */, + 7B17777921009FC8182549288BF9C4D0 /* PurchasesCoreSwift-prefix.pch */, + 0E485310A76B571F082279DB13567531 /* PurchasesCoreSwift-umbrella.h */, + 8B1EC43D035484A9B9F6B0010A518907 /* PurchasesCoreSwift.debug.xcconfig */, + C2B686595E51771F6F825E2789FFD5AE /* PurchasesCoreSwift.release.xcconfig */, + ); + name = "Support Files"; + path = "../Target Support Files/PurchasesCoreSwift"; + sourceTree = ""; + }; + 742C4C8CBEE50796731D626B8731E034 /* Pods */ = { + isa = PBXGroup; + children = ( + 3AF13BB7E34A4E57530CA3208B5A9F98 /* Purchases */, + EB8B193620C9C494F714B9F248AE9423 /* PurchasesCoreSwift */, + ); + name = Pods; + sourceTree = ""; + }; + 82972E33A40B4C522213A835C0E49947 /* Products */ = { + isa = PBXGroup; + children = ( + E3758240AA9C1AB477C1A5A3379A7AEE /* Pods_calm_mind.framework */, + FBA7F9EF3FAEB0BC7D79BB1AFF265A06 /* Purchases.framework */, + A8744BFCBA0EFF3EFCB22EF6E0408B42 /* PurchasesCoreSwift.framework */, + ); + name = Products; + sourceTree = ""; + }; + 90F4948415D08ADEF27A57E7F05A9A46 /* Pods-calm-mind */ = { + isa = PBXGroup; + children = ( + E8FE76D998DEF8D11647FFB144B011C7 /* Pods-calm-mind.modulemap */, + 43EA8BB6508F44B39158007B6FBEEAE8 /* Pods-calm-mind-acknowledgements.markdown */, + 5153D49110BC9C6B6321BF255DDDFD03 /* Pods-calm-mind-acknowledgements.plist */, + A45955A573A5BED016455CF3CE612B77 /* Pods-calm-mind-dummy.m */, + 4DC908152C7FF585FD72F10305133610 /* Pods-calm-mind-frameworks.sh */, + A777D9939AFEC140F0C60BDBFD6A8DD2 /* Pods-calm-mind-Info.plist */, + 6FE2E828245B552334F9F54A18F829DF /* Pods-calm-mind-umbrella.h */, + FD4CC8CD46EB75E1D83BB0B9297466E9 /* Pods-calm-mind.debug.xcconfig */, + 5EB42D7C1DE3D1C019814488DC99F2D0 /* Pods-calm-mind.release.xcconfig */, + ); + name = "Pods-calm-mind"; + path = "Target Support Files/Pods-calm-mind"; + sourceTree = ""; + }; + 9306B6970893A583397278C12F6425F1 /* Support Files */ = { + isa = PBXGroup; + children = ( + F7A588C75347128A4E82225E568D4F1D /* Purchases.modulemap */, + 51A1650DA24673A3A9727E0513CD4D89 /* Purchases-dummy.m */, + 5A1F7866BEEF2AF2C8D2A88FD7944481 /* Purchases-Info.plist */, + 9D2E720DEC9EC21251E5887717D984CC /* Purchases-prefix.pch */, + 0D0B09DAC8910E8F46B124066C156A0B /* Purchases-umbrella.h */, + 173403D9233DC83DD4A2D8EEED0880BF /* Purchases.debug.xcconfig */, + 087225F362E5FB6A81CC8F5C4BFD86A2 /* Purchases.release.xcconfig */, + ); + name = "Support Files"; + path = "../Target Support Files/Purchases"; + sourceTree = ""; + }; + AFB35A79D1046D67324B5CFACB45E896 /* Targets Support Files */ = { + isa = PBXGroup; + children = ( + 90F4948415D08ADEF27A57E7F05A9A46 /* Pods-calm-mind */, + ); + name = "Targets Support Files"; + sourceTree = ""; + }; + CF1408CF629C7361332E53B88F7BD30C = { + isa = PBXGroup; + children = ( + 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */, + 615502CC4B88CFAE6D4E77F46BCC2F18 /* Frameworks */, + 742C4C8CBEE50796731D626B8731E034 /* Pods */, + 82972E33A40B4C522213A835C0E49947 /* Products */, + AFB35A79D1046D67324B5CFACB45E896 /* Targets Support Files */, + ); + sourceTree = ""; + }; + DE86B01FC114C3FA41B4914EBC7CC7E1 /* iOS */ = { + isa = PBXGroup; + children = ( + 7C7CDA4363CBEC03E06A56F9FD1A9E3A /* Foundation.framework */, + BE1000410AABAC8444B7B26CD6F6BDB0 /* StoreKit.framework */, + ); + name = iOS; + sourceTree = ""; + }; + EB8B193620C9C494F714B9F248AE9423 /* PurchasesCoreSwift */ = { + isa = PBXGroup; + children = ( + DF77AFF86606093AF1F7E11773246218 /* AppleReceipt.swift */, + F9E9965A8AB4C947148376228F54644A /* AppleReceiptBuilder.swift */, + E426521AD968BA19443CC3EA3AD72A71 /* ArraySlice_UInt8+Extensions.swift */, + DF9A37949EF51C553DFF415DCA9825B4 /* ASN1Container.swift */, + B4C794EE6592B00A40C018046C38A5B8 /* ASN1ContainerBuilder.swift */, + 311EDFA3232595897C0F1D363AD5A96B /* ASN1ObjectIdentifier.swift */, + C39202D54CEED9CDE54EF82F0FAB78B9 /* ASN1ObjectIdentifierBuilder.swift */, + FC67BC7F7282D8655B55A4F3481A21A0 /* AttributionStrings.swift */, + 58EFCD12F97032C865F401FE49D18175 /* DateExtensions.swift */, + 7394DB39CF5B2EB6009AEA6D3571E3F3 /* InAppPurchase.swift */, + C838FD0A18FE48AE8D1237C6A8F24FCB /* InAppPurchaseBuilder.swift */, + AF25A9E7FA44BFD4EC6B13767F8FFACD /* IntroEligibilityCalculator.swift */, + C526273982E86AFBDBBDA8697EF8467E /* ISO3601DateFormatter.swift */, + 85B6AD8CB509157DE4DA8C3DAEF15D29 /* OperationDispatcher.swift */, + 6E92C9EE4CF219E24F8A3A9536E5EAF2 /* ProductsManager.swift */, + 644D1F4D40113420225A5F981BB1D8D7 /* ProductsRequestFactory.swift */, + D3123EB27C9638F33576412BF2CDE500 /* ReceiptParser.swift */, + 428F420B70D82F82760CC4B95DF17CAD /* ReceiptParsingError.swift */, + 2E6D36D415ED94FECA27A8C1123507DB /* Strings.swift */, + 733D005D38DF2017B18A8D090CAAD63F /* Transaction.swift */, + 114910BCF10B7F8073A5D6E9A0393223 /* TransactionsFactory.swift */, + A053C4FCA4F5CF5F08EBCB564E90B5B9 /* UInt8+Extensions.swift */, + 70F5EE37CF01DC8BEF961DE92F8D6590 /* Support Files */, + ); + name = PurchasesCoreSwift; + path = PurchasesCoreSwift; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 1AED479331F3D8A47E125AA4C3E4190E /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 7D787FBC2622A66C158F64C52978BCF1 /* PurchasesCoreSwift-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AB571A87174D9CA3CD4570E8B01F0129 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 3DFCA7264A1CEC49E99113565F04EA84 /* NSData+RCExtensions.h in Headers */, + ED29727A66C6064B17613A2153B1C450 /* NSDate+RCExtensions.h in Headers */, + 270AD49631F9FFECB3141C060983A2B9 /* NSDictionary+RCExtensions.h in Headers */, + 14B1925DABCB3E22E64078867A9341AD /* NSError+RCExtensions.h in Headers */, + 6750743F1C6783FAC8E4161CD57F315B /* NSLocale+RCExtensions.h in Headers */, + F1134D60B6B50216BD1D284AF30F8F61 /* Purchases-umbrella.h in Headers */, + C5D14153A9A27A52EF745D23AC05A224 /* Purchases.h in Headers */, + DE93F9CB669648DDD8A707EB79ACE95B /* RCAttributionData.h in Headers */, + A678028AF0BFEE6861252AC39F94513B /* RCAttributionFetcher+Protected.h in Headers */, + B0BE61DE96E7DEAA2C0E205A9405373E /* RCAttributionFetcher.h in Headers */, + 452BE9C5120CA543ECE0313F9C6FDFEE /* RCAttributionNetwork.h in Headers */, + 0653BAEADF463B2AA3DDCFA7FA45FBD0 /* RCBackend.h in Headers */, + E04092BB9BC901014BB66DF9EC50F2DF /* RCCrossPlatformSupport.h in Headers */, + 1F8426DB406567E4CDAC3B8AC97458B5 /* RCDateProvider.h in Headers */, + C1FEDDC5A620BD30471F5021411AB54F /* RCDeviceCache+Protected.h in Headers */, + 13CAA41D53F756C71703E697A65CD165 /* RCDeviceCache.h in Headers */, + 4278DE079796BE3122519B09535A85BD /* RCEntitlementInfo+Protected.h in Headers */, + 5CEDA03C5DAFC9D0975D9CF6562087A0 /* RCEntitlementInfo.h in Headers */, + 428F24D52D4333BF9D4E70CC310900A7 /* RCEntitlementInfos+Protected.h in Headers */, + 3E2C1D4612C2DA8886CEF2FA4903AE93 /* RCEntitlementInfos.h in Headers */, + 46F3AE65F1B549D5F211B2459AD5E549 /* RCHTTPClient.h in Headers */, + 40C895B65A73DE0B1DD2568196255B52 /* RCHTTPRequest.h in Headers */, + B0B995B8CA9C6C5574AF99B67A35A661 /* RCHTTPStatusCodes.h in Headers */, + E184D53C48FE168C9977624473676A1E /* RCIdentityManager.h in Headers */, + 37E05321FF1723AC6DFBFEC30D8BDE67 /* RCInMemoryCachedObject+Protected.h in Headers */, + CD7EF1412B7FC27E8F43588291EC1A03 /* RCInMemoryCachedObject.h in Headers */, + 1D4EDC4A4AE28D148570C0C47860E193 /* RCIntroEligibility+Protected.h in Headers */, + 298207D63E4FE3C8A5AFDCE6FFAB967A /* RCIntroEligibility.h in Headers */, + CFC6D608887CDC187FFCD82C17BF6C98 /* RCISOPeriodFormatter.h in Headers */, + 64AF544E5B5329574EF5037E46D5C7FA /* RCLogUtils.h in Headers */, + C32AE550265BE3734DE37379C92AB172 /* RCOffering+Protected.h in Headers */, + 0EA5CA5E695DD29759048E3050557004 /* RCOffering.h in Headers */, + 5AFB6B9138598A7ABC3BAF9A2A4903BB /* RCOfferings+Protected.h in Headers */, + 40C86BE73AC9784C373B522E45C6FE8B /* RCOfferings.h in Headers */, + 0D0879E27EC40A0C11E5BAA90DDE4057 /* RCOfferingsFactory.h in Headers */, + 9966D70E0E9E00D82605AD97F4086716 /* RCPackage+Protected.h in Headers */, + 052E739017171D657D8E2692CCA20E04 /* RCPackage.h in Headers */, + 6C570D0F32E71D4AFBC834293A683169 /* RCProductInfo.h in Headers */, + D99983BF93297FA586A0171683CCB407 /* RCProductInfoExtractor.h in Headers */, + EFD468C47E81A1D3E9EE03F907EC724B /* RCPromotionalOffer+Protected.h in Headers */, + BC3809E415A4C1CF361130ACB7F3C145 /* RCPromotionalOffer.h in Headers */, + C5A1ACFBBDC55643594562B2DBB98273 /* RCPurchaserInfo+Protected.h in Headers */, + FCF3A8A035E869F33B56468E097C3FED /* RCPurchaserInfo.h in Headers */, + 5F3FC37844F858D7EF4AB3E61576140B /* RCPurchases+Protected.h in Headers */, + 733B1A205CCD9F93D4D6A2930E912D97 /* RCPurchases+SubscriberAttributes.h in Headers */, + 45AA3B9447875657972E31005E2A227C /* RCPurchases.h in Headers */, + 25CEF2FAC4E39E0E65417CE47C827E20 /* RCPurchasesErrors.h in Headers */, + C11E0DAB9E71DA372242FB956E9AC209 /* RCPurchasesErrorUtils+Protected.h in Headers */, + C23235DB8FFB65AB7662D13B07F1B016 /* RCPurchasesErrorUtils.h in Headers */, + 0D93143BB57C99571484EB3E85AB46AF /* RCReceiptFetcher.h in Headers */, + 88EA2ED684283EB2D9B350748CC1BA74 /* RCReceiptRefreshPolicy.h in Headers */, + 8451B2FD631DCD15286F36DB5C964B0A /* RCSpecialSubscriberAttributes.h in Headers */, + 33B863578764A476F05F49FBA686F7A6 /* RCStoreKitRequestFetcher.h in Headers */, + 52E78F29B7B6E85D600B1041342109DB /* RCStoreKitWrapper.h in Headers */, + C8678F96B402A67762933E6F7BD261F3 /* RCSubscriberAttribute+Protected.h in Headers */, + 4283E4027EF567DAC64F3A445DEC7990 /* RCSubscriberAttribute.h in Headers */, + 195024D5BD9D0B94DA711E4EE21F6E14 /* RCSubscriberAttributesManager.h in Headers */, + 152C78F28E05148F25344309D5D8BC19 /* RCSystemInfo.h in Headers */, + 459E0A9E40A0591B346A83402FFF4328 /* RCTransaction.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D7957C975DC7B48F13E18A77431EB820 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 335945CDC95AAC3776B77531F1FC6EB3 /* Pods-calm-mind-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 19EF5509BA60F0CD4916B9F24A2F7886 /* Purchases */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2A1727B394648EE901A10F39348B26A3 /* Build configuration list for PBXNativeTarget "Purchases" */; + buildPhases = ( + AB571A87174D9CA3CD4570E8B01F0129 /* Headers */, + 7607BB298F3F1CA697E1A2BF8BF826B8 /* Sources */, + CE2A589D03A024565296CDCE4F55F3F8 /* Frameworks */, + D213BD03DA9F557C9EB0AB545EB9BE64 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + E6B71A2BB54DB9A4CCCA49260886A727 /* PBXTargetDependency */, + ); + name = Purchases; + productName = Purchases; + productReference = FBA7F9EF3FAEB0BC7D79BB1AFF265A06 /* Purchases.framework */; + productType = "com.apple.product-type.framework"; + }; + 2BC2CC6A5EB8F91429DC14E6BDC342DF /* PurchasesCoreSwift */ = { + isa = PBXNativeTarget; + buildConfigurationList = FBB6CBDEC825F7CADC4B664A0D2499F5 /* Build configuration list for PBXNativeTarget "PurchasesCoreSwift" */; + buildPhases = ( + 1AED479331F3D8A47E125AA4C3E4190E /* Headers */, + 9DA668080EF0D52D1D90EE7C8A8C9001 /* Sources */, + 8B7BC0C8B05C89E642A2962B832A599B /* Frameworks */, + C4B1399555DE6BD45DB6B811C824FF0C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = PurchasesCoreSwift; + productName = PurchasesCoreSwift; + productReference = A8744BFCBA0EFF3EFCB22EF6E0408B42 /* PurchasesCoreSwift.framework */; + productType = "com.apple.product-type.framework"; + }; + B6BDD7306446261D50EB14B97376F699 /* Pods-calm-mind */ = { + isa = PBXNativeTarget; + buildConfigurationList = F9A8E851625B816D11F2D4132A218FA3 /* Build configuration list for PBXNativeTarget "Pods-calm-mind" */; + buildPhases = ( + D7957C975DC7B48F13E18A77431EB820 /* Headers */, + F76961E7C1D8EE699680C871ADEE7247 /* Sources */, + DD7F3A9A3038508C13C10CF9EC20EF1F /* Frameworks */, + ACD2068599F983F16B781EB6FD96F6F3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + C84C7AABECF66233A062BAC9CCF8AE96 /* PBXTargetDependency */, + F819BE1067F504415EAA2767D83B1A51 /* PBXTargetDependency */, + ); + name = "Pods-calm-mind"; + productName = "Pods-calm-mind"; + productReference = E3758240AA9C1AB477C1A5A3379A7AEE /* Pods_calm_mind.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + BFDFE7DC352907FC980B868725387E98 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1100; + LastUpgradeCheck = 1100; + }; + buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = CF1408CF629C7361332E53B88F7BD30C; + productRefGroup = 82972E33A40B4C522213A835C0E49947 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + B6BDD7306446261D50EB14B97376F699 /* Pods-calm-mind */, + 19EF5509BA60F0CD4916B9F24A2F7886 /* Purchases */, + 2BC2CC6A5EB8F91429DC14E6BDC342DF /* PurchasesCoreSwift */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + ACD2068599F983F16B781EB6FD96F6F3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C4B1399555DE6BD45DB6B811C824FF0C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D213BD03DA9F557C9EB0AB545EB9BE64 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 7607BB298F3F1CA697E1A2BF8BF826B8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3F682A9EEF9225584D975F0725D2639A /* NSData+RCExtensions.m in Sources */, + 9E86B1F2450339BCCEB67DB8F36EE434 /* NSDate+RCExtensions.m in Sources */, + BF6E0C4C5186DA1B2955634AD84E40BC /* NSDictionary+RCExtensions.m in Sources */, + 89D0FF1B40D5E981514FE669C8A8643F /* NSError+RCExtensions.m in Sources */, + 4E318D0E488A7CD6F340A59483E11FA7 /* NSLocale+RCExtensions.m in Sources */, + D60BF519F5703F1638570F7DBE1F7850 /* Purchases-dummy.m in Sources */, + 268C923D5753A23AC098C97B7CFBBE5F /* RCAttributionData.m in Sources */, + 6A4BEC3FEA72DC3C0B91509A850533DA /* RCAttributionFetcher.m in Sources */, + 13CA36D487C0BC06ADFE309D4D37252D /* RCBackend.m in Sources */, + 66592755E68603B4F9762B111EDA3526 /* RCDateProvider.m in Sources */, + FE02197675E297AEEDB249FB33CBBD6A /* RCDeviceCache.m in Sources */, + 5215CCABC9B7AFACB1C0B93B0A2AD672 /* RCEntitlementInfo.m in Sources */, + F544AB89C05AAC723FC1089709E7377F /* RCEntitlementInfos.m in Sources */, + 6C9B5A0CE8BF3253E20229EDAFDAC12A /* RCHTTPClient.m in Sources */, + 25FBF59814A4D17212888706FF692D40 /* RCHTTPRequest.m in Sources */, + F09515456D553C816B5A87A53EAA1FCE /* RCIdentityManager.m in Sources */, + 89AB6DBE4DF5C18347827AEC4899A780 /* RCInMemoryCachedObject.m in Sources */, + A1FB2E1D613974FB63ABEF649DA49457 /* RCIntroEligibility.m in Sources */, + EA8F11A20AFA442A54285C79A822E78D /* RCISOPeriodFormatter.m in Sources */, + BFD2CBC47F4B91B6DF0904EE3ECCF054 /* RCLogUtils.m in Sources */, + 46FE7DD5DA778F124DF8465BEFD7CFF4 /* RCOffering.m in Sources */, + EC3328778FFE688561C8176F182EBCBE /* RCOfferings.m in Sources */, + 67EBC24E08D6DC0D3A53FE5AF7F6AE6F /* RCOfferingsFactory.m in Sources */, + E8E9D76A9EA04553588F12A46EC5CC3E /* RCPackage.m in Sources */, + 323CC37A59200E1139B68DC6F800A760 /* RCProductInfo.m in Sources */, + D9D26CCD5543A430129CE008A01E5ECA /* RCProductInfoExtractor.m in Sources */, + 7AFA33F27E3FE5278AA577AABCB58726 /* RCPromotionalOffer.m in Sources */, + FBECA83B8E280FDB6DAFCE9048B8550F /* RCPurchaserInfo.m in Sources */, + 54BA9227A6734B2E20217E646C93A5B0 /* RCPurchases+SubscriberAttributes.m in Sources */, + 64DDCDEBE4C7663DE788A835067C1923 /* RCPurchases.m in Sources */, + 4D60B35A9692461A83C67FDC444FEBB5 /* RCPurchasesErrorUtils.m in Sources */, + 71861AAA6F7FAC798B789F4C4837A51C /* RCReceiptFetcher.m in Sources */, + 5C4055F7B4015328AF63A134C16D79C8 /* RCStoreKitRequestFetcher.m in Sources */, + 3899A105EEED58439560FCA4B0C43701 /* RCStoreKitWrapper.m in Sources */, + 37A839D754E695E553DF46FAB66D3354 /* RCSubscriberAttribute.m in Sources */, + F3995BBD875D1474CA1A0F57E4ED4B42 /* RCSubscriberAttributesManager.m in Sources */, + B20598774B6B58A3852514196429AD83 /* RCSystemInfo.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9DA668080EF0D52D1D90EE7C8A8C9001 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 508B969E293E9D530C35D452ECC7F2C7 /* AppleReceipt.swift in Sources */, + BE1799BE116B4C087916E28AB71F081B /* AppleReceiptBuilder.swift in Sources */, + BB7167CA498C8FE4C1CA3AF31F9744E2 /* ArraySlice_UInt8+Extensions.swift in Sources */, + F792580943AF69C52023DE9060E9EEC4 /* ASN1Container.swift in Sources */, + 2A56178D9AE4CA3D9A344827807E7124 /* ASN1ContainerBuilder.swift in Sources */, + 69321C7A5D5F3E23833EABBC41150392 /* ASN1ObjectIdentifier.swift in Sources */, + 55FC26FB3398D6D794E287AEBA5F9BC4 /* ASN1ObjectIdentifierBuilder.swift in Sources */, + C8F7B485A358F76FD93036187080D3D4 /* AttributionStrings.swift in Sources */, + 8D57263836FD33060ACBAD9F14083C9E /* DateExtensions.swift in Sources */, + 3A2FCCF03349542E73EF906625CF2339 /* InAppPurchase.swift in Sources */, + 688C879CAB8FA63547436A5FC9218554 /* InAppPurchaseBuilder.swift in Sources */, + 08DA5DC6F3DEA5F41B46B4D9A8503576 /* IntroEligibilityCalculator.swift in Sources */, + C7FD55F097E9884ABBB6CC54782769D1 /* ISO3601DateFormatter.swift in Sources */, + 686EF1EE35E0D02AE6FD9751FB3C3839 /* OperationDispatcher.swift in Sources */, + 84E4D2CB4BAF15436064DD440F86A43E /* ProductsManager.swift in Sources */, + FE889AE379F62C9ED547A83C42A2A9AE /* ProductsRequestFactory.swift in Sources */, + 79EB563DC883ABF028727C110F14F46C /* PurchasesCoreSwift-dummy.m in Sources */, + 89682F97BA5CCB7AF043549CD4BD2789 /* ReceiptParser.swift in Sources */, + 817AC795B4736545B1C77CCDFD5F6A81 /* ReceiptParsingError.swift in Sources */, + 69010EF98D54267770E13579A1D5EFC3 /* Strings.swift in Sources */, + B3D2DBB77672221B4126DCB8C11821D7 /* Transaction.swift in Sources */, + 080B618ABD58BA804F310D1738069665 /* TransactionsFactory.swift in Sources */, + 65028C2B3E7B787B704C42606E736371 /* UInt8+Extensions.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F76961E7C1D8EE699680C871ADEE7247 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 397093C3046EBFE4036A8D6E4C25278F /* Pods-calm-mind-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + C84C7AABECF66233A062BAC9CCF8AE96 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Purchases; + target = 19EF5509BA60F0CD4916B9F24A2F7886 /* Purchases */; + targetProxy = 740944A13D88C81B42BD9BFA75034603 /* PBXContainerItemProxy */; + }; + E6B71A2BB54DB9A4CCCA49260886A727 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = PurchasesCoreSwift; + target = 2BC2CC6A5EB8F91429DC14E6BDC342DF /* PurchasesCoreSwift */; + targetProxy = 9E44F9EE268BAB5681A411DAC8AF5513 /* PBXContainerItemProxy */; + }; + F819BE1067F504415EAA2767D83B1A51 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = PurchasesCoreSwift; + target = 2BC2CC6A5EB8F91429DC14E6BDC342DF /* PurchasesCoreSwift */; + targetProxy = FCC298E20725D272E532AF5F65CBC491 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 075ACB22495D886E8128905350D0C125 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FD4CC8CD46EB75E1D83BB0B9297466E9 /* Pods-calm-mind.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-calm-mind/Pods-calm-mind-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-calm-mind/Pods-calm-mind.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 4BC7450F9457737EE3E637BA155B56F7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "POD_CONFIGURATION_DEBUG=1", + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Debug; + }; + 56AE30C00FACD293546E7C7A5BE5947A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8B1EC43D035484A9B9F6B0010A518907 /* PurchasesCoreSwift.debug.xcconfig */; + buildSettings = { + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift.modulemap"; + PRODUCT_MODULE_NAME = PurchasesCoreSwift; + PRODUCT_NAME = PurchasesCoreSwift; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 6446ECC48FE1DEA730AF52A529414C6E /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C2B686595E51771F6F825E2789FFD5AE /* PurchasesCoreSwift.release.xcconfig */; + buildSettings = { + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift.modulemap"; + PRODUCT_MODULE_NAME = PurchasesCoreSwift; + PRODUCT_NAME = PurchasesCoreSwift; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 8B5A46FF8D3C1289CDEE3BAFACABCD2A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "POD_CONFIGURATION_RELEASE=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Release; + }; + 9CCC6E2AD885621E7304DD0E5BC02D8E /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5EB42D7C1DE3D1C019814488DC99F2D0 /* Pods-calm-mind.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-calm-mind/Pods-calm-mind-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-calm-mind/Pods-calm-mind.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + DC18AB0B9D01D7A0BEC517D3AD99A9C6 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 173403D9233DC83DD4A2D8EEED0880BF /* Purchases.debug.xcconfig */; + buildSettings = { + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/Purchases/Purchases-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/Purchases/Purchases-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/Purchases/Purchases.modulemap"; + PRODUCT_MODULE_NAME = Purchases; + PRODUCT_NAME = Purchases; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + FD38B3CE058D6CB6FB213CA368FA8620 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 087225F362E5FB6A81CC8F5C4BFD86A2 /* Purchases.release.xcconfig */; + buildSettings = { + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/Purchases/Purchases-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/Purchases/Purchases-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/Purchases/Purchases.modulemap"; + PRODUCT_MODULE_NAME = Purchases; + PRODUCT_NAME = Purchases; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2A1727B394648EE901A10F39348B26A3 /* Build configuration list for PBXNativeTarget "Purchases" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DC18AB0B9D01D7A0BEC517D3AD99A9C6 /* Debug */, + FD38B3CE058D6CB6FB213CA368FA8620 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4BC7450F9457737EE3E637BA155B56F7 /* Debug */, + 8B5A46FF8D3C1289CDEE3BAFACABCD2A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F9A8E851625B816D11F2D4132A218FA3 /* Build configuration list for PBXNativeTarget "Pods-calm-mind" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 075ACB22495D886E8128905350D0C125 /* Debug */, + 9CCC6E2AD885621E7304DD0E5BC02D8E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + FBB6CBDEC825F7CADC4B664A0D2499F5 /* Build configuration list for PBXNativeTarget "PurchasesCoreSwift" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 56AE30C00FACD293546E7C7A5BE5947A /* Debug */, + 6446ECC48FE1DEA730AF52A529414C6E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = BFDFE7DC352907FC980B868725387E98 /* Project object */; +} diff --git a/Pods/Pods.xcodeproj/xcuserdata/Chris.xcuserdatad/xcschemes/Pods-calm-mind.xcscheme b/Pods/Pods.xcodeproj/xcuserdata/Chris.xcuserdatad/xcschemes/Pods-calm-mind.xcscheme new file mode 100644 index 0000000..3f09a0a --- /dev/null +++ b/Pods/Pods.xcodeproj/xcuserdata/Chris.xcuserdatad/xcschemes/Pods-calm-mind.xcscheme @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Pods/Pods.xcodeproj/xcuserdata/Chris.xcuserdatad/xcschemes/Purchases.xcscheme b/Pods/Pods.xcodeproj/xcuserdata/Chris.xcuserdatad/xcschemes/Purchases.xcscheme new file mode 100644 index 0000000..6fb14f3 --- /dev/null +++ b/Pods/Pods.xcodeproj/xcuserdata/Chris.xcuserdatad/xcschemes/Purchases.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Pods/Pods.xcodeproj/xcuserdata/Chris.xcuserdatad/xcschemes/PurchasesCoreSwift.xcscheme b/Pods/Pods.xcodeproj/xcuserdata/Chris.xcuserdatad/xcschemes/PurchasesCoreSwift.xcscheme new file mode 100644 index 0000000..710b739 --- /dev/null +++ b/Pods/Pods.xcodeproj/xcuserdata/Chris.xcuserdatad/xcschemes/PurchasesCoreSwift.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Pods/Pods.xcodeproj/xcuserdata/Chris.xcuserdatad/xcschemes/xcschememanagement.plist b/Pods/Pods.xcodeproj/xcuserdata/Chris.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..00e737b --- /dev/null +++ b/Pods/Pods.xcodeproj/xcuserdata/Chris.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,32 @@ + + + + + SchemeUserState + + Pods-calm-mind.xcscheme + + isShown + + orderHint + 0 + + Purchases.xcscheme + + isShown + + orderHint + 1 + + PurchasesCoreSwift.xcscheme + + isShown + + orderHint + 2 + + + SuppressBuildableAutocreation + + + diff --git a/Pods/Purchases/LICENSE b/Pods/Purchases/LICENSE new file mode 100644 index 0000000..91dd957 --- /dev/null +++ b/Pods/Purchases/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Jacob Eiting + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Pods/Purchases/Purchases/Caching/RCDeviceCache.h b/Pods/Purchases/Purchases/Caching/RCDeviceCache.h new file mode 100644 index 0000000..06bcea7 --- /dev/null +++ b/Pods/Purchases/Purchases/Caching/RCDeviceCache.h @@ -0,0 +1,86 @@ +// +// RCDeviceCache.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 Purchases. All rights reserved. +// + +#import +#import "RCSubscriberAttribute.h" + +@class RCOfferings; + +NS_ASSUME_NONNULL_BEGIN + +@interface RCDeviceCache : NSObject + +- (instancetype)initWith:(NSUserDefaults *)userDefaults; + +#pragma mark - appUserID + +@property (nonatomic, readonly, nullable) NSString *cachedAppUserID; + +@property (nonatomic, readonly, nullable) NSString *cachedLegacyAppUserID; + +- (void)cacheAppUserID:(NSString *)appUserID; + +- (void)clearCachesForAppUserID:(NSString *)oldAppUserID andSaveNewUserID:(NSString *)newUserID; + +#pragma mark - purchaserInfo + +- (nullable NSData *)cachedPurchaserInfoDataForAppUserID:(NSString *)appUserID; + +- (void)cachePurchaserInfo:(NSData *)data forAppUserID:(NSString *)appUserID; + +- (BOOL)isPurchaserInfoCacheStaleForAppUserID:(NSString *)appUserID isAppBackgrounded:(BOOL)isAppBackgrounded; + +- (void)clearPurchaserInfoCacheTimestampForAppUserID:(NSString *)appUserID; + +- (void)clearPurchaserInfoCacheForAppUserID:(NSString *)appUserID; + +- (void)setPurchaserInfoCacheTimestampToNowForAppUserID:(NSString *)appUserID; + +#pragma mark - offerings + +@property (nonatomic, readonly, nullable) RCOfferings *cachedOfferings; + +- (void)cacheOfferings:(RCOfferings *)offerings; + +- (BOOL)isOfferingsCacheStaleWithIsAppBackgrounded:(BOOL)isAppBackgrounded; + +- (void)clearOfferingsCacheTimestamp; + +- (void)setOfferingsCacheTimestampToNow; + +#pragma mark - subscriber attributes + +- (void)storeSubscriberAttribute:(RCSubscriberAttribute *)attribute appUserID:(NSString *)appUserID; + +- (void)storeSubscriberAttributes:(RCSubscriberAttributeDict)attributesByKey + appUserID:(NSString *)appUserID; + +- (nullable RCSubscriberAttribute *)subscriberAttributeWithKey:(NSString *)attributeKey appUserID:(NSString *)appUserID; + +- (RCSubscriberAttributeDict)unsyncedAttributesByKeyForAppUserID:(NSString *)appUserID; + +- (NSUInteger)numberOfUnsyncedAttributesForAppUserID:(NSString *)appUserID; + +- (void)cleanupSubscriberAttributes; + +- (NSDictionary *)unsyncedAttributesForAllUsers; + +- (void)deleteAttributesIfSyncedForAppUserID:(NSString *)appUserID; + +#pragma mark - attribution + +- (nullable NSDictionary *)latestNetworkAndAdvertisingIdsSentForAppUserID:(NSString *)appUserID; + +- (void)setLatestNetworkAndAdvertisingIdsSent:(nullable NSDictionary *)latestNetworkAndAdvertisingIdsSent + forAppUserID:(nullable NSString *)appUserID; + +- (void)clearLatestNetworkAndAdvertisingIdsSentForAppUserID:(nullable NSString *)appUserID; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Caching/RCDeviceCache.m b/Pods/Purchases/Purchases/Caching/RCDeviceCache.m new file mode 100644 index 0000000..84a19e9 --- /dev/null +++ b/Pods/Purchases/Purchases/Caching/RCDeviceCache.m @@ -0,0 +1,431 @@ +// +// RCDeviceCache.m +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 Purchases. All rights reserved. +// + +#import "RCDeviceCache.h" +#import "RCDeviceCache+Protected.h" + +@interface RCDeviceCache () + +@property (nonatomic) NSUserDefaults *userDefaults; +@property (nonatomic) NSNotificationCenter *notificationCenter; +@property (nonatomic, nonnull) RCInMemoryCachedObject *offeringsCachedObject; + +@property (nonatomic, assign) BOOL appUserIDHasBeenSet; + +@end + + +#define RC_CACHE_KEY_PREFIX @"com.revenuecat.userdefaults" + +NSString *RCLegacyGeneratedAppUserDefaultsKey = RC_CACHE_KEY_PREFIX @".appUserID"; +NSString *RCAppUserDefaultsKey = RC_CACHE_KEY_PREFIX @".appUserID.new"; +NSString *RCPurchaserInfoAppUserDefaultsKeyBase = RC_CACHE_KEY_PREFIX @".purchaserInfo."; +NSString *RCPurchaserInfoLastUpdatedKeyBase = RC_CACHE_KEY_PREFIX @".purchaserInfoLastUpdated."; +NSString *RCLegacySubscriberAttributesKeyBase = RC_CACHE_KEY_PREFIX @".subscriberAttributes."; +NSString *RCSubscriberAttributesKey = RC_CACHE_KEY_PREFIX @".subscriberAttributes"; +NSString *RCAttributionDataDefaultsKeyBase = RC_CACHE_KEY_PREFIX @".attribution."; +int cacheDurationInSecondsInForeground = 60 * 5; +int cacheDurationInSecondsInBackground = 60 * 60 * 24; + +@implementation RCDeviceCache + +- (instancetype)initWith:(NSUserDefaults *)userDefaults { + return [self initWith:userDefaults offeringsCachedObject:nil notificationCenter:nil]; +} + +- (instancetype)initWith:(NSUserDefaults *)userDefaults + offeringsCachedObject:(RCInMemoryCachedObject *)offeringsCachedObject + notificationCenter:(NSNotificationCenter *)notificationCenter { + self = [super init]; + if (self) { + if (offeringsCachedObject == nil) { + offeringsCachedObject = [[RCInMemoryCachedObject alloc] init]; + } + self.offeringsCachedObject = offeringsCachedObject; + + if (notificationCenter == nil) { + notificationCenter = NSNotificationCenter.defaultCenter; + } + self.notificationCenter = notificationCenter; + + if (userDefaults == nil) { + userDefaults = NSUserDefaults.standardUserDefaults; + } + self.userDefaults = userDefaults; + self.appUserIDHasBeenSet = self.cachedAppUserID != nil; + [self observeAppUserIDChanges]; + } + + return self; +} + +#pragma mark - UserDefaults Observer + +- (void)observeAppUserIDChanges { + [self.notificationCenter addObserver:self + selector:@selector(handleUserDefaultsChanged:) + name:NSUserDefaultsDidChangeNotification + object:self.userDefaults]; +} + +- (void)handleUserDefaultsChanged:(NSNotification *)notification { + if (self.appUserIDHasBeenSet && notification.object == self.userDefaults) { + if (!self.cachedAppUserID) { + NSAssert(false, @"[Purchases] - Cached appUserID has been deleted from user defaults. " + "This leaves the SDK in an undetermined state. Please make sure that RevenueCat " + "entries in user defaults don't get deleted by anything other than the SDK. " + "More info: https://support.revenuecat.com/hc/en-us/articles/360047927393"); + } + } +} + +- (void)dealloc { + [self.notificationCenter removeObserver:self]; +} + +#pragma mark - appUserID + +- (nullable NSString *)cachedLegacyAppUserID { + return [self.userDefaults stringForKey:RCLegacyGeneratedAppUserDefaultsKey]; +} + +- (nullable NSString *)cachedAppUserID { + return [self.userDefaults stringForKey:RCAppUserDefaultsKey]; +} + +- (void)cacheAppUserID:(NSString *)appUserID { + @synchronized (self) { + [self.userDefaults setObject:appUserID forKey:RCAppUserDefaultsKey]; + self.appUserIDHasBeenSet = YES; + } +} + +- (void)clearCachesForAppUserID:(NSString *)oldAppUserID andSaveNewUserID:(NSString *)newUserID { + @synchronized (self) { + [self.userDefaults removeObjectForKey:RCLegacyGeneratedAppUserDefaultsKey]; + [self.userDefaults removeObjectForKey:[self purchaserInfoUserDefaultCacheKeyForAppUserID:oldAppUserID]]; + [self clearPurchaserInfoCacheTimestampForAppUserID:oldAppUserID]; + [self clearOfferingsCache]; + + [self deleteAttributesIfSyncedForAppUserID:oldAppUserID]; + + [self cacheAppUserID:newUserID]; + } +} + +#pragma mark - purchaserInfo + +- (nullable NSData *)cachedPurchaserInfoDataForAppUserID:(NSString *)appUserID { + return [self.userDefaults dataForKey:[self purchaserInfoUserDefaultCacheKeyForAppUserID:appUserID]]; +} + +- (void)cachePurchaserInfo:(NSData *)data forAppUserID:(NSString *)appUserID { + @synchronized (self) { + [self.userDefaults setObject:data + forKey:[self purchaserInfoUserDefaultCacheKeyForAppUserID:appUserID]]; + [self setPurchaserInfoCacheTimestampToNowForAppUserID:appUserID]; + } +} + +- (BOOL)isPurchaserInfoCacheStaleForAppUserID:(NSString *)appUserID isAppBackgrounded:(BOOL)isAppBackgrounded { + NSDate * _Nullable purchaserInfoCachesLastUpdated = [self purchaserInfoCachesLastUpdatedForAppUserID:appUserID]; + if (!purchaserInfoCachesLastUpdated) { + return YES; + } + NSTimeInterval timeSinceLastCheck = -[purchaserInfoCachesLastUpdated timeIntervalSinceNow]; + int cacheDurationInSeconds = [self cacheDurationInSecondsWithIsAppBackgrounded:isAppBackgrounded]; + return timeSinceLastCheck >= cacheDurationInSeconds; +} + +- (int)cacheDurationInSecondsWithIsAppBackgrounded:(BOOL)isAppBackgrounded { + return (isAppBackgrounded ? cacheDurationInSecondsInBackground + : cacheDurationInSecondsInForeground); +} + +- (void)clearPurchaserInfoCacheTimestampForAppUserID:(NSString *)appUserID { + NSString *cacheKey = [self purchaserInfoLastUpdatedCacheKeyForAppUserID:appUserID]; + [self.userDefaults removeObjectForKey:cacheKey]; +} + +- (void)clearPurchaserInfoCacheForAppUserID:(NSString *)appUserID { + @synchronized (self) { + [self clearPurchaserInfoCacheTimestampForAppUserID:appUserID]; + [self.userDefaults removeObjectForKey:[self purchaserInfoUserDefaultCacheKeyForAppUserID:appUserID]]; + } +} + +- (void)setPurchaserInfoCacheTimestampToNowForAppUserID:(NSString *)appUserID { + [self setPurchaserInfoCacheTimestamp:[NSDate date] forAppUserID:appUserID]; +} + +- (void)setPurchaserInfoCacheTimestamp:(NSDate *)timestamp forAppUserID:(NSString *)appUserID { + NSString *cacheKey = [self purchaserInfoLastUpdatedCacheKeyForAppUserID:appUserID]; + [self.userDefaults setObject:timestamp forKey:cacheKey]; +} + +- (nullable NSDate *)purchaserInfoCachesLastUpdatedForAppUserID:(NSString *)appUserID { + NSString *cacheKey = [self purchaserInfoLastUpdatedCacheKeyForAppUserID:appUserID]; + return (NSDate * _Nullable) [self.userDefaults objectForKey:cacheKey]; +} + +- (NSString *)purchaserInfoUserDefaultCacheKeyForAppUserID:(NSString *)appUserID { + return [RCPurchaserInfoAppUserDefaultsKeyBase stringByAppendingString:appUserID]; +} + +- (NSString *)purchaserInfoLastUpdatedCacheKeyForAppUserID:(NSString *)appUserID { + return [RCPurchaserInfoLastUpdatedKeyBase stringByAppendingString:appUserID]; +} + +#pragma mark - offerings + +- (nullable RCOfferings *)cachedOfferings { + return self.offeringsCachedObject.cachedInstance; +} + +- (void)cacheOfferings:(RCOfferings *)offerings { + [self.offeringsCachedObject cacheInstance:offerings]; +} + +- (BOOL)isOfferingsCacheStaleWithIsAppBackgrounded:(BOOL)isAppBackgrounded { + int cacheDurationInSeconds = [self cacheDurationInSecondsWithIsAppBackgrounded:isAppBackgrounded]; + return [self.offeringsCachedObject isCacheStaleWithDurationInSeconds:cacheDurationInSeconds]; +} + +- (void)clearOfferingsCacheTimestamp { + [self.offeringsCachedObject clearCacheTimestamp]; +} + +- (void)setOfferingsCacheTimestampToNow { + [self.offeringsCachedObject updateCacheTimestampWithDate:[NSDate date]]; +} + +- (void)clearOfferingsCache { + [self.offeringsCachedObject clearCache]; +} + +#pragma mark - subscriber attributes + +- (void)storeSubscriberAttribute:(RCSubscriberAttribute *)attribute + appUserID:(NSString *)appUserID { + [self storeSubscriberAttributes:@{attribute.key: attribute} + appUserID:appUserID]; +} + +- (void)storeSubscriberAttributes:(RCSubscriberAttributeDict)attributesByKey + appUserID:(NSString *)appUserID { + if (attributesByKey.count == 0) { + return; + } + + @synchronized (self) { + NSMutableDictionary *groupedSubscriberAttributes = self.storedAttributesForAllUsers.mutableCopy; + NSMutableDictionary + *subscriberAttributesForAppUserID = ((NSDictionary *) groupedSubscriberAttributes[appUserID] ?: @{}) + .mutableCopy; + + for (NSString *key in attributesByKey) { + subscriberAttributesForAppUserID[key] = attributesByKey[key].asDictionary; + } + + groupedSubscriberAttributes[appUserID] = subscriberAttributesForAppUserID; + [self storeAttributesForAllUsers:groupedSubscriberAttributes]; + } +} + +- (NSDictionary *)subscriberAttributesForAppUserID:(NSString *)appUserID { + return self.storedAttributesForAllUsers[appUserID] ?: @{}; +} + +- (nullable RCSubscriberAttribute *)subscriberAttributeWithKey:(NSString *)attributeKey + appUserID:(NSString *)appUserID { + @synchronized (self) { + RCSubscriberAttributeDict + allSubscriberAttributesByKey = [self storedSubscriberAttributesForAppUserID:appUserID]; + return allSubscriberAttributesByKey[attributeKey]; + } +} + +- (RCSubscriberAttributeDict)unsyncedAttributesByKeyForAppUserID:(NSString *)appUserID { + @synchronized (self) { + RCSubscriberAttributeDict + allSubscriberAttributesByKey = [self storedSubscriberAttributesForAppUserID:appUserID]; + RCSubscriberAttributeMutableDict unsyncedAttributesByKey = [[NSMutableDictionary alloc] init]; + for (NSString *key in allSubscriberAttributesByKey) { + RCSubscriberAttribute *attribute = allSubscriberAttributesByKey[key]; + if (!attribute.isSynced) { + unsyncedAttributesByKey[attribute.key] = attribute; + } + } + return unsyncedAttributesByKey; + } +} + +- (RCSubscriberAttributeDict)storedSubscriberAttributesForAppUserID:(NSString *)appUserID { + NSDictionary + *allAttributesObjectsByKey = [self subscriberAttributesForAppUserID:appUserID]; + RCSubscriberAttributeMutableDict allSubscriberAttributesByKey = [[NSMutableDictionary alloc] init]; + + for (NSString *key in allAttributesObjectsByKey) { + NSDictionary *attributeAsDict = + (NSDictionary *) allAttributesObjectsByKey[key]; + allSubscriberAttributesByKey[key] = [[RCSubscriberAttribute alloc] + initWithDictionary:attributeAsDict]; + } + return allSubscriberAttributesByKey; +} + +- (NSUInteger)numberOfUnsyncedAttributesForAppUserID:(NSString *)appUserID { + return [self unsyncedAttributesByKeyForAppUserID:appUserID].count; +} + +- (NSDictionary *)storedAttributesForAllUsers { + return [self.userDefaults dictionaryForKey:RCSubscriberAttributesKey] ?: @{}; +} + +- (void)storeAttributesForAllUsers:(NSMutableDictionary *)groupedAttributes { + [self.userDefaults setObject:groupedAttributes forKey:RCSubscriberAttributesKey]; +} + +- (NSDictionary *)unsyncedAttributesForAllUsers { + NSDictionary *attributesDict = self.storedAttributesForAllUsers; + NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init]; + + for (NSString *appUserID in attributesDict.allKeys) { + NSDictionary *attributesDictForUser = (NSDictionary *) attributesDict[appUserID]; + NSMutableDictionary *attributesForUser = [[NSMutableDictionary alloc] init]; + + for (NSString *attributeKey in attributesDictForUser.allKeys) { + NSDictionary *attributeDict = (NSDictionary *) attributesDictForUser[attributeKey]; + RCSubscriberAttribute *attribute = [[RCSubscriberAttribute alloc] initWithDictionary:attributeDict]; + if (!attribute.isSynced) { + attributesForUser[attributeKey] = attribute; + } + } + if (attributesForUser.count > 0) { + attributes[appUserID] = attributesForUser; + } + } + return attributes; +} + +- (void)deleteAttributesIfSyncedForAppUserID:(NSString *)appUserID { + @synchronized (self) { + if ([self numberOfUnsyncedAttributesForAppUserID:appUserID] != 0) { + return; + } + NSMutableDictionary + *groupedAttributes = self.storedAttributesForAllUsers.mutableCopy; + [groupedAttributes removeObjectForKey:appUserID]; + [self storeAttributesForAllUsers:groupedAttributes]; + } +} + +# pragma mark - subscriber attributes migration from per-user key to grouped key + +- (void)cleanupSubscriberAttributes { + @synchronized (self) { + [self migrateSubscriberAttributes]; + [self deleteSyncedSubscriberAttributesForOtherUsers]; + } +} + +- (void)migrateSubscriberAttributes { + NSArray *appUserIDsWithLegacyAttributes = [self appUserIDsWithLegacyAttributes]; + NSMutableDictionary *attributesInNewFormat = self.storedAttributesForAllUsers.mutableCopy; + for (NSString *appUserID in appUserIDsWithLegacyAttributes) { + NSDictionary *legacyAttributes = [self valueForLegacySubscriberAttributes:appUserID] ?: @{}; + NSDictionary *existingAttributes = self.storedAttributesForAllUsers[appUserID] ?: @{}; + + NSMutableDictionary *allAttributesForUser = legacyAttributes.mutableCopy; + [allAttributesForUser addEntriesFromDictionary:existingAttributes]; + + attributesInNewFormat[appUserID] = allAttributesForUser; + + NSString *legacyAttributesKey = [self legacySubscriberAttributesCacheKeyForAppUserID:appUserID]; + [self.userDefaults removeObjectForKey:legacyAttributesKey]; + } + [self storeAttributesForAllUsers:attributesInNewFormat]; +} + +- (void)deleteSyncedSubscriberAttributesForOtherUsers { + NSDictionary *allStoredAttributes = self.storedAttributesForAllUsers; + NSMutableDictionary *filteredAttributes = [[NSMutableDictionary alloc] init]; + + NSString *currentAppUserID = self.cachedAppUserID; + NSParameterAssert(currentAppUserID); + filteredAttributes[currentAppUserID] = allStoredAttributes[currentAppUserID]; + + for (NSString *appUserID in allStoredAttributes.allKeys) { + if (![appUserID isEqualToString:currentAppUserID]) { + NSMutableDictionary *unsyncedAttributesForUser = [[NSMutableDictionary alloc] init]; + for (NSString *attributeKey in allStoredAttributes[appUserID].allKeys) { + NSDictionary + *storedAttributesForUser = allStoredAttributes[appUserID][attributeKey]; + + RCSubscriberAttribute *attribute = [[RCSubscriberAttribute alloc] + initWithDictionary:storedAttributesForUser]; + if (!attribute.isSynced) { + unsyncedAttributesForUser[attributeKey] = storedAttributesForUser; + } + } + if (unsyncedAttributesForUser.count > 0) { + filteredAttributes[appUserID] = unsyncedAttributesForUser; + } + } + } + + [self storeAttributesForAllUsers:filteredAttributes]; +} + +- (NSArray *)appUserIDsWithLegacyAttributes { + NSMutableArray *appUserIDsWithLegacyAttributes = [[NSMutableArray alloc] init]; + NSDictionary *userDefaultsDict = [self.userDefaults dictionaryRepresentation]; + for (NSString *key in userDefaultsDict.allKeys) { + if ([key containsString:RCLegacySubscriberAttributesKeyBase]) { + NSString *appUserID = [key stringByReplacingOccurrencesOfString:RCLegacySubscriberAttributesKeyBase + withString:@""]; + [appUserIDsWithLegacyAttributes addObject:appUserID]; + } + } + return appUserIDsWithLegacyAttributes; +} + +- (nullable NSDictionary *)valueForLegacySubscriberAttributes:(NSString *)appUserID { + return [self.userDefaults dictionaryForKey:[self legacySubscriberAttributesCacheKeyForAppUserID:appUserID]]; +} + +- (NSString *)legacySubscriberAttributesCacheKeyForAppUserID:(NSString *)appUserID { + NSString *attributeKey = [NSString stringWithFormat:@"%@", appUserID]; + return [RCLegacySubscriberAttributesKeyBase stringByAppendingString:attributeKey]; +} + +#pragma mark - attribution + +- (nullable NSDictionary *)latestNetworkAndAdvertisingIdsSentForAppUserID:(NSString *)appUserID { + NSString *cacheKey = [self attributionDataCacheKeyForAppForAppUserID:appUserID]; + return [self.userDefaults objectForKey:cacheKey]; +} + +- (void)setLatestNetworkAndAdvertisingIdsSent:(nullable NSDictionary *)latestNetworkAndAdvertisingIdsSent + forAppUserID:(nullable NSString *)appUserID { + NSString *cacheKey = [self attributionDataCacheKeyForAppForAppUserID:appUserID]; + [self.userDefaults setObject:latestNetworkAndAdvertisingIdsSent + forKey:cacheKey]; +} + +- (void)clearLatestNetworkAndAdvertisingIdsSentForAppUserID:(nullable NSString *)appUserID { + NSString *cacheKey = [self attributionDataCacheKeyForAppForAppUserID:appUserID]; + [self.userDefaults removeObjectForKey:cacheKey]; +} + +- (NSString *)attributionDataCacheKeyForAppForAppUserID:(NSString *)appUserID { + return [RCAttributionDataDefaultsKeyBase stringByAppendingString:appUserID]; +} + +@end + diff --git a/Pods/Purchases/Purchases/Caching/RCInMemoryCachedObject.h b/Pods/Purchases/Purchases/Caching/RCInMemoryCachedObject.h new file mode 100644 index 0000000..bbc7cc6 --- /dev/null +++ b/Pods/Purchases/Purchases/Caching/RCInMemoryCachedObject.h @@ -0,0 +1,28 @@ +// +// RCInMemoryCachedObject.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2020 Purchases. All rights reserved. +// + +NS_ASSUME_NONNULL_BEGIN + +@interface RCInMemoryCachedObject> : NSObject + +- (BOOL)isCacheStaleWithDurationInSeconds:(int)durationInSeconds; + +- (void)clearCacheTimestamp; + +- (void)clearCache; + +- (void)updateCacheTimestampWithDate:(NSDate *)date; + +- (void)cacheInstance:(ObjectType)instance; + +- (nullable ObjectType)cachedInstance; + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Caching/RCInMemoryCachedObject.m b/Pods/Purchases/Purchases/Caching/RCInMemoryCachedObject.m new file mode 100644 index 0000000..a496343 --- /dev/null +++ b/Pods/Purchases/Purchases/Caching/RCInMemoryCachedObject.m @@ -0,0 +1,60 @@ +// +// RCInMemoryCachedObject.m +// Purchases +// +// Created by RevenueCat. +// Copyright © 2020 Purchases. All rights reserved. +// +#import + +#import "RCInMemoryCachedObject.h" +#import "RCInMemoryCachedObject+Protected.h" + +NS_ASSUME_NONNULL_BEGIN + + +@interface RCInMemoryCachedObject () + +@property (nonatomic, nullable) NSDate *lastUpdatedAt; +@property (nonatomic, nullable) id cachedInstance; + +@end + + +@implementation RCInMemoryCachedObject + +- (BOOL)isCacheStaleWithDurationInSeconds:(int)durationInSeconds { + if (self.lastUpdatedAt == nil) { + return YES; + } + + NSTimeInterval timeSinceLastCheck = -1 * [self.lastUpdatedAt timeIntervalSinceNow]; + return timeSinceLastCheck >= durationInSeconds; +} + +- (void)clearCacheTimestamp { + self.lastUpdatedAt = nil; +} + +- (void)clearCache { + @synchronized(self) { + [self clearCacheTimestamp]; + self.cachedInstance = nil; + } +} + +- (void)cacheInstance:(id)instance { + @synchronized (self) { + self.cachedInstance = instance; + self.lastUpdatedAt = [NSDate date]; + } +} + +- (void)updateCacheTimestampWithDate:(NSDate *)date { + self.lastUpdatedAt = date; +} + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/FoundationExtensions/NSData+RCExtensions.h b/Pods/Purchases/Purchases/FoundationExtensions/NSData+RCExtensions.h new file mode 100644 index 0000000..1fd547f --- /dev/null +++ b/Pods/Purchases/Purchases/FoundationExtensions/NSData+RCExtensions.h @@ -0,0 +1,18 @@ +// +// Created by Andrés Boedo on 3/4/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + + +@interface NSData (RCExtensions) + +- (NSString *)asString; + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/FoundationExtensions/NSData+RCExtensions.m b/Pods/Purchases/Purchases/FoundationExtensions/NSData+RCExtensions.m new file mode 100644 index 0000000..bb9ae14 --- /dev/null +++ b/Pods/Purchases/Purchases/FoundationExtensions/NSData+RCExtensions.m @@ -0,0 +1,29 @@ +// +// Created by Andrés Boedo on 3/4/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import "NSData+RCExtensions.h" + +NS_ASSUME_NONNULL_BEGIN + + +@implementation NSData (RCExtensions) + +- (NSString *)asString { + NSMutableString *deviceTokenString = [NSMutableString string]; + [self enumerateByteRangesUsingBlock:^(const void *bytes, + NSRange byteRange, + BOOL *stop) { + + for (NSUInteger i = 0; i < byteRange.length; ++i) { + [deviceTokenString appendFormat:@"%02x", ((uint8_t *) bytes)[i]]; + } + }]; + return deviceTokenString; +} + +NS_ASSUME_NONNULL_END + + +@end diff --git a/Pods/Purchases/Purchases/FoundationExtensions/NSDate+RCExtensions.h b/Pods/Purchases/Purchases/FoundationExtensions/NSDate+RCExtensions.h new file mode 100644 index 0000000..1b88312 --- /dev/null +++ b/Pods/Purchases/Purchases/FoundationExtensions/NSDate+RCExtensions.h @@ -0,0 +1,18 @@ +// +// Created by Andrés Boedo on 3/4/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + + +@interface NSDate (RCExtensions) + +- (UInt64)millisecondsSince1970; + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/FoundationExtensions/NSDate+RCExtensions.m b/Pods/Purchases/Purchases/FoundationExtensions/NSDate+RCExtensions.m new file mode 100644 index 0000000..716a26c --- /dev/null +++ b/Pods/Purchases/Purchases/FoundationExtensions/NSDate+RCExtensions.m @@ -0,0 +1,20 @@ +// +// Created by Andrés Boedo on 3/4/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import "NSDate+RCExtensions.h" + +NS_ASSUME_NONNULL_BEGIN + + +@implementation NSDate (RCExtensions) + +- (UInt64)millisecondsSince1970 { + return (UInt64)([self timeIntervalSince1970] * 1000.0); +} + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/FoundationExtensions/NSDictionary+RCExtensions.h b/Pods/Purchases/Purchases/FoundationExtensions/NSDictionary+RCExtensions.h new file mode 100644 index 0000000..eca4e26 --- /dev/null +++ b/Pods/Purchases/Purchases/FoundationExtensions/NSDictionary+RCExtensions.h @@ -0,0 +1,21 @@ +// +// NSDictionary+RCExtensions.h +// Purchases +// +// Created by Andrés Boedo on 9/29/20. +// Copyright © 2020 Purchases. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + + +@interface NSDictionary (RCExtensions) + +- (NSDictionary *)removingNSNullValues; + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/FoundationExtensions/NSDictionary+RCExtensions.m b/Pods/Purchases/Purchases/FoundationExtensions/NSDictionary+RCExtensions.m new file mode 100644 index 0000000..306c257 --- /dev/null +++ b/Pods/Purchases/Purchases/FoundationExtensions/NSDictionary+RCExtensions.m @@ -0,0 +1,34 @@ +// +// NSDictionary+RCExtensions.m +// Purchases +// +// Created by Andrés Boedo on 9/29/20. +// Copyright © 2020 Purchases. All rights reserved. +// + +#import "NSDictionary+RCExtensions.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation NSDictionary (RCExtensions) + +- (NSDictionary *)removingNSNullValues { + NSMutableDictionary *result = [[NSMutableDictionary alloc] init]; + + NSEnumerator *enumerator = [self keyEnumerator]; + id key; + + while ((key = enumerator.nextObject)) { + id value = self[key]; + if (![value isKindOfClass:NSNull.class]) { + result[key] = value; + } + } + + return result; +} + +NS_ASSUME_NONNULL_END + + +@end diff --git a/Pods/Purchases/Purchases/FoundationExtensions/NSError+RCExtensions.h b/Pods/Purchases/Purchases/FoundationExtensions/NSError+RCExtensions.h new file mode 100644 index 0000000..385de4e --- /dev/null +++ b/Pods/Purchases/Purchases/FoundationExtensions/NSError+RCExtensions.h @@ -0,0 +1,19 @@ +// +// Created by Andrés Boedo on 2/24/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + + +@interface NSError (RCExtensions) + +- (BOOL)successfullySynced; +- (nullable NSDictionary *)subscriberAttributesErrors; + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/FoundationExtensions/NSError+RCExtensions.m b/Pods/Purchases/Purchases/FoundationExtensions/NSError+RCExtensions.m new file mode 100644 index 0000000..bed3c1c --- /dev/null +++ b/Pods/Purchases/Purchases/FoundationExtensions/NSError+RCExtensions.m @@ -0,0 +1,36 @@ +// +// Created by Andrés Boedo on 2/24/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import "NSError+RCExtensions.h" +#import "RCPurchasesErrors.h" +#import "RCBackend.h" + +NS_ASSUME_NONNULL_BEGIN + + +@implementation NSError (RCExtensions) + +- (BOOL)successfullySynced { + if (self.code == RCNetworkError) { + return NO; + } + + if (self.userInfo[RCSuccessfullySyncedKey] == nil) { + return NO; + } + + NSNumber *successfullySyncedNumber = self.userInfo[RCSuccessfullySyncedKey]; + + return successfullySyncedNumber.boolValue; +} + +- (nullable NSDictionary *)subscriberAttributesErrors { + return self.userInfo[RCAttributeErrorsKey]; +} + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/FoundationExtensions/NSLocale+RCExtensions.h b/Pods/Purchases/Purchases/FoundationExtensions/NSLocale+RCExtensions.h new file mode 100644 index 0000000..3158929 --- /dev/null +++ b/Pods/Purchases/Purchases/FoundationExtensions/NSLocale+RCExtensions.h @@ -0,0 +1,15 @@ +// +// NSLocale+RCExtensions.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import + +@interface NSLocale (RCExtensions) + +- (nullable NSString *)rc_currencyCode; + +@end diff --git a/Pods/Purchases/Purchases/FoundationExtensions/NSLocale+RCExtensions.m b/Pods/Purchases/Purchases/FoundationExtensions/NSLocale+RCExtensions.m new file mode 100644 index 0000000..dca00d4 --- /dev/null +++ b/Pods/Purchases/Purchases/FoundationExtensions/NSLocale+RCExtensions.m @@ -0,0 +1,21 @@ +// +// NSLocale+RCExtensions.m +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import "NSLocale+RCExtensions.h" + +@implementation NSLocale (RCExtensions) + +- (nullable NSString *)rc_currencyCode { + if(@available(iOS 10.0, macOS 10.12, tvos 10.0, macCatalyst 13.0, *)) { + return self.currencyCode; + } else { + return [self objectForKey:NSLocaleCurrencyCode]; + } +} + +@end diff --git a/Pods/Purchases/Purchases/Misc/RCCrossPlatformSupport.h b/Pods/Purchases/Purchases/Misc/RCCrossPlatformSupport.h new file mode 100644 index 0000000..89482cc --- /dev/null +++ b/Pods/Purchases/Purchases/Misc/RCCrossPlatformSupport.h @@ -0,0 +1,63 @@ +// +// RCCrossPlatformSupport.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#if TARGET_OS_IOS || TARGET_OS_TV +#define APP_DID_BECOME_ACTIVE_NOTIFICATION_NAME UIApplicationDidBecomeActiveNotification +#define APP_WILL_RESIGN_ACTIVE_NOTIFICATION_NAME UIApplicationWillResignActiveNotification +#elif TARGET_OS_OSX +#define APP_DID_BECOME_ACTIVE_NOTIFICATION_NAME NSApplicationDidBecomeActiveNotification +#define APP_WILL_RESIGN_ACTIVE_NOTIFICATION_NAME NSApplicationWillResignActiveNotification +#elif TARGET_OS_WATCH +#define APP_DID_BECOME_ACTIVE_NOTIFICATION_NAME NSExtensionHostDidBecomeActiveNotification +#define APP_WILL_RESIGN_ACTIVE_NOTIFICATION_NAME NSExtensionHostWillResignActiveNotification +#endif + +#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_MACCATALYST +#import +#elif TARGET_OS_OSX +#import +#elif TARGET_OS_WATCH +#import +#import +#endif + +#if TARGET_OS_MACCATALYST +#define PLATFORM_HEADER @"uikitformac" +#elif TARGET_OS_IOS +#define PLATFORM_HEADER @"iOS" +#elif TARGET_OS_OSX +#define PLATFORM_HEADER @"macOS" +#elif TARGET_OS_WATCH +#define PLATFORM_HEADER @"watchOS" +#elif TARGET_OS_TV +#define PLATFORM_HEADER @"tvOS" +#endif + +// Should match available platforms in +// https://developer.apple.com/documentation/uikit/uidevice?language=objc +#if TARGET_OS_IOS || TARGET_OS_TV +#define UI_DEVICE_AVAILABLE 1 +#else +#define UI_DEVICE_AVAILABLE 0 +#endif + +// Should match available platforms in +// https://developer.apple.com/documentation/iad/adclient?language=objc +#if TARGET_OS_IOS +#define AD_CLIENT_AVAILABLE 1 +#else +#define AD_CLIENT_AVAILABLE 0 +#endif + +// Should match available platforms in +// https://developer.apple.com/documentation/storekit/skpaymenttransactionobserver/2877502-paymentqueue?language=objc +#if TARGET_OS_TV || (TARGET_OS_IOS && !TARGET_OS_MACCATALYST) +#define PURCHASES_INITIATED_FROM_APP_STORE_AVAILABLE 1 +#else +#define PURCHASES_INITIATED_FROM_APP_STORE_AVAILABLE 0 +#endif diff --git a/Pods/Purchases/Purchases/Misc/RCDateProvider.h b/Pods/Purchases/Purchases/Misc/RCDateProvider.h new file mode 100644 index 0000000..75459d0 --- /dev/null +++ b/Pods/Purchases/Purchases/Misc/RCDateProvider.h @@ -0,0 +1,18 @@ +// +// Created by RevenueCat on 2/27/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + + +@interface RCDateProvider : NSObject + +- (NSDate *)now; + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Misc/RCDateProvider.m b/Pods/Purchases/Purchases/Misc/RCDateProvider.m new file mode 100644 index 0000000..8ecaa83 --- /dev/null +++ b/Pods/Purchases/Purchases/Misc/RCDateProvider.m @@ -0,0 +1,21 @@ +// +// Created by RevenueCat on 2/27/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import "RCDateProvider.h" + +NS_ASSUME_NONNULL_BEGIN + + +@implementation RCDateProvider + +- (NSDate *)now { + return NSDate.date; +} + + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Misc/RCISOPeriodFormatter.h b/Pods/Purchases/Purchases/Misc/RCISOPeriodFormatter.h new file mode 100644 index 0000000..6d74e3a --- /dev/null +++ b/Pods/Purchases/Purchases/Misc/RCISOPeriodFormatter.h @@ -0,0 +1,20 @@ +// +// RCISOPeriodFormatter.h +// Purchases +// +// Created by Andrés Boedo on 5/26/20. +// Copyright © 2020 Purchases. All rights reserved. +// + +#import +#import + + + +API_AVAILABLE(ios(11.2), macos(10.13.2), tvos(11.2)) +NS_SWIFT_NAME(Purchases.ISOPeriodFormatter) +@interface RCISOPeriodFormatter: NSObject + +- (NSString *)stringFromProductSubscriptionPeriod:(SKProductSubscriptionPeriod *)period; + +@end diff --git a/Pods/Purchases/Purchases/Misc/RCISOPeriodFormatter.m b/Pods/Purchases/Purchases/Misc/RCISOPeriodFormatter.m new file mode 100644 index 0000000..0ff4d24 --- /dev/null +++ b/Pods/Purchases/Purchases/Misc/RCISOPeriodFormatter.m @@ -0,0 +1,35 @@ +// +// RCISOPeriodFormatter.h +// Purchases +// +// Created by Andrés Boedo on 5/26/20. +// Copyright © 2020 Purchases. All rights reserved. +// + +#import +#import +#import "RCISOPeriodFormatter.h" + + +API_AVAILABLE(ios(11.2), macos(10.13.2), tvos(11.2)) +@implementation RCISOPeriodFormatter + +- (NSString *)stringFromProductSubscriptionPeriod:(SKProductSubscriptionPeriod *)period { + NSString *unitString = [self periodFromUnit:period.unit]; + return [NSString stringWithFormat:@"P%@%@", @(period.numberOfUnits), unitString]; +} + +- (NSString *)periodFromUnit:(SKProductPeriodUnit)subscriptionPeriodUnit { + switch (subscriptionPeriodUnit) { + case SKProductPeriodUnitDay: + return @"D"; + case SKProductPeriodUnitWeek: + return @"W"; + case SKProductPeriodUnitMonth: + return @"M"; + case SKProductPeriodUnitYear: + return @"Y"; + } +} + +@end diff --git a/Pods/Purchases/Purchases/Misc/RCLogUtils.h b/Pods/Purchases/Purchases/Misc/RCLogUtils.h new file mode 100644 index 0000000..6d14c17 --- /dev/null +++ b/Pods/Purchases/Purchases/Misc/RCLogUtils.h @@ -0,0 +1,19 @@ +// +// RCLogUtils.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +void RCSetShowDebugLogs(BOOL showDebugLogs); +BOOL RCShowDebugLogs(void); +void RCDebugLog(NSString *format, ...); +void RCErrorLog(NSString *format, ...); +void RCLog(NSString *format, ...); + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Misc/RCLogUtils.m b/Pods/Purchases/Purchases/Misc/RCLogUtils.m new file mode 100644 index 0000000..7086472 --- /dev/null +++ b/Pods/Purchases/Purchases/Misc/RCLogUtils.m @@ -0,0 +1,59 @@ +// +// RCLogUtils.m +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import "RCLogUtils.h" + +static BOOL RCShouldShowLogs = NO; + +void RCSetShowDebugLogs(BOOL showDebugLogs) +{ + RCShouldShowLogs = showDebugLogs; +} + +BOOL RCShowDebugLogs() +{ + return RCShouldShowLogs; +} + +void RCDebugLog(NSString *format, ...) +{ + if (!RCShouldShowLogs) + return; + + va_list args; + va_start(args, format); + + format = [NSString stringWithFormat:@"[Purchases] - DEBUG: %@", format]; + + NSLogv(format, args); + va_end(args); +} + +void RCErrorLog(NSString *format, ...) +{ + if (!RCShouldShowLogs) + return; + + va_list args; + va_start(args, format); + + format = [NSString stringWithFormat:@"[Purchases] - ERROR: %@", format]; + + NSLogv(format, args); + va_end(args); +} + +void RCLog(NSString *format, ...) +{ + va_list args; + va_start(args, format); + + format = [NSString stringWithFormat:@"[Purchases] - INFO: %@", format]; + NSLogv(format, args); + va_end(args); +} diff --git a/Pods/Purchases/Purchases/Misc/RCSystemInfo.h b/Pods/Purchases/Purchases/Misc/RCSystemInfo.h new file mode 100644 index 0000000..f34d40f --- /dev/null +++ b/Pods/Purchases/Purchases/Misc/RCSystemInfo.h @@ -0,0 +1,38 @@ +// +// Created by Andrés Boedo on 5/7/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + + +@interface RCSystemInfo : NSObject + +- (instancetype)initWithPlatformFlavor:(nullable NSString *)platformFlavor + platformFlavorVersion:(nullable NSString *)platformFlavorVersion + finishTransactions:(BOOL)finishTransactions NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; + +@property(nonatomic, assign) BOOL finishTransactions; +@property(nonatomic, copy, readonly) NSString *platformFlavor; +@property(nonatomic, copy, readonly) NSString *platformFlavorVersion; +@property(class, nonatomic, assign) BOOL forceUniversalAppStore; + +- (void)isApplicationBackgroundedWithCompletion:(void(^)(BOOL))completion; // calls completion on the main thread + ++ (BOOL)isSandbox; ++ (NSString *)frameworkVersion; ++ (NSString *)systemVersion; ++ (NSString *)appVersion; ++ (NSString *)platformHeader; + ++ (NSURL *)serverHostURL; ++ (nullable NSURL *)proxyURL; ++ (void)setProxyURL:(nullable NSURL *)newProxyURL; + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Misc/RCSystemInfo.m b/Pods/Purchases/Purchases/Misc/RCSystemInfo.m new file mode 100644 index 0000000..11af01b --- /dev/null +++ b/Pods/Purchases/Purchases/Misc/RCSystemInfo.m @@ -0,0 +1,137 @@ +// +// Created by Andrés Boedo on 5/7/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import "RCSystemInfo.h" +#import "RCCrossPlatformSupport.h" +#import "RCLogUtils.h" + +NS_ASSUME_NONNULL_BEGIN + + +@interface RCSystemInfo() + +@property(nonatomic, copy, nullable) NSString *platformFlavor; +@property(nonatomic, copy, nullable) NSString *platformFlavorVersion; + +@end + +@implementation RCSystemInfo + +NSString *const defaultServerHostName = @"https://api.revenuecat.com"; +static NSURL * _Nullable proxyURL; +static BOOL _forceUniversalAppStore = NO; + +- (instancetype)initWithPlatformFlavor:(nullable NSString *)platformFlavor + platformFlavorVersion:(nullable NSString *)platformFlavorVersion + finishTransactions:(BOOL)finishTransactions { + if (self = [super init]) { + NSAssert((platformFlavor && platformFlavorVersion) || (!platformFlavor && !platformFlavorVersion), + @"RCSystemInfo initialized with non-matching platform flavor and platform flavor versions!"); + + if (!platformFlavor) { + platformFlavor = @"native"; + } + + self.platformFlavor = platformFlavor; + self.platformFlavorVersion = platformFlavorVersion; + self.finishTransactions = finishTransactions; + } + return self; +} + ++ (BOOL)isSandbox { + NSURL *url = NSBundle.mainBundle.appStoreReceiptURL; + NSString *receiptURLString = url.path; + return ([receiptURLString rangeOfString:@"sandboxReceipt"].location != NSNotFound); +} + ++ (NSString *)frameworkVersion { + return @"3.8.0"; +} + ++ (NSString *)systemVersion { + NSProcessInfo *info = [[NSProcessInfo alloc] init]; + return info.operatingSystemVersionString; +} + ++ (NSString *)appVersion { + NSString *version = NSBundle.mainBundle.infoDictionary[@"CFBundleShortVersionString"]; + return version ?: @""; +} + ++ (NSString *)platformHeader { + return self.forceUniversalAppStore ? @"iOS" : PLATFORM_HEADER; +} + ++ (NSURL *)defaultServerHostURL { + return [NSURL URLWithString:defaultServerHostName]; +} + ++ (NSURL *)serverHostURL { + return proxyURL ?: self.defaultServerHostURL; +} + ++ (nullable NSURL *)proxyURL { + return proxyURL; +} + ++ (BOOL)forceUniversalAppStore { + return _forceUniversalAppStore; +} + ++ (void)setForceUniversalAppStore:(BOOL)forceUniversalAppStore { + _forceUniversalAppStore = forceUniversalAppStore; +} + ++ (void)setProxyURL:(nullable NSURL *)newProxyURL { + proxyURL = newProxyURL; + if (newProxyURL) { + RCLog(@"Purchases is being configured using a proxy for RevenueCat with URL: %@", newProxyURL); + } +} + +- (void)isApplicationBackgroundedWithCompletion:(void(^)(BOOL))completion { + dispatch_async(dispatch_get_main_queue(), ^{ + BOOL isApplicationBackgrounded = self.isApplicationBackgrounded; + completion(isApplicationBackgrounded); + }); +} + +- (BOOL)isApplicationBackgrounded { +#if TARGET_OS_IOS + return self.isApplicationBackgroundedIOS; +#elif TARGET_OS_TV + return UIApplication.sharedApplication.applicationState == UIApplicationStateBackground; +#elif TARGET_OS_OSX + return NO; +#elif TARGET_OS_WATCH + return WKExtension.sharedExtension.applicationState == WKApplicationStateBackground; +#endif +} + +#if TARGET_OS_IOS +// iOS App extensions can't access UIApplication.sharedApplication, and will fail to compile if any calls to +// it are made. There are no pre-processor macros available to check if the code is running in an app extension, +// so we check if we're running in an app extension at runtime, and if not, we use KVC to call sharedApplication. +- (BOOL)isApplicationBackgroundedIOS { + if (self.isAppExtension) { + return YES; + } + NSString *sharedApplicationPropertyName = @"sharedApplication"; + + UIApplication *sharedApplication = [UIApplication valueForKey:sharedApplicationPropertyName]; + return sharedApplication.applicationState == UIApplicationStateBackground; +} + +- (BOOL)isAppExtension { + return [NSBundle.mainBundle.bundlePath hasSuffix:@".appex"]; +} + +#endif + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Networking/RCBackend.h b/Pods/Purchases/Purchases/Networking/RCBackend.h new file mode 100644 index 0000000..ac4c2f1 --- /dev/null +++ b/Pods/Purchases/Purchases/Networking/RCBackend.h @@ -0,0 +1,88 @@ +// +// RCBackend.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import +#import + +#import "RCSubscriberAttribute.h" +#import "RCProductInfo.h" +#import "RCAttributionNetwork.h" + + +NS_ASSUME_NONNULL_BEGIN + +@class RCPurchaserInfo, RCHTTPClient, RCIntroEligibility, RCPromotionalOffer, RCSubscriberAttribute, RCSystemInfo; + +extern NSErrorUserInfoKey const RCSuccessfullySyncedKey; +extern NSString * const RCAttributeErrorsKey; +extern NSString * const RCAttributeErrorsResponseKey; + +typedef void(^RCBackendPurchaserInfoResponseHandler)(RCPurchaserInfo * _Nullable, + NSError * _Nullable); + +typedef void(^RCIntroEligibilityResponseHandler)(NSDictionary *); + +typedef void(^RCOfferingsResponseHandler)(NSDictionary * _Nullable, NSError * _Nullable); + +typedef void(^RCOfferSigningResponseHandler)(NSString * _Nullable signature, + NSString * _Nullable keyIdentifier, + NSUUID * _Nullable nonce, + NSNumber * _Nullable timestamp, + NSError * _Nullable error); + +@interface RCBackend : NSObject + +- (nullable instancetype)initWithAPIKey:(NSString *)APIKey systemInfo:(RCSystemInfo *)systemInfo; + +- (nullable instancetype)initWithHTTPClient:(RCHTTPClient *)client + APIKey:(NSString *)APIKey; + +- (void) postReceiptData:(NSData *)data + appUserID:(NSString *)appUserID + isRestore:(BOOL)isRestore + productInfo:(nullable RCProductInfo *)productInfo +presentedOfferingIdentifier:(nullable NSString *)offeringIdentifier + observerMode:(BOOL)observerMode + subscriberAttributes:(nullable RCSubscriberAttributeDict)subscriberAttributesByKey + completion:(RCBackendPurchaserInfoResponseHandler)completion; + +- (void)getSubscriberDataWithAppUserID:(NSString *)appUserID + completion:(RCBackendPurchaserInfoResponseHandler)completion; + +- (void)getIntroEligibilityForAppUserID:(NSString *)appUserID + receiptData:(NSData *)receiptData + productIdentifiers:(NSArray *)productIdentifiers + completion:(RCIntroEligibilityResponseHandler)completion; + +- (void)getOfferingsForAppUserID:(NSString *)appUserID + completion:(RCOfferingsResponseHandler)completion; + +- (void)postAttributionData:(NSDictionary *)data + fromNetwork:(RCAttributionNetwork)network + forAppUserID:(NSString *)appUserID + completion:(nullable void (^)(NSError * _Nullable error))completion; + +- (void)createAliasForAppUserID:(NSString *)appUserID + withNewAppUserID:(NSString *)newAppUserID + completion:(void (^ _Nullable)(NSError * _Nullable error))completion; + +- (void)postOfferForSigning:(NSString *)offerIdentifier + withProductIdentifier:(NSString *)productIdentifier + subscriptionGroup:(NSString *)subscriptionGroup + receiptData:(NSData *)data + appUserID:(NSString *)applicationUsername + completion:(RCOfferSigningResponseHandler)completion; + +- (void)postSubscriberAttributes:(RCSubscriberAttributeDict)subscriberAttributes + appUserID:(NSString *)appUserID + completion:(nullable void (^)(NSError * _Nullable error))completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Networking/RCBackend.m b/Pods/Purchases/Purchases/Networking/RCBackend.m new file mode 100644 index 0000000..500df2c --- /dev/null +++ b/Pods/Purchases/Purchases/Networking/RCBackend.m @@ -0,0 +1,505 @@ +// +// RCBackend.m +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat, Inc. All rights reserved. +// + +#import "RCBackend.h" + +#import "RCHTTPClient.h" +#import "RCPurchaserInfo+Protected.h" +#import "RCIntroEligibility.h" +#import "RCIntroEligibility+Protected.h" +#import "RCPurchasesErrorUtils.h" +#import "RCPurchasesErrorUtils+Protected.h" +#import "RCLogUtils.h" +#import "RCSystemInfo.h" +#import "RCHTTPStatusCodes.h" + +#define RC_HAS_KEY(dictionary, key) (dictionary[key] == nil || dictionary[key] != [NSNull null]) +NSErrorUserInfoKey const RCSuccessfullySyncedKey = @"successfullySynced"; +NSString *const RCAttributeErrorsKey = @"attribute_errors"; +NSString *const RCAttributeErrorsResponseKey = @"attributes_error_response"; + +@interface RCBackend () + +@property (nonatomic) RCHTTPClient *httpClient; +@property (nonatomic) NSString *APIKey; + +@property (nonatomic) NSMutableDictionary *callbacksCache; + +@end + +@implementation RCBackend + +- (nullable instancetype)initWithAPIKey:(NSString *)APIKey systemInfo:(RCSystemInfo *)systemInfo { + RCHTTPClient *client = [[RCHTTPClient alloc] initWithSystemInfo:systemInfo]; + return [self initWithHTTPClient:client + APIKey:APIKey]; +} + +- (nullable instancetype)initWithHTTPClient:(RCHTTPClient *)client + APIKey:(NSString *)APIKey { + if (self = [super init]) { + self.httpClient = client; + self.APIKey = APIKey; + + self.callbacksCache = [NSMutableDictionary new]; + } + return self; +} + +- (NSDictionary *)headers +{ + return @{ + @"Authorization": + [NSString stringWithFormat:@"Bearer %@", self.APIKey] + }; +} + +- (void)handle:(NSInteger)statusCode + withResponse:(nullable NSDictionary *)response + error:(nullable NSError *)error + completion:(RCBackendPurchaserInfoResponseHandler)completion +{ + if (error != nil) { + completion(nil, [RCPurchasesErrorUtils networkErrorWithUnderlyingError:error]); + return; + } + + RCPurchaserInfo *info = nil; + NSError *responseError = nil; + BOOL isErrorStatusCode = (statusCode >= RC_REDIRECT); + + if (!isErrorStatusCode) { + info = [[RCPurchaserInfo alloc] initWithData:response]; + if (info == nil) { + responseError = [RCPurchasesErrorUtils unexpectedBackendResponseError]; + completion(info, responseError); + return; + } + } + + NSDictionary *subscriberAttributesErrorInfo = [self attributesUserInfoFromResponse:response + statusCode:statusCode]; + + BOOL hasError = (isErrorStatusCode || subscriberAttributesErrorInfo[RCAttributeErrorsKey] != nil); + + if (hasError) { + BOOL finishable = (statusCode < RC_INTERNAL_SERVER_ERROR); + NSMutableDictionary *extraUserInfo = @{ + RCFinishableKey: @(finishable) + }.mutableCopy; + [extraUserInfo addEntriesFromDictionary:subscriberAttributesErrorInfo]; + responseError = [RCPurchasesErrorUtils backendErrorWithBackendCode:response[@"code"] + backendMessage:response[@"message"] + extraUserInfo:extraUserInfo]; + } + completion(info, responseError); +} + +- (void)handle:(NSInteger)statusCode + withResponse:(nullable NSDictionary *)response + error:(nullable NSError *)error + errorHandler:(void (^)(NSError * _Nullable error))errorHandler +{ + + if (error != nil) { + errorHandler([RCPurchasesErrorUtils networkErrorWithUnderlyingError:error]); + return; + } + + NSError *responseError = nil; + + if (statusCode > RC_REDIRECT) { + responseError = [RCPurchasesErrorUtils backendErrorWithBackendCode:response[@"code"] + backendMessage:response[@"message"]]; + } + + if (errorHandler != nil) { + errorHandler(responseError); + } +} + +- (NSString *)escapedAppUserID:(NSString *)appUserID { + return [appUserID stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]]; +} + +- (BOOL)addCallback:(id)completion forKey:(NSString *)key +{ + @synchronized(self) { + NSMutableArray *callbacks = self.callbacksCache[key]; + BOOL cacheMiss = callbacks == nil; + + if (cacheMiss) { + callbacks = [NSMutableArray new]; + self.callbacksCache[key] = callbacks; + } + + [callbacks addObject:[completion copy]]; + + BOOL requestAlreadyInFlight = !cacheMiss; + return requestAlreadyInFlight; + } +} + +- (NSMutableArray *)getCallbacksAndClearForKey:(NSString *)key { + @synchronized(self) { + NSMutableArray *callbacks = self.callbacksCache[key]; + NSParameterAssert(callbacks); + + self.callbacksCache[key] = nil; + + return callbacks; + } +} + +- (void) postReceiptData:(NSData *)data + appUserID:(NSString *)appUserID + isRestore:(BOOL)isRestore + productInfo:(nullable RCProductInfo *)productInfo +presentedOfferingIdentifier:(nullable NSString *)offeringIdentifier + observerMode:(BOOL)observerMode + subscriberAttributes:(nullable RCSubscriberAttributeDict)subscriberAttributesByKey + completion:(RCBackendPurchaserInfoResponseHandler)completion { + NSString *fetchToken = [data base64EncodedStringWithOptions:0]; + NSMutableDictionary *body = [NSMutableDictionary dictionaryWithDictionary: + @{ + @"fetch_token": fetchToken, + @"app_user_id": appUserID, + @"is_restore": @(isRestore), + @"observer_mode": @(observerMode) + }]; + + NSString *cacheKey = [NSString stringWithFormat:@"%@-%@-%@-%@-%@-%@-%@", + appUserID, + @(isRestore), + fetchToken, + productInfo.cacheKey, + offeringIdentifier, + @(observerMode), + subscriberAttributesByKey]; + + if ([self addCallback:completion forKey:cacheKey]) { + return; + } + + if (productInfo) { + [body addEntriesFromDictionary:productInfo.asDictionary]; + } + + if (subscriberAttributesByKey) { + NSDictionary *attributesInBackendFormat = [self subscriberAttributesByKey:subscriberAttributesByKey]; + body[@"attributes"] = attributesInBackendFormat; + } + + if (offeringIdentifier) { + body[@"presented_offering_identifier"] = offeringIdentifier; + } + + [self.httpClient performRequest:@"POST" + serially:YES + path:@"/receipts" + body:body + headers:self.headers + completionHandler:^(NSInteger status, NSDictionary *response, NSError *error) { + NSArray *callbacks = [self getCallbacksAndClearForKey:cacheKey]; + for (RCBackendPurchaserInfoResponseHandler callback in callbacks) { + [self handle:status withResponse:response error:error completion:callback]; + } + }]; +} + +- (void)getSubscriberDataWithAppUserID:(NSString *)appUserID + completion:(RCBackendPurchaserInfoResponseHandler)completion +{ + NSString *escapedAppUserID = [self escapedAppUserID:appUserID]; + NSString *path = [NSString stringWithFormat:@"/subscribers/%@", escapedAppUserID]; + + if ([self addCallback:completion forKey:path]) { + return; + } + + [self.httpClient performRequest:@"GET" + serially:YES + path:path + body:nil + headers:self.headers + completionHandler:^(NSInteger status, NSDictionary *response, NSError *error) { + for (RCBackendPurchaserInfoResponseHandler completion in [self getCallbacksAndClearForKey:path]) { + [self handle:status withResponse:response error:error completion:completion]; + } + }]; +} + +- (void)getIntroEligibilityForAppUserID:(NSString *)appUserID + receiptData:(NSData *)receiptData + productIdentifiers:(NSArray *)productIdentifiers + completion:(RCIntroEligibilityResponseHandler)completion +{ + if (productIdentifiers.count == 0) { + completion(@{}); + return; + } + if (receiptData.length == 0) { + if (RCSystemInfo.isSandbox) { + RCLog(@"App running on sandbox without a receipt file. Unable to determine into eligibility unless you've purchased before and there is a receipt available."); + } + NSMutableDictionary *eligibilities = [NSMutableDictionary new]; + for (NSString *productID in productIdentifiers) { + eligibilities[productID] = [[RCIntroEligibility alloc] initWithEligibilityStatus:RCIntroEligibilityStatusUnknown]; + } + completion([NSDictionary dictionaryWithDictionary:eligibilities]); + return; + } + + NSString *fetchToken = [receiptData base64EncodedStringWithOptions:0]; + + NSString *escapedAppUserID = [self escapedAppUserID:appUserID]; + NSString *path = [NSString stringWithFormat:@"/subscribers/%@/intro_eligibility", escapedAppUserID]; + [self.httpClient performRequest:@"POST" + serially:YES + path:path + body:@{ + @"product_identifiers": productIdentifiers, + @"fetch_token": fetchToken + } + headers:self.headers + completionHandler:^(NSInteger statusCode, NSDictionary * _Nullable response, NSError * _Nullable error) { + if (statusCode >= RC_REDIRECT || error != nil) { + response = @{}; + } + + NSMutableDictionary *eligibilities = [NSMutableDictionary new]; + for (NSString *productID in productIdentifiers) { + NSNumber *e = response[productID]; + RCIntroEligibilityStatus status; + if (e == nil || [e isKindOfClass:[NSNull class]]) { + status = RCIntroEligibilityStatusUnknown; + } else if ([e boolValue]) { + status = RCIntroEligibilityStatusEligible; + } else { + status = RCIntroEligibilityStatusIneligible; + } + + eligibilities[productID] = [[RCIntroEligibility alloc] initWithEligibilityStatus:status]; + } + + completion([NSDictionary dictionaryWithDictionary:eligibilities]); + }]; +} + +- (void)getOfferingsForAppUserID:(NSString *)appUserID + completion:(RCOfferingsResponseHandler)completion +{ + NSString *escapedAppUserID = [self escapedAppUserID:appUserID]; + NSString *path = [NSString stringWithFormat:@"/subscribers/%@/offerings", escapedAppUserID]; + + if ([self addCallback:completion forKey:path]) { + return; + } + + [self.httpClient performRequest:@"GET" + serially:NO + path:path + body:nil + headers:self.headers + completionHandler:^(NSInteger statusCode, NSDictionary * _Nullable response, NSError * _Nullable error) { + if (error == nil && statusCode < RC_REDIRECT) { + for (RCOfferingsResponseHandler callback in [self getCallbacksAndClearForKey:path]) { + callback(response, nil); + } + return; + } + + if (error != nil) { + error = [RCPurchasesErrorUtils networkErrorWithUnderlyingError:error]; + } else if (statusCode > RC_REDIRECT) { + error = [RCPurchasesErrorUtils backendErrorWithBackendCode:response[@"code"] + backendMessage:response[@"message"]]; + } + for (RCOfferingsResponseHandler callback in [self getCallbacksAndClearForKey:path]) { + callback(nil, error); + } + }]; +} + +- (void)postAttributionData:(NSDictionary *)data + fromNetwork:(RCAttributionNetwork)network + forAppUserID:(NSString *)appUserID + completion:(nullable void (^)(NSError * _Nullable error))completion +{ + NSString *escapedAppUserID = [self escapedAppUserID:appUserID]; + NSString *path = [NSString stringWithFormat:@"/subscribers/%@/attribution", escapedAppUserID]; + + [self.httpClient performRequest:@"POST" + serially:YES + path:path + body:@{ + @"network": @(network), + @"data": data + } + headers:self.headers + completionHandler:^(NSInteger status, NSDictionary *_Nullable response, NSError *_Nullable error) { + [self handle:status withResponse:response error:error errorHandler:completion]; + }]; +} + +- (void)createAliasForAppUserID:(NSString *)appUserID + withNewAppUserID:(NSString *)newAppUserID + completion:(nullable void (^)(NSError * _Nullable error))completion +{ + NSString *escapedAppUserID = [self escapedAppUserID:appUserID]; + NSString *path = [NSString stringWithFormat:@"/subscribers/%@/alias", escapedAppUserID]; + [self.httpClient performRequest:@"POST" + serially:YES + path:path + body:@{ + @"new_app_user_id": newAppUserID + } + headers:self.headers + completionHandler:^(NSInteger status, NSDictionary *_Nullable response, NSError *_Nullable error) { + [self handle:status withResponse:response error:error errorHandler:completion]; + }]; +} + + +- (void)postOfferForSigning:(NSString *)offerIdentifier + withProductIdentifier:(NSString *)productIdentifier + subscriptionGroup:(NSString *)subscriptionGroup + receiptData:(NSData *)receiptData + appUserID:(NSString *)appUserID + completion:(RCOfferSigningResponseHandler)completion +{ + NSString *fetchToken = [receiptData base64EncodedStringWithOptions:0]; + [self.httpClient performRequest:@"POST" + serially:YES + path:@"/offers" + body:@{ + @"app_user_id": appUserID, + @"fetch_token": fetchToken, + @"generate_offers": @[@{ + @"offer_id": offerIdentifier, + @"product_id": productIdentifier, + @"subscription_group": subscriptionGroup + }], + } + headers:self.headers + completionHandler:^(NSInteger statusCode, NSDictionary *_Nullable response, NSError *_Nullable error) { + if (error != nil) { + completion(nil, nil, nil, nil, [RCPurchasesErrorUtils networkErrorWithUnderlyingError:error]); + return; + } + + NSArray *offers = nil; + + if (statusCode < RC_REDIRECT) { + offers = response[@"offers"]; + if (offers == nil || offers.count == 0) { + error = [RCPurchasesErrorUtils unexpectedBackendResponseError]; + } else { + NSDictionary *offer = offers[0]; + if (RC_HAS_KEY(offer, @"signature_error")) { + error = [RCPurchasesErrorUtils backendErrorWithBackendCode:offer[@"signature_error"][@"code"] backendMessage:offer[@"signature_error"][@"message"]]; + } else if (RC_HAS_KEY(offer, @"signature_data")) { + NSDictionary *signatureData = offer[@"signature_data"]; + NSString *signature = signatureData[@"signature"]; + NSString *keyIdentifier = offer[@"key_id"]; + NSUUID *nonce = [[NSUUID alloc] initWithUUIDString:signatureData[@"nonce"]]; + NSNumber *timestamp = signatureData[@"timestamp"]; + completion(signature, keyIdentifier, nonce, timestamp, nil); + return; + } else { + error = [RCPurchasesErrorUtils unexpectedBackendResponseError]; + } + } + } else { + error = [RCPurchasesErrorUtils backendErrorWithBackendCode:response[@"code"] backendMessage:response[@"message"]]; + } + + completion(nil, nil, nil, nil, error); + }]; +} + +- (void)postSubscriberAttributes:(RCSubscriberAttributeDict)subscriberAttributes + appUserID:(NSString *)appUserID + completion:(nullable void (^)(NSError *_Nullable error))completion { + if (subscriberAttributes.count == 0) { + RCLog(@"called post subscriber attributes with an empty attributes dict!"); + return; + } + NSString *escapedAppUserID = [self escapedAppUserID:appUserID]; + NSString *path = [NSString stringWithFormat:@"/subscribers/%@/attributes", escapedAppUserID]; + NSDictionary *attributesInBackendFormat = [self subscriberAttributesByKey:subscriberAttributes]; + [self.httpClient performRequest:@"POST" + serially:YES + path:path + body:@{ + @"attributes": attributesInBackendFormat + } + headers:self.headers + completionHandler:^(NSInteger status, NSDictionary *_Nullable response, NSError *_Nullable error) { + [self handleSubscriberAttributesResultWithStatusCode:status + response:response + error:error + completion:completion]; + }]; + +} + +- (NSDictionary *)subscriberAttributesByKey:(RCSubscriberAttributeDict)subscriberAttributes { + NSMutableDictionary *attributesByKey = [[NSMutableDictionary alloc] init]; + for (NSString *key in subscriberAttributes) { + attributesByKey[key] = subscriberAttributes[key].asBackendDictionary; + } + return attributesByKey; +} + +- (void)handleSubscriberAttributesResultWithStatusCode:(NSInteger)statusCode + response:(nullable NSDictionary *)response + error:(nullable NSError *)error + completion:(void (^)(NSError *_Nullable error))completion { + + if (completion == nil) { + return; + } + + if (error != nil) { + completion([RCPurchasesErrorUtils networkErrorWithUnderlyingError:error]); + return; + } + NSError *responseError = nil; + + if (statusCode > RC_REDIRECT) { + NSDictionary *extraUserInfo = [self attributesUserInfoFromResponse:response + statusCode:statusCode]; + responseError = [RCPurchasesErrorUtils backendErrorWithBackendCode:response[@"code"] + backendMessage:response[@"message"] + extraUserInfo:extraUserInfo]; + } + + completion(responseError); +} + +- (NSDictionary *)attributesUserInfoFromResponse:(NSDictionary *)response statusCode:(NSInteger)statusCode { + NSMutableDictionary *resultDict = [[NSMutableDictionary alloc] init]; + BOOL isInternalServerError = statusCode >= RC_INTERNAL_SERVER_ERROR; + BOOL isNotFoundError = statusCode == RC_NOT_FOUND_ERROR; + BOOL successfullySynced = !(isInternalServerError || isNotFoundError); + resultDict[RCSuccessfullySyncedKey] = @(successfullySynced); + + BOOL hasAttributesResponseContainerKey = (response[RCAttributeErrorsResponseKey] != nil); + NSDictionary *attributesResponseDict = hasAttributesResponseContainerKey + ? response[RCAttributeErrorsResponseKey] + : response; + + BOOL hasAttributeErrors = (attributesResponseDict[RCAttributeErrorsKey] != nil); + if (hasAttributeErrors) { + resultDict[RCAttributeErrorsKey] = attributesResponseDict[RCAttributeErrorsKey]; + } + return resultDict; +} + +@end diff --git a/Pods/Purchases/Purchases/Networking/RCHTTPClient.h b/Pods/Purchases/Purchases/Networking/RCHTTPClient.h new file mode 100644 index 0000000..e3cadba --- /dev/null +++ b/Pods/Purchases/Purchases/Networking/RCHTTPClient.h @@ -0,0 +1,35 @@ +// +// RCHTTPClient.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RCSystemInfo; + +typedef void(^RCHTTPClientResponseHandler)(NSInteger statusCode, + NSDictionary *_Nullable response, + NSError *_Nullable error); + + +@interface RCHTTPClient : NSObject + +- (instancetype)initWithSystemInfo:(RCSystemInfo *)systemInfo NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; + +- (void)performRequest:(NSString *)httpMethod + serially:(BOOL)performSerially + path:(NSString *)path + body:(nullable NSDictionary *)requestBody + headers:(nullable NSDictionary *)headers + completionHandler:(nullable RCHTTPClientResponseHandler)completionHandler; + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Networking/RCHTTPClient.m b/Pods/Purchases/Purchases/Networking/RCHTTPClient.m new file mode 100644 index 0000000..93ac6bc --- /dev/null +++ b/Pods/Purchases/Purchases/Networking/RCHTTPClient.m @@ -0,0 +1,216 @@ +// +// RCHTTPClient.m +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import "RCHTTPClient.h" +#import "RCLogUtils.h" +#import "RCHTTPStatusCodes.h" +#import "RCSystemInfo.h" +#import "RCHTTPRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface RCHTTPClient () + +@property (nonatomic) NSURLSession *session; +@property (nonatomic) RCSystemInfo *systemInfo; +@property (nonatomic) NSMutableArray *queuedRequests; +@property (nonatomic, nullable) RCHTTPRequest *currentSerialRequest; + +@end + + +@implementation RCHTTPClient + +- (instancetype)initWithSystemInfo:(RCSystemInfo *)systemInfo { + if (self = [super init]) { + NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration]; + config.HTTPMaximumConnectionsPerHost = 1; + self.session = [NSURLSession sessionWithConfiguration:config]; + self.systemInfo = systemInfo; + self.queuedRequests = [[NSMutableArray alloc] init]; + self.currentSerialRequest = nil; + } + return self; +} + +- (void)performRequest:(NSString *)httpMethod + path:(NSString *)path + body:(nullable NSDictionary *)requestBody + headers:(nullable NSDictionary *)headers + completionHandler:(nullable RCHTTPClientResponseHandler)completionHandler { + [self performRequest:httpMethod + serially:NO + path:path + body:requestBody + headers:headers + completionHandler:completionHandler]; +} + +- (void)performRequest:(NSString *)httpMethod + serially:(BOOL)performSerially + path:(NSString *)path + body:(nullable NSDictionary *)requestBody + headers:(nullable NSDictionary *)headers + completionHandler:(nullable RCHTTPClientResponseHandler)completionHandler { + if (performSerially) { + RCHTTPRequest *rcRequest = [[RCHTTPRequest alloc] initWithHTTPMethod:httpMethod + path:path + body:requestBody + headers:headers + completionHandler:completionHandler]; + @synchronized (self) { + if (self.currentSerialRequest) { + RCDebugLog(@"There's a request currently running and %ld requests left in the queue, queueing %@ %@", + self.queuedRequests.count, + httpMethod, + path); + [self.queuedRequests addObject:rcRequest]; + return; + } else { + RCDebugLog(@"there are no requests currently running, starting request %@ %@", httpMethod, path); + self.currentSerialRequest = rcRequest; + } + } + } + + [self assertIsValidRequestWithMethod:httpMethod body:requestBody]; + + NSMutableDictionary *defaultHeaders = self.defaultHeaders.mutableCopy; + [defaultHeaders addEntriesFromDictionary:headers]; + + NSMutableURLRequest *urlRequest = [self createRequestWithMethod:httpMethod + path:path + requestBody:requestBody + headers:defaultHeaders]; + + typedef void (^SessionCompletionBlock)(NSData *_Nullable, NSURLResponse *_Nullable, NSError *_Nullable); + + SessionCompletionBlock block = ^void(NSData *_Nullable data, + NSURLResponse *_Nullable response, + NSError *_Nullable error) { + [self handleResponse:response + data:data + error:error + request:urlRequest + completionHandler:completionHandler +beginNextRequestWhenFinished:performSerially]; + }; + + RCDebugLog(@"%@ %@", urlRequest.HTTPMethod, urlRequest.URL.path); + NSURLSessionTask *task = [self.session dataTaskWithRequest:urlRequest + completionHandler:block]; + [task resume]; +} + +- (void) handleResponse:(NSURLResponse *)response + data:(NSData *)data + error:(NSError *)error + request:(NSMutableURLRequest *)request + completionHandler:(RCHTTPClientResponseHandler)completionHandler +beginNextRequestWhenFinished:(BOOL)beginNextRequestWhenFinished { + NSInteger statusCode = RC_NETWORK_CONNECT_TIMEOUT_ERROR; + NSDictionary *responseObject = nil; + + if (error == nil) { + statusCode = ((NSHTTPURLResponse *) response).statusCode; + + RCDebugLog(@"%@ %@ %d", request.HTTPMethod, request.URL.path, statusCode); + + NSError *jsonError; + responseObject = [NSJSONSerialization JSONObjectWithData:data + options:0 + error:&jsonError]; + + if (jsonError) { + RCLog(@"Error parsing JSON %@", jsonError.localizedDescription); + RCLog(@"Data received: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); + error = jsonError; + } + } + + if (completionHandler != nil) { + completionHandler(statusCode, responseObject, error); + } + + if (beginNextRequestWhenFinished) { + @synchronized (self) { + RCDebugLog(@"serial request done: %@ %@, %ld requests left in the queue", + self.currentSerialRequest.httpMethod, self.currentSerialRequest.path, self.queuedRequests.count); + RCHTTPRequest *nextRequest = nil; + self.currentSerialRequest = nil; + if (self.queuedRequests.count > 0) { + nextRequest = self.queuedRequests[0]; + [self.queuedRequests removeObjectAtIndex:0]; + } + if (nextRequest) { + RCDebugLog(@"starting the next request in the queue, %@", nextRequest); + [self performRequest:nextRequest.httpMethod + serially:YES + path:nextRequest.path + body:nextRequest.requestBody + headers:nextRequest.headers + completionHandler:nextRequest.completionHandler]; + } + } + } +} + +- (NSMutableURLRequest *)createRequestWithMethod:(NSString *)HTTPMethod + path:(NSString *)path + requestBody:(NSDictionary *)requestBody + headers:(NSMutableDictionary *)defaultHeaders { + NSString *relativeURLString = [NSString stringWithFormat:@"/v1%@", path]; + NSURL *requestURL = [NSURL URLWithString:relativeURLString relativeToURL:RCSystemInfo.serverHostURL]; + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:requestURL]; + + request.HTTPMethod = HTTPMethod; + request.allHTTPHeaderFields = defaultHeaders; + + if ([HTTPMethod isEqualToString:@"POST"]) { + NSError *JSONParseError; + NSData *body = [NSJSONSerialization dataWithJSONObject:requestBody options:0 error:&JSONParseError]; + if (JSONParseError) { + RCLog(@"Error creating request JSON: %@", requestBody); + } + request.HTTPBody = body; + } + return request; +} + +- (void)assertIsValidRequestWithMethod:(NSString *)HTTPMethod body:(NSDictionary *)requestBody { + NSParameterAssert(!([HTTPMethod isEqualToString:@"GET"] && requestBody)); + NSParameterAssert(([HTTPMethod isEqualToString:@"GET"] || [HTTPMethod isEqualToString:@"POST"])); +} + +- (NSDictionary *)defaultHeaders { + NSString *observerMode = [NSString stringWithFormat:@"%@", self.systemInfo.finishTransactions ? @"false" : @"true"]; + NSMutableDictionary *headers = [[NSMutableDictionary alloc] init]; + [headers addEntriesFromDictionary: @{ + @"content-type": @"application/json", + @"X-Version": RCSystemInfo.frameworkVersion, + @"X-Platform": RCSystemInfo.platformHeader, + @"X-Platform-Version": RCSystemInfo.systemVersion, + @"X-Platform-Flavor": self.systemInfo.platformFlavor, + @"X-Client-Version": RCSystemInfo.appVersion, + @"X-Observer-Mode-Enabled": observerMode + }]; + + NSString * _Nullable platformFlavorVersion = self.systemInfo.platformFlavorVersion; + if (platformFlavorVersion) { + headers[@"X-Platform-Flavor-Version"] = platformFlavorVersion; + } + + return headers; +} + + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Networking/RCHTTPRequest.h b/Pods/Purchases/Purchases/Networking/RCHTTPRequest.h new file mode 100644 index 0000000..3722fee --- /dev/null +++ b/Pods/Purchases/Purchases/Networking/RCHTTPRequest.h @@ -0,0 +1,32 @@ +// +// Created by Andrés Boedo on 9/4/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + + +#import +#import "RCHTTPClient.h" + +NS_ASSUME_NONNULL_BEGIN + + +@interface RCHTTPRequest : NSObject + +- (instancetype)initWithHTTPMethod:(NSString *)httpMethod + path:(NSString *)path + body:(nullable NSDictionary *)requestBody + headers:(nullable NSDictionary *)headers + completionHandler:(nullable RCHTTPClientResponseHandler)completionHandler; +- (id)copyWithZone:(nullable NSZone *)zone; +- (NSString *)description; + +@property (readonly, copy, nonatomic) NSString *httpMethod; +@property (readonly, copy, nonatomic) NSString *path; +@property (readonly, copy, nonatomic, nullable) NSDictionary *requestBody; +@property (readonly, copy, nonatomic, nullable) NSDictionary *headers; +@property (readonly, copy, nonatomic, nullable) RCHTTPClientResponseHandler completionHandler; + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Networking/RCHTTPRequest.m b/Pods/Purchases/Purchases/Networking/RCHTTPRequest.m new file mode 100644 index 0000000..47fa166 --- /dev/null +++ b/Pods/Purchases/Purchases/Networking/RCHTTPRequest.m @@ -0,0 +1,64 @@ +// +// Created by Andrés Boedo on 9/4/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import "RCHTTPRequest.h" + +NS_ASSUME_NONNULL_BEGIN + + +@interface RCHTTPRequest () + +@property(copy, nonatomic) NSString *httpMethod; +@property(copy, nonatomic) NSString *path; +@property(copy, nonatomic, nullable) NSDictionary *requestBody; +@property(copy, nonatomic, nullable) NSDictionary *headers; +@property(copy, nonatomic, nullable) RCHTTPClientResponseHandler completionHandler; + +@end + + +@implementation RCHTTPRequest + +- (instancetype)initWithHTTPMethod:(NSString *)httpMethod + path:(NSString *)path + body:(nullable NSDictionary *)requestBody + headers:(nullable NSDictionary *)headers + completionHandler:(nullable RCHTTPClientResponseHandler)completionHandler { + if (self = [super init]) { + self.httpMethod = httpMethod; + self.path = path; + self.requestBody = requestBody; + self.headers = headers; + self.completionHandler = completionHandler; + } + return self; +} + +- (id)copyWithZone:(nullable NSZone *)zone { + RCHTTPRequest *copy = [[RCHTTPRequest allocWithZone:zone] + initWithHTTPMethod:self.httpMethod + path:self.path + body:self.requestBody + headers:self.headers + completionHandler:self.completionHandler]; + + return copy; +} + +- (NSString *)description { + NSMutableString *description = [NSMutableString stringWithFormat:@"<%@: ", NSStringFromClass([self class])]; + [description appendFormat:@"httpMethod=%@\n", self.httpMethod]; + [description appendFormat:@"path=%@\n", self.path]; + [description appendFormat:@"requestBody=%@\n", self.requestBody]; + [description appendFormat:@"headers=%@\n", self.headers]; + [description appendString:@">"]; + return description; +} + + +@end + + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Pods/Purchases/Purchases/Networking/RCHTTPStatusCodes.h b/Pods/Purchases/Purchases/Networking/RCHTTPStatusCodes.h new file mode 100644 index 0000000..0550073 --- /dev/null +++ b/Pods/Purchases/Purchases/Networking/RCHTTPStatusCodes.h @@ -0,0 +1,16 @@ +// +// Created by Andrés Boedo on 5/6/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, RCHTTPStatusCodes) { + RC_REDIRECT = 300, + RC_INTERNAL_SERVER_ERROR = 500, + RC_NOT_FOUND_ERROR = 404, + RC_NETWORK_CONNECT_TIMEOUT_ERROR = 599 +}; + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/ProtectedExtensions/RCAttributionFetcher+Protected.h b/Pods/Purchases/Purchases/ProtectedExtensions/RCAttributionFetcher+Protected.h new file mode 100644 index 0000000..c3f4b52 --- /dev/null +++ b/Pods/Purchases/Purchases/ProtectedExtensions/RCAttributionFetcher+Protected.h @@ -0,0 +1,17 @@ +// +// Created by RevenueCat. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import +#import "RCAttributionFetcher.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface RCAttributionFetcher (Protected) + +- (NSString *)rot13:(NSString *)string; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/ProtectedExtensions/RCDeviceCache+Protected.h b/Pods/Purchases/Purchases/ProtectedExtensions/RCDeviceCache+Protected.h new file mode 100644 index 0000000..5949a27 --- /dev/null +++ b/Pods/Purchases/Purchases/ProtectedExtensions/RCDeviceCache+Protected.h @@ -0,0 +1,25 @@ +// +// Created by RevenueCat on 2/3/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import +#import "RCDeviceCache.h" +#import "RCInMemoryCachedObject.h" +#import "RCOfferings.h" + +NS_ASSUME_NONNULL_BEGIN + + +@interface RCDeviceCache (Protected) + +- (void)setPurchaserInfoCacheTimestamp:(NSDate *)timestamp forAppUserID:(NSString *)appUserID; + +- (nullable instancetype)initWith:(nullable NSUserDefaults *)userDefaults + offeringsCachedObject:(nullable RCInMemoryCachedObject *)offeringsCachedObject + notificationCenter:(nullable NSNotificationCenter *)notificationCenter; + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/ProtectedExtensions/RCEntitlementInfo+Protected.h b/Pods/Purchases/Purchases/ProtectedExtensions/RCEntitlementInfo+Protected.h new file mode 100644 index 0000000..065be40 --- /dev/null +++ b/Pods/Purchases/Purchases/ProtectedExtensions/RCEntitlementInfo+Protected.h @@ -0,0 +1,16 @@ +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import "RCEntitlementInfo.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface RCEntitlementInfo (Protected) + +- (instancetype)initWithEntitlementId:(NSString *)entitlementId entitlementData:(NSDictionary *)entitlementData productData:(NSDictionary *)productData dateFormatter:(NSDateFormatter *)dateFormatter requestDate:(NSDate *)date; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/ProtectedExtensions/RCEntitlementInfos+Protected.h b/Pods/Purchases/Purchases/ProtectedExtensions/RCEntitlementInfos+Protected.h new file mode 100644 index 0000000..37fa7f8 --- /dev/null +++ b/Pods/Purchases/Purchases/ProtectedExtensions/RCEntitlementInfos+Protected.h @@ -0,0 +1,16 @@ +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import "RCEntitlementInfos.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface RCEntitlementInfos (Protected) + +- (instancetype)initWithEntitlementsData:(NSDictionary *)entitlementsData purchasesData:(NSDictionary *)purchasesData dateFormatter:(NSDateFormatter *)dateFormatter requestDate:(NSDate *)requestDate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/ProtectedExtensions/RCInMemoryCachedObject+Protected.h b/Pods/Purchases/Purchases/ProtectedExtensions/RCInMemoryCachedObject+Protected.h new file mode 100644 index 0000000..1ae3c21 --- /dev/null +++ b/Pods/Purchases/Purchases/ProtectedExtensions/RCInMemoryCachedObject+Protected.h @@ -0,0 +1,17 @@ +// +// Created by RevenueCat on 2/3/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import +#import "RCInMemoryCachedObject.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface RCInMemoryCachedObject (Protected) + +@property (nonatomic, nullable) NSDate *lastUpdatedAt; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/ProtectedExtensions/RCIntroEligibility+Protected.h b/Pods/Purchases/Purchases/ProtectedExtensions/RCIntroEligibility+Protected.h new file mode 100644 index 0000000..9eb75cd --- /dev/null +++ b/Pods/Purchases/Purchases/ProtectedExtensions/RCIntroEligibility+Protected.h @@ -0,0 +1,17 @@ +// +// RCIntroEligibility+Protected.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import "RCIntroEligibility.h" + +@interface RCIntroEligibility (Protected) + +- (instancetype)initWithEligibilityStatus:(RCIntroEligibilityStatus)status; +- (instancetype)initWithEligibilityStatusCode:(NSNumber *)statusCode; + +@end + diff --git a/Pods/Purchases/Purchases/ProtectedExtensions/RCOffering+Protected.h b/Pods/Purchases/Purchases/ProtectedExtensions/RCOffering+Protected.h new file mode 100644 index 0000000..52b221f --- /dev/null +++ b/Pods/Purchases/Purchases/ProtectedExtensions/RCOffering+Protected.h @@ -0,0 +1,19 @@ +// +// RCOffering+Protected.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat, Inc. All rights reserved. +// + +#import "RCOffering.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface RCOffering (Protected) + +- (instancetype)initWithIdentifier:(NSString *)identifier serverDescription:(NSString *)serverDescription availablePackages:(NSArray *)availablePackages; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/ProtectedExtensions/RCOfferings+Protected.h b/Pods/Purchases/Purchases/ProtectedExtensions/RCOfferings+Protected.h new file mode 100644 index 0000000..a9884e8 --- /dev/null +++ b/Pods/Purchases/Purchases/ProtectedExtensions/RCOfferings+Protected.h @@ -0,0 +1,21 @@ +// +// RCOffering+Protected.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat, Inc. All rights reserved. +// + +#import "RCOfferings.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface RCOfferings (Protected) + +@property (readonly) NSDictionary *all; + +- (instancetype)initWithOfferings:(NSDictionary *)offerings currentOfferingID:(NSString *)currentOfferingID; + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Pods/Purchases/Purchases/ProtectedExtensions/RCPackage+Protected.h b/Pods/Purchases/Purchases/ProtectedExtensions/RCPackage+Protected.h new file mode 100644 index 0000000..1ff5075 --- /dev/null +++ b/Pods/Purchases/Purchases/ProtectedExtensions/RCPackage+Protected.h @@ -0,0 +1,28 @@ +// +// RCPackage+Protected.h +// Purchases +// +// Created by RevenueCat. +// Copyright (c) 2019 Purchases. All rights reserved. +// + +#import +#import "RCPackage.h" + +#define PACKAGE_TYPE_STRINGS (@[@"$rc_lifetime", @"$rc_annual", @"$rc_six_month", @"$rc_three_month", @"$rc_two_month", @"$rc_monthly", @"$rc_weekly"]) + +NS_ASSUME_NONNULL_BEGIN + +@interface RCPackage (Protected) + +@property (readonly) NSString *offeringIdentifier; + ++ (nullable NSString *)stringFromPackageType:(RCPackageType)packageType; + ++ (RCPackageType)packageTypeFromString:(NSString *)string; + +- (instancetype)initWithIdentifier:(NSString *)identifier packageType:(RCPackageType)packageType product:(SKProduct *)product offeringIdentifier:(NSString *)offeringIdentifier; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/ProtectedExtensions/RCPromotionalOffer+Protected.h b/Pods/Purchases/Purchases/ProtectedExtensions/RCPromotionalOffer+Protected.h new file mode 100644 index 0000000..152be09 --- /dev/null +++ b/Pods/Purchases/Purchases/ProtectedExtensions/RCPromotionalOffer+Protected.h @@ -0,0 +1,23 @@ +// +// RCPromotionalOffer+Protected.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import "RCPromotionalOffer.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface RCPromotionalOffer (Protected) + +@property (nonatomic, readwrite) NSString *offerIdentifier; + +@property (nonatomic, readwrite) NSDecimalNumber *price; + +@property (nonatomic, readwrite) enum RCPaymentMode paymentMode; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/ProtectedExtensions/RCPurchaserInfo+Protected.h b/Pods/Purchases/Purchases/ProtectedExtensions/RCPurchaserInfo+Protected.h new file mode 100644 index 0000000..8829c80 --- /dev/null +++ b/Pods/Purchases/Purchases/ProtectedExtensions/RCPurchaserInfo+Protected.h @@ -0,0 +1,26 @@ +// +// RCPurchaserInfo+Protected.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import "RCPurchaserInfo.h" +NS_ASSUME_NONNULL_BEGIN +@interface RCPurchaserInfo (Protected) + +- (nullable instancetype)initWithData:(NSDictionary *)data; + +@property (nonatomic, readonly) NSDictionary *expirationDatesByProduct; +@property (nonatomic, readonly) NSDictionary *purchaseDatesByProduct; +@property (nonatomic, readonly) NSSet *nonConsumablePurchases; +@property (nonatomic, readonly, nullable) NSString *originalApplicationVersion; +@property (nonatomic, readonly, nullable) NSDate *originalPurchaseDate; +@property (nonatomic, readonly, nullable) NSString *schemaVersion; + +- (NSDictionary *)JSONObject; ++ (NSString *)currentSchemaVersion; + +@end +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/ProtectedExtensions/RCPurchases+Protected.h b/Pods/Purchases/Purchases/ProtectedExtensions/RCPurchases+Protected.h new file mode 100644 index 0000000..84430ed --- /dev/null +++ b/Pods/Purchases/Purchases/ProtectedExtensions/RCPurchases+Protected.h @@ -0,0 +1,57 @@ +// +// RCPurchases+Protected.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + + +@class RCPurchases, + RCStoreKitRequestFetcher, + RCBackend, + RCStoreKitWrapper, + RCReceiptFetcher, + RCAttributionFetcher, + RCOfferingsFactory, + RCDeviceCache, + RCIdentityManager, + RCSubscriberAttributesManager, + RCSystemInfo, + RCOperationDispatcher, + RCIntroEligibilityCalculator, + RCReceiptParser; + +NS_ASSUME_NONNULL_BEGIN + + +@interface RCPurchases (Protected) + +- (instancetype)initWithAppUserID:(nullable NSString *)appUserID + requestFetcher:(RCStoreKitRequestFetcher *)requestFetcher + receiptFetcher:(RCReceiptFetcher *)receiptFetcher + attributionFetcher:(RCAttributionFetcher *)attributionFetcher + backend:(RCBackend *)backend + storeKitWrapper:(RCStoreKitWrapper *)storeKitWrapper + notificationCenter:(NSNotificationCenter *)notificationCenter + systemInfo:(RCSystemInfo *)systemInfo + offeringsFactory:(RCOfferingsFactory *)offeringsFactory + deviceCache:(RCDeviceCache *)deviceCache + identityManager:(RCIdentityManager *)identityManager + subscriberAttributesManager:(RCSubscriberAttributesManager *)subscriberAttributesManager + operationDispatcher:(RCOperationDispatcher *)operationDispatcher + introEligibilityCalculator:(RCIntroEligibilityCalculator *)introEligibilityCalculator + receiptParser:(RCReceiptParser *)receiptParser; + ++ (void)setDefaultInstance:(nullable RCPurchases *)instance; + +- (void)_setPushTokenString:(nullable NSString *)pushToken; + +@property (nonatomic) RCDeviceCache *deviceCache; +@property (nonatomic) RCBackend *backend; +@property (nonatomic) NSNotificationCenter *notificationCenter; + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/ProtectedExtensions/RCPurchasesErrorUtils+Protected.h b/Pods/Purchases/Purchases/ProtectedExtensions/RCPurchasesErrorUtils+Protected.h new file mode 100644 index 0000000..31c9f07 --- /dev/null +++ b/Pods/Purchases/Purchases/ProtectedExtensions/RCPurchasesErrorUtils+Protected.h @@ -0,0 +1,21 @@ +// +// Created by RevenueCat on 2/27/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import +#import "RCPurchasesErrorUtils.h" +#import "RCPurchasesErrors.h" + +NS_ASSUME_NONNULL_BEGIN + + +@interface RCPurchasesErrorUtils (Protected) + ++ (NSError *)backendErrorWithBackendCode:(nullable NSNumber *)backendCode + backendMessage:(nullable NSString *)backendMessage + extraUserInfo:(nullable NSDictionary *)extraUserInfo; +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/ProtectedExtensions/RCSubscriberAttribute+Protected.h b/Pods/Purchases/Purchases/ProtectedExtensions/RCSubscriberAttribute+Protected.h new file mode 100644 index 0000000..a10ddd1 --- /dev/null +++ b/Pods/Purchases/Purchases/ProtectedExtensions/RCSubscriberAttribute+Protected.h @@ -0,0 +1,27 @@ +// +// Created by RevenueCat on 2/26/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import +#import "RCSubscriberAttribute.h" + +NS_ASSUME_NONNULL_BEGIN + +@class RCDateProvider; + +@interface RCSubscriberAttribute (Protected) + +- (instancetype)initWithKey:(NSString *)key + value:(NSString *)value + isSynced:(BOOL)isSynced + setTime:(NSDate *)setTime; + +- (instancetype)initWithKey:(NSString *)key + value:(NSString *)value + dateProvider:(RCDateProvider *)dateProvider; + +@end + + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Pods/Purchases/Purchases/Public/Purchases.h b/Pods/Purchases/Purchases/Public/Purchases.h new file mode 100644 index 0000000..cf06317 --- /dev/null +++ b/Pods/Purchases/Purchases/Public/Purchases.h @@ -0,0 +1,30 @@ +// +// Purchases.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import + +/** + Purchases version number + */ +FOUNDATION_EXPORT double PurchasesVersionNumber; +/** + Purchases version string + */ +FOUNDATION_EXPORT const unsigned char PurchasesVersionString[]; + +#import "RCPurchases.h" +#import "RCPurchaserInfo.h" +#import "RCIntroEligibility.h" +#import "RCOfferings.h" +#import "RCOffering.h" +#import "RCPackage.h" +#import "RCPurchasesErrorUtils.h" +#import "RCPurchasesErrors.h" +#import "RCEntitlementInfo.h" +#import "RCEntitlementInfos.h" +#import "RCTransaction.h" diff --git a/Pods/Purchases/Purchases/Public/RCAttributionNetwork.h b/Pods/Purchases/Purchases/Public/RCAttributionNetwork.h new file mode 100644 index 0000000..3e9afd7 --- /dev/null +++ b/Pods/Purchases/Purchases/Public/RCAttributionNetwork.h @@ -0,0 +1,41 @@ +// +// Created by Andrés Boedo on 9/3/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + + +#import + +/** + Enum of supported attribution networks + */ +typedef NS_ENUM(NSInteger, RCAttributionNetwork) { + /** + Apple's search ads + */ + RCAttributionNetworkAppleSearchAds = 0, + /** + Adjust https://www.adjust.com/ + */ + RCAttributionNetworkAdjust, + /** + AppsFlyer https://www.appsflyer.com/ + */ + RCAttributionNetworkAppsFlyer, + /** + Branch https://www.branch.io/ + */ + RCAttributionNetworkBranch, + /** + Tenjin https://www.tenjin.io/ + */ + RCAttributionNetworkTenjin, + /** + Facebook https://developers.facebook.com/ + */ + RCAttributionNetworkFacebook, + /** + mParticle https://www.mparticle.com/ + */ + RCAttributionNetworkMParticle +}; diff --git a/Pods/Purchases/Purchases/Public/RCEntitlementInfo.h b/Pods/Purchases/Purchases/Public/RCEntitlementInfo.h new file mode 100644 index 0000000..20cf34b --- /dev/null +++ b/Pods/Purchases/Purchases/Public/RCEntitlementInfo.h @@ -0,0 +1,119 @@ +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + Enum of supported stores + */ +typedef NS_ENUM(NSInteger, RCStore) { + /// For entitlements granted via Apple App Store. + RCAppStore = 0, + /// For entitlements granted via Apple Mac App Store. + RCMacAppStore, + /// For entitlements granted via Google Play Store. + RCPlayStore, + /// For entitlements granted via Stripe. + RCStripe, + /// For entitlements granted via a promo in RevenueCat. + RCPromotional, + /// For entitlements granted via an unknown store. + RCUnknownStore, +} NS_SWIFT_NAME(Purchases.Store); + +/** + Enum of supported period types for an entitlement. + */ +typedef NS_ENUM(NSInteger, RCPeriodType) { + /// If the entitlement is not under an introductory or trial period. + RCNormal = 0, + /// If the entitlement is under a introductory price period. + RCIntro, + /// If the entitlement is under a trial period. + RCTrial, +} NS_SWIFT_NAME(Purchases.PeriodType); + +/** + The EntitlementInfo object gives you access to all of the information about the status of a user entitlement. + */ +NS_SWIFT_NAME(Purchases.EntitlementInfo) +@interface RCEntitlementInfo : NSObject + +/** + The entitlement identifier configured in the RevenueCat dashboard + */ +@property (readonly) NSString *identifier; + +/** + True if the user has access to this entitlement + */ +@property (readonly) BOOL isActive; + +/** + True if the underlying subscription is set to renew at the end of + the billing period (expirationDate). Will always be True if entitlement + is for lifetime access. + */ +@property (readonly) BOOL willRenew; + +/** + The last period type this entitlement was in + Either: RCNormal, RCIntro, RCTrial + */ +@property (readonly) RCPeriodType periodType; + +/** + The latest purchase or renewal date for the entitlement. + */ +@property (readonly) NSDate *latestPurchaseDate; + +/** + The first date this entitlement was purchased + */ +@property (readonly) NSDate *originalPurchaseDate; + +/** + The expiration date for the entitlement, can be `nil` for lifetime access. + If the `periodType` is `trial`, this is the trial expiration date. + */ +@property (readonly, nullable) NSDate *expirationDate; + +/** + The store where this entitlement was unlocked from + Either: RCAppStore, RCMacAppStore, RCPlayStore, RCStripe, RCPromotional, RCUnknownStore + */ +@property (readonly) RCStore store; + +/** + The product identifier that unlocked this entitlement + */ +@property (readonly) NSString *productIdentifier; + +/** + False if this entitlement is unlocked via a production purchase + */ +@property (readonly) BOOL isSandbox; + +/** + The date an unsubscribe was detected. Can be `nil`. + + Note: Entitlement may still be active even if user has unsubscribed. Check the `isActive` property. + */ +@property (readonly, nullable) NSDate *unsubscribeDetectedAt; + +/** + The date a billing issue was detected. Can be `nil` if there is no + billing issue or an issue has been resolved. + + Note: Entitlement may still be active even if there is a billing issue. + Check the `isActive` property. + */ +@property (readonly, nullable) NSDate *billingIssueDetectedAt; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Public/RCEntitlementInfo.m b/Pods/Purchases/Purchases/Public/RCEntitlementInfo.m new file mode 100644 index 0000000..26b4036 --- /dev/null +++ b/Pods/Purchases/Purchases/Public/RCEntitlementInfo.m @@ -0,0 +1,173 @@ +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import "RCEntitlementInfo.h" +#import "RCEntitlementInfo+Protected.h" + +@interface RCEntitlementInfo () + +@property (readwrite) NSString *identifier; +@property (readwrite) BOOL isActive; +@property (readwrite) RCPeriodType periodType; +@property (readwrite) NSDate *latestPurchaseDate; +@property (readwrite) NSDate *originalPurchaseDate; +@property (readwrite, nullable) NSDate *expirationDate; +@property (readwrite) RCStore store; +@property (readwrite) NSString *productIdentifier; +@property (readwrite) BOOL isSandbox; +@property (readwrite, nullable) NSDate *unsubscribeDetectedAt; +@property (readwrite, nullable) NSDate *billingIssueDetectedAt; +@property (readwrite) BOOL willRenew; + +@end + +@implementation RCEntitlementInfo + +- (instancetype)initWithEntitlementId:(NSString *)entitlementId entitlementData:(NSDictionary *)entitlementData productData:(NSDictionary *)productData dateFormatter:(NSDateFormatter *)dateFormatter requestDate:(NSDate *)requestDate +{ + if (self = [super init]) { + self.identifier = entitlementId; + self.isActive = [self isDateActive:[self parseDate:entitlementData[@"expires_date"] withDateFormatter:dateFormatter] forRequestDate:requestDate]; + self.periodType = [self parsePeriodType:productData[@"period_type"]]; + self.latestPurchaseDate = [self parseDate:entitlementData[@"purchase_date"] withDateFormatter:dateFormatter]; + self.originalPurchaseDate = [self parseDate:productData[@"original_purchase_date"] withDateFormatter:dateFormatter]; + self.expirationDate = [self parseDate:productData[@"expires_date"] withDateFormatter:dateFormatter]; + self.store = [self parseStore:productData[@"store"]]; + self.productIdentifier = entitlementData[@"product_identifier"]; + self.isSandbox = [productData[@"is_sandbox"] boolValue]; + self.unsubscribeDetectedAt = [self parseDate:productData[@"unsubscribe_detected_at"] withDateFormatter:dateFormatter]; + self.billingIssueDetectedAt = [self parseDate:productData[@"billing_issues_detected_at"] withDateFormatter:dateFormatter]; + if ([entitlementData[@"expires_date"] isKindOfClass:NSNull.class]) { + self.willRenew = true; + } else { + self.willRenew = self.unsubscribeDetectedAt == nil && self.billingIssueDetectedAt == nil; + } + } + return self; +} + +- (BOOL)isDateActive:(nullable NSDate *)expirationDate forRequestDate:(NSDate *)requestDate +{ + NSDate *referenceDate = requestDate ?: [NSDate date]; + return ((expirationDate == nil) || [expirationDate timeIntervalSinceDate:referenceDate] > 0); +} + +- (nullable NSDate *)parseDate:(id)dateString withDateFormatter:(NSDateFormatter *)dateFormatter +{ + if ([dateString isKindOfClass:NSString.class]) { + return [dateFormatter dateFromString:(NSString *)dateString]; + } + return nil; +} + +- (RCPeriodType)parsePeriodType:(NSString *)periodType +{ + if ([periodType isEqualToString:@"normal"]) { + return RCNormal; + } else if ([periodType isEqualToString:@"intro"]) { + return RCIntro; + } else if ([periodType isEqualToString:@"trial"]) { + return RCTrial; + } + return RCNormal; +} + +- (RCStore)parseStore:(NSString *)store +{ + if ([store isEqualToString:@"app_store"]) { + return RCAppStore; + } else if ([store isEqualToString:@"mac_app_store"]) { + return RCMacAppStore; + } else if ([store isEqualToString:@"play_store"]) { + return RCPlayStore; + } else if ([store isEqualToString:@"stripe"]) { + return RCStripe; + } else if ([store isEqualToString:@"promotional"]) { + return RCPromotional; + } + return RCUnknownStore; +} + +- (NSString *)description +{ + NSMutableString *description = [NSMutableString stringWithFormat:@"<%@: ", NSStringFromClass([self class])]; + [description appendFormat:@"identifier=%@,\n", self.identifier]; + [description appendFormat:@"isActive=%d,\n", self.isActive]; + [description appendFormat:@"willRenew=%d,\n", self.willRenew]; + [description appendFormat:@"periodType=%li,\n", (long) self.periodType]; + [description appendFormat:@"latestPurchaseDate=%@,\n", self.latestPurchaseDate]; + [description appendFormat:@"originalPurchaseDate=%@,\n", self.originalPurchaseDate]; + [description appendFormat:@"expirationDate=%@,\n", self.expirationDate]; + [description appendFormat:@"store=%li,\n", (long) self.store]; + [description appendFormat:@"productIdentifier=%@,\n", self.productIdentifier]; + [description appendFormat:@"isSandbox=%d,\n", self.isSandbox]; + [description appendFormat:@"unsubscribeDetectedAt=%@,\n", self.unsubscribeDetectedAt]; + [description appendFormat:@"billingIssueDetectedAt=%@,\n", self.billingIssueDetectedAt]; + [description appendString:@">"]; + return description; +} + +- (BOOL)isEqual:(id)other +{ + if (other == self) + return YES; + if (!other || ![[other class] isEqual:[self class]]) + return NO; + + return [self isEqualToInfo:other]; +} + +- (BOOL)isEqualToInfo:(RCEntitlementInfo *)info +{ + if (self == info) + return YES; + if (info == nil) + return NO; + if (self.identifier != info.identifier && ![self.identifier isEqualToString:info.identifier]) + return NO; + if (self.isActive != info.isActive) + return NO; + if (self.willRenew != info.willRenew) + return NO; + if (self.periodType != info.periodType) + return NO; + if (self.latestPurchaseDate != info.latestPurchaseDate && ![self.latestPurchaseDate isEqualToDate:info.latestPurchaseDate]) + return NO; + if (self.originalPurchaseDate != info.originalPurchaseDate && ![self.originalPurchaseDate isEqualToDate:info.originalPurchaseDate]) + return NO; + if (self.expirationDate != info.expirationDate && ![self.expirationDate isEqualToDate:info.expirationDate]) + return NO; + if (self.store != info.store) + return NO; + if (self.productIdentifier != info.productIdentifier && ![self.productIdentifier isEqualToString:info.productIdentifier]) + return NO; + if (self.isSandbox != info.isSandbox) + return NO; + if (self.unsubscribeDetectedAt != info.unsubscribeDetectedAt && ![self.unsubscribeDetectedAt isEqualToDate:info.unsubscribeDetectedAt]) + return NO; + if (self.billingIssueDetectedAt != info.billingIssueDetectedAt && ![self.billingIssueDetectedAt isEqualToDate:info.billingIssueDetectedAt]) + return NO; + return YES; +} + +- (NSUInteger)hash +{ + NSUInteger hash = [self.identifier hash]; + hash = hash * 31u + self.isActive; + hash = hash * 31u + self.willRenew; + hash = hash * 31u + (NSUInteger) self.periodType; + hash = hash * 31u + [self.latestPurchaseDate hash]; + hash = hash * 31u + [self.originalPurchaseDate hash]; + hash = hash * 31u + [self.expirationDate hash]; + hash = hash * 31u + (NSUInteger) self.store; + hash = hash * 31u + [self.productIdentifier hash]; + hash = hash * 31u + self.isSandbox; + hash = hash * 31u + [self.unsubscribeDetectedAt hash]; + hash = hash * 31u + [self.billingIssueDetectedAt hash]; + return hash; +} + + +@end diff --git a/Pods/Purchases/Purchases/Public/RCEntitlementInfos.h b/Pods/Purchases/Purchases/Public/RCEntitlementInfos.h new file mode 100644 index 0000000..f5d47e3 --- /dev/null +++ b/Pods/Purchases/Purchases/Public/RCEntitlementInfos.h @@ -0,0 +1,36 @@ +// +// RCEntitlementInfos.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import + +@class RCEntitlementInfo; + +NS_ASSUME_NONNULL_BEGIN + +/** + This class contains all the entitlements associated to the user. + */ +NS_SWIFT_NAME(Purchases.EntitlementInfos) +@interface RCEntitlementInfos : NSObject + +/** + Dictionary of all EntitlementInfo (`RCEntitlementInfo`) objects (active and inactive) keyed by entitlement identifier. This dictionary can also be accessed by using an index subscript on EntitlementInfos, e.g. `entitlementInfos[@"pro_entitlement_id"]`. + */ +@property (readonly) NSDictionary *all; + +/** + Dictionary of active EntitlementInfo (`RCEntitlementInfo`) objects keyed by entitlement identifier. + */ +@property (readonly) NSDictionary *active; + +/// :nodoc: +- (nullable RCEntitlementInfo *)objectForKeyedSubscript:(id)key; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Public/RCEntitlementInfos.m b/Pods/Purchases/Purchases/Public/RCEntitlementInfos.m new file mode 100644 index 0000000..68a80ae --- /dev/null +++ b/Pods/Purchases/Purchases/Public/RCEntitlementInfos.m @@ -0,0 +1,89 @@ +// +// RCEntitlementInfos.m +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import "RCEntitlementInfos.h" +#import "RCEntitlementInfo.h" +#import "RCEntitlementInfo+Protected.h" + + +@interface RCEntitlementInfos () +@property (readwrite) NSDictionary *all; +@end + +@implementation RCEntitlementInfos + +- (instancetype)initWithEntitlementsData:(NSDictionary *)entitlementsData purchasesData:(NSDictionary *)purchasesData dateFormatter:(NSDateFormatter *)dateFormatter requestDate:(NSDate *)requestDate +{ + if (self = [super init]) { + NSMutableDictionary *entitlementInfos = [[NSMutableDictionary alloc] init]; + for (NSString *identifier in entitlementsData) { + id entitlement = entitlementsData[identifier]; + if ([entitlement isKindOfClass:NSDictionary.class]) { + id productIdentifier = entitlement[@"product_identifier"]; + if ([productIdentifier isKindOfClass:NSString.class]) { + id productData = purchasesData[productIdentifier]; + if ([productData isKindOfClass:NSDictionary.class]) { + RCEntitlementInfo *entitlementInfo = [[RCEntitlementInfo alloc] initWithEntitlementId:identifier entitlementData:entitlement productData:productData dateFormatter:dateFormatter requestDate:requestDate]; + entitlementInfos[identifier] = entitlementInfo; + } + } + } + } + self.all = [NSDictionary dictionaryWithDictionary:entitlementInfos]; + } + return self; +} + +- (NSDictionary *)active +{ + NSMutableDictionary *activeInfos = [[NSMutableDictionary alloc] init]; + for (NSString *identifier in self.all) { + RCEntitlementInfo *info = self.all[identifier]; + if (info.isActive) { + activeInfos[identifier] = info; + } + } + return activeInfos.copy; +} + +- (nullable RCEntitlementInfo *)objectForKeyedSubscript:(id)key +{ + return self.all[key]; +} + +- (NSString *)description +{ + NSMutableString *description = [NSMutableString stringWithFormat:@"<%@: ", NSStringFromClass([self class])]; + [description appendFormat:@"self.all=%@", self.all]; + [description appendFormat:@", self.active=%@", self.active]; + [description appendString:@">"]; + return description; +} + +- (BOOL)isEqual:(id)other +{ + if (other == self) + return YES; + if (!other || ![[other class] isEqual:[self class]]) + return NO; + + return [self isEqualToInfos:other]; +} + +- (BOOL)isEqualToInfos:(RCEntitlementInfos *)infos +{ + if (self == infos) + return YES; + if (infos == nil) + return NO; + if (self.all != infos.all && ![self.all isEqualToDictionary:infos.all]) + return NO; + return YES; +} + +@end diff --git a/Pods/Purchases/Purchases/Public/RCIntroEligibility.h b/Pods/Purchases/Purchases/Public/RCIntroEligibility.h new file mode 100644 index 0000000..fe3b139 --- /dev/null +++ b/Pods/Purchases/Purchases/Public/RCIntroEligibility.h @@ -0,0 +1,43 @@ +// +// RCIntroEligibility.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import + +/** + @typedef RCIntroEligibilityStatus + @brief Enum of different possible states for intro price eligibility status. + @constant RCIntroEligibilityStatusUnknown RevenueCat doesn't have enough information to determine eligibility. + @constant RCIntroEligibilityStatusIneligible The user is not eligible for a free trial or intro pricing for this product. + @constant RCIntroEligibilityStatusEligible The user is eligible for a free trial or intro pricing for this product. + */ +typedef NS_ENUM(NSInteger, RCIntroEligibilityStatus) { + /** + RevenueCat doesn't have enough information to determine eligibility. + */ + RCIntroEligibilityStatusUnknown = 0, + /** + The user is not eligible for a free trial or intro pricing for this product. + */ + RCIntroEligibilityStatusIneligible, + /** + The user is eligible for a free trial or intro pricing for this product. + */ + RCIntroEligibilityStatusEligible +}; + +/** + Class that holds the introductory price status + */ +@interface RCIntroEligibility : NSObject + +/** + The introductory price eligibility status + */ +@property (readonly) RCIntroEligibilityStatus status; + +@end diff --git a/Pods/Purchases/Purchases/Public/RCIntroEligibility.m b/Pods/Purchases/Purchases/Public/RCIntroEligibility.m new file mode 100644 index 0000000..0e1c1b9 --- /dev/null +++ b/Pods/Purchases/Purchases/Public/RCIntroEligibility.m @@ -0,0 +1,44 @@ +// +// RCIntroEligibility.m +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import "RCIntroEligibility.h" + +@interface RCIntroEligibility () + +@property (nonatomic) RCIntroEligibilityStatus status; + +@end + +@implementation RCIntroEligibility + +- (instancetype)initWithEligibilityStatus:(RCIntroEligibilityStatus)status { + if (self = [super init]) { + self.status = status; + } + return self; +} + +- (instancetype)initWithEligibilityStatusCode:(NSNumber *)statusCode { + RCIntroEligibilityStatus status = (RCIntroEligibilityStatus)statusCode.intValue; + NSParameterAssert(status >= RCIntroEligibilityStatusUnknown && status <= RCIntroEligibilityStatusEligible); + return [self initWithEligibilityStatus:status]; +} + +- (NSString *)description { + switch (self.status) { + case RCIntroEligibilityStatusEligible: + return @"Eligible for trial or introductory price."; + case RCIntroEligibilityStatusIneligible: + return @"Not eligible for trial or introductory price."; + case RCIntroEligibilityStatusUnknown: + default: + return @"Status indeterminate."; + } +} + +@end diff --git a/Pods/Purchases/Purchases/Public/RCOffering.h b/Pods/Purchases/Purchases/Public/RCOffering.h new file mode 100644 index 0000000..60235ba --- /dev/null +++ b/Pods/Purchases/Purchases/Public/RCOffering.h @@ -0,0 +1,82 @@ +// +// RCOffering.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class SKProduct; +@class RCPackage, RCOffering; + +/** + An offering is a collection of Packages (`RCPackage`) available for the user to purchase. For more info see https://docs.revenuecat.com/docs/entitlements + */ +NS_SWIFT_NAME(Purchases.Offering) +@interface RCOffering : NSObject + +/** + Unique identifier defined in RevenueCat dashboard. + */ +@property (readonly) NSString *identifier; + +/** + Offering description defined in RevenueCat dashboard. + */ +@property (readonly) NSString *serverDescription; + +/** + Array of `RCPackage` objects available for purchase. + */ +@property (readonly) NSArray *availablePackages; + +/** + Lifetime package type configured in the RevenueCat dashboard, if available. + */ +@property (readonly, nullable) RCPackage *lifetime; + +/** + Annual package type configured in the RevenueCat dashboard, if available. + */ +@property (readonly, nullable) RCPackage *annual; + +/** + Six month package type configured in the RevenueCat dashboard, if available. + */ +@property (readonly, nullable) RCPackage *sixMonth; + +/** + Three month package type configured in the RevenueCat dashboard, if available. + */ +@property (readonly, nullable) RCPackage *threeMonth; + +/** + Two month package type configured in the RevenueCat dashboard, if available. + */ +@property (readonly, nullable) RCPackage *twoMonth; + +/** + Monthly package type configured in the RevenueCat dashboard, if available. + */ +@property (readonly, nullable) RCPackage *monthly; + +/** + Weekly package type configured in the RevenueCat dashboard, if available. + */ +@property (readonly, nullable) RCPackage *weekly; + +/** + Retrieves a specific package by identifier, use this to access custom package types configured in the RevenueCat dashboard, e.g. `[offering packageWithIdentifier:@"custom_package_id"]` or `offering[@"custom_package_id"]`. + */ +- (nullable RCPackage *)packageWithIdentifier:(nullable NSString *)identifier NS_SWIFT_NAME(package(identifier:)); + +/// :nodoc: +- (nullable RCPackage *)objectForKeyedSubscript:(NSString *)key; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Public/RCOffering.m b/Pods/Purchases/Purchases/Public/RCOffering.m new file mode 100644 index 0000000..abbafad --- /dev/null +++ b/Pods/Purchases/Purchases/Public/RCOffering.m @@ -0,0 +1,102 @@ +// +// RCOffering.m +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 Purchases. All rights reserved. +// + +#import "RCOffering.h" +#import "RCPackage.h" + +@interface RCOffering () + +@property (readwrite) NSString *identifier; +@property (readwrite) NSString *serverDescription; +@property (readwrite) NSArray *availablePackages; +@property (readwrite, nullable) RCPackage *lifetime; +@property (readwrite, nullable) RCPackage *annual; +@property (readwrite, nullable) RCPackage *sixMonth; +@property (readwrite, nullable) RCPackage *threeMonth; +@property (readwrite, nullable) RCPackage *twoMonth; +@property (readwrite, nullable) RCPackage *monthly; +@property (readwrite, nullable) RCPackage *weekly; + +@end + +@implementation RCOffering + +- (instancetype)initWithIdentifier:(NSString *)identifier serverDescription:(NSString *)serverDescription availablePackages:(NSArray *)availablePackages +{ + self = [super init]; + if (self) { + self.identifier = identifier; + self.serverDescription = serverDescription; + self.availablePackages = availablePackages; + for (RCPackage *package in availablePackages) { + switch (package.packageType) { + case RCPackageTypeUnknown: + case RCPackageTypeCustom: + break; + case RCPackageTypeLifetime: + self.lifetime = package; + break; + case RCPackageTypeAnnual: + self.annual = package; + break; + case RCPackageTypeSixMonth: + self.sixMonth = package; + break; + case RCPackageTypeThreeMonth: + self.threeMonth = package; + break; + case RCPackageTypeTwoMonth: + self.twoMonth = package; + break; + case RCPackageTypeMonthly: + self.monthly = package; + break; + case RCPackageTypeWeekly: + self.weekly = package; + break; + } + } + } + + return self; +} + +- (nullable RCPackage *)packageWithIdentifier:(nullable NSString *)identifier +{ + for (RCPackage *package in self.availablePackages) { + if ([package.identifier isEqualToString:identifier]) { + return package; + } + } + return nil; +} + +- (nullable RCPackage *)objectForKeyedSubscript:(NSString *)key +{ + return [self packageWithIdentifier:key]; +} + +- (NSString *)description +{ + NSMutableString *description = [NSMutableString stringWithFormat:@""]; + return description; +} + + +@end diff --git a/Pods/Purchases/Purchases/Public/RCOfferings.h b/Pods/Purchases/Purchases/Public/RCOfferings.h new file mode 100644 index 0000000..36e2402 --- /dev/null +++ b/Pods/Purchases/Purchases/Public/RCOfferings.h @@ -0,0 +1,41 @@ +// +// RCOfferings.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 Purchases. All rights reserved. +// + +#import + +@class RCOffering; + +NS_ASSUME_NONNULL_BEGIN + +/** + This class contains all the offerings configured in RevenueCat dashboard. For more info see https://docs.revenuecat.com/docs/entitlements +*/ +NS_SWIFT_NAME(Purchases.Offerings) +@interface RCOfferings : NSObject + +/** + Current offering configured in the RevenueCat dashboard. +*/ +@property (readonly, nullable) RCOffering *current; + +/** + Dictionary of all Offerings (`RCOffering`) objects keyed by their identifier. This dictionary can also be accessed by using an index subscript on RCOfferings, e.g. `offerings[@"offering_id"]`. To access the current offering use `RCOfferings.current`. +*/ +@property (readonly) NSDictionary *all; + +/** + Retrieves a specific offering by its identifier, use this to access additional offerings configured in the RevenueCat dashboard, e.g. `[offerings offeringWithIdentifier:@"offering_id"]` or `offerings[@"offering_id"]`. To access the current offering use `RCOfferings.current`. +*/ +- (nullable RCOffering *)offeringWithIdentifier:(nullable NSString *)identifier NS_SWIFT_NAME(offering(identifier:)); + +/// :nodoc: +- (nullable RCOffering *)objectForKeyedSubscript:(NSString *)key; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Public/RCOfferings.m b/Pods/Purchases/Purchases/Public/RCOfferings.m new file mode 100644 index 0000000..0f81c07 --- /dev/null +++ b/Pods/Purchases/Purchases/Public/RCOfferings.m @@ -0,0 +1,60 @@ +// +// RCOfferings.m +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 Purchases. All rights reserved. +// + +#import "RCOfferings+Protected.h" +#import "RCOffering+Protected.h" + +@interface RCOfferings () +@property (readwrite, nullable) NSString *currentOfferingID; +@property (readwrite) NSDictionary *all; +@end + +@implementation RCOfferings +- (instancetype)initWithOfferings:(NSDictionary *)offerings currentOfferingID:(NSString *)currentOfferingID +{ + self = [super init]; + if (self) { + self.all = offerings; + self.currentOfferingID = currentOfferingID; + } + + return self; +} + +- (nullable RCOffering *)offeringWithIdentifier:(nullable NSString *)identifier +{ + return self.all[identifier]; +} + +- (nullable RCOffering *)objectForKeyedSubscript:(NSString *)key +{ + return [self offeringWithIdentifier:key]; +} + +- (nullable RCOffering *)current +{ + if (self.currentOfferingID) { + return self.all[self.currentOfferingID]; + } + return nil; +} + +- (NSString *)description +{ + NSMutableString *description = [NSMutableString stringWithFormat:@""]; + return description; +} + +@end diff --git a/Pods/Purchases/Purchases/Public/RCPackage.h b/Pods/Purchases/Purchases/Public/RCPackage.h new file mode 100644 index 0000000..2ecd544 --- /dev/null +++ b/Pods/Purchases/Purchases/Public/RCPackage.h @@ -0,0 +1,72 @@ +// +// RCPackage.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class SKProduct; + +/** + Enumeration of all possible Package types. +*/ +typedef NS_ENUM(NSInteger, RCPackageType) { + /// A package that was defined with a custom identifier. + RCPackageTypeUnknown = -2, + /// A package that was defined with a custom identifier. + RCPackageTypeCustom, + /// A package configured with the predefined lifetime identifier. + RCPackageTypeLifetime, + /// A package configured with the predefined annual identifier. + RCPackageTypeAnnual, + /// A package configured with the predefined six month identifier. + RCPackageTypeSixMonth, + /// A package configured with the predefined three month identifier. + RCPackageTypeThreeMonth, + /// A package configured with the predefined two month identifier. + RCPackageTypeTwoMonth, + /// A package configured with the predefined monthly identifier. + RCPackageTypeMonthly, + /// A package configured with the predefined weekly identifier. + RCPackageTypeWeekly +} NS_SWIFT_NAME(Purchases.PackageType); + +/** + Contains information about the product available for the user to purchase. For more info see https://docs.revenuecat.com/docs/entitlements +*/ +NS_SWIFT_NAME(Purchases.Package) +@interface RCPackage : NSObject + +/** + Unique identifier for this package. Can be one a predefined package type or a custom one. +*/ +@property (readonly) NSString *identifier; + +/** + Package type for the product. Will be one of `RCPackageType`. +*/ +@property (readonly) RCPackageType packageType; + +/** + `SKProduct` assigned to this package. https://developer.apple.com/documentation/storekit/skproduct +*/ +@property (readonly) SKProduct *product; + +/** + A String containing the localized price + */ +@property (readonly) NSString *localizedPriceString; + +/** + A String containing the localized introductory price + */ +@property (readonly) NSString *localizedIntroductoryPriceString; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Public/RCPackage.m b/Pods/Purchases/Purchases/Public/RCPackage.m new file mode 100644 index 0000000..25158e6 --- /dev/null +++ b/Pods/Purchases/Purchases/Public/RCPackage.m @@ -0,0 +1,80 @@ +// +// RCPackage.m +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import "RCPackage+Protected.h" +#import + +@interface RCPackage () + +@property (readwrite) NSString *identifier; +@property (readwrite) RCPackageType packageType; +@property (readwrite) SKProduct *product; +@property (readwrite) NSString *offeringIdentifier; + +@end + +@implementation RCPackage + ++ (nullable NSString *)stringFromPackageType:(RCPackageType)packageType +{ + if (packageType > PACKAGE_TYPE_STRINGS.count) { + return nil; + } + return PACKAGE_TYPE_STRINGS[packageType]; +} + ++ (RCPackageType)packageTypeFromString:(NSString *)string +{ + NSInteger index = [PACKAGE_TYPE_STRINGS indexOfObject:string]; + if (NSNotFound == index) { + if ([string hasPrefix:@"$rc_"]) { + return RCPackageTypeUnknown; + } else { + return RCPackageTypeCustom; + } + } + return (RCPackageType)(index); +} + + +- (instancetype)initWithIdentifier:(NSString *)identifier packageType:(RCPackageType)packageType product:(SKProduct *)product offeringIdentifier:(NSString *)offeringIdentifier +{ + self = [super init]; + if (self) { + self.identifier = identifier; + self.packageType = packageType; + self.product = product; + self.offeringIdentifier = offeringIdentifier; + } + + return self; +} + +- (NSString *)localizedPriceString { + if (!self.product) return @""; + + NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; + formatter.numberStyle = NSNumberFormatterCurrencyStyle; + formatter.locale = self.product.priceLocale; + + return [formatter stringFromNumber:self.product.price]; +} + +- (NSString *)localizedIntroductoryPriceString { + if (@available(iOS 11.2, macOS 10.13.2, tvOS 11.2, *)) { + NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; + formatter.numberStyle = NSNumberFormatterCurrencyStyle; + formatter.locale = self.product.priceLocale; + + return [formatter stringFromNumber:self.product.introductoryPrice.price]; + } else { + return @""; + } +} + +@end diff --git a/Pods/Purchases/Purchases/Public/RCPurchaserInfo.h b/Pods/Purchases/Purchases/Public/RCPurchaserInfo.h new file mode 100644 index 0000000..fffaca8 --- /dev/null +++ b/Pods/Purchases/Purchases/Public/RCPurchaserInfo.h @@ -0,0 +1,114 @@ +// +// RCPurchaserInfo.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import + +@class RCEntitlementInfos, RCTransaction; + +NS_ASSUME_NONNULL_BEGIN + +/** + A container for the most recent purchaser info returned from `RCPurchases`. These objects are non-mutable and do not update automatically. + */ +NS_SWIFT_NAME(Purchases.PurchaserInfo) +@interface RCPurchaserInfo : NSObject + +/// Entitlements attached to this purchaser info +@property (nonatomic, readonly) RCEntitlementInfos *entitlements; + +/// All *subscription* product identifiers with expiration dates in the future. +@property (nonatomic, readonly) NSSet *activeSubscriptions; + +/// All product identifiers purchases by the user regardless of expiration. +@property (nonatomic, readonly) NSSet *allPurchasedProductIdentifiers; + +/// Returns the latest expiration date of all products, nil if there are none +@property (readonly, nullable) NSDate *latestExpirationDate; + +/// Returns all product IDs of the non-subscription purchases a user has made. +@property (nonatomic, readonly) NSSet *nonConsumablePurchases DEPRECATED_MSG_ATTRIBUTE("use nonSubscriptionTransactions"); + +/// Returns all the non-subscription purchases a user has made. +/// The purchases are ordered by purchase date in ascending order. +@property (nonatomic, readonly) NSArray *nonSubscriptionTransactions; + +/** +Returns the build number (in iOS) or the marketing version (in macOS) for the version of the application when the user bought the app. +This corresponds to the value of CFBundleVersion (in iOS) or CFBundleShortVersionString (in macOS) in the Info.plist file when the purchase was originally made. +Use this for grandfathering users when migrating to subscriptions. + + + @note This can be nil, see -[RCPurchases restoreTransactionsForAppStore:] + */ +@property (nonatomic, readonly, nullable) NSString *originalApplicationVersion; + +/** +Returns the purchase date for the version of the application when the user bought the app. +Use this for grandfathering users when migrating to subscriptions. + +@note This can be nil, see -[RCPurchases restoreTransactionsForAppStore:] + */ +@property (nonatomic, readonly, nullable) NSDate *originalPurchaseDate; + +/** + Returns the fetch date of this Purchaser info. + @note Can be nil if was cached before we added this + */ +@property (nonatomic, readonly, nullable) NSDate *requestDate; + +/// The date this user was first seen in RevenueCat. +@property (nonatomic, readonly) NSDate *firstSeen; + +/// The original App User Id recorded for this user. +@property (nonatomic, readonly) NSString *originalAppUserId; + +/// URL to manage the active subscription of the user. +/// If this user has an active iOS subscription, this will point to the App Store, +/// if the user has an active Play Store subscription it will point there. +/// If there are no active subscriptions it will be null. +/// If there are multiple for different platforms, it will point to the App Store +@property (nonatomic, readonly, nullable) NSURL *managementURL; + +/** + Get the expiration date for a given product identifier. You should use Entitlements though! + + @param productIdentifier Product identifier for product + + @return The expiration date for `productIdentifier`, `nil` if product never purchased + */ +- (nullable NSDate *)expirationDateForProductIdentifier:(NSString *)productIdentifier; + +/** + Get the latest purchase or renewal date for a given product identifier. You should use Entitlements though! + + @param productIdentifier Product identifier for subscription product + + @return The purchase date for `productIdentifier`, `nil` if product never purchased + */ +- (nullable NSDate *)purchaseDateForProductIdentifier:(NSString *)productIdentifier; + +/** Get the expiration date for a given entitlement. + + @param entitlementId The id of the entitlement. + + @return The expiration date for the passed in `entitlement`, can be `nil` + */ +- (nullable NSDate *)expirationDateForEntitlement:(NSString *)entitlementId; + +/** + Get the latest purchase or renewal date for a given entitlement identifier. + + @param entitlementId Entitlement identifier for entitlement + + @return The purchase date for `entitlementId`, `nil` if product never purchased + */ +- (nullable NSDate *)purchaseDateForEntitlement:(NSString *)entitlementId; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Public/RCPurchaserInfo.m b/Pods/Purchases/Purchases/Public/RCPurchaserInfo.m new file mode 100644 index 0000000..a999e44 --- /dev/null +++ b/Pods/Purchases/Purchases/Public/RCPurchaserInfo.m @@ -0,0 +1,304 @@ +// +// RCPurchaserInfo.m +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import "RCPurchaserInfo.h" +#import "RCPurchaserInfo+Protected.h" +#import "RCEntitlementInfos.h" +#import "RCEntitlementInfos+Protected.h" +#import "RCEntitlementInfo.h" +@import PurchasesCoreSwift; + +@interface RCPurchaserInfo () + +@property (nonatomic) NSDictionary *expirationDatesByProduct; +@property (nonatomic) NSDictionary *purchaseDatesByProduct; +@property (nonatomic) NSSet *nonConsumablePurchases; +@property (nonatomic) NSArray *nonSubscriptionTransactions; +@property (nonatomic, nullable) NSString *originalApplicationVersion; +@property (nonatomic, nullable) NSDate *originalPurchaseDate; +@property (nonatomic) NSDictionary *originalData; +@property (nonatomic, nullable) NSDate *requestDate; +@property (nonatomic) NSDate *firstSeen; +@property (nonatomic) RCEntitlementInfos *entitlements; +@property (nonatomic) NSString *originalAppUserId; +@property (nonatomic, nullable) NSString *schemaVersion; +@property (nonatomic, nullable) NSURL *managementURL; + +@end + +static NSDateFormatter *dateFormatter; +static dispatch_once_t onceToken; + +@implementation RCPurchaserInfo + +- (nullable instancetype)initWithData:(NSDictionary *)data { + if (self = [super init]) { + if (data[@"subscriber"] == nil) { + return nil; + } + [self setUpDateFormatter]; + + self.originalData = data; + self.schemaVersion = data[@"schema_version"]; + self.requestDate = [dateFormatter dateFromString:(NSString *)data[@"request_date"]]; + + NSDictionary *subscriberData = data[@"subscriber"]; + + NSDictionary *subscriptions = subscriberData[@"subscriptions"]; + if (subscriptions == nil) { + return nil; + } + + [self configureWithSubscriberData:subscriberData subscriptions:subscriptions]; + } + return self; +} + +- (void)configureWithSubscriberData:(NSDictionary *)subscriberData subscriptions:(NSDictionary *)subscriptions { + [self initializePurchasesAndEntitlementsWithSubscriberData:subscriberData subscriptions:subscriptions]; + [self initializeMetadataWithSubscriberData:subscriberData]; +} + +- (void)initializeMetadataWithSubscriberData:(NSDictionary *)subscriberData { + NSObject *originalApplicationVersionOrNull = subscriberData[@"original_application_version"]; + self.originalApplicationVersion = [originalApplicationVersionOrNull isKindOfClass:[NSNull class]] + ? nil + : (NSString *)originalApplicationVersionOrNull; + + self.originalPurchaseDate = [self parseDate:subscriberData[@"original_purchase_date"] + withDateFormatter:dateFormatter]; + + self.firstSeen = [self parseDate:subscriberData[@"first_seen"] withDateFormatter:dateFormatter]; + + self.originalAppUserId = subscriberData[@"original_app_user_id"]; + + self.managementURL = [self parseURL:subscriberData[@"management_url"]]; +} + +- (void)initializePurchasesAndEntitlementsWithSubscriberData:(NSDictionary *)subscriberData + subscriptions:(NSDictionary *)subscriptions { + NSDictionary *nonSubscriptionsData = subscriberData[@"non_subscriptions"]; + self.nonConsumablePurchases = [NSSet setWithArray:[nonSubscriptionsData allKeys]]; + + RCTransactionsFactory *transactionsFactory = [[RCTransactionsFactory alloc] init]; + self.nonSubscriptionTransactions = [transactionsFactory nonSubscriptionTransactionsWithSubscriptionsData:nonSubscriptionsData dateFormatter:dateFormatter]; + + NSMutableDictionary *nonSubscriptionsLatestPurchases = [[NSMutableDictionary alloc] init]; + for (NSString* productId in nonSubscriptionsData) { + NSArray *arrayOfPurchases = nonSubscriptionsData[productId]; + if (arrayOfPurchases.count > 0) { + nonSubscriptionsLatestPurchases[productId] = arrayOfPurchases[arrayOfPurchases.count - 1]; + } + } + + NSMutableDictionary *allPurchases = [[NSMutableDictionary alloc] init]; + [allPurchases addEntriesFromDictionary:nonSubscriptionsLatestPurchases]; + [allPurchases addEntriesFromDictionary:subscriptions]; + NSDictionary *entitlements = subscriberData[@"entitlements"]; + self.entitlements = [[RCEntitlementInfos alloc] initWithEntitlementsData:entitlements + purchasesData:allPurchases + dateFormatter:dateFormatter + requestDate:self.requestDate]; + + self.expirationDatesByProduct = [self parseExpirationDate:subscriptions]; + self.purchaseDatesByProduct = [self parsePurchaseDate:allPurchases]; +} + +- (void)setUpDateFormatter { + dispatch_once(&onceToken, ^{ + dateFormatter = [[NSDateFormatter alloc] init]; + dateFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"]; + dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ"; + dateFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + }); +} + +- (nullable NSDate *)parseDate:(id)dateString withDateFormatter:(NSDateFormatter *)dateFormatter { + if ([dateString isKindOfClass:NSString.class]) { + return [dateFormatter dateFromString:(NSString *)dateString]; + } + return nil; +} + +- (nullable NSURL *)parseURL:(id)urlString { + if ([urlString isKindOfClass:NSString.class]) { + return [NSURL URLWithString:urlString]; + } + return nil; +} + +- (NSDictionary *)parseExpirationDate:(NSDictionary *)expirationDates { + return [self parseDatesIn:expirationDates withLabel:@"expires_date"]; +} + +- (NSDictionary *)parsePurchaseDate:(NSDictionary *)purchaseDates { + return [self parseDatesIn:purchaseDates withLabel:@"purchase_date"]; +} + +- (NSDictionary *)parseDatesIn:(NSDictionary *)dates + withLabel:(NSString *)label { + NSMutableDictionary *parsedDates = [NSMutableDictionary new]; + + for (NSString *identifier in dates) { + id dateString = dates[identifier][label]; + + if ([dateString isKindOfClass:NSString.class]) { + NSDate *date = [dateFormatter dateFromString:(NSString *)dateString]; + + if (date != nil) { + parsedDates[identifier] = date; + } + } else { + parsedDates[identifier] = [NSNull null]; + } + } + + return [NSDictionary dictionaryWithDictionary:parsedDates]; +} + +- (NSSet *)allPurchasedProductIdentifiers { + return [self.nonConsumablePurchases setByAddingObjectsFromArray:self.expirationDatesByProduct.allKeys]; +} + +- (NSSet *)activeKeys:(NSDictionary *)dates { + NSMutableSet *activeSubscriptions = [NSMutableSet setWithCapacity:dates.count]; + + for (NSString *identifier in dates) { + NSDate *dateOrNull = (NSDate *)dates[identifier]; + if ([dateOrNull isKindOfClass:NSNull.class] || [self isAfterReferenceDate:dateOrNull]) { + [activeSubscriptions addObject:identifier]; + } + } + + return [NSSet setWithSet:activeSubscriptions]; +} + +- (BOOL)isAfterReferenceDate:(NSDate *)date { + NSDate *referenceDate = self.requestDate ?: [NSDate date]; + return [date timeIntervalSinceDate:referenceDate] > 0; +} + +- (NSSet *)activeSubscriptions { + return [self activeKeys:self.expirationDatesByProduct]; +} + +- (nullable NSDate *)latestExpirationDate { + NSDate *maxDate = nil; + + for (NSDate *date in self.expirationDatesByProduct.allValues) { + if (date.timeIntervalSince1970 > maxDate.timeIntervalSince1970) { + maxDate = date; + } + } + + return maxDate; +} + +- (NSSet *)activeEntitlements { + return [NSSet setWithArray:self.entitlements.active.allKeys]; +} + +- (nullable NSDate *)expirationDateForProductIdentifier:(NSString *)productIdentifier { + return self.expirationDatesByProduct[productIdentifier]; +} + +- (nullable NSDate *)purchaseDateForProductIdentifier:(NSString *)productIdentifier { + NSObject *dateOrNull = self.purchaseDatesByProduct[productIdentifier]; + return [dateOrNull isKindOfClass:NSNull.class] ? nil : (NSDate *)dateOrNull; +} + +- (nullable NSDate *)expirationDateForEntitlement:(NSString *)entitlementId { + return self.entitlements[entitlementId].expirationDate; +} + +- (nullable NSDate *)purchaseDateForEntitlement:(NSString *)entitlementId { + return self.entitlements[entitlementId].latestPurchaseDate; +} + +- (NSDictionary *)JSONObject { + NSMutableDictionary *dictionary = [self.originalData mutableCopy]; + dictionary[@"schema_version"] = [RCPurchaserInfo currentSchemaVersion]; + return dictionary; +} + ++ (NSString *)currentSchemaVersion { + return @"2"; +} + +- (BOOL)isEqual:(RCPurchaserInfo *)other { + BOOL isEqual = ([self.expirationDatesByProduct isEqual:other.expirationDatesByProduct] + && [self.purchaseDatesByProduct isEqual:other.purchaseDatesByProduct] + && [self.nonConsumablePurchases isEqual:other.nonConsumablePurchases]); + + isEqual &= ([self.entitlements isEqual:other.entitlements]); + + + if (self.originalApplicationVersion != nil || other.originalApplicationVersion != nil) { + isEqual &= ([self.originalApplicationVersion isEqual:other.originalApplicationVersion]); + } + + return isEqual; +} + +- (NSDictionary *)descriptionDictionaryForEntitlementInfo:(RCEntitlementInfo *)info { + return @{ + @"expiresDate": info.expirationDate ?: @"null", + @"latestPurchaseDate": info.latestPurchaseDate ?: @"null", + @"originalPurchaseDate": info.originalPurchaseDate ?: @"null", + @"periodType": info.periodType ? @(info.periodType) : @"null", + @"isActive": info.isActive ? @"Yes" : @"No", + @"willRenew": info.willRenew ? @"Yes" : @"No", + @"store": @(info.store), + @"productIdentifier": info.productIdentifier ?: @"null", + @"isSandbox": info.isSandbox ? @"Yes" : @"No", + @"unsubscribeDetectedAt": info.unsubscribeDetectedAt ?: @"null", + @"billingIssueDetectedAt": info.billingIssueDetectedAt ?: @"null" + }; +} + +- (NSString *)description { + NSMutableDictionary *activeSubscriptions = [NSMutableDictionary dictionary]; + for (NSString *activeSubscriptionId in self.activeSubscriptions) { + activeSubscriptions[activeSubscriptionId] = @{ + @"expiresDate": [self expirationDateForProductIdentifier:activeSubscriptionId] ?: @"null", + }; + } + + NSMutableDictionary *activeEntitlements = [NSMutableDictionary dictionary]; + for (NSString *entitlementId in self.entitlements.active) { + activeEntitlements[entitlementId] = [self descriptionDictionaryForEntitlementInfo:self.entitlements.active[entitlementId]]; + } + + NSMutableDictionary *entitlements = [NSMutableDictionary dictionary]; + for (NSString *entitlementId in self.entitlements.all) { + entitlements[entitlementId] = [self descriptionDictionaryForEntitlementInfo:self.entitlements[entitlementId]]; + } + + return [NSString stringWithFormat:@"", + self.originalApplicationVersion, + self.latestExpirationDate, + activeEntitlements, + activeSubscriptions, + self.nonConsumablePurchases, + self.requestDate, + self.firstSeen, + self.originalAppUserId, + entitlements]; +} + +@end diff --git a/Pods/Purchases/Purchases/Public/RCPurchases.h b/Pods/Purchases/Purchases/Public/RCPurchases.h new file mode 100644 index 0000000..3e6ac1d --- /dev/null +++ b/Pods/Purchases/Purchases/Public/RCPurchases.h @@ -0,0 +1,574 @@ +// +// RCPurchases.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import + +#import "RCAttributionNetwork.h" + +@class SKProduct, SKPayment, SKPaymentTransaction, SKPaymentDiscount, SKProductDiscount, RCPurchaserInfo, RCIntroEligibility, RCOfferings, RCOffering, RCPackage; +@protocol RCPurchasesDelegate; + +NS_ASSUME_NONNULL_BEGIN + +/** + Completion block for calls that send back a `PurchaserInfo` + */ +typedef void (^RCReceivePurchaserInfoBlock)(RCPurchaserInfo * _Nullable, NSError * _Nullable) NS_SWIFT_NAME(Purchases.ReceivePurchaserInfoBlock); + +/** + Completion block for `-[RCPurchases checkTrialOrIntroductoryPriceEligibility:completionBlock:]` + */ +typedef void (^RCReceiveIntroEligibilityBlock)(NSDictionary *) NS_SWIFT_NAME(Purchases.ReceiveIntroEligibilityBlock); + +/** + Completion block for `-[RCPurchases offeringsWithCompletionBlock:]` + */ +typedef void (^RCReceiveOfferingsBlock)(RCOfferings * _Nullable, NSError * _Nullable) NS_SWIFT_NAME(Purchases.ReceiveOfferingsBlock); + +/** + Completion block for `-[RCPurchases productsWithIdentifiers:completionBlock:]` + */ +typedef void (^RCReceiveProductsBlock)(NSArray *) NS_SWIFT_NAME(Purchases.ReceiveProductsBlock); + +/** + Completion block for `-[RCPurchases purchaseProduct:withCompletionBlock:]` + */ +typedef void (^RCPurchaseCompletedBlock)(SKPaymentTransaction * _Nullable, RCPurchaserInfo * _Nullable, NSError * _Nullable, BOOL userCancelled) NS_SWIFT_NAME(Purchases.PurchaseCompletedBlock); + +/** + Deferred block for `purchases:shouldPurchasePromoProduct:defermentBlock:` + */ +typedef void (^RCDeferredPromotionalPurchaseBlock)(RCPurchaseCompletedBlock); + +/** + * Deferred block for `-[RCPurchases paymentDiscountForProductDiscount:product:completion:]` + */ +API_AVAILABLE(ios(12.2), macos(10.14.4)) +typedef void (^RCPaymentDiscountBlock)(SKPaymentDiscount * _Nullable, NSError * _Nullable) NS_SWIFT_NAME(Purchases.PaymentDiscountBlock); + +/** + `RCPurchases` is the entry point for Purchases.framework. It should be instantiated as soon as your app has a unique user id for your user. This can be when a user logs in if you have accounts or on launch if you can generate a random user identifier. + + @warning Only one instance of RCPurchases should be instantiated at a time! Use a configure method to let the framework handle the singleton instance for you. + */ +NS_SWIFT_NAME(Purchases) +@interface RCPurchases : NSObject + +/** + Enable automatic collection of Apple Search Ads attribution. Disabled by default + */ +@property (class, nonatomic, assign) BOOL automaticAppleSearchAdsAttributionCollection; + +/** + Enable debug logging. Useful for debugging issues with the lovely team @RevenueCat +*/ +@property (class, nonatomic, assign) BOOL debugLogsEnabled; + +/** + Set this property to your proxy URL before configuring Purchases *only* if you've received a proxy key value from your RevenueCat contact. +*/ +@property (class, nonatomic, copy, nullable) NSURL *proxyURL; + +/** + Set this property to true *only* if you're transitioning an existing Mac app from the Legacy Mac App Store + into the Universal Store, and you've configured your RevenueCat app accordingly. Contact support before using this. +*/ +@property (class, nonatomic, assign) BOOL forceUniversalAppStore; + +/** + Configures an instance of the Purchases SDK with a specified API key. The instance will be set as a singleton. You should access the singleton instance using [RCPurchases sharedPurchases] + + @note Use this initializer if your app does not have an account system. `RCPurchases` will generate a unique identifier for the current device and persist it to `NSUserDefaults`. This also affects the behavior of `restoreTransactionsForAppStoreAccount`. + + @param APIKey The API Key generated for your app from https://app.revenuecat.com/ + + @return An instantiated `RCPurchases` object that has been set as a singleton. + */ ++ (instancetype)configureWithAPIKey:(NSString *)APIKey; + +/** + Configures an instance of the Purchases SDK with a specified API key and app user ID. The instance will be set as a singleton. You should access the singleton instance using [RCPurchases sharedPurchases] + + @note Best practice is to use a salted hash of your unique app user ids. + + @warning Use this initializer if you have your own user identifiers that you manage. + + @param APIKey The API Key generated for your app from https://app.revenuecat.com/ + + @param appUserID The unique app user id for this user. This user id will allow users to share their purchases and subscriptions across devices. Pass nil if you want `RCPurchases` to generate this for you. + + @return An instantiated `RCPurchases` object that has been set as a singleton. + */ ++ (instancetype)configureWithAPIKey:(NSString *)APIKey appUserID:(nullable NSString *)appUserID; + +/** + Configures an instance of the Purchases SDK with a custom userDefaults. Use this constructor if you want to sync status across a shared container, such as between a host app and an extension. The instance of the Purchases SDK will be set as a singleton. You should access the singleton instance using [RCPurchases sharedPurchases] + + @param APIKey The API Key generated for your app from https://app.revenuecat.com/ + + @param appUserID The unique app user id for this user. This user id will allow users to share their purchases and subscriptions across devices. Pass nil if you want `RCPurchases` to generate this for you. + + @param observerMode Set this to TRUE if you have your own IAP implementation and want to use only RevenueCat's backend. Default is FALSE. + + @return An instantiated `RCPurchases` object that has been set as a singleton. + */ ++ (instancetype)configureWithAPIKey:(NSString *)APIKey + appUserID:(nullable NSString *)appUserID + observerMode:(BOOL)observerMode; + +/** + Configures an instance of the Purchases SDK with a custom userDefaults. Use this constructor if you want to sync status across a shared container, such as between a host app and an extension. The instance of the Purchases SDK will be set as a singleton. You should access the singleton instance using [RCPurchases sharedPurchases] + + @param APIKey The API Key generated for your app from https://app.revenuecat.com/ + + @param appUserID The unique app user id for this user. This user id will allow users to share their purchases and subscriptions across devices. Pass nil if you want `RCPurchases` to generate this for you. + + @param observerMode Set this to TRUE if you have your own IAP implementation and want to use only RevenueCat's backend. Default is FALSE. + + @param userDefaults Custom userDefaults to use + + @return An instantiated `RCPurchases` object that has been set as a singleton. + */ ++ (instancetype)configureWithAPIKey:(NSString *)APIKey + appUserID:(nullable NSString *)appUserID + observerMode:(BOOL)observerMode + userDefaults:(nullable NSUserDefaults *)userDefaults; + +/** + Indicates whether the user is allowed to make payments. + */ ++ (BOOL)canMakePayments; + +/** + @return A singleton `RCPurchases` object. Call this after a configure method to access the singleton. + */ +@property (class, nonatomic, readonly) RCPurchases *sharedPurchases; + +#pragma mark Configuration + +/** Set this to true if you are passing in an appUserID but it is anonymous, this is true by default if you didn't pass an appUserID + If a user tries to purchase a product that is active on the current app store account, we will treat it as a restore and alias + the new ID with the previous id. + */ +@property (nonatomic) BOOL allowSharingAppStoreAccount; + +/// Default to YES, set this to NO if you are finishing transactions with your own StoreKit queue listener +@property (nonatomic) BOOL finishTransactions; + +/// This version of the Purchases framework ++ (NSString *)frameworkVersion; + +/// Delegate for `RCPurchases` instance. The delegate is responsible for handling promotional product purchases and changes to purchaser information. +@property (nonatomic, weak, nullable) id delegate; + +#pragma mark Identity + +/// The `appUserID` used by `RCPurchases`. If not passed on initialization this will be generated and cached by `RCPurchases`. +@property (nonatomic, readonly) NSString *appUserID; + +/// If the `appUserID` has been generated by RevenueCat +@property (nonatomic, readonly) BOOL isAnonymous; + +/** + This function will alias two appUserIDs together. + @param alias The new appUserID that should be linked to the currently identified appUserID + @param completion An optional completion block called when the aliasing has been successful. This completion block will receive an error if there's been one. + */ +- (void)createAlias:(NSString *)alias completionBlock:(nullable RCReceivePurchaserInfoBlock)completion +NS_SWIFT_NAME(createAlias(_:_:)); + +/** + This function will identify the current user with an appUserID. Typically this would be used after a logout to identify a new user without calling configure + @param appUserID The appUserID that should be linked to the currently user + */ +- (void)identify:(NSString *)appUserID completionBlock:(nullable RCReceivePurchaserInfoBlock)completion +NS_SWIFT_NAME(identify(_:_:)); + +/** + * Resets the Purchases client clearing the saved appUserID. This will generate a random user id and save it in the cache. + */ +- (void)resetWithCompletionBlock:(nullable RCReceivePurchaserInfoBlock)completion +NS_SWIFT_NAME(reset(_:)); + +#pragma mark Attribution + +/** + Send your attribution data to RevenueCat so you can track the revenue generated by your different campaigns. + + @param data Dictionary provided by the network. See https://docs.revenuecat.com/docs/attribution + @param network Enum for the network the data is coming from, see `RCAttributionNetwork` for supported networks + */ ++ (void)addAttributionData:(NSDictionary *)data + fromNetwork:(RCAttributionNetwork)network __attribute((deprecated("Use the set functions instead."))); + +/** + Send your attribution data to RevenueCat so you can track the revenue generated by your different campaigns. + + @param data Dictionary provided by the network. See https://docs.revenuecat.com/docs/attribution + @param network Enum for the network the data is coming from, see `RCAttributionNetwork` for supported networks + @param networkUserId User Id that should be sent to the network. Default is the current App User Id + */ ++ (void)addAttributionData:(NSDictionary *)data + fromNetwork:(RCAttributionNetwork)network + forNetworkUserId:(nullable NSString *)networkUserId NS_SWIFT_NAME(addAttributionData(_:from:forNetworkUserId:)) +__attribute((deprecated("Use the set functions instead."))); + +#pragma mark Purchases + +/** + Get latest available purchaser info. + + @param completion A completion block called when purchaser info is available and not stale. Called immediately if purchaser info is cached. Purchaser info can be nil if an error occurred. + */ +- (void)purchaserInfoWithCompletionBlock:(RCReceivePurchaserInfoBlock)completion +NS_SWIFT_NAME(purchaserInfo(_:)); + +/** + Fetch the configured offerings for this users. Offerings allows you to configure your in-app products via RevenueCat and greatly simplifies management. See the guide (https://docs.revenuecat.com/entitlements) for more info. + + Offerings will be fetched and cached on instantiation so that, by the time they are needed, your prices are loaded for your purchase flow. Time is money. + + @param completion A completion block called when offerings are available. Called immediately if offerings are cached. Offerings will be nil if an error occurred. + */ +- (void)offeringsWithCompletionBlock:(RCReceiveOfferingsBlock)completion NS_SWIFT_NAME(offerings(_:)); + +/** + Fetches the `SKProducts` for your IAPs for given `productIdentifiers`. Use this method if you aren't using `-offeringsWithCompletionBlock:`. + You should use offerings though. + + @note `completion` may be called without `SKProduct`s that you are expecting. This is usually caused by iTunesConnect configuration errors. Ensure your IAPs have the "Ready to Submit" status in iTunesConnect. Also ensure that you have an active developer program subscription and you have signed the latest paid application agreements. If you're having trouble see: https://www.revenuecat.com/2018/10/11/configuring-in-app-products-is-hard + + @param productIdentifiers A set of product identifiers for in app purchases setup via iTunesConnect. This should be either hard coded in your application, from a file, or from a custom endpoint if you want to be able to deploy new IAPs without an app update. + @param completion An @escaping callback that is called with the loaded products. If the fetch fails for any reason it will return an empty array. + */ +- (void)productsWithIdentifiers:(NSArray *)productIdentifiers + completionBlock:(RCReceiveProductsBlock)completion +NS_SWIFT_NAME(products(_:_:)); + +/** + Use this function if you are not using the Offerings system to purchase an `SKProduct`. If you are using the Offerings system, use `-[RCPurchases purchasePackage:withCompletionBlock]` instead. + + Call this method when a user has decided to purchase a product. Only call this in direct response to user input. + + From here `Purchases` will handle the purchase with `StoreKit` and call the `RCPurchaseCompletedBlock`. + + @note You do not need to finish the transaction yourself in the completion callback, Purchases will handle this for you. + + @param product The `SKProduct` the user intends to purchase + + @param completion A completion block that is called when the purchase completes. If the purchase was successful there will be a `SKPaymentTransaction` and a `RCPurchaserInfo`. If the purchase was not successful, there will be an `NSError`. If the user cancelled, `userCancelled` will be `YES`. + */ +- (void)purchaseProduct:(SKProduct *)product withCompletionBlock:(RCPurchaseCompletedBlock)completion +NS_SWIFT_NAME(purchaseProduct(_:_:)); + +/** + Purchase the passed `RCPackage`. + + Call this method when a user has decided to purchase a product. Only call this in direct response to user input. + + From here `Purchases` will handle the purchase with `StoreKit` and call the `RCPurchaseCompletedBlock`. + + @note You do not need to finish the transaction yourself in the completion callback, Purchases will handle this for you. + + @param package The `RCPackage` the user intends to purchase + + @param completion A completion block that is called when the purchase completes. If the purchase was successful there will be a `SKPaymentTransaction` and a `RCPurchaserInfo`. If the purchase was not successful, there will be an `NSError`. If the user cancelled, `userCancelled` will be `YES`. + */ +- (void)purchasePackage:(RCPackage *)package withCompletionBlock:(RCPurchaseCompletedBlock)completion +NS_SWIFT_NAME(purchasePackage(_:_:)); + +/** + This method will post all purchases associated with the current App Store account to RevenueCat and become associated with the current `appUserID`. + If the receipt is being used by an existing user, the current `appUserID` will be aliased together with the `appUserID` of the existing user. + Going forward, either `appUserID` will be able to reference the same user. + + You shouldn't use this method if you have your own account system. In that case "restoration" is provided by your app passing + the same `appUserId` used to purchase originally. + + @note This may force your users to enter the App Store password so should only be performed on request of the user. + Typically with a button in settings or near your purchase UI. Use syncPurchasesWithCompletionBlock + if you need to restore transactions programmatically. + */ +- (void)restoreTransactionsWithCompletionBlock:(nullable RCReceivePurchaserInfoBlock)completion +NS_SWIFT_NAME(restoreTransactions(_:)); + +/** + This method will post all purchases associated with the current App Store account to RevenueCat and become associated with the current `appUserID`. + If the receipt is being used by an existing user, the current `appUserID` will be aliased together with the `appUserID` of the existing user. + Going forward, either `appUserID` will be able to reference the same user. + + @warning This function should only be called if you're not calling any purchase method. + + @note This method will not trigger a login prompt from App Store. However, if the receipt currently on the device does not contain subscriptions, + but the user has made subscription purchases, this method won't be able to restore them. Use restoreTransactionsWithCompletionBlock to cover those cases. + */ +- (void)syncPurchasesWithCompletionBlock:(nullable RCReceivePurchaserInfoBlock)completion +NS_SWIFT_NAME(syncPurchases(_:)); + + +/** + Computes whether or not a user is eligible for the introductory pricing period of a given product. You should use this method to determine whether or not you show the user the normal product price or the introductory price. This also applies to trials (trials are considered a type of introductory pricing). + + @note Subscription groups are automatically collected for determining eligibility. If RevenueCat can't definitively compute the eligibilty, most likely because of missing group information, it will return `RCIntroEligibilityStatusUnknown`. The best course of action on unknown status is to display the non-intro pricing, to not create a misleading situation. To avoid this, make sure you are testing with the latest version of iOS so that the subscription group can be collected by the SDK. + + @param productIdentifiers Array of product identifiers for which you want to compute eligibility + @param receiveEligibility A block that receives a dictionary of product_id -> `RCIntroEligibility`. +*/ +- (void)checkTrialOrIntroductoryPriceEligibility:(NSArray *)productIdentifiers + completionBlock:(RCReceiveIntroEligibilityBlock)receiveEligibility; + +/** + Use this function to retrieve the `SKPaymentDiscount` for a given `SKProduct`. + + @param discount The `SKProductDiscount` to apply to the product. + + @param product The `SKProduct` the user intends to purchase. + + @param completion A completion block that is called when the `SKPaymentDiscount` is returned. If it was not successful, there will be an `NSError`. +*/ +- (void)paymentDiscountForProductDiscount:(SKProductDiscount *)discount + product:(SKProduct *)product + completion:(RCPaymentDiscountBlock)completion API_AVAILABLE(ios(12.2), macosx(10.14.4)); + + +/** + Use this function if you are not using the Offerings system to purchase an `SKProduct` with an applied `SKPaymentDiscount`. If you are using the Offerings system, use `-[RCPurchases purchasePackage:withDiscount:withCompletionBlock]` instead. + + Call this method when a user has decided to purchase a product with an applied discount. Only call this in direct response to user input. + + From here `Purchases` will handle the purchase with `StoreKit` and call the `RCPurchaseCompletedBlock`. + + @note You do not need to finish the transaction yourself in the completion callback, Purchases will handle this for you. + + @param product The `SKProduct` the user intends to purchase + + @param discount The `SKPaymentDiscount` to apply to the purchase + + @param completion A completion block that is called when the purchase completes. If the purchase was successful there will be a `SKPaymentTransaction` and a `RCPurchaserInfo`. If the purchase was not successful, there will be an `NSError`. If the user cancelled, `userCancelled` will be `YES`. + */ +- (void)purchaseProduct:(SKProduct *)product + withDiscount:(SKPaymentDiscount *)discount + completionBlock:(RCPurchaseCompletedBlock)completion NS_SWIFT_NAME(purchaseProduct(_:discount:_:)) API_AVAILABLE(ios(12.2), macosx(10.14.4)); + +/** + Purchase the passed `RCPackage`. + + Call this method when a user has decided to purchase a product with an applied discount. Only call this in direct response to user input. + + From here `Purchases` will handle the purchase with `StoreKit` and call the `RCPurchaseCompletedBlock`. + + @note You do not need to finish the transaction yourself in the completion callback, Purchases will handle this for you. + + @param package The `RCPackage` the user intends to purchase + + @param discount The `SKPaymentDiscount` to apply to the purchase + + @param completion A completion block that is called when the purchase completes. If the purchase was successful there will be a `SKPaymentTransaction` and a `RCPurchaserInfo`. If the purchase was not successful, there will be an `NSError`. If the user cancelled, `userCancelled` will be `YES`. + */ +- (void)purchasePackage:(RCPackage *)package + withDiscount:(SKPaymentDiscount *)discount + completionBlock:(RCPurchaseCompletedBlock)completion NS_SWIFT_NAME(purchasePackage(_:discount:_:)) API_AVAILABLE(ios(12.2), macosx(10.14.4)); + + +/** + Invalidates the cache for purchaser information. + + Most apps will not need to use this method; invalidating the cache can leave your app in an invalid state. + Refer to https://docs.revenuecat.com/docs/purchaserinfo#section-get-user-information for more information on + using the cache properly. + + This is useful for cases where purchaser information might have been updated outside of the app, like if a + promotional subscription is granted through the RevenueCat dashboard. + */ +- (void)invalidatePurchaserInfoCache; + +/** + Displays a sheet that enables users to redeem subscription offer codes that you generated in App Store Connect. + */ +- (void)presentCodeRedemptionSheet API_AVAILABLE(ios(14.0)) API_UNAVAILABLE(tvos, macos, watchos); + + +#pragma mark Subscriber Attributes + +/** + Subscriber attributes are useful for storing additional, structured information on a user. + Since attributes are writable using a public key they should not be used for + managing secure or sensitive information such as subscription status, coins, etc. + + Key names starting with "$" are reserved names used by RevenueCat. For a full list of key + restrictions refer to our guide: https://docs.revenuecat.com/docs/subscriber-attributes + + @param attributes Map of attributes by key. Set the value as an empty string to delete an attribute. +*/ +- (void)setAttributes:(NSDictionary *)attributes; + +/** + * Subscriber attribute associated with the email address for the user + * + * @param email Empty String or nil will delete the subscriber attribute. + */ +- (void)setEmail:(nullable NSString *)email; + +/** + * Subscriber attribute associated with the phone number for the user + * + * @param phoneNumber Empty String or nil will delete the subscriber attribute. + */ +- (void)setPhoneNumber:(nullable NSString *)phoneNumber; + +/** + * Subscriber attribute associated with the display name for the user + * + * @param displayName Empty String or nil will delete the subscriber attribute. + */ +- (void)setDisplayName:(nullable NSString *)displayName; + +/** + * Subscriber attribute associated with the push token for the user + * + * @param pushToken nil will delete the subscriber attribute. + */ +- (void)setPushToken:(nullable NSData *)pushToken; + +/** + * Subscriber attribute associated with the Adjust Id for the user + * Required for the RevenueCat Adjust integration + * + * @param adjustID nil will delete the subscriber attribute + */ +- (void)setAdjustID:(nullable NSString *)adjustID; + +/** + * Subscriber attribute associated with the Appsflyer Id for the user + * Required for the RevenueCat Appsflyer integration + * + * @param appsflyerID nil will delete the subscriber attribute + */ +- (void)setAppsflyerID:(nullable NSString *)appsflyerID; + +/** + * Subscriber attribute associated with the Facebook SDK Anonymous Id for the user + * Recommended for the RevenueCat Facebook integration + * + * @param fbAnonymousID nil will delete the subscriber attribute + */ +- (void)setFBAnonymousID:(nullable NSString *)fbAnonymousID; + +/** + * Subscriber attribute associated with the mParticle Id for the user + * Recommended for the RevenueCat mParticle integration + * + * @param mparticleID nil will delete the subscriber attribute + */ +- (void)setMparticleID:(nullable NSString *)mparticleID; + +/** + * Subscriber attribute associated with the OneSignal Player Id for the user + * Required for the RevenueCat OneSignal integration + * + * @param onesignalID nil will delete the subscriber attribute + */ +- (void)setOnesignalID:(nullable NSString *)onesignalID; + +/** + * Subscriber attribute associated with the install media source for the user + * + * @param mediaSource nil will delete the subscriber attribute. + */ +- (void)setMediaSource:(nullable NSString *)mediaSource; + +/** + * Subscriber attribute associated with the install campaign for the user + * + * @param campaign nil will delete the subscriber attribute. + */ +- (void)setCampaign:(nullable NSString *)campaign; + +/** + * Subscriber attribute associated with the install ad group for the user + * + * @param adGroup nil will delete the subscriber attribute. + */ +- (void)setAdGroup:(nullable NSString *)adGroup; + +/** + * Subscriber attribute associated with the install ad for the user + * + * @param ad nil will delete the subscriber attribute. + */ +- (void)setAd:(nullable NSString *)ad; + +/** + * Subscriber attribute associated with the install keyword for the user + * + * @param keyword nil will delete the subscriber attribute. + */ +- (void)setKeyword:(nullable NSString *)keyword; + +/** + * Subscriber attribute associated with the install ad creative for the user + * + * @param creative nil will delete the subscriber attribute. + */ +- (void)setCreative:(nullable NSString *)creative; + +/** + * Automatically collect subscriber attributes associated with the device identifiers + * $idfa, $idfv, $ip + * + */ +- (void)collectDeviceIdentifiers; + +#pragma mark Unavailable Methods +#define RC_UNAVAILABLE(msg) __attribute__((unavailable(msg))); +/// :nodoc: +typedef void (^RCReceiveEntitlementsBlock)(id _Nullable, NSError * _Nullable) NS_SWIFT_NAME(Purchases.ReceiveEntitlementsBlock); +/// :nodoc: +- (void)makePurchase:(SKProduct *)product withCompletionBlock:(RCPurchaseCompletedBlock)block +NS_SWIFT_NAME(makePurchaseSwift(_:_:)) RC_UNAVAILABLE("makePurchase: has been replaced by purchaseProduct:"); +/// :nodoc: +- (void)entitlementsWithCompletionBlock:(RCReceiveEntitlementsBlock)completion +NS_SWIFT_NAME(entitlements(_:)) RC_UNAVAILABLE("entitlements: has been replaced with offerings:. See https://docs.revenuecat.com/docs/offerings-migration"); +/// :nodoc: +- (void)makePurchase:(SKProduct *)product + withDiscount:(nullable SKPaymentDiscount *)discount + completionBlock:(RCPurchaseCompletedBlock)completion NS_SWIFT_NAME(makePurchase(_:discount:_:)) API_AVAILABLE(ios(12.2), macosx(10.14.4)) __attribute__((unavailable("makePurchase:withDiscount: has been replaced by purchaseProduct:withDiscount:")));; + +#undef RC_UNAVAILABLE + +@end + +/** + Delegate for `RCPurchases` responsible for handling updating your app's state in response to updated purchaser info or promotional product purchases. + + @note Delegate methods can be called at any time after the `delegate` is set, not just in response to `purchaserInfo:` calls. Ensure your app is capable of handling these calls at anytime if `delegate` is set. + */ +NS_SWIFT_NAME(PurchasesDelegate) +@protocol RCPurchasesDelegate +@optional + +/** + Called whenever `RCPurchases` receives updated purchaser info. This may happen periodically + throughout the life of the app if new information becomes available (e.g. UIApplicationDidBecomeActive). + + @param purchases Related `RCPurchases` object + @param purchaserInfo Updated `RCPurchaserInfo` + */ +- (void)purchases:(RCPurchases *)purchases didReceiveUpdatedPurchaserInfo:(RCPurchaserInfo *)purchaserInfo +NS_SWIFT_NAME(purchases(_:didReceiveUpdated:)); + +/** + Called when a user initiates a promotional in-app purchase from the App Store. If your app is able to handle a purchase at the current time, run the deferment block in this method. If the app is not in a state to make a purchase: cache the defermentBlock, then call the defermentBlock when the app is ready to make the promotional purchase. If the purchase should never be made, you don't need to ever call the defermentBlock and `RCPurchases` will not proceed with promotional purchases. + + @param product `SKProduct` the product that was selected from the app store + */ +- (void)purchases:(RCPurchases *)purchases shouldPurchasePromoProduct:(SKProduct *)product defermentBlock:(RCDeferredPromotionalPurchaseBlock)makeDeferredPurchase; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Public/RCPurchases.m b/Pods/Purchases/Purchases/Public/RCPurchases.m new file mode 100644 index 0000000..1482f94 --- /dev/null +++ b/Pods/Purchases/Purchases/Public/RCPurchases.m @@ -0,0 +1,1233 @@ +// +// RCPurchases.m +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import "RCPurchases.h" +#import "RCPurchases+SubscriberAttributes.h" +#import "RCPurchases+Protected.h" + +#import "RCStoreKitRequestFetcher.h" +#import "RCBackend.h" +#import "RCStoreKitWrapper.h" +#import "RCPurchaserInfo+Protected.h" +#import "RCLogUtils.h" +#import "RCCrossPlatformSupport.h" +#import "RCPurchasesErrors.h" +#import "RCPurchasesErrorUtils.h" +#import "RCReceiptFetcher.h" +#import "RCAttributionFetcher.h" +#import "RCAttributionData.h" +#import "RCOfferingsFactory.h" +#import "RCPackage+Protected.h" +#import "RCDeviceCache.h" +#import "RCIdentityManager.h" +#import "RCSubscriberAttributesManager.h" +#import "RCSystemInfo.h" +#import "RCProductInfoExtractor.h" +#import "RCIntroEligibility+Protected.h" +#import "RCReceiptRefreshPolicy.h" +@import PurchasesCoreSwift; + + +#define CALL_IF_SET_ON_MAIN_THREAD(completion, ...) if (completion) [self.operationDispatcher dispatchOnMainThread:^{ completion(__VA_ARGS__); }]; +#define CALL_IF_SET_ON_SAME_THREAD(completion, ...) if (completion) completion(__VA_ARGS__); + +@interface RCPurchases () { + NSNumber * _Nullable _allowSharingAppStoreAccount; +} + +/** + * Completion block for calls that send back receipt data + */ +typedef void (^RCReceiveReceiptDataBlock)(NSData *); + +@property (nonatomic) RCStoreKitRequestFetcher *requestFetcher; +@property (nonatomic) RCReceiptFetcher *receiptFetcher; +@property (nonatomic) RCBackend *backend; +@property (nonatomic) RCStoreKitWrapper *storeKitWrapper; +@property (nonatomic) NSNotificationCenter *notificationCenter; + +@property (nonatomic) NSMutableDictionary *productsByIdentifier; +@property (nonatomic) NSMutableDictionary *presentedOfferingsByProductIdentifier; +@property (nonatomic) NSMutableDictionary *purchaseCompleteCallbacks; +@property (nonatomic) RCPurchaserInfo *lastSentPurchaserInfo; +@property (nonatomic) RCAttributionFetcher *attributionFetcher; +@property (nonatomic) RCOfferingsFactory *offeringsFactory; +@property (nonatomic) RCDeviceCache *deviceCache; +@property (nonatomic) RCIdentityManager *identityManager; +@property (nonatomic) RCSystemInfo *systemInfo; +@property (nonatomic) RCIntroEligibilityCalculator *introEligibilityCalculator; +@property (nonatomic) RCReceiptParser *receiptParser; + +@end + +static RCPurchases *_sharedPurchases = nil; + +@implementation RCPurchases + +#pragma mark - Configuration + +- (BOOL)allowSharingAppStoreAccount { + if (_allowSharingAppStoreAccount == nil) { + return self.isAnonymous; + } + + return [_allowSharingAppStoreAccount boolValue]; +} + +- (void)setAllowSharingAppStoreAccount:(BOOL)allow { + _allowSharingAppStoreAccount = @(allow); +} + +static BOOL _automaticAppleSearchAdsAttributionCollection = NO; + ++ (void)setAutomaticAppleSearchAdsAttributionCollection:(BOOL)automaticAppleSearchAdsAttributionCollection { + _automaticAppleSearchAdsAttributionCollection = automaticAppleSearchAdsAttributionCollection; +} + ++ (BOOL)automaticAppleSearchAdsAttributionCollection { + return _automaticAppleSearchAdsAttributionCollection; +} + ++ (void)setDebugLogsEnabled:(BOOL)enabled { + RCSetShowDebugLogs(enabled); +} + ++ (BOOL)debugLogsEnabled { + return RCShowDebugLogs(); +} + ++ (NSURL *)proxyURL { + return RCSystemInfo.proxyURL; +} + ++ (void)setProxyURL:(nullable NSURL *)proxyURL { + RCSystemInfo.proxyURL = proxyURL; +} + ++ (BOOL)forceUniversalAppStore { + return RCSystemInfo.forceUniversalAppStore; +} + ++ (void)setForceUniversalAppStore:(BOOL)forceUniversalAppStore { + RCSystemInfo.forceUniversalAppStore = forceUniversalAppStore; +} + ++ (NSString *)frameworkVersion { + return RCSystemInfo.frameworkVersion; +} + +- (BOOL)finishTransactions { + return self.systemInfo.finishTransactions; +} + +- (void)setFinishTransactions:(BOOL)finishTransactions { + self.systemInfo.finishTransactions = finishTransactions; +} + ++ (instancetype)sharedPurchases { + if (!_sharedPurchases) { + RCLog(@"There is no singleton instance. Make sure you configure Purchases before trying to get the default instance."); + } + return _sharedPurchases; +} + ++ (void)setDefaultInstance:(RCPurchases *)instance { + @synchronized([RCPurchases class]) { + if (_sharedPurchases) { + RCLog(@"Purchases instance already set. Did you mean to configure two Purchases objects?"); + } + _sharedPurchases = instance; + } +} + ++ (BOOL)canMakePayments { + return [SKPaymentQueue canMakePayments]; +} + ++ (instancetype)configureWithAPIKey:(NSString *)APIKey { + return [self configureWithAPIKey:APIKey appUserID:nil]; +} + ++ (instancetype)configureWithAPIKey:(NSString *)APIKey appUserID:(nullable NSString *)appUserID { + return [self configureWithAPIKey:APIKey appUserID:appUserID observerMode:false]; +} + ++ (instancetype)configureWithAPIKey:(NSString *)APIKey + appUserID:(nullable NSString *)appUserID + observerMode:(BOOL)observerMode { + return [self configureWithAPIKey:APIKey appUserID:appUserID observerMode:observerMode userDefaults:nil]; +} + ++ (instancetype)configureWithAPIKey:(NSString *)APIKey + appUserID:(nullable NSString *)appUserID + observerMode:(BOOL)observerMode + userDefaults:(nullable NSUserDefaults *)userDefaults { + return [self configureWithAPIKey:APIKey + appUserID:appUserID + observerMode:observerMode + userDefaults:userDefaults + platformFlavor:nil + platformFlavorVersion:nil]; +} + ++ (instancetype)configureWithAPIKey:(NSString *)APIKey + appUserID:(nullable NSString *)appUserID + observerMode:(BOOL)observerMode + userDefaults:(nullable NSUserDefaults *)userDefaults + platformFlavor:(NSString *)platformFlavor + platformFlavorVersion:(NSString *)platformFlavorVersion { + RCPurchases *purchases = [[self alloc] initWithAPIKey:APIKey + appUserID:appUserID + userDefaults:userDefaults + observerMode:observerMode + platformFlavor:platformFlavor + platformFlavorVersion:platformFlavorVersion]; + [self setDefaultInstance:purchases]; + return purchases; +} + +- (instancetype)initWithAPIKey:(NSString *)APIKey appUserID:(nullable NSString *)appUserID { + return [self initWithAPIKey:APIKey + appUserID:appUserID + userDefaults:nil + observerMode:false + platformFlavor:nil + platformFlavorVersion:nil]; +} + +- (instancetype)initWithAPIKey:(NSString *)APIKey + appUserID:(nullable NSString *)appUserID + userDefaults:(nullable NSUserDefaults *)userDefaults + observerMode:(BOOL)observerMode + platformFlavor:(nullable NSString *)platformFlavor + platformFlavorVersion:(nullable NSString *)platformFlavorVersion { + RCStoreKitRequestFetcher *fetcher = [[RCStoreKitRequestFetcher alloc] init]; + RCReceiptFetcher *receiptFetcher = [[RCReceiptFetcher alloc] init]; + RCSystemInfo *systemInfo = [[RCSystemInfo alloc] initWithPlatformFlavor:platformFlavor + platformFlavorVersion:platformFlavorVersion + finishTransactions:!observerMode]; + RCBackend *backend = [[RCBackend alloc] initWithAPIKey:APIKey systemInfo:systemInfo]; + RCStoreKitWrapper *storeKitWrapper = [[RCStoreKitWrapper alloc] init]; + RCOfferingsFactory *offeringsFactory = [[RCOfferingsFactory alloc] init]; + + if (userDefaults == nil) { + userDefaults = [NSUserDefaults standardUserDefaults]; + } + + RCDeviceCache *deviceCache = [[RCDeviceCache alloc] initWith:userDefaults]; + RCIdentityManager *identityManager = [[RCIdentityManager alloc] initWith:deviceCache backend:backend]; + RCAttributionFetcher *attributionFetcher = [[RCAttributionFetcher alloc] initWithDeviceCache:deviceCache + identityManager:identityManager + backend:backend]; + RCSubscriberAttributesManager *subscriberAttributesManager = + [[RCSubscriberAttributesManager alloc] initWithBackend:backend + deviceCache:deviceCache + attributionFetcher:attributionFetcher]; + RCOperationDispatcher *operationDispatcher = [[RCOperationDispatcher alloc] init]; + RCIntroEligibilityCalculator *introCalculator = [[RCIntroEligibilityCalculator alloc] init]; + RCReceiptParser *receiptParser = [[RCReceiptParser alloc] init]; + + return [self initWithAppUserID:appUserID + requestFetcher:fetcher + receiptFetcher:receiptFetcher + attributionFetcher:attributionFetcher + backend:backend + storeKitWrapper:storeKitWrapper + notificationCenter:[NSNotificationCenter defaultCenter] + systemInfo:systemInfo + offeringsFactory:offeringsFactory + deviceCache:deviceCache + identityManager:identityManager + subscriberAttributesManager:subscriberAttributesManager + operationDispatcher:operationDispatcher + introEligibilityCalculator:introCalculator + receiptParser:receiptParser]; +} + +- (instancetype)initWithAppUserID:(nullable NSString *)appUserID + requestFetcher:(RCStoreKitRequestFetcher *)requestFetcher + receiptFetcher:(RCReceiptFetcher *)receiptFetcher + attributionFetcher:(RCAttributionFetcher *)attributionFetcher + backend:(RCBackend *)backend + storeKitWrapper:(RCStoreKitWrapper *)storeKitWrapper + notificationCenter:(NSNotificationCenter *)notificationCenter + systemInfo:(RCSystemInfo *)systemInfo + offeringsFactory:(RCOfferingsFactory *)offeringsFactory + deviceCache:(RCDeviceCache *)deviceCache + identityManager:(RCIdentityManager *)identityManager + subscriberAttributesManager:(RCSubscriberAttributesManager *)subscriberAttributesManager + operationDispatcher:(RCOperationDispatcher *)operationDispatcher + introEligibilityCalculator:(RCIntroEligibilityCalculator *)introEligibilityCalculator + receiptParser:(RCReceiptParser *)receiptParser { + if (self = [super init]) { + RCDebugLog(@"Debug logging enabled."); + RCDebugLog(@"SDK Version - %@", self.class.frameworkVersion); + RCDebugLog(@"Initial App User ID - %@", appUserID); + + self.requestFetcher = requestFetcher; + self.receiptFetcher = receiptFetcher; + self.attributionFetcher = attributionFetcher; + self.backend = backend; + self.storeKitWrapper = storeKitWrapper; + self.offeringsFactory = offeringsFactory; + self.deviceCache = deviceCache; + self.identityManager = identityManager; + + self.notificationCenter = notificationCenter; + + self.productsByIdentifier = [NSMutableDictionary new]; + self.presentedOfferingsByProductIdentifier = [NSMutableDictionary new]; + self.purchaseCompleteCallbacks = [NSMutableDictionary new]; + + self.systemInfo = systemInfo; + self.subscriberAttributesManager = subscriberAttributesManager; + self.operationDispatcher = operationDispatcher; + self.introEligibilityCalculator = introEligibilityCalculator; + self.receiptParser = receiptParser; + + RCReceivePurchaserInfoBlock callDelegate = ^void(RCPurchaserInfo *info, NSError *error) { + if (info) { + [self sendUpdatedPurchaserInfoToDelegateIfChanged:info]; + } + }; + + [self.identityManager configureWithAppUserID:appUserID]; + + [self.systemInfo isApplicationBackgroundedWithCompletion:^(BOOL isBackgrounded) { + if (!isBackgrounded) { + [self.operationDispatcher dispatchOnWorkerThreadWithRandomDelay:NO block:^{ + [self updateAllCachesWithCompletionBlock:callDelegate]; + }]; + } else { + [self sendCachedPurchaserInfoIfAvailable]; + } + }]; + self.storeKitWrapper.delegate = self; + + [self subscribeToAppStateNotifications]; + + [self.attributionFetcher postPostponedAttributionDataIfNeeded]; + if (_automaticAppleSearchAdsAttributionCollection) { + [self.attributionFetcher postAppleSearchAdsAttributionCollection]; + } + } + + return self; +} + +- (void)subscribeToAppStateNotifications { + [self.notificationCenter addObserver:self + selector:@selector(applicationDidBecomeActive:) + name:APP_DID_BECOME_ACTIVE_NOTIFICATION_NAME object:nil]; + [self.notificationCenter addObserver:self + selector:@selector(applicationWillResignActive:) + name:APP_WILL_RESIGN_ACTIVE_NOTIFICATION_NAME + object:nil]; +} + +- (void)dealloc { + self.storeKitWrapper.delegate = nil; + [self.notificationCenter removeObserver:self]; + self.delegate = nil; +} + +@synthesize delegate = _delegate; + +- (void)setDelegate:(id )delegate { + _delegate = delegate; + RCDebugLog(@"Delegate set"); + + [self sendCachedPurchaserInfoIfAvailable]; +} + +#pragma mark - Public Methods + +#pragma mark Attribution + +- (void)postAttributionData:(NSDictionary *)data + fromNetwork:(RCAttributionNetwork)network + forNetworkUserId:(nullable NSString *)networkUserId { + [self.attributionFetcher postAttributionData:data + fromNetwork:network + forNetworkUserId:networkUserId]; +} + ++ (void)addAttributionData:(NSDictionary *)data + fromNetwork:(RCAttributionNetwork)network { + [self addAttributionData:data fromNetwork:network forNetworkUserId:nil]; +} + ++ (void)addAttributionData:(NSDictionary *)data + fromNetwork:(RCAttributionNetwork)network + forNetworkUserId:(nullable NSString *)networkUserId { + if (_sharedPurchases) { + RCLog(RCStrings.attribution.instance_configured_posting_attribution); + [_sharedPurchases postAttributionData:data fromNetwork:network forNetworkUserId:networkUserId]; + } else { + RCLog(RCStrings.attribution.no_instance_configured_caching_attribution); + [RCAttributionFetcher storePostponedAttributionData:data + fromNetwork:network + forNetworkUserId:networkUserId]; + } +} + +#pragma mark Identity + +- (NSString *)appUserID { + return [self.identityManager currentAppUserID]; +} + +- (BOOL)isAnonymous { + return [self.identityManager currentUserIsAnonymous]; +} + +- (void)createAlias:(NSString *)alias completionBlock:(nullable RCReceivePurchaserInfoBlock)completion { + if ([alias isEqualToString:self.identityManager.currentAppUserID]) { + [self purchaserInfoWithCompletionBlock:completion]; + } else { + [self.identityManager createAlias:alias withCompletionBlock:^(NSError * _Nullable error) { + if (error == nil) { + [self updateAllCachesWithCompletionBlock:completion]; + } else { + CALL_IF_SET_ON_MAIN_THREAD(completion, nil, error); + } + }]; + } +} + +- (void)identify:(NSString *)appUserID completionBlock:(nullable RCReceivePurchaserInfoBlock)completion { + if ([appUserID isEqualToString:self.identityManager.currentAppUserID]) { + [self purchaserInfoWithCompletionBlock:completion]; + } else { + [self.identityManager identifyAppUserID:appUserID withCompletionBlock:^(NSError *error) { + if (error == nil) { + [self updateAllCachesWithCompletionBlock:completion]; + } else { + CALL_IF_SET_ON_MAIN_THREAD(completion, nil, error); + } + }]; + + } +} + +- (void)resetWithCompletionBlock:(nullable RCReceivePurchaserInfoBlock)completion { + [self.deviceCache clearLatestNetworkAndAdvertisingIdsSentForAppUserID:self.identityManager.currentAppUserID]; + [self.identityManager resetAppUserID]; + [self updateAllCachesWithCompletionBlock:completion]; +} + +- (void)purchaserInfoWithCompletionBlock:(RCReceivePurchaserInfoBlock)completion { + [self.systemInfo isApplicationBackgroundedWithCompletion:^(BOOL isAppBackgrounded) { + RCPurchaserInfo *infoFromCache = [self readPurchaserInfoFromCache]; + if (infoFromCache) { + RCDebugLog(@"Vending purchaserInfo from cache"); + CALL_IF_SET_ON_MAIN_THREAD(completion, infoFromCache, nil); + if ([self.deviceCache isPurchaserInfoCacheStaleForAppUserID:self.appUserID isAppBackgrounded:isAppBackgrounded]) { + RCDebugLog(@"Cache is stale, updating caches"); + [self fetchAndCachePurchaserInfoWithCompletion:nil isAppBackgrounded:isAppBackgrounded]; + } + } else { + RCDebugLog(@"No cached purchaser info, fetching"); + [self fetchAndCachePurchaserInfoWithCompletion:completion isAppBackgrounded:isAppBackgrounded]; + } + }]; +} + +#pragma mark Purchasing + +- (void)productsWithIdentifiers:(NSArray *)productIdentifiers + completionBlock:(RCReceiveProductsBlock)completion { + NSMutableArray *products = [NSMutableArray array]; + NSMutableSet *missingProductIdentifiers = [NSMutableSet set]; + + @synchronized(self) { + for (NSString *identifier in productIdentifiers) { + SKProduct *product = self.productsByIdentifier[identifier]; + if (product) { + [products addObject:product]; + } else { + [missingProductIdentifiers addObject:identifier]; + } + } + } + + if (missingProductIdentifiers.count > 0) { + [self.requestFetcher fetchProducts:missingProductIdentifiers + completion:^(NSArray * _Nonnull newProducts) { + @synchronized (self) { + for (SKProduct *p in newProducts) { + if (p.productIdentifier) { + self.productsByIdentifier[p.productIdentifier] = p; + } + } + } + CALL_IF_SET_ON_MAIN_THREAD(completion, [products arrayByAddingObjectsFromArray:newProducts]); + }]; + } else { + CALL_IF_SET_ON_MAIN_THREAD(completion, products); + } +} + +- (void)purchaseProduct:(SKProduct *)product + withCompletionBlock:(RCPurchaseCompletedBlock)completion { + SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product]; + [self purchaseProduct:product withPayment:payment withPresentedOfferingIdentifier:nil completion:completion]; +} + +- (void)purchasePackage:(RCPackage *)package + withCompletionBlock:(RCPurchaseCompletedBlock)completion { + SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:package.product]; + [self purchaseProduct:package.product withPayment:payment withPresentedOfferingIdentifier:package.offeringIdentifier completion:completion]; +} + +- (void)purchaseProduct:(SKProduct *)product + withDiscount:(SKPaymentDiscount *)discount + completionBlock:(RCPurchaseCompletedBlock)completion { + SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product]; + payment.paymentDiscount = discount; + [self purchaseProduct:product withPayment:payment withPresentedOfferingIdentifier:nil completion:completion]; +} + +- (void)purchasePackage:(RCPackage *)package + withDiscount:(SKPaymentDiscount *)discount + completionBlock:(RCPurchaseCompletedBlock)completion { + SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:package.product]; + payment.paymentDiscount = discount; + [self purchaseProduct:package.product withPayment:payment withPresentedOfferingIdentifier:package.offeringIdentifier completion:completion]; +} + +- (void) purchaseProduct:(SKProduct *)product + withPayment:(SKMutablePayment *)payment +withPresentedOfferingIdentifier:(nullable NSString *)presentedOfferingIdentifier + completion:(RCPurchaseCompletedBlock)completion { + RCDebugLog(@"makePurchase"); + + if (!product || !payment) { + RCLog(@"makePurchase - Could not purchase SKProduct."); + RCLog(@"makePurchase - Ensure your products are correctly configured in App Store Connect"); + RCLog(@"makePurchase - See https://www.revenuecat.com/2018/10/11/configuring-in-app-products-is-hard"); + completion(nil, nil, [NSError errorWithDomain:RCPurchasesErrorDomain + code:RCProductNotAvailableForPurchaseError + userInfo:@{ + NSLocalizedDescriptionKey: @"There was problem purchasing the product." + }], false); + return; + } + + NSString *productIdentifier; + if (product.productIdentifier) { + productIdentifier = product.productIdentifier; + } else if (payment.productIdentifier) { + productIdentifier = payment.productIdentifier; + } else { + RCLog(@"makePurchase - Could not purchase SKProduct. Couldn't find its product identifier. This is possibly an App Store quirk."); + completion(nil, nil, [NSError errorWithDomain:RCPurchasesErrorDomain + code:RCUnknownError + userInfo:@{ + NSLocalizedDescriptionKey: @"There was problem purchasing the product." + }], false); + return; + } + + if (!self.finishTransactions) { + RCDebugLog(@"makePurchase - Observer mode is active (finishTransactions is set to false) and makePurchase has been called. Are you sure you want to do this?"); + } + NSString *appUserID = self.appUserID; + payment.applicationUsername = appUserID; + + // This is to prevent the UIApplicationDidBecomeActive call from the purchase popup + // from triggering a refresh. + [self.deviceCache setPurchaserInfoCacheTimestampToNowForAppUserID:appUserID]; + [self.deviceCache setOfferingsCacheTimestampToNow]; + + if (presentedOfferingIdentifier) { + RCDebugLog(@"makePurchase - %@ - Offering: %@", productIdentifier, presentedOfferingIdentifier); + } else { + RCDebugLog(@"makePurchase - %@", productIdentifier); + } + + @synchronized (self) { + self.productsByIdentifier[productIdentifier] = product; + } + + @synchronized (self) { + self.presentedOfferingsByProductIdentifier[productIdentifier] = presentedOfferingIdentifier; + } + + @synchronized (self) { + if (self.purchaseCompleteCallbacks[productIdentifier]) { + completion(nil, nil, [NSError errorWithDomain:RCPurchasesErrorDomain + code:RCOperationAlreadyInProgressError + userInfo:@{ + NSLocalizedDescriptionKey: @"Purchase already in progress for this product." + }], false); + return; + } + self.purchaseCompleteCallbacks[productIdentifier] = [completion copy]; + } + + [self.storeKitWrapper addPayment:[payment copy]]; +} + +- (void)syncPurchasesWithCompletionBlock:(nullable RCReceivePurchaserInfoBlock)completion { + [self syncPurchasesWithReceiptRefreshPolicy:RCReceiptRefreshPolicyNever + isRestore:self.allowSharingAppStoreAccount + completion:completion]; +} + +- (void)restoreTransactionsWithCompletionBlock:(nullable RCReceivePurchaserInfoBlock)completion { + [self syncPurchasesWithReceiptRefreshPolicy:RCReceiptRefreshPolicyAlways + isRestore:YES + completion:completion]; +} + +- (void)syncPurchasesWithReceiptRefreshPolicy:(RCReceiptRefreshPolicy)refreshPolicy + isRestore:(BOOL)isRestore + completion:(nullable RCReceivePurchaserInfoBlock)completion { + if (!self.allowSharingAppStoreAccount) { + RCDebugLog(@"allowSharingAppStoreAccount is set to false and restoreTransactions has been called. " + "Are you sure you want to do this?"); + } + // Refresh the receipt and post to backend, this will allow the transactions to be transferred. + // https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Restoring.html + [self receiptDataWithReceiptRefreshPolicy:refreshPolicy completion:^(NSData *_Nonnull data) { + if (data.length == 0) { + if (RCSystemInfo.isSandbox) { + RCLog(@"App running on sandbox without a receipt file. Restoring transactions won't work unless " + "you've purchased before and there is a receipt available."); + } + CALL_IF_SET_ON_MAIN_THREAD(completion, nil, [RCPurchasesErrorUtils missingReceiptFileError]); + return; + } + + RCPurchaserInfo * _Nullable cachedPurchaserInfo = [self readPurchaserInfoFromCache]; + BOOL hasOriginalPurchaseDate = cachedPurchaserInfo != nil && cachedPurchaserInfo.originalPurchaseDate != nil; + BOOL receiptHasTransactions = [self.receiptParser receiptHasTransactionsWithReceiptData:data]; + if (!receiptHasTransactions && hasOriginalPurchaseDate) { + CALL_IF_SET_ON_MAIN_THREAD(completion, cachedPurchaserInfo, nil); + return; + } + + RCSubscriberAttributeDict subscriberAttributes = self.unsyncedAttributesByKey; + [self.backend postReceiptData:data + appUserID:self.appUserID + isRestore:isRestore + productInfo:nil + presentedOfferingIdentifier:nil + observerMode:!self.finishTransactions + subscriberAttributes:subscriberAttributes + completion:^(RCPurchaserInfo *_Nullable info, NSError *_Nullable error) { + [self handleRestoreReceiptPostWithInfo:info + error:error + subscriberAttributes:subscriberAttributes + completion:completion]; + }]; + }]; +} + +- (void)handleRestoreReceiptPostWithInfo:(RCPurchaserInfo *)info + error:(NSError *)error + subscriberAttributes:(RCSubscriberAttributeDict)subscriberAttributes + completion:(RCReceivePurchaserInfoBlock)completion { + [self.operationDispatcher dispatchOnMainThread:^{ + if (error) { + [self markAttributesAsSyncedIfNeeded:subscriberAttributes + appUserID:self.appUserID + error:error]; + CALL_IF_SET_ON_MAIN_THREAD(completion, nil, error); + } else if (info) { + [self cachePurchaserInfo:info forAppUserID:self.appUserID]; + [self sendUpdatedPurchaserInfoToDelegateIfChanged:info]; + [self markAttributesAsSyncedIfNeeded:subscriberAttributes + appUserID:self.appUserID + error:nil]; + CALL_IF_SET_ON_MAIN_THREAD(completion, info, nil); + } + }]; +} + +- (void)checkTrialOrIntroductoryPriceEligibility:(NSArray *)productIdentifiers + completionBlock:(RCReceiveIntroEligibilityBlock)receiveEligibility +{ + [self receiptData:^(NSData *data) { + if (data != nil && data.length > 0) { + if (@available(iOS 12.0, macOS 10.14, macCatalyst 13.0, tvOS 12.0, watchOS 6.2, *)) { + NSSet *productIdentifiersSet = [[NSSet alloc] initWithArray:productIdentifiers]; + [self.introEligibilityCalculator checkTrialOrIntroductoryPriceEligibilityWith:data + productIdentifiers:productIdentifiersSet + completion:^(NSDictionary * _Nonnull receivedEligibility, + NSError * _Nullable error) { + if (!error) { + NSMutableDictionary *convertedEligibility = [[NSMutableDictionary alloc] init]; + + for (NSString *key in receivedEligibility.allKeys) { + convertedEligibility[key] = [[RCIntroEligibility alloc] initWithEligibilityStatusCode:receivedEligibility[key]]; + } + + CALL_IF_SET_ON_MAIN_THREAD(receiveEligibility, convertedEligibility); + } else { + NSLog(@"There was an error when trying to parse the receipt locally, details: %@", error.localizedDescription); + [self.backend getIntroEligibilityForAppUserID:self.appUserID + receiptData:data + productIdentifiers:productIdentifiers + completion:^(NSDictionary * _Nonnull result) { + CALL_IF_SET_ON_MAIN_THREAD(receiveEligibility, result); + }]; + } + }]; + } else { + [self.backend getIntroEligibilityForAppUserID:self.appUserID + receiptData:data + productIdentifiers:productIdentifiers + completion:^(NSDictionary *_Nonnull result) { + CALL_IF_SET_ON_MAIN_THREAD(receiveEligibility, result); + }]; + } + } else { + [self.backend getIntroEligibilityForAppUserID:self.appUserID + receiptData:data + productIdentifiers:productIdentifiers + completion:^(NSDictionary * _Nonnull result) { + CALL_IF_SET_ON_MAIN_THREAD(receiveEligibility, result); + }]; + } + }]; +} + +- (void)paymentDiscountForProductDiscount:(SKProductDiscount *)discount + product:(SKProduct *)product + completion:(RCPaymentDiscountBlock)completion { + [self receiptData:^(NSData *data) { + if (data == nil || data.length == 0) { + completion(nil, RCPurchasesErrorUtils.missingReceiptFileError); + } else { + [self.backend postOfferForSigning:discount.identifier + withProductIdentifier:product.productIdentifier + subscriptionGroup:product.subscriptionGroupIdentifier + receiptData:data + appUserID:self.appUserID + completion:^(NSString *_Nullable signature, + NSString *_Nullable keyIdentifier, + NSUUID *_Nullable nonce, + NSNumber *_Nullable timestamp, + NSError *_Nullable error) { + SKPaymentDiscount *paymentDiscount = [[SKPaymentDiscount alloc] initWithIdentifier:discount.identifier + keyIdentifier:keyIdentifier + nonce:nonce + signature:signature + timestamp:timestamp]; + completion(paymentDiscount, error); + }]; + } + }]; +} + +- (void)invalidatePurchaserInfoCache { + RCDebugLog(@"Purchaser info cache is invalidated"); + [self.deviceCache clearPurchaserInfoCacheForAppUserID:self.appUserID]; +} + +- (void)presentCodeRedemptionSheet API_AVAILABLE(ios(14.0)) API_UNAVAILABLE(tvos, macos, watchos) { + RCDebugLog(@"Presenting code redemption sheet"); + [self.storeKitWrapper presentCodeRedemptionSheet]; +} + +#pragma mark Subcriber Attributes + +- (void)setAttributes:(NSDictionary *)attributes { + RCDebugLog(@"setAttributes called"); + [self.subscriberAttributesManager setAttributes:attributes appUserID:self.appUserID]; +} + +- (void)setEmail:(nullable NSString *)email { + RCDebugLog(@"setEmail called"); + [self.subscriberAttributesManager setEmail:email appUserID:self.appUserID]; +} + +- (void)setPhoneNumber:(nullable NSString *)phoneNumber { + RCDebugLog(@"setPhoneNumber called"); + [self.subscriberAttributesManager setPhoneNumber:phoneNumber appUserID:self.appUserID]; +} + +- (void)setDisplayName:(nullable NSString *)displayName { + RCDebugLog(@"setDisplayName called"); + [self.subscriberAttributesManager setDisplayName:displayName appUserID:self.appUserID]; +} + +- (void)setPushToken:(nullable NSData *)pushToken { + RCDebugLog(@"setPushToken called"); + [self.subscriberAttributesManager setPushToken:pushToken appUserID:self.appUserID]; +} + +- (void)_setPushTokenString:(nullable NSString *)pushToken { + RCDebugLog(@"setPushTokenString called"); + [self.subscriberAttributesManager setPushTokenString:pushToken appUserID:self.appUserID]; +} + +- (void)setAdjustID:(nullable NSString *)adjustID { + RCDebugLog(@"setAdjustID called"); + [self.subscriberAttributesManager setAdjustID:adjustID appUserID:self.appUserID]; +} + +- (void)setAppsflyerID:(nullable NSString *)appsflyerID { + RCDebugLog(@"setAppsflyerID called"); + [self.subscriberAttributesManager setAppsflyerID:appsflyerID appUserID:self.appUserID]; +} + +- (void)setFBAnonymousID:(nullable NSString *)fbAnonymousID { + RCDebugLog(@"setFBAnonymousID called"); + [self.subscriberAttributesManager setFBAnonymousID:fbAnonymousID appUserID:self.appUserID]; +} + +- (void)setMparticleID:(nullable NSString *)mparticleID { + RCDebugLog(@"setMparticleID called"); + [self.subscriberAttributesManager setMparticleID:mparticleID appUserID:self.appUserID]; +} + +- (void)setOnesignalID:(nullable NSString *)onesignalID { + RCDebugLog(@"setOnesignalID called"); + [self.subscriberAttributesManager setOnesignalID:onesignalID appUserID:self.appUserID]; +} + +- (void)setMediaSource:(nullable NSString *)mediaSource { + RCDebugLog(@"setMediaSource called"); + [self.subscriberAttributesManager setMediaSource:mediaSource appUserID:self.appUserID]; +} + +- (void)setCampaign:(nullable NSString *)campaign { + RCDebugLog(@"setCampaign called"); + [self.subscriberAttributesManager setCampaign:campaign appUserID:self.appUserID]; +} + +- (void)setAdGroup:(nullable NSString *)adGroup { + RCDebugLog(@"setAdGroup called"); + [self.subscriberAttributesManager setAdGroup:adGroup appUserID:self.appUserID]; +} + +- (void)setAd:(nullable NSString *)ad { + RCDebugLog(@"setAd called"); + [self.subscriberAttributesManager setAd:ad appUserID:self.appUserID]; +} + +- (void)setKeyword:(nullable NSString *)keyword { + RCDebugLog(@"setKeyword called"); + [self.subscriberAttributesManager setKeyword:keyword appUserID:self.appUserID]; +} + +- (void)setCreative:(nullable NSString *)creative { + RCDebugLog(@"setCreative called"); + [self.subscriberAttributesManager setCreative:creative appUserID:self.appUserID]; +} + +- (void)collectDeviceIdentifiers { + RCDebugLog(@"collectDeviceIdentifiers called"); + [self.subscriberAttributesManager collectDeviceIdentifiersForAppUserID:self.appUserID]; +} + +#pragma mark - Private Methods + +- (void)applicationDidBecomeActive:(__unused NSNotification *)notif { + [self updateAllCachesIfNeeded]; + [self syncSubscriberAttributesIfNeeded]; +} + +- (void)applicationWillResignActive:(__unused NSNotification *)notif { + [self syncSubscriberAttributesIfNeeded]; +} + +- (void)sendCachedPurchaserInfoIfAvailable { + RCPurchaserInfo *infoFromCache = [self readPurchaserInfoFromCache]; + if (infoFromCache) { + [self sendUpdatedPurchaserInfoToDelegateIfChanged:infoFromCache]; + } +} + +- (void)updateAllCachesIfNeeded { + RCDebugLog(@"applicationDidBecomeActive"); + [self.systemInfo isApplicationBackgroundedWithCompletion:^(BOOL isAppBackgrounded) { + if ([self.deviceCache isPurchaserInfoCacheStaleForAppUserID:self.appUserID isAppBackgrounded:isAppBackgrounded]) { + RCDebugLog(@"PurchaserInfo cache is stale, updating caches"); + [self fetchAndCachePurchaserInfoWithCompletion:nil isAppBackgrounded:isAppBackgrounded]; + } + if ([self.deviceCache isOfferingsCacheStaleWithIsAppBackgrounded:isAppBackgrounded]) { + RCDebugLog(@"Offerings cache is stale, updating caches"); + [self updateOfferingsCache:nil isAppBackgrounded:isAppBackgrounded]; + } + }]; +} + +- (RCPurchaserInfo *)readPurchaserInfoFromCache { + NSData *purchaserInfoData = [self.deviceCache cachedPurchaserInfoDataForAppUserID:self.appUserID]; + if (purchaserInfoData) { + NSError *jsonError; + NSDictionary *infoDict = [NSJSONSerialization JSONObjectWithData:purchaserInfoData options:0 error:&jsonError]; + if (jsonError == nil && infoDict != nil) { + RCPurchaserInfo *info = [[RCPurchaserInfo alloc] initWithData:infoDict]; + if (info.schemaVersion != nil && [info.schemaVersion isEqual:[RCPurchaserInfo currentSchemaVersion]]) { + return info; + } + } + } + return nil; +} + +- (void)cachePurchaserInfo:(RCPurchaserInfo *)info forAppUserID:(NSString *)appUserID { + if (info) { + [self.operationDispatcher dispatchOnMainThread:^{ + if (info.JSONObject) { + NSError *jsonError = nil; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:info.JSONObject + options:0 + error:&jsonError]; + if (jsonError == nil) { + [self.deviceCache cachePurchaserInfo:jsonData forAppUserID:appUserID]; + } + } + }]; + } +} + +- (void)updateAllCachesWithCompletionBlock:(nullable RCReceivePurchaserInfoBlock)completion { + [self.systemInfo isApplicationBackgroundedWithCompletion:^(BOOL isAppBackgrounded) { + [self fetchAndCachePurchaserInfoWithCompletion:completion isAppBackgrounded:isAppBackgrounded]; + [self updateOfferingsCache:nil isAppBackgrounded:isAppBackgrounded]; + }]; +} + +- (void)fetchAndCachePurchaserInfoWithCompletion:(nullable RCReceivePurchaserInfoBlock)completion + isAppBackgrounded:(BOOL)isAppBackgrounded { + NSString *appUserID = self.identityManager.currentAppUserID; + [self.deviceCache setPurchaserInfoCacheTimestampToNowForAppUserID:appUserID]; + [self.operationDispatcher dispatchOnWorkerThreadWithRandomDelay:isAppBackgrounded block:^{ + [self.backend getSubscriberDataWithAppUserID:appUserID + completion:^(RCPurchaserInfo * _Nullable info, + NSError * _Nullable error) { + if (error == nil) { + [self cachePurchaserInfo:info forAppUserID:appUserID]; + [self sendUpdatedPurchaserInfoToDelegateIfChanged:info]; + } else { + [self.deviceCache clearPurchaserInfoCacheTimestampForAppUserID:appUserID]; + } + + CALL_IF_SET_ON_MAIN_THREAD(completion, info, error); + }]; + }]; +} + +- (void)performOnEachProductIdentifierInOfferings:(NSDictionary *)offeringsData + block:(void (^)(NSString *productIdentifier))block { + for (NSDictionary *offering in offeringsData[@"offerings"]) { + for (NSDictionary *package in offering[@"packages"]) { + block(package[@"platform_product_identifier"]); + } + } +} + +- (void)offeringsWithCompletionBlock:(RCReceiveOfferingsBlock)completion { + [self.systemInfo isApplicationBackgroundedWithCompletion:^(BOOL isAppBackgrounded) { + if (self.deviceCache.cachedOfferings) { + RCDebugLog(@"Vending offerings from cache"); + CALL_IF_SET_ON_MAIN_THREAD(completion, self.deviceCache.cachedOfferings, nil); + if ([self.deviceCache isOfferingsCacheStaleWithIsAppBackgrounded:isAppBackgrounded]) { + RCDebugLog(@"Offerings cache is stale, updating cache"); + [self updateOfferingsCache:nil isAppBackgrounded:isAppBackgrounded]; + } + } else { + RCDebugLog(@"No cached offerings, fetching"); + [self updateOfferingsCache:completion isAppBackgrounded:isAppBackgrounded]; + } + }]; +} + +- (void)updateOfferingsCache:(nullable RCReceiveOfferingsBlock)completion isAppBackgrounded:(BOOL)isAppBackgrounded { + [self.deviceCache setOfferingsCacheTimestampToNow]; + [self.operationDispatcher dispatchOnWorkerThreadWithRandomDelay:isAppBackgrounded block:^{ + [self.backend getOfferingsForAppUserID:self.appUserID + completion:^(NSDictionary *data, NSError *error) { + if (error != nil) { + [self handleOfferingsUpdateError:error completion:completion]; + return; + } + [self handleOfferingsBackendResultWithData:data completion:completion]; + }]; + }]; + +} + +- (void)handleOfferingsBackendResultWithData:(NSDictionary *)data completion:(RCReceiveOfferingsBlock)completion { + NSMutableSet *productIdentifiers = [NSMutableSet new]; + [self performOnEachProductIdentifierInOfferings:data block:^(NSString *productIdentifier) { + [productIdentifiers addObject:productIdentifier]; + }]; + + [self productsWithIdentifiers:productIdentifiers.allObjects completionBlock:^(NSArray *_Nonnull products) { + + NSMutableDictionary *productsById = [NSMutableDictionary new]; + for (SKProduct *p in products) { + productsById[p.productIdentifier] = p; + } + RCOfferings *offerings = [self.offeringsFactory createOfferingsWithProducts:productsById data:data]; + if (offerings) { + NSMutableArray *missingProducts = [NSMutableArray new]; + [self performOnEachProductIdentifierInOfferings:data block:^(NSString *productIdentifier) { + SKProduct *product = productsById[productIdentifier]; + + if (product == nil) { + [missingProducts addObject:productIdentifier]; + } + }]; + + if (missingProducts.count > 0) { + RCLog(@"Could not find SKProduct for %@", missingProducts); + RCLog(@"Ensure your products are correctly configured in App Store Connect"); + RCLog(@"See https://www.revenuecat.com/2018/10/11/configuring-in-app-products-is-hard"); + } + [self.deviceCache cacheOfferings:offerings]; + + CALL_IF_SET_ON_MAIN_THREAD(completion, offerings, nil); + } else { + [self handleOfferingsUpdateError:RCPurchasesErrorUtils.unexpectedBackendResponseError completion:completion]; + } + }]; +} + +- (void)handleOfferingsUpdateError:(NSError *)error completion:(RCReceiveOfferingsBlock)completion { + RCLog(@"Error fetching offerings - %@", error); + [self.deviceCache clearOfferingsCacheTimestamp]; + CALL_IF_SET_ON_MAIN_THREAD(completion, nil, error); +} + +- (void)receiptData:(RCReceiveReceiptDataBlock)completion { + [self receiptDataWithReceiptRefreshPolicy:RCReceiptRefreshPolicyOnlyIfEmpty + completion:completion]; +} + +- (void)receiptDataWithReceiptRefreshPolicy:(RCReceiptRefreshPolicy)refreshPolicy + completion:(RCReceiveReceiptDataBlock)completion { + if (refreshPolicy == RCReceiptRefreshPolicyAlways) { + RCDebugLog(@"Forced receipt refresh"); + [self refreshReceipt:completion]; + return; + } + NSData *receiptData = [self.receiptFetcher receiptData]; + BOOL receiptIsEmpty = receiptData == nil || receiptData.length == 0; + if (receiptIsEmpty && refreshPolicy == RCReceiptRefreshPolicyOnlyIfEmpty) { + RCDebugLog(@"Receipt empty, fetching"); + [self refreshReceipt:completion]; + } else { + completion(receiptData); + } +} + +- (void)refreshReceipt:(RCReceiveReceiptDataBlock)completion { + [self.requestFetcher fetchReceiptData:^{ + NSData *newReceiptData = [self.receiptFetcher receiptData]; + if (newReceiptData == nil || newReceiptData.length == 0) { + RCLog(@"Unable to load receipt, ensure you are logged in to the correct iTunes account."); + } + completion(newReceiptData ?: [NSData data]); + }]; +} + +- (void)handleReceiptPostWithTransaction:(SKPaymentTransaction *)transaction + purchaserInfo:(nullable RCPurchaserInfo *)info + subscriberAttributes:(nullable RCSubscriberAttributeDict)subscriberAttributes + error:(nullable NSError *)error { + [self.operationDispatcher dispatchOnMainThread:^{ + [self markAttributesAsSyncedIfNeeded:subscriberAttributes appUserID:self.appUserID error:error]; + + RCPurchaseCompletedBlock _Nullable completion = [self getAndRemovePurchaseCompletedBlockFor:transaction]; + if (info) { + [self cachePurchaserInfo:info forAppUserID:self.appUserID]; + + [self sendUpdatedPurchaserInfoToDelegateIfChanged:info]; + + CALL_IF_SET_ON_SAME_THREAD(completion, transaction, info, nil, false); + + if (self.finishTransactions) { + [self.storeKitWrapper finishTransaction:transaction]; + } + } else if ([error.userInfo[RCFinishableKey] boolValue]) { + CALL_IF_SET_ON_SAME_THREAD(completion, transaction, nil, error, false); + if (self.finishTransactions) { + [self.storeKitWrapper finishTransaction:transaction]; + } + } else if (![error.userInfo[RCFinishableKey] boolValue]) { + CALL_IF_SET_ON_SAME_THREAD(completion, transaction, nil, error, false); + } else { + RCLog(@"Unexpected error from backend"); + CALL_IF_SET_ON_SAME_THREAD(completion, transaction, nil, error, false); + } + }]; +} + +- (void)sendUpdatedPurchaserInfoToDelegateIfChanged:(RCPurchaserInfo *)info { + + if ([self.delegate respondsToSelector:@selector(purchases:didReceiveUpdatedPurchaserInfo:)]) { + @synchronized (self) { + if (![self.lastSentPurchaserInfo isEqual:info]) { + if (self.lastSentPurchaserInfo) { + RCDebugLog(@"Purchaser info updated, sending to delegate"); + } else { + RCDebugLog(@"Sending latest purchaser info to delegate"); + } + self.lastSentPurchaserInfo = info; + [self.operationDispatcher dispatchOnMainThread:^{ + [self.delegate purchases:self didReceiveUpdatedPurchaserInfo:info]; + }]; + } + } + } +} + +/* + RCStoreKitWrapperDelegate + */ + +- (void)storeKitWrapper:(RCStoreKitWrapper *)storeKitWrapper + updatedTransaction:(SKPaymentTransaction *)transaction { + switch (transaction.transactionState) { + case SKPaymentTransactionStateRestored: // For observer mode + case SKPaymentTransactionStatePurchased: { + [self handlePurchasedTransaction:transaction]; + break; + } + case SKPaymentTransactionStateFailed: { + _Nullable RCPurchaseCompletedBlock completion = [self getAndRemovePurchaseCompletedBlockFor:transaction]; + + CALL_IF_SET_ON_MAIN_THREAD( + completion, + transaction, + nil, + [RCPurchasesErrorUtils purchasesErrorWithSKError:transaction.error], + transaction.error.code == SKErrorPaymentCancelled); + + if (self.finishTransactions) { + [self.storeKitWrapper finishTransaction:transaction]; + } + break; + } + case SKPaymentTransactionStateDeferred: { + _Nullable RCPurchaseCompletedBlock completion = [self getAndRemovePurchaseCompletedBlockFor:transaction]; + + NSError *pendingError = [RCPurchasesErrorUtils paymentDeferredError]; + CALL_IF_SET_ON_MAIN_THREAD(completion, + transaction, + nil, + pendingError, + transaction.error.code == SKErrorPaymentCancelled); + break; + } + case SKPaymentTransactionStatePurchasing: + break; + } +} + +- (nullable RCPurchaseCompletedBlock)getAndRemovePurchaseCompletedBlockFor:(SKPaymentTransaction *)transaction { + RCPurchaseCompletedBlock completion = nil; + NSString * _Nullable productIdentifier = [self productIdentifierFrom:transaction]; + if (productIdentifier) { + @synchronized (self) { + completion = self.purchaseCompleteCallbacks[productIdentifier]; + self.purchaseCompleteCallbacks[productIdentifier] = nil; + } + } + return completion; +} + +- (void)storeKitWrapper:(RCStoreKitWrapper *)storeKitWrapper + removedTransaction:(SKPaymentTransaction *)transaction { +} + +- (BOOL)storeKitWrapper:(nonnull RCStoreKitWrapper *)storeKitWrapper shouldAddStorePayment:(nonnull SKPayment *)payment forProduct:(nonnull SKProduct *)product { + @synchronized(self) { + self.productsByIdentifier[product.productIdentifier] = product; + } + + if ([self.delegate respondsToSelector:@selector(purchases:shouldPurchasePromoProduct:defermentBlock:)]) { + [self.delegate purchases:self + shouldPurchasePromoProduct:product + defermentBlock:^(RCPurchaseCompletedBlock completion) { + self.purchaseCompleteCallbacks[product.productIdentifier] = [completion copy]; + [self.storeKitWrapper addPayment:payment]; + }]; + } + + return NO; +} + +- (void)handlePurchasedTransaction:(SKPaymentTransaction *)transaction { + [self receiptData:^(NSData * _Nonnull data) { + if (data.length == 0) { + [self handleReceiptPostWithTransaction:transaction + purchaserInfo:nil + subscriberAttributes:nil + error:RCPurchasesErrorUtils.missingReceiptFileError]; + } else { + [self fetchProductsAndPostReceiptWithTransaction:transaction data:data]; + } + }]; +} + +- (void)fetchProductsAndPostReceiptWithTransaction:(SKPaymentTransaction *)transaction data:(NSData *)data { + if ([self productIdentifierFrom:transaction]) { + [self productsWithIdentifiers:@[[self productIdentifierFrom:transaction]] + completionBlock:^(NSArray *products) { + [self postReceiptWithTransaction:transaction data:data products:products]; + }]; + } else { + [self handleReceiptPostWithTransaction:transaction + purchaserInfo:nil + subscriberAttributes:nil + error:RCPurchasesErrorUtils.unknownError]; + } +} + +- (void)postReceiptWithTransaction:(SKPaymentTransaction *)transaction + data:(NSData *)data + products:(NSArray *)products { + SKProduct *product = products.lastObject; + RCSubscriberAttributeDict subscriberAttributes = self.unsyncedAttributesByKey; + RCProductInfo *productInfo = nil; + NSString *presentedOffering = nil; + if (product) { + RCProductInfoExtractor *productInfoExtractor = [[RCProductInfoExtractor alloc] init]; + productInfo = [productInfoExtractor extractInfoFromProduct:product]; + + @synchronized (self) { + presentedOffering = self.presentedOfferingsByProductIdentifier[productInfo.productIdentifier]; + [self.presentedOfferingsByProductIdentifier removeObjectForKey:productInfo.productIdentifier]; + } + } + [self.backend postReceiptData:data + appUserID:self.appUserID + isRestore:self.allowSharingAppStoreAccount + productInfo:productInfo + presentedOfferingIdentifier:presentedOffering + observerMode:!self.finishTransactions + subscriberAttributes:subscriberAttributes + completion:^(RCPurchaserInfo *_Nullable info, + NSError *_Nullable error) { + [self handleReceiptPostWithTransaction:transaction + purchaserInfo:info + subscriberAttributes:subscriberAttributes + error:error]; + }]; +} + +- (nullable NSString *)productIdentifierFrom:(SKPaymentTransaction *)transaction { + if (transaction.payment == nil) { + RCLog(@"There is a problem with the payment. Couldn't find the payment. This is possibly an App Store quirk."); + } else if (transaction.payment.productIdentifier == nil) { + RCLog(@"There is a problem with the payment. Couldn't find its product identifier. This is possibly an App Store quirk."); + } + return transaction.payment.productIdentifier; +} + +@end + diff --git a/Pods/Purchases/Purchases/Public/RCPurchasesErrorUtils.h b/Pods/Purchases/Purchases/Public/RCPurchasesErrorUtils.h new file mode 100644 index 0000000..614a85c --- /dev/null +++ b/Pods/Purchases/Purchases/Public/RCPurchasesErrorUtils.h @@ -0,0 +1,105 @@ +// +// RCPurchasesErrorUtils.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Utility class used to construct [NSError] instances. + */ +NS_SWIFT_NAME(Purchases.ErrorUtils) +@interface RCPurchasesErrorUtils : NSObject + +/** + * Constructs an NSError with the [RCNetworkError] code and a populated [RCUnderlyingErrorKey] in + * the [NSError.userInfo] dictionary. + * + * @param underlyingError The value of the [NSUnderlyingErrorKey] key. + * + * @note This error is used when there is an error performing network request returns an error or when there + * is an [NSJSONSerialization] error. + */ ++ (NSError *)networkErrorWithUnderlyingError:(NSError *)underlyingError; + +/** + * Maps an RCBackendError code to a RCPurchasesErrorCode code. Constructs an NSError with the mapped code and adds a + * [RCUnderlyingErrorKey] in the [NSError.userInfo] dictionary. The backend error code will be mapped using + * [RCPurchasesErrorCodeFromRCBackendErrorCode]. + * + * @param backendCode The value of the error key. + * @param backendMessage The value of the [NSUnderlyingErrorKey] key. + * + * @note This error is used when an network request returns an error. The backend error returned is wrapped in + * this internal error code. + */ ++ (NSError *)backendErrorWithBackendCode:(nullable NSNumber *)backendCode backendMessage:(nullable NSString *)backendMessage; + +/** + * Maps an RCBackendError code to a [RCPurchasesErrorCode] code. Constructs an NSError with the mapped code and adds a + * [RCUnderlyingErrorKey] in the [NSError.userInfo] dictionary. The backend error code will be mapped using + * [RCPurchasesErrorCodeFromRCBackendErrorCode]. + * + * @param backendCode The value of the error key. + * @param backendMessage The value of the [NSUnderlyingErrorKey] key. + * @param finishable Will be added to the UserInfo dictionary under the [RCFinishableKey] to indicate if the transaction + * should be finished after this error. + * + * @note This error is used when an network request returns an error. The backend error returned is wrapped in + * this internal error code. + */ ++ (NSError *)backendErrorWithBackendCode:(nullable NSNumber *)backendCode backendMessage:(nullable NSString *)backendMessage finishable:(BOOL)finishable; + +/** + * Constructs an NSError with the [RCUnexpectedBackendResponseError] code. + * + * @note This error is used when an network request returns an unexpected response. + */ ++ (NSError *)unexpectedBackendResponseError; + +/** + * Constructs an NSError with the [RCMissingReceiptFileError] code. + * + * @note This error is used when the receipt is missing in the device. This can happen if the user is in sandbox or + * if there are no previous purchases. + */ ++ (NSError *)missingReceiptFileError; + +/** + * Constructs an NSError with the [RCInvalidAppUserIdError] code. + * + * @note This error is used when the appUserID can't be found in user defaults. This can happen if user defaults + * are removed manually or if the OS deletes entries when running out of space. + */ ++ (NSError *)missingAppUserIDError; + +/** + * Constructs an NSError with the [RCPaymentPendingError] code. + * + * @note This error is used during an “ask to buy” flow for a payment. The completion block of the purchasing function + * will get this error to indicate the guardian has to complete the purchase. + */ ++ (NSError *)paymentDeferredError; + +/** + * Constructs an NSError with the [RCUnknownError] code. + */ ++ (NSError *)unknownError; + +/** + * Maps an SKErrorCode code to a RCPurchasesErrorCode code. Constructs an NSError with the mapped code and adds a + * [RCUnderlyingErrorKey] in the NSError.userInfo dictionary. The SKErrorCode code will be mapped using + * [RCPurchasesErrorCodeFromSKError]. + * + * @param skError The originating [SKError]. + */ ++ (NSError *)purchasesErrorWithSKError:(NSError *)skError; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Public/RCPurchasesErrorUtils.m b/Pods/Purchases/Purchases/Public/RCPurchasesErrorUtils.m new file mode 100644 index 0000000..50a5433 --- /dev/null +++ b/Pods/Purchases/Purchases/Public/RCPurchasesErrorUtils.m @@ -0,0 +1,334 @@ +// +// RCPurchasesErrorUtils.m +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import +#import "RCPurchasesErrors.h" +#import "RCPurchasesErrorUtils.h" +#import "RCLogUtils.h" + +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - Error Domains and UserInfo keys + +NSErrorDomain const RCPurchasesErrorDomain = @"RCPurchasesErrorDomain"; +NSErrorDomain const RCBackendErrorDomain = @"RCBackendErrorDomain"; +NSErrorUserInfoKey const RCFinishableKey = @"finishable"; +NSErrorUserInfoKey const RCReadableErrorCodeKey = @"readable_error_code"; + +#pragma mark - Standard Error Messages + +/** + * The error description, based on the error code. + * @note No default case so that we get a compiler warning if a new value was added to the enum. + */ +static NSString *RCPurchasesErrorDescription(RCPurchasesErrorCode code) { + switch (code) { + case RCNetworkError: + return @"Error performing request."; + case RCUnknownError: + return @"Unknown error."; + case RCPurchaseCancelledError: + return @"Purchase was cancelled."; + case RCStoreProblemError: + return @"There was a problem with the App Store."; + case RCPurchaseNotAllowedError: + return @"The device or user is not allowed to make the purchase."; + case RCPurchaseInvalidError: + return @"One or more of the arguments provided are invalid."; + case RCProductNotAvailableForPurchaseError: + return @"The product is not available for purchase."; + case RCProductAlreadyPurchasedError: + return @"This product is already active for the user."; + case RCReceiptAlreadyInUseError: + return @"There is already another active subscriber using the same receipt."; + case RCMissingReceiptFileError: + return @"The receipt is missing."; + case RCInvalidCredentialsError: + return @"There was a credentials issue. Check the underlying error for more details."; + case RCUnexpectedBackendResponseError: + return @"Received malformed response from the backend."; + case RCInvalidReceiptError: + return @"The receipt is not valid."; + case RCInvalidAppUserIdError: + return @"The app user id is not valid."; + case RCOperationAlreadyInProgressError: + return @"The operation is already in progress."; + case RCUnknownBackendError: + return @"There was an unknown backend error."; + case RCReceiptInUseByOtherSubscriberError: + return @"The receipt is in use by other subscriber."; + case RCInvalidAppleSubscriptionKeyError: + return @"Apple Subscription Key is invalid or not present. In order to provide subscription offers, you must first generate a subscription key. Please see https://docs.revenuecat.com/docs/ios-subscription-offers for more info."; + case RCIneligibleError: + return @"The User is ineligible for that action."; + case RCInsufficientPermissionsError: + return @"App does not have sufficient permissions to make purchases"; + case RCPaymentPendingError: + return @"The payment is pending."; + case RCInvalidSubscriberAttributesError: + return @"One or more of the attributes sent could not be saved."; + } + return @"Something went wrong."; +} + + +/** + * The error short string, based on the error code. + * @note No default case so that we get a compiler warning if a new value was added to the enum. + */ +static NSString *const RCPurchasesErrorCodeString(RCPurchasesErrorCode code) { + switch (code) { + case RCNetworkError: + return @"NETWORK_ERROR"; + case RCUnknownError: + return @"UNKNOWN"; + case RCPurchaseCancelledError: + return @"PURCHASE_CANCELLED"; + case RCStoreProblemError: + return @"STORE_PROBLEM"; + case RCPurchaseNotAllowedError: + return @"PURCHASE_NOT_ALLOWED"; + case RCPurchaseInvalidError: + return @"PURCHASE_INVALID"; + case RCProductNotAvailableForPurchaseError: + return @"PRODUCT_NOT_AVAILABLE_FOR_PURCHASE"; + case RCProductAlreadyPurchasedError: + return @"PRODUCT_ALREADY_PURCHASED"; + case RCReceiptAlreadyInUseError: + return @"RECEIPT_ALREADY_IN_USE"; + case RCMissingReceiptFileError: + return @"MISSING_RECEIPT_FILE"; + case RCInvalidCredentialsError: + return @"INVALID_CREDENTIALS"; + case RCUnexpectedBackendResponseError: + return @"UNEXPECTED_BACKEND_RESPONSE_ERROR"; + case RCInvalidReceiptError: + return @"INVALID_RECEIPT"; + case RCInvalidAppUserIdError: + return @"INVALID_APP_USER_ID"; + case RCOperationAlreadyInProgressError: + return @"OPERATION_ALREADY_IN_PROGRESS"; + case RCUnknownBackendError: + return @"UNKNOWN_BACKEND_ERROR"; + case RCReceiptInUseByOtherSubscriberError: + return @"RECEIPT_IN_USE_BY_OTHER_SUBSCRIBER"; + case RCInvalidAppleSubscriptionKeyError: + return @"INVALID_APPLE_SUBSCRIPTION_KEY"; + case RCIneligibleError: + return @"INELIGIBLE_ERROR"; + case RCInsufficientPermissionsError: + return @"INSUFFICIENT_PERMISSIONS_ERROR"; + case RCPaymentPendingError: + return @"PAYMENT_PENDING_ERROR"; + case RCInvalidSubscriberAttributesError: + return @"INVALID_SUBSCRIBER_ATTRIBUTES"; + } + return @"UNRECOGNIZED_ERROR"; +} + +static RCPurchasesErrorCode RCPurchasesErrorCodeFromRCBackendErrorCode(RCBackendErrorCode code) { + switch (code) { + case RCBackendInvalidPlatform: + return RCUnknownError; + case RCBackendStoreProblem: + case RCBackendPlayStoreQuotaExceeded: + case RCBackendPlayStoreInvalidPackageName: + case RCBackendPlayStoreGenericError: + return RCStoreProblemError; + case RCBackendCannotTransferPurchase: + return RCReceiptAlreadyInUseError; + case RCBackendInvalidReceiptToken: + return RCInvalidReceiptError; + case RCBackendInvalidAppStoreSharedSecret: + case RCBackendInvalidPlayStoreCredentials: + case RCBackendInvalidAuthToken: + case RCBackendInvalidAPIKey: + return RCInvalidCredentialsError; + case RCBackendInvalidPaymentModeOrIntroPriceNotProvided: + case RCBackendProductIdForGoogleReceiptNotProvided: + return RCPurchaseInvalidError; + case RCBackendEmptyAppUserId: + return RCInvalidAppUserIdError; + case RCBackendInvalidAppleSubscriptionKey: + return RCInvalidAppleSubscriptionKeyError; + case RCBackendUserIneligibleForPromoOffer: + return RCIneligibleError; + case RCBackendInvalidSubscriberAttributes: + case RCBackendInvalidSubscriberAttributesBody: + return RCInvalidSubscriberAttributesError; + } + return RCUnknownError; +} + +#if TARGET_OS_IPHONE + #define CODE_IF_TARGET_IPHONE(code, value) code +#else + #define CODE_IF_TARGET_IPHONE(code, value) ((SKErrorCode) value) +#endif + +static RCPurchasesErrorCode RCPurchasesErrorCodeFromSKError(NSError *skError) { + if ([[skError domain] isEqualToString:SKErrorDomain]) { + NSInteger code = (SKErrorCode) skError.code; + switch (code) { + case SKErrorUnknown: + case CODE_IF_TARGET_IPHONE(SKErrorCloudServiceNetworkConnectionFailed, 7): // Available on iOS 9.3 + case CODE_IF_TARGET_IPHONE(SKErrorCloudServiceRevoked, 8): // Available on iOS 10.3 + return RCStoreProblemError; + case SKErrorClientInvalid: + case SKErrorPaymentNotAllowed: + case CODE_IF_TARGET_IPHONE(SKErrorCloudServicePermissionDenied, 6): // Available on iOS 9.3 + case SKErrorPrivacyAcknowledgementRequired: + return RCPurchaseNotAllowedError; + case SKErrorPaymentCancelled: + return RCPurchaseCancelledError; + case SKErrorPaymentInvalid: + case SKErrorUnauthorizedRequestData: + case SKErrorMissingOfferParams: + case SKErrorInvalidOfferPrice: + case SKErrorInvalidSignature: + case SKErrorInvalidOfferIdentifier: + return RCPurchaseInvalidError; + case CODE_IF_TARGET_IPHONE(SKErrorStoreProductNotAvailable, 5): + return RCProductNotAvailableForPurchaseError; + #ifdef __IPHONE_14_0 + case SKErrorOverlayCancelled: + return RCPurchaseCancelledError; + case SKErrorIneligibleForOffer: + return RCPurchaseNotAllowedError; + #if (TARGET_OS_IOS && !TARGET_OS_MACCATALYST) + case SKErrorOverlayInvalidConfiguration: + return RCPurchaseNotAllowedError; + case SKErrorOverlayTimeout: + return RCStoreProblemError; + #endif + #endif + } + } + return RCUnknownError; +} + +@implementation RCPurchasesErrorUtils + ++ (NSError *)errorWithCode:(RCPurchasesErrorCode)code { + return [self errorWithCode:code message:nil]; +} + ++ (NSError *)errorWithCode:(RCPurchasesErrorCode)code + message:(nullable NSString *)message { + return [self errorWithCode:code message:message underlyingError:nil]; +} + ++ (NSError *)errorWithCode:(RCPurchasesErrorCode)code + underlyingError:(nullable NSError *)underlyingError { + return [self errorWithCode:code message:nil underlyingError:underlyingError extraUserInfo:nil]; +} + ++ (NSError *)errorWithCode:(RCPurchasesErrorCode)code + message:(nullable NSString *)message + underlyingError:(nullable NSError *)underlyingError { + return [self errorWithCode:code message:message underlyingError:underlyingError extraUserInfo:nil]; +} + ++ (NSError *)errorWithCode:(RCPurchasesErrorCode)code + message:(nullable NSString *)message + underlyingError:(nullable NSError *)underlyingError + extraUserInfo:(nullable NSDictionary *)extraUserInfo { + + NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:extraUserInfo]; + userInfo[NSLocalizedDescriptionKey] = message ?: RCPurchasesErrorDescription(code); + if (underlyingError) { + userInfo[NSUnderlyingErrorKey] = underlyingError; + } + userInfo[RCReadableErrorCodeKey] = RCPurchasesErrorCodeString(code); + return [self errorWithCode:code userInfo:userInfo]; +} + ++ (NSError *)errorWithCode:(RCPurchasesErrorCode)code + userInfo:(NSDictionary *)userInfo { + RCErrorLog(@"%@", RCPurchasesErrorDescription(code)); + return [NSError errorWithDomain:RCPurchasesErrorDomain code:code userInfo:userInfo]; +} + ++ (NSError *)networkErrorWithUnderlyingError:(NSError *)underlyingError { + return [self errorWithCode:RCNetworkError + underlyingError:underlyingError]; +} + ++ (NSError *)backendUnderlyingError:(nullable NSNumber *)backendCode + backendMessage:(nullable NSString *)backendMessage { + + return [NSError errorWithDomain:RCBackendErrorDomain + code:[backendCode integerValue] ?: RCUnknownError + userInfo:@{ + NSLocalizedDescriptionKey: backendMessage ?: @"" + }]; +} + ++ (NSError *)backendErrorWithBackendCode:(nullable NSNumber *)backendCode + backendMessage:(nullable NSString *)backendMessage { + return [self backendErrorWithBackendCode:backendCode backendMessage:backendMessage extraUserInfo:nil]; +} + ++ (NSError *)backendErrorWithBackendCode:(nullable NSNumber *)backendCode + backendMessage:(nullable NSString *)backendMessage + finishable:(BOOL)finishable { + return [self backendErrorWithBackendCode:backendCode + backendMessage:backendMessage + extraUserInfo:@{ + RCFinishableKey: @(finishable) + }]; +} + ++ (NSError *)backendErrorWithBackendCode:(nullable NSNumber *)backendCode + backendMessage:(nullable NSString *)backendMessage + extraUserInfo:(nullable NSDictionary *)extraUserInfo { + RCPurchasesErrorCode errorCode; + if (backendCode != nil) { + errorCode = RCPurchasesErrorCodeFromRCBackendErrorCode((RCBackendErrorCode) [backendCode integerValue]); + } else { + errorCode = RCUnknownBackendError; + } + + return [self errorWithCode:errorCode + message:RCPurchasesErrorDescription(errorCode) + underlyingError:[self backendUnderlyingError:backendCode backendMessage:backendMessage] + extraUserInfo:extraUserInfo]; +} + ++ (NSError *)unexpectedBackendResponseError { + return [self errorWithCode:RCUnexpectedBackendResponseError]; +} + ++ (NSError *)missingReceiptFileError { + return [self errorWithCode:RCMissingReceiptFileError]; +} + ++ (NSError *)missingAppUserIDError { + return [self errorWithCode:RCInvalidAppUserIdError]; +} + ++ (NSError *)paymentDeferredError { + return [self errorWithCode:RCPaymentPendingError + message:@"The payment is deferred."]; +} + ++ (NSError *)unknownError { + return [self errorWithCode:RCUnknownError]; +} + ++ (NSError *)purchasesErrorWithSKError:(NSError *)skError { + + RCPurchasesErrorCode errorCode = RCPurchasesErrorCodeFromSKError(skError); + return [self errorWithCode:errorCode + message:RCPurchasesErrorDescription(errorCode) + underlyingError:skError]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Public/RCPurchasesErrors.h b/Pods/Purchases/Purchases/Public/RCPurchasesErrors.h new file mode 100644 index 0000000..73bf1f0 --- /dev/null +++ b/Pods/Purchases/Purchases/Public/RCPurchasesErrors.h @@ -0,0 +1,82 @@ +// +// RCPurchasesErrors.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import + + +NS_SWIFT_NAME(Purchases.Errors) +@interface RCPurchasesErrors + +/** + `NSErrorDomain` for errors occurring within the scope of the Purchases SDK. + */ +extern NSErrorDomain const RCPurchasesErrorDomain NS_SWIFT_NAME(Purchases.ErrorDomain); + +/** + `NSErrorDomain` for errors occurring within the scope of the RevenueCat Backend. + */ +extern NSErrorDomain const RCBackendErrorDomain NS_SWIFT_NAME(Purchases.RevenueCatBackendErrorDomain); + +extern NSErrorUserInfoKey const RCFinishableKey NS_SWIFT_NAME(Purchases.FinishableKey); + +extern NSErrorUserInfoKey const RCReadableErrorCodeKey NS_SWIFT_NAME(Purchases.ReadableErrorCodeKey); + + +/** + Error codes used by the Purchases SDK + */ +typedef NS_ERROR_ENUM(RCPurchasesErrorDomain, RCPurchasesErrorCode) { + RCUnknownError = 0, + RCPurchaseCancelledError, + RCStoreProblemError, + RCPurchaseNotAllowedError, + RCPurchaseInvalidError, + RCProductNotAvailableForPurchaseError, + RCProductAlreadyPurchasedError, + RCReceiptAlreadyInUseError, + RCInvalidReceiptError, + RCMissingReceiptFileError, + RCNetworkError, + RCInvalidCredentialsError, + RCUnexpectedBackendResponseError, + RCReceiptInUseByOtherSubscriberError, + RCInvalidAppUserIdError, + RCOperationAlreadyInProgressError, + RCUnknownBackendError, + RCInvalidAppleSubscriptionKeyError, + RCIneligibleError, + RCInsufficientPermissionsError, + RCPaymentPendingError, + RCInvalidSubscriberAttributesError, +} NS_SWIFT_NAME(Purchases.ErrorCode); + +/** + Error codes sent by the RevenueCat backend. This only includes the errors that matter to the SDK + */ +typedef NS_ENUM(NSInteger, RCBackendErrorCode) { + RCBackendInvalidPlatform = 7000, + RCBackendStoreProblem = 7101, + RCBackendCannotTransferPurchase = 7102, + RCBackendInvalidReceiptToken = 7103, + RCBackendInvalidAppStoreSharedSecret = 7104, + RCBackendInvalidPaymentModeOrIntroPriceNotProvided = 7105, + RCBackendProductIdForGoogleReceiptNotProvided = 7106, + RCBackendInvalidPlayStoreCredentials = 7107, + RCBackendEmptyAppUserId = 7220, + RCBackendInvalidAuthToken = 7224, + RCBackendInvalidAPIKey = 7225, + RCBackendPlayStoreQuotaExceeded = 7229, + RCBackendPlayStoreInvalidPackageName = 7230, + RCBackendPlayStoreGenericError = 7231, + RCBackendUserIneligibleForPromoOffer = 7232, + RCBackendInvalidAppleSubscriptionKey = 7234, + RCBackendInvalidSubscriberAttributes = 7263, + RCBackendInvalidSubscriberAttributesBody = 7264 +} NS_SWIFT_NAME(Purchases.RevenueCatBackendErrorCode); + +@end diff --git a/Pods/Purchases/Purchases/Public/RCTransaction.h b/Pods/Purchases/Purchases/Public/RCTransaction.h new file mode 100644 index 0000000..c4046a6 --- /dev/null +++ b/Pods/Purchases/Purchases/Public/RCTransaction.h @@ -0,0 +1,28 @@ +// +// RCTransaction.h +// Purchases +// +// Created by Andrés Boedo on 8/13/20. +// Copyright © 2020 Purchases. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +NS_SWIFT_NAME(Purchases.Transaction) +@interface RCTransaction : NSObject + +@property (nonatomic, readonly, copy) NSString *revenueCatId; +@property (nonatomic, readonly, copy) NSString *productId; +@property (nonatomic, readonly, copy) NSDate *purchaseDate; + +- (instancetype)initWithTransactionId:(NSString *)transactionId + productId:(NSString *)productId + purchaseDate:(NSDate *)purchaseDate NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Purchasing/RCAttributionData.h b/Pods/Purchases/Purchases/Purchasing/RCAttributionData.h new file mode 100644 index 0000000..51ecb38 --- /dev/null +++ b/Pods/Purchases/Purchases/Purchasing/RCAttributionData.h @@ -0,0 +1,24 @@ +// +// RCAttributionData.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import +#import "Purchases.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface RCAttributionData : NSObject + +@property (readwrite, nonatomic) NSDictionary *data; +@property (readwrite, nonatomic) RCAttributionNetwork network; +@property (readwrite, nonatomic, nullable) NSString * networkUserId; + +- (nullable instancetype)initWithData:(NSDictionary *)data fromNetwork:(RCAttributionNetwork)network forNetworkUserId:(nullable NSString *)networkUserId; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Purchasing/RCAttributionData.m b/Pods/Purchases/Purchases/Purchasing/RCAttributionData.m new file mode 100644 index 0000000..38d3005 --- /dev/null +++ b/Pods/Purchases/Purchases/Purchasing/RCAttributionData.m @@ -0,0 +1,24 @@ +// +// RCAttributionData.m +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import +#import "RCAttributionData.h" + +@implementation RCAttributionData + +- (nullable instancetype)initWithData:(NSDictionary *)data fromNetwork:(RCAttributionNetwork)network forNetworkUserId:(nullable NSString *)networkUserId +{ + if (self = [super init]) { + self.data = data; + self.network = network; + self.networkUserId = networkUserId; + } + return self; +} + +@end diff --git a/Pods/Purchases/Purchases/Purchasing/RCAttributionFetcher.h b/Pods/Purchases/Purchases/Purchasing/RCAttributionFetcher.h new file mode 100644 index 0000000..e6b5ff4 --- /dev/null +++ b/Pods/Purchases/Purchases/Purchasing/RCAttributionFetcher.h @@ -0,0 +1,47 @@ +// +// RCAttributionFetcher.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import +#import +#import "RCAttributionNetwork.h" + +NS_ASSUME_NONNULL_BEGIN + +@class RCDeviceCache, RCIdentityManager, RCBackend, RCAttributionData; + +typedef void (^RCAttributionDetailsBlock)(NSDictionary *_Nullable, NSError *_Nullable); + +@interface RCAttributionFetcher : NSObject + +- (instancetype)initWithDeviceCache:(RCDeviceCache *)deviceCache + identityManager:(RCIdentityManager *)identityManager + backend:(RCBackend *)backend NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; + +- (nullable NSString *)identifierForAdvertisers; + +- (nullable NSString *)identifierForVendor; + +- (void)adClientAttributionDetailsWithCompletionBlock:(RCAttributionDetailsBlock)completionHandler; + +- (void)postAttributionData:(NSDictionary *)data + fromNetwork:(RCAttributionNetwork)network + forNetworkUserId:(nullable NSString *)networkUserId; + +- (void)postAppleSearchAdsAttributionCollection; + +- (void)postPostponedAttributionDataIfNeeded; + ++ (void)storePostponedAttributionData:(NSDictionary *)data + fromNetwork:(RCAttributionNetwork)network + forNetworkUserId:(nullable NSString *)networkUserId; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Purchasing/RCAttributionFetcher.m b/Pods/Purchases/Purchases/Purchasing/RCAttributionFetcher.m new file mode 100644 index 0000000..b838c69 --- /dev/null +++ b/Pods/Purchases/Purchases/Purchasing/RCAttributionFetcher.m @@ -0,0 +1,210 @@ +// +// RCAttributionFetcher.m +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import "RCAttributionFetcher.h" +#import "RCCrossPlatformSupport.h" +#import "RCLogUtils.h" +#import "RCDeviceCache.h" +#import "RCIdentityManager.h" +#import "RCBackend.h" +#import "RCAttributionData.h" + + +@protocol FakeAdClient + ++ (instancetype)sharedClient; +- (void)requestAttributionDetailsWithBlock:(RCAttributionDetailsBlock)completionHandler; + +@end + + +@protocol FakeASIdentifierManager + ++ (instancetype)sharedManager; + +@end + + +static NSMutableArray *_Nullable postponedAttributionData; + + +@interface RCAttributionFetcher () + +@property (strong, nonatomic) RCDeviceCache *deviceCache; +@property (strong, nonatomic) RCIdentityManager *identityManager; +@property (strong, nonatomic) RCBackend *backend; + +@end + + +@implementation RCAttributionFetcher : NSObject + +- (instancetype)initWithDeviceCache:(RCDeviceCache *)deviceCache + identityManager:(RCIdentityManager *)identityManager + backend:(RCBackend *)backend { + if (self = [super init]) { + self.deviceCache = deviceCache; + self.identityManager = identityManager; + self.backend = backend; + } + return self; +} + +- (NSString *)rot13:(NSString *)string { + NSMutableString *rotatedString = [NSMutableString string]; + for (NSUInteger charIdx = 0; charIdx < string.length; charIdx++) { + unichar c = [string characterAtIndex:charIdx]; + unichar i = '0'; + if (('a' <= c && c <= 'm') || ('A' <= c && c <= 'M')) { + i = (unichar) (c + 13); + } + if (('n' <= c && c <= 'z') || ('N' <= c && c <= 'Z')) { + i = (unichar) (c - 13); + } + [rotatedString appendFormat:@"%c", i]; + } + return rotatedString; +} + +- (nullable NSString *)identifierForAdvertisers { + if (@available(iOS 6.0, macOS 10.14, *)) { + // We need to do this mangling to avoid Kid apps being rejected for getting idfa. + // It looks like during the app review process Apple does some string matching looking for + // functions in the AdSupport.framework. We apply rot13 on these functions and classes names + // so that Apple can't find them during the review, but we can still access them on runtime. + NSString *mangledClassName = @"NFVqragvsvreZnantre"; + NSString *mangledIdentifierPropertyName = @"nqiregvfvatVqragvsvre"; + + NSString *className = [self rot13:mangledClassName]; + id asIdentifierManagerClass = (id ) NSClassFromString(className); + if (asIdentifierManagerClass) { + NSString *identifierPropertyName = [self rot13:mangledIdentifierPropertyName]; + id sharedManager = [asIdentifierManagerClass sharedManager]; + NSUUID *identifierValue = [sharedManager valueForKey:identifierPropertyName]; + return identifierValue.UUIDString; + } else { + RCDebugLog(@"AdSupport framework not imported. Attribution data incomplete."); + } + } + return nil; +} + +- (nullable NSString *)identifierForVendor { +#if UI_DEVICE_AVAILABLE + if ([UIDevice class]) { + return UIDevice.currentDevice.identifierForVendor.UUIDString; + } +#endif + return nil; +} + +- (void)adClientAttributionDetailsWithCompletionBlock:(RCAttributionDetailsBlock)completionHandler { +#if AD_CLIENT_AVAILABLE + id adClientClass = (id)NSClassFromString(@"ADClient"); + + if (adClientClass) { + [[adClientClass sharedClient] requestAttributionDetailsWithBlock:completionHandler]; + } +#endif +} + +- (NSString *)latestNetworkIdAndAdvertisingIdentifierSentForNetwork:(RCAttributionNetwork)network { + NSString *networkID = [NSString stringWithFormat:@"%ld", (long) network]; + NSDictionary *cachedDict = + [self.deviceCache latestNetworkAndAdvertisingIdsSentForAppUserID:self.identityManager.currentAppUserID]; + return cachedDict[networkID]; +} + +- (void)postAttributionData:(NSDictionary *)data + fromNetwork:(RCAttributionNetwork)network + forNetworkUserId:(nullable NSString *)networkUserId { + if (data[@"rc_appsflyer_id"]) { + RCErrorLog(@"⚠️ The parameter key rc_appsflyer_id is deprecated. Pass networkUserId to addAttribution instead. ⚠️"); + } + if (network == RCAttributionNetworkAppsFlyer && networkUserId == nil) { + RCErrorLog(@"⚠️ The parameter networkUserId is REQUIRED for AppsFlyer. ⚠️"); + } + NSString *appUserID = self.identityManager.currentAppUserID; + NSString *networkKey = [NSString stringWithFormat:@"%ld", (long) network]; + NSString *identifierForAdvertisers = [self identifierForAdvertisers]; + NSDictionary *dictOfLatestNetworkIdsAndAdvertisingIdsSentToNetworks = + [self.deviceCache latestNetworkAndAdvertisingIdsSentForAppUserID:appUserID]; + NSString *latestSentToNetwork = dictOfLatestNetworkIdsAndAdvertisingIdsSentToNetworks[networkKey]; + NSString *newValueForNetwork = [NSString stringWithFormat:@"%@_%@", identifierForAdvertisers, networkUserId]; + + if ([latestSentToNetwork isEqualToString:newValueForNetwork]) { + RCDebugLog(@"Attribution data is the same as latest. Skipping."); + } else { + NSMutableDictionary *newDictToCache = + [NSMutableDictionary dictionaryWithDictionary:dictOfLatestNetworkIdsAndAdvertisingIdsSentToNetworks]; + newDictToCache[networkKey] = newValueForNetwork; + + NSMutableDictionary *newData = [NSMutableDictionary dictionaryWithDictionary:data]; + newData[@"rc_idfa"] = identifierForAdvertisers; + newData[@"rc_idfv"] = [self identifierForVendor]; + newData[@"rc_attribution_network_id"] = networkUserId; + + if (newData.count > 0) { + [self.backend postAttributionData:newData + fromNetwork:network + forAppUserID:appUserID + completion:^(NSError *_Nullable error) { + if (error == nil) { + [self.deviceCache setLatestNetworkAndAdvertisingIdsSent:newDictToCache + forAppUserID:appUserID]; + } + }]; + } + } +} + +- (void)postAppleSearchAdsAttributionCollection { + NSString *latestNetworkIdAndAdvertisingIdSentToAppleSearchAds = [self + latestNetworkIdAndAdvertisingIdentifierSentForNetwork:RCAttributionNetworkAppleSearchAds]; + if (latestNetworkIdAndAdvertisingIdSentToAppleSearchAds == nil) { + [self adClientAttributionDetailsWithCompletionBlock:^(NSDictionary *_Nullable attributionDetails, + NSError *_Nullable error) { + NSArray *values = [attributionDetails allValues]; + + bool hasIadAttribution = values.count != 0 && [values[0][@"iad-attribution"] boolValue]; + if (hasIadAttribution) { + [self postAttributionData:attributionDetails + fromNetwork:RCAttributionNetworkAppleSearchAds + forNetworkUserId:nil]; + } + }]; + } +} + +- (void)postPostponedAttributionDataIfNeeded { + if (postponedAttributionData) { + for (RCAttributionData *attributionData in postponedAttributionData) { + [self postAttributionData:attributionData.data + fromNetwork:attributionData.network + forNetworkUserId:attributionData.networkUserId]; + } + } + + postponedAttributionData = nil; +} + +static NSMutableArray *_Nullable postponedAttributionData; + ++ (void)storePostponedAttributionData:(NSDictionary *)data + fromNetwork:(RCAttributionNetwork)network + forNetworkUserId:(nullable NSString *)networkUserId { + if (postponedAttributionData == nil) { + postponedAttributionData = [NSMutableArray array]; + } + [postponedAttributionData addObject:[[RCAttributionData alloc] initWithData:data + fromNetwork:network + forNetworkUserId:networkUserId]]; +} + +@end + diff --git a/Pods/Purchases/Purchases/Purchasing/RCIdentityManager.h b/Pods/Purchases/Purchases/Purchasing/RCIdentityManager.h new file mode 100644 index 0000000..f7239ac --- /dev/null +++ b/Pods/Purchases/Purchases/Purchasing/RCIdentityManager.h @@ -0,0 +1,37 @@ +// +// Created by RevenueCat. +// Copyright (c) 2019 Purchases. All rights reserved. +// + +#import +#import "RCDeviceCache.h" + +@class RCPurchaserInfo, RCBackend; + +NS_ASSUME_NONNULL_BEGIN + +@interface RCIdentityManager : NSObject + +@property (nonatomic, readonly) NSString *currentAppUserID; + +@property (nonatomic, readonly) RCDeviceCache *deviceCache; + +@property (nonatomic, readonly) RCBackend *backend; + +@property (nonatomic, readonly) BOOL currentUserIsAnonymous; + +- (instancetype)initWith:(RCDeviceCache *)deviceCache backend:(RCBackend *)backend; + +- (NSString *)generateRandomID; + +- (void)configureWithAppUserID:(nullable NSString *)appUserID; + +- (void)identifyAppUserID:(NSString *)appUserID withCompletionBlock:(void (^)(NSError * _Nullable error))completion; + +- (void)createAlias:(NSString *)alias withCompletionBlock:(void (^)(NSError * _Nullable error))completion; + +- (void)resetAppUserID; + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Pods/Purchases/Purchases/Purchasing/RCIdentityManager.m b/Pods/Purchases/Purchases/Purchasing/RCIdentityManager.m new file mode 100644 index 0000000..31e3514 --- /dev/null +++ b/Pods/Purchases/Purchases/Purchasing/RCIdentityManager.m @@ -0,0 +1,101 @@ +// +// Created by RevenueCat. +// Copyright (c) 2019 Purchases. All rights reserved. +// + +#import "RCIdentityManager.h" +#import "RCLogUtils.h" +#import "RCBackend.h" +#import "RCPurchasesErrorUtils.h" + + +@interface RCIdentityManager () + +@property (nonatomic) RCDeviceCache *deviceCache; + +@property (nonatomic) RCBackend *backend; + +@end + +@implementation RCIdentityManager + +- (instancetype)initWith:(RCDeviceCache *)deviceCache backend:(RCBackend *)backend { + self = [super init]; + if (self) { + self.deviceCache = deviceCache; + self.backend = backend; + } + + return self; +} + +- (NSString *)generateRandomID { + NSString *uuid = [NSUUID.new.UUIDString stringByReplacingOccurrencesOfString:@"-" withString:@""]; + return [NSString stringWithFormat:@"$RCAnonymousID:%@", uuid.lowercaseString]; +} + +- (void)configureWithAppUserID:(nullable NSString *)appUserID { + if (appUserID == nil) { + appUserID = [self.deviceCache cachedAppUserID]; + if (appUserID == nil) { + appUserID = [self.deviceCache cachedLegacyAppUserID]; + if (appUserID == nil) { + appUserID = [self generateRandomID]; + RCDebugLog(@"Generated New App User ID - %@", appUserID); + } + } + } + + [self saveAppUserID:appUserID]; + [self.deviceCache cleanupSubscriberAttributes]; +} + +- (void)identifyAppUserID:(NSString *)appUserID withCompletionBlock:(void (^)(NSError *_Nullable error))completion { + if (self.currentUserIsAnonymous) { + RCDebugLog(@"Identifying from an anonymous ID: %@. An alias will be created.", self.currentAppUserID); + [self createAlias:appUserID withCompletionBlock:completion]; + } else { + RCDebugLog(@"Changing App User ID: %@ -> %@", self.currentAppUserID, appUserID); + [self.deviceCache clearCachesForAppUserID:self.currentAppUserID andSaveNewUserID:appUserID]; + completion(nil); + } +} + +- (void)saveAppUserID:(NSString *)appUserID { + [self.deviceCache cacheAppUserID:appUserID]; +} + +- (void)createAlias:(NSString *)alias withCompletionBlock:(void (^)(NSError *_Nullable error))completion { + NSString *currentAppUserID = self.currentAppUserID; + if (!currentAppUserID) { + RCDebugLog(@"Couldn't create an alias because the currentAppUserID is null. " + "This might happen if the entry in UserDefaults is missing."); + completion(RCPurchasesErrorUtils.missingAppUserIDError); + return; + } + RCDebugLog(@"Creating an alias from %@ to %@", currentAppUserID, alias); + [self.backend createAliasForAppUserID:currentAppUserID withNewAppUserID:alias completion:^(NSError *_Nullable error) { + if (error == nil) { + RCDebugLog(@"Alias created"); + [self.deviceCache clearCachesForAppUserID:currentAppUserID andSaveNewUserID:alias]; + } + completion(error); + }]; +} + +- (void)resetAppUserID { + NSString *randomId = [self generateRandomID]; + [self.deviceCache clearCachesForAppUserID:self.currentAppUserID andSaveNewUserID:randomId]; +} + +- (NSString *)currentAppUserID { + return [self.deviceCache cachedAppUserID]; +} + +- (BOOL)currentUserIsAnonymous { + BOOL currentAppUserIDLooksAnonymous = [[self.deviceCache cachedAppUserID] rangeOfString:@"\\$RCAnonymousID:([a-z0-9]{32})$" options:NSRegularExpressionSearch].length > 0; + BOOL isLegacyAnonymousAppUserID = [self.deviceCache.cachedAppUserID isEqualToString:self.deviceCache.cachedLegacyAppUserID]; + return currentAppUserIDLooksAnonymous || isLegacyAnonymousAppUserID; +} + +@end diff --git a/Pods/Purchases/Purchases/Purchasing/RCOfferingsFactory.h b/Pods/Purchases/Purchases/Purchasing/RCOfferingsFactory.h new file mode 100644 index 0000000..9f7573f --- /dev/null +++ b/Pods/Purchases/Purchases/Purchasing/RCOfferingsFactory.h @@ -0,0 +1,26 @@ +// +// RCOfferingsFactory.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 Purchases. All rights reserved. +// + +#import + +@class RCOfferings; +@class RCOffering; +@class RCPackage; + +NS_ASSUME_NONNULL_BEGIN + +@interface RCOfferingsFactory : NSObject + +- (nullable RCOfferings *)createOfferingsWithProducts:(NSDictionary *)products data:(NSDictionary *)data; + +- (nullable RCOffering *)createOfferingWithProducts:(NSDictionary *)products offeringData:(NSDictionary *)offeringData; + +- (nullable RCPackage *)createPackageWithData:(NSDictionary *)data products:(NSDictionary *)products offeringIdentifier:(NSString *)offeringIdentifier; +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Purchasing/RCOfferingsFactory.m b/Pods/Purchases/Purchases/Purchasing/RCOfferingsFactory.m new file mode 100644 index 0000000..2452b5a --- /dev/null +++ b/Pods/Purchases/Purchases/Purchasing/RCOfferingsFactory.m @@ -0,0 +1,71 @@ +// +// RCOfferingsFactory.m +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 Purchases. All rights reserved. +// + +#import +#import "RCOfferingsFactory.h" +#import "RCOffering.h" +#import "RCPackage.h" +#import "RCOfferings.h" +#import "RCOfferings+Protected.h" +#import "RCPackage+Protected.h" +#import "RCOffering+Protected.h" + + +@interface RCOfferingsFactory () + +@end + +@implementation RCOfferingsFactory + +- (RCOfferings *)createOfferingsWithProducts:(NSDictionary *)products data:(NSDictionary *)data +{ + NSArray *offeringsData = data[@"offerings"]; + NSString *currentOfferingID = data[@"current_offering_id"]; + if (offeringsData && currentOfferingID) { + NSMutableDictionary *offerings = [NSMutableDictionary dictionary]; + for (NSDictionary *offeringData in offeringsData) { + RCOffering *offering = [self createOfferingWithProducts:products offeringData:offeringData]; + if (offering) { + offerings[offering.identifier] = offering; + } + } + + return [[RCOfferings alloc] initWithOfferings:[NSDictionary dictionaryWithDictionary:offerings] currentOfferingID:currentOfferingID]; + } + return nil; +} + +- (nullable RCOffering *)createOfferingWithProducts:(NSDictionary *)products offeringData:(NSDictionary *)offeringData +{ + NSMutableArray *availablePackages = [NSMutableArray array]; + NSString *offeringIdentifier = offeringData[@"identifier"]; + for (NSDictionary *packageData in offeringData[@"packages"]) { + RCPackage *package = [self createPackageWithData:packageData products:products offeringIdentifier:offeringIdentifier]; + if (package) { + [availablePackages addObject:package]; + } + } + + if (availablePackages.count != 0) { + return [[RCOffering alloc] initWithIdentifier:offeringIdentifier serverDescription:offeringData[@"description"] availablePackages:[NSArray arrayWithArray:availablePackages]]; + } + return nil; +} + +- (nullable RCPackage *)createPackageWithData:(NSDictionary *)data products:(NSDictionary *)products offeringIdentifier:(NSString *)offeringIdentifier +{ + SKProduct *product = products[data[@"platform_product_identifier"]]; + if (product) { + NSString *identifier = data[@"identifier"]; + RCPackageType packageType = [RCPackage packageTypeFromString:identifier]; + return [[RCPackage alloc] initWithIdentifier:identifier packageType:packageType product:product offeringIdentifier:offeringIdentifier]; + } + return nil; +} + +@end diff --git a/Pods/Purchases/Purchases/Purchasing/RCProductInfo.h b/Pods/Purchases/Purchases/Purchasing/RCProductInfo.h new file mode 100644 index 0000000..f9e3cd0 --- /dev/null +++ b/Pods/Purchases/Purchases/Purchasing/RCProductInfo.h @@ -0,0 +1,62 @@ +// +// Created by Andrés Boedo on 5/29/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, RCIntroDurationType) { + RCIntroDurationTypeNone = -1, + RCIntroDurationTypeFreeTrial = 0, + RCIntroDurationTypeIntroPrice = 1 +}; + +typedef NS_ENUM(NSInteger, RCPaymentMode) { + RCPaymentModeNone = -1, + RCPaymentModePayAsYouGo = 0, + RCPaymentModePayUpFront = 1, + RCPaymentModeFreeTrial = 2 +}; + +API_AVAILABLE(ios(11.2), macos(10.13.2)) +RCPaymentMode RCPaymentModeFromSKProductDiscountPaymentMode(SKProductDiscountPaymentMode paymentMode); + +@class RCPromotionalOffer; + +@interface RCProductInfo : NSObject + +@property (nonatomic, readonly, copy) NSString *productIdentifier; +@property (nonatomic, readonly, assign) RCPaymentMode paymentMode; +@property (nonatomic, readonly, copy) NSString *currencyCode; +@property (nonatomic, readonly, copy) NSDecimalNumber *price; +@property (nonatomic, nullable, readonly, copy) NSString *normalDuration; +@property (nonatomic, nullable, readonly, copy) NSString *introDuration; +@property (nonatomic, readonly, assign) RCIntroDurationType introDurationType; +@property (nonatomic, readonly, copy) NSDecimalNumber *introPrice; +@property (nonatomic, readonly, copy) NSString *subscriptionGroup; +@property (nonatomic, readonly, copy) NSArray *discounts; + +- (instancetype)initWithProductIdentifier:(NSString *)productIdentifier + paymentMode:(RCPaymentMode)paymentMode + currencyCode:(NSString *)currencyCode + price:(NSDecimalNumber *)price + normalDuration:(nullable NSString *)normalDuration + introDuration:(nullable NSString *)introDuration + introDurationType:(RCIntroDurationType)introDurationType + introPrice:(nullable NSDecimalNumber *)introPrice + subscriptionGroup:(nullable NSString *)subscriptionGroup + discounts:(nullable NSArray *)discounts NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; + +- (NSDictionary *)asDictionary; + +- (NSString *)cacheKey; + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Purchasing/RCProductInfo.m b/Pods/Purchases/Purchases/Purchasing/RCProductInfo.m new file mode 100644 index 0000000..fd1416b --- /dev/null +++ b/Pods/Purchases/Purchases/Purchasing/RCProductInfo.m @@ -0,0 +1,163 @@ +// +// Created by Andrés Boedo on 5/29/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import "RCProductInfo.h" +#import "RCPromotionalOffer.h" + +NS_ASSUME_NONNULL_BEGIN + + +@interface RCProductInfo () + +@property (nonatomic, copy) NSString *productIdentifier; +@property (nonatomic, assign) RCPaymentMode paymentMode; +@property (nonatomic, copy) NSString *currencyCode; +@property (nonatomic, copy) NSDecimalNumber *price; +@property (nonatomic, nullable, copy) NSString *normalDuration; +@property (nonatomic, nullable, copy) NSString *introDuration; +@property (nonatomic, assign) RCIntroDurationType introDurationType; +@property (nonatomic, nullable, copy) NSDecimalNumber *introPrice; +@property (nonatomic, nullable, copy) NSString *subscriptionGroup; +@property (nonatomic, nullable, copy) NSArray *discounts; + +@end + + +API_AVAILABLE(ios(11.2), macos(10.13.2)) +RCPaymentMode +RCPaymentModeFromSKProductDiscountPaymentMode(SKProductDiscountPaymentMode paymentMode) { + switch (paymentMode) { + case SKProductDiscountPaymentModePayUpFront: + return RCPaymentModePayUpFront; + case SKProductDiscountPaymentModePayAsYouGo: + return RCPaymentModePayAsYouGo; + case SKProductDiscountPaymentModeFreeTrial: + return RCPaymentModeFreeTrial; + default: + return RCPaymentModeNone; + } +} + + +@implementation RCProductInfo + +- (instancetype)initWithProductIdentifier:(NSString *)productIdentifier + paymentMode:(RCPaymentMode)paymentMode + currencyCode:(NSString *)currencyCode + price:(NSDecimalNumber *)price + normalDuration:(nullable NSString *)normalDuration + introDuration:(nullable NSString *)introDuration + introDurationType:(RCIntroDurationType)introDurationType + introPrice:(nullable NSDecimalNumber *)introPrice + subscriptionGroup:(nullable NSString *)subscriptionGroup + discounts:(nullable NSArray *)discounts { + self = [super init]; + if (self) { + self.productIdentifier = productIdentifier; + self.paymentMode = paymentMode; + self.currencyCode = currencyCode; + self.price = price; + self.normalDuration = normalDuration; + self.introDuration = introDuration; + self.introDurationType = introDurationType; + self.introPrice = introPrice; + self.subscriptionGroup = subscriptionGroup; + self.discounts = discounts; + } + + return self; +} + +- (NSDictionary *)asDictionary { + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + + if (self.productIdentifier) { + dict[@"product_id"] = self.productIdentifier; + } + + if (self.price) { + dict[@"price"] = self.price; + } + + if (self.currencyCode) { + dict[@"currency"] = self.currencyCode; + } + + if (self.paymentMode != RCPaymentModeNone) { + dict[@"payment_mode"] = @((NSUInteger) self.paymentMode); + } + + if (self.introPrice) { + dict[@"introductory_price"] = self.introPrice; + } + + if (self.subscriptionGroup) { + dict[@"subscription_group_id"] = self.subscriptionGroup; + } + + [dict addEntriesFromDictionary:self.discountsAsDictionary]; + [dict addEntriesFromDictionary:self.productDurationsAsDictionary]; + return dict; +} + +- (NSDictionary *)discountsAsDictionary { + NSMutableDictionary *discounts = [[NSMutableDictionary alloc] init]; + if (@available(iOS 12.2, macOS 10.14.4, tvOS 12.2, *)) { + if (self.discounts) { + NSMutableArray *offers = [NSMutableArray array]; + for (RCPromotionalOffer *discount in self.discounts) { + [offers addObject:@{ + @"offer_identifier": discount.offerIdentifier, + @"price": discount.price, + @"payment_mode": @((NSUInteger) discount.paymentMode) + }]; + } + discounts[@"offers"] = offers; + } + } + return discounts; +} + +- (NSDictionary *)productDurationsAsDictionary { + NSMutableDictionary *durations = [[NSMutableDictionary alloc] init]; + + if (self.normalDuration) { + durations[@"normal_duration"] = self.normalDuration; + } + if (self.introDurationType == RCIntroDurationTypeIntroPrice) { + durations[@"intro_duration"] = self.introDuration; + } + if (self.introDurationType == RCIntroDurationTypeFreeTrial) { + durations[@"trial_duration"] = self.introDuration; + } + + return durations; +} + +- (NSString *)cacheKey { + NSString *cacheKey = [NSString stringWithFormat:@"%@-%@-%@-%@-%@-%@-%@-%@-%@", + self.productIdentifier, + self.price, + self.currencyCode, + @((NSUInteger) self.paymentMode), + self.introPrice, + self.subscriptionGroup, + self.normalDuration, + self.introDuration, + @((NSUInteger) self.introDurationType)]; + + if (@available(iOS 12.2, macOS 10.14.4, tvOS 12.2, *)) { + for (RCPromotionalOffer *discount in self.discounts) { + cacheKey = [NSString stringWithFormat:@"%@-%@", cacheKey, discount.offerIdentifier]; + } + } + + return cacheKey; +} + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Purchasing/RCProductInfoExtractor.h b/Pods/Purchases/Purchases/Purchasing/RCProductInfoExtractor.h new file mode 100644 index 0000000..97c43ff --- /dev/null +++ b/Pods/Purchases/Purchases/Purchasing/RCProductInfoExtractor.h @@ -0,0 +1,19 @@ +// +// Created by Andrés Boedo on 5/29/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import +@class SKProduct, RCProductInfo; + +NS_ASSUME_NONNULL_BEGIN + + +@interface RCProductInfoExtractor : NSObject + +- (RCProductInfo *)extractInfoFromProduct:(SKProduct *)product; + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Purchasing/RCProductInfoExtractor.m b/Pods/Purchases/Purchases/Purchasing/RCProductInfoExtractor.m new file mode 100644 index 0000000..a7e9438 --- /dev/null +++ b/Pods/Purchases/Purchases/Purchasing/RCProductInfoExtractor.m @@ -0,0 +1,148 @@ +// +// Created by Andrés Boedo on 5/29/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import "RCProductInfoExtractor.h" +#import "RCProductInfo.h" +#import "RCISOPeriodFormatter.h" +#import "RCPromotionalOffer.h" +#import "NSLocale+RCExtensions.h" +#import + +NS_ASSUME_NONNULL_BEGIN + + +@interface RCProductInfoExtractor () + +@property (nonatomic) RCISOPeriodFormatter *formatter API_AVAILABLE(ios(11.2), macos(10.13.2), tvos(11.2)); + +@end + + +@implementation RCProductInfoExtractor + +# pragma mark - public methods + +- (instancetype)init { + if (self = [super init]) { + if (@available(iOS 11.2, macOS 10.13.2, tvOS 11.2, *)) { + self.formatter = [[RCISOPeriodFormatter alloc] init]; + } + } + return self; +} + +- (RCProductInfo *)extractInfoFromProduct:(SKProduct *)product { + NSString *productIdentifier = product.productIdentifier; + NSDecimalNumber *price = product.price; + NSString *currencyCode = product.priceLocale.rc_currencyCode; + + RCPaymentMode paymentMode = [self extractPaymentModeForProduct:product]; + NSDecimalNumber *introPrice = [self extractIntroPriceForProduct:product]; + + NSString *normalDuration = [self extractNormalDurationForProduct:product]; + NSString *introDuration = [self extractIntroDurationForProduct:product]; + RCIntroDurationType introDurationType = [self extractIntroDurationTypeForProduct:product]; + + NSString *subscriptionGroup = [self extractSubscriptionGroupForProduct:product]; + NSArray *discounts = [self extractDiscountsForProduct:product]; + + RCProductInfo *productInfo = [[RCProductInfo alloc] initWithProductIdentifier:productIdentifier + paymentMode:paymentMode + currencyCode:currencyCode + price:price + normalDuration:normalDuration + introDuration:introDuration + introDurationType:introDurationType + introPrice:introPrice + subscriptionGroup:subscriptionGroup + discounts:discounts]; + + return productInfo; +} + +# pragma mark - private methods + +- (RCIntroDurationType)extractIntroDurationTypeForProduct:(SKProduct *)product { + RCIntroDurationType introDurationType = RCIntroDurationTypeNone; + + if (@available(iOS 11.2, macOS 10.13.2, tvOS 11.2, *)) { + if (product.introductoryPrice) { + introDurationType = [self isFreeTrial:product] ? RCIntroDurationTypeFreeTrial + : RCIntroDurationTypeIntroPrice; + } + } + return introDurationType; +} + +- (BOOL)isFreeTrial:(SKProduct *)product API_AVAILABLE(ios(11.2), macos(10.13.2), tvos(11.2)) { + return product.introductoryPrice.paymentMode == SKProductDiscountPaymentModeFreeTrial; +} + +- (nullable NSString *)extractSubscriptionGroupForProduct:(SKProduct *)product { + NSString *subscriptionGroup = nil; + if (@available(iOS 12.0, macOS 10.14.0, tvOS 12.0, *)) { + subscriptionGroup = product.subscriptionGroupIdentifier; + } + return subscriptionGroup; +} + +- (nullable NSArray *)extractDiscountsForProduct:(SKProduct *)product { + NSMutableArray *discounts = nil; + if (@available(iOS 12.2, macOS 10.14.4, tvOS 12.2, *)) { + discounts = [NSMutableArray new]; + for (SKProductDiscount *discount in product.discounts) { + [discounts addObject:[[RCPromotionalOffer alloc] initWithProductDiscount:discount]]; + } + } + return discounts; +} + +- (RCPaymentMode)extractPaymentModeForProduct:(SKProduct *)product { + RCPaymentMode paymentMode = RCPaymentModeNone; + if (@available(iOS 11.2, macOS 10.13.2, tvOS 11.2, *)) { + if (product.introductoryPrice) { + paymentMode = RCPaymentModeFromSKProductDiscountPaymentMode(product.introductoryPrice.paymentMode); + } + } + return paymentMode; +} + +- (nullable NSDecimalNumber *)extractIntroPriceForProduct:(SKProduct *)product { + NSDecimalNumber *introPrice = nil; + if (@available(iOS 11.2, macOS 10.13.2, tvOS 11.2, *)) { + if (product.introductoryPrice) { + introPrice = product.introductoryPrice.price; + } + } + return introPrice; +} + +- (nullable NSString *)extractNormalDurationForProduct:(SKProduct *)product { + NSString *normalDuration = nil; + if (@available(iOS 11.2, macOS 10.13.2, tvOS 11.2, *)) { + if (product.subscriptionPeriod && product.subscriptionPeriod.numberOfUnits != 0) { + normalDuration = [self.formatter stringFromProductSubscriptionPeriod:product.subscriptionPeriod]; + } + } + return normalDuration; +} + +- (nullable NSString *)extractIntroDurationForProduct:(SKProduct *)product { + NSString *introDuration = nil; + if (@available(iOS 11.2, macOS 10.13.2, tvOS 11.2, *)) { + if (product.introductoryPrice) { + SKProductSubscriptionPeriod *subscriptionPeriod = product.introductoryPrice.subscriptionPeriod; + NSString *introPriceDuration = [self.formatter stringFromProductSubscriptionPeriod:subscriptionPeriod]; + introDuration = introPriceDuration; + } + } + return introDuration; +} + +@end + + +NS_ASSUME_NONNULL_END + diff --git a/Pods/Purchases/Purchases/Purchasing/RCPromotionalOffer.h b/Pods/Purchases/Purchases/Purchasing/RCPromotionalOffer.h new file mode 100644 index 0000000..1120332 --- /dev/null +++ b/Pods/Purchases/Purchases/Purchasing/RCPromotionalOffer.h @@ -0,0 +1,26 @@ +// +// RCPromotionalOffer.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import +#import "RCBackend.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface RCPromotionalOffer : NSObject + +@property (nonatomic, readonly) NSString *offerIdentifier; + +@property (nonatomic, readonly) NSDecimalNumber *price; + +@property (nonatomic, readonly) enum RCPaymentMode paymentMode; + +- (instancetype)initWithProductDiscount:(SKProductDiscount *)productDiscount API_AVAILABLE(ios(12.2), macos(10.14.4)); + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Purchasing/RCPromotionalOffer.m b/Pods/Purchases/Purchases/Purchasing/RCPromotionalOffer.m new file mode 100644 index 0000000..131b0ff --- /dev/null +++ b/Pods/Purchases/Purchases/Purchasing/RCPromotionalOffer.m @@ -0,0 +1,31 @@ +// +// RCPromotionalOffer.m +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import "RCPromotionalOffer.h" +#import "RCBackend.h" + + +@interface RCPromotionalOffer () +@property (nonatomic, readwrite) NSString *offerIdentifier; +@property (readwrite) NSDecimalNumber *price; +@property (readwrite) RCPaymentMode paymentMode; +@end + +@implementation RCPromotionalOffer + +- (instancetype)initWithProductDiscount:(SKProductDiscount *)productDiscount +API_AVAILABLE(ios(12.2), macos(10.14.4)) { + if (self = [super init]) { + self.offerIdentifier = productDiscount.identifier; + self.price = productDiscount.price; + self.paymentMode = RCPaymentModeFromSKProductDiscountPaymentMode(productDiscount.paymentMode); + } + return self; +} + +@end diff --git a/Pods/Purchases/Purchases/Purchasing/RCReceiptFetcher.h b/Pods/Purchases/Purchases/Purchasing/RCReceiptFetcher.h new file mode 100644 index 0000000..a63635f --- /dev/null +++ b/Pods/Purchases/Purchases/Purchasing/RCReceiptFetcher.h @@ -0,0 +1,16 @@ +// +// RCReceiptFetcher.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import +#import + +@interface RCReceiptFetcher : NSObject + +- (NSData *)receiptData; + +@end diff --git a/Pods/Purchases/Purchases/Purchasing/RCReceiptFetcher.m b/Pods/Purchases/Purchases/Purchasing/RCReceiptFetcher.m new file mode 100644 index 0000000..c37cbf5 --- /dev/null +++ b/Pods/Purchases/Purchases/Purchasing/RCReceiptFetcher.m @@ -0,0 +1,40 @@ +// +// RCReceiptFetcher.m +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import "RCReceiptFetcher.h" +#import "RCLogUtils.h" +#import "RCSystemInfo.h" + +@implementation RCReceiptFetcher : NSObject + +- (NSData *)receiptData { + NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; + +#if TARGET_OS_WATCH + // as of watchOS 6.2.8, there's a bug where the receipt is stored in the sandbox receipt location, + // but the appStoreReceiptURL method returns the URL for the production receipt. + // This code replaces "sandboxReceipt" with "receipt" as the last component of the receiptURL so that we get the + // correct receipt. + // This has been filed as radar FB7699277. More info in https://github.com/RevenueCat/purchases-ios/issues/207. + + NSOperatingSystemVersion minimumOSVersionWithoutBug = { .majorVersion = 7, .minorVersion = 0, .patchVersion = 0 }; + BOOL isBelowMinimumOSVersionWithoutBug = ![NSProcessInfo.processInfo isOperatingSystemAtLeastVersion:minimumOSVersionWithoutBug]; + if (isBelowMinimumOSVersionWithoutBug && RCSystemInfo.isSandbox) { + NSString *receiptURLFolder = [[receiptURL absoluteString] stringByDeletingLastPathComponent]; + NSURL *productionReceiptURL = [NSURL URLWithString:[receiptURLFolder stringByAppendingPathComponent:@"receipt"]]; + receiptURL = productionReceiptURL; + } +#endif + + NSData *data = [NSData dataWithContentsOfURL:receiptURL]; + RCDebugLog(@"Loaded receipt from %@", receiptURL); + return data; +} + +@end + diff --git a/Pods/Purchases/Purchases/Purchasing/RCReceiptRefreshPolicy.h b/Pods/Purchases/Purchases/Purchasing/RCReceiptRefreshPolicy.h new file mode 100644 index 0000000..9fee5d4 --- /dev/null +++ b/Pods/Purchases/Purchases/Purchasing/RCReceiptRefreshPolicy.h @@ -0,0 +1,17 @@ +// +// Created by Andrés Boedo on 11/12/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, RCReceiptRefreshPolicy) { + RCReceiptRefreshPolicyAlways = 0, + RCReceiptRefreshPolicyOnlyIfEmpty, + RCReceiptRefreshPolicyNever +}; + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Purchasing/RCStoreKitRequestFetcher.h b/Pods/Purchases/Purchases/Purchasing/RCStoreKitRequestFetcher.h new file mode 100644 index 0000000..9efb0b1 --- /dev/null +++ b/Pods/Purchases/Purchases/Purchasing/RCStoreKitRequestFetcher.h @@ -0,0 +1,36 @@ +// +// RCProductFetcher.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef void(^RCFetchProductsCompletionHandler)(NSArray *products); + +typedef void(^RCFetchReceiptCompletionHandler)(void); + +@class SKProduct, SKProductsRequest; + +@interface RCProductsRequestFactory : NSObject +- (SKProductsRequest *)requestForProductIdentifiers:(NSSet *)identifiers; +- (SKReceiptRefreshRequest *)receiptRefreshRequest; +@end + +@interface RCStoreKitRequestFetcher : NSObject + +- (nullable instancetype)initWithRequestFactory:(RCProductsRequestFactory *)requestFactory; + +- (void)fetchProducts:(NSSet *)identifiers + completion:(RCFetchProductsCompletionHandler)completion; + +- (void)fetchReceiptData:(RCFetchReceiptCompletionHandler)completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Purchasing/RCStoreKitRequestFetcher.m b/Pods/Purchases/Purchases/Purchasing/RCStoreKitRequestFetcher.m new file mode 100644 index 0000000..7f1957b --- /dev/null +++ b/Pods/Purchases/Purchases/Purchasing/RCStoreKitRequestFetcher.m @@ -0,0 +1,174 @@ +// +// RCProductFetcher.m +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import +#import "RCStoreKitRequestFetcher.h" +#import "RCLogUtils.h" + +@implementation RCProductsRequestFactory : NSObject +- (SKProductsRequest *)requestForProductIdentifiers:(NSSet *)identifiers +{ + return [[SKProductsRequest alloc] initWithProductIdentifiers:identifiers]; +} + +- (SKReceiptRefreshRequest *)receiptRefreshRequest +{ + return [[SKReceiptRefreshRequest alloc] init]; +} + +@end + +@interface RCStoreKitRequestFetcher () +@property (nonatomic) RCProductsRequestFactory *requestFactory; + +@property (nonatomic) NSMutableDictionary *productsRequests; +@property (nonatomic) NSMutableDictionary *> *productsCompletionHandlers; + +@property (nonatomic) SKRequest *receiptRefreshRequest; +@property (nonatomic) NSMutableArray *receiptRefreshCompletionHandlers; + +@end + +@implementation RCStoreKitRequestFetcher + +- (nullable instancetype)init { + return [self initWithRequestFactory:[RCProductsRequestFactory new]]; +} + +- (nullable instancetype)initWithRequestFactory:(RCProductsRequestFactory *)requestFactory; +{ + if (self = [super init]) { + self.requestFactory = requestFactory; + self.productsRequests = [NSMutableDictionary new]; + self.productsCompletionHandlers = [NSMutableDictionary new]; + + self.receiptRefreshRequest = nil; + self.receiptRefreshCompletionHandlers = [NSMutableArray new]; + } + return self; +} + +- (void)fetchProducts:(NSSet *)identifiers + completion:(RCFetchProductsCompletionHandler)completion; +{ + + @synchronized(self) { + SKProductsRequest *newRequest = nil; + + if (self.productsRequests[identifiers] == nil) { + RCDebugLog(@"Requesting products with identifiers: %@", identifiers); + newRequest = [self.requestFactory requestForProductIdentifiers:identifiers]; + newRequest.delegate = self; + + self.productsRequests[identifiers] = newRequest; + self.productsCompletionHandlers[identifiers] = [NSMutableArray new]; + } + + NSMutableArray *handlers = self.productsCompletionHandlers[identifiers]; + NSAssert(handlers != nil, @"Corrupted handler storage"); + + [handlers addObject:completion]; + + + [newRequest start]; + } + + NSAssert(self.productsRequests.count == self.productsCompletionHandlers.count, @"Corrupted handler storage"); +} + +- (void)fetchReceiptData:(void (^ _Nonnull)(void))completion +{ + @synchronized(self) { + [self.receiptRefreshCompletionHandlers addObject:[completion copy]]; + + if (self.receiptRefreshRequest == nil) { + self.receiptRefreshRequest = [self.requestFactory receiptRefreshRequest]; + self.receiptRefreshRequest.delegate = self; + [self.receiptRefreshRequest start]; + } + } +} + +- (NSArray *)finishProductsRequest:(SKRequest *)request +{ + NSMutableArray *handlers; + @synchronized(self) { + NSSet *associatedProductIdentifiers = nil; + for (NSSet *productIdentifiers in self.productsRequests) { + SKRequest *r = self.productsRequests[productIdentifiers]; + if (r == request) { + NSAssert(associatedProductIdentifiers == nil, @"Request maps to multiple product sets"); + associatedProductIdentifiers = productIdentifiers; + } + } + NSAssert(associatedProductIdentifiers != nil, @"Could not find request in storage"); + + handlers = self.productsCompletionHandlers[associatedProductIdentifiers]; + [self.productsRequests removeObjectForKey:associatedProductIdentifiers]; + [self.productsCompletionHandlers removeObjectForKey:associatedProductIdentifiers]; + } + NSAssert(self.productsRequests.count == self.productsCompletionHandlers.count, @"Corrupted handler storage"); + return handlers; +} + +- (NSArray *)finishReceiptRequest:(SKRequest *)request +{ + @synchronized(self) { + self.receiptRefreshRequest = nil; + NSArray *handlers = [NSArray arrayWithArray:self.receiptRefreshCompletionHandlers]; + self.receiptRefreshCompletionHandlers = [NSMutableArray new]; + return handlers; + } +} + +- (void)requestDidFinish:(SKRequest *)request +{ + if ([request isKindOfClass:SKReceiptRefreshRequest.class]) { + NSArray *receiptHandlers = [self finishReceiptRequest:request]; + for (RCFetchReceiptCompletionHandler receiptHandler in receiptHandlers) { + receiptHandler(); + } + } +} + +- (void)request:(SKRequest *)request didFailWithError:(NSError *)error +{ + RCDebugLog(@"SKRequest failed: %@", error.localizedDescription); + if ([request isKindOfClass:SKReceiptRefreshRequest.class]) { + NSArray *receiptHandlers = [self finishReceiptRequest:request]; + for (RCFetchReceiptCompletionHandler receiptHandler in receiptHandlers) { + receiptHandler(); + } + } else if ([request isKindOfClass:SKProductsRequest.class]) { + NSArray *productsHandlers = [self finishProductsRequest:request]; + for (RCFetchProductsCompletionHandler handler in productsHandlers) + { + handler(@[]); + } + } +} + +- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response +{ + RCDebugLog(@"Products request finished"); + RCDebugLog(@"Valid Products:"); + for (SKProduct *p in response.products) + { + RCDebugLog(@"%@ - %@", p.productIdentifier, p); + } + RCDebugLog(@"Invalid Product Identifiers - %@", response.invalidProductIdentifiers); + + NSArray *handlers = [self finishProductsRequest:request]; + RCDebugLog(@"%d completion handlers waiting on products", handlers.count); + for (RCFetchProductsCompletionHandler handler in handlers) + { + handler(response.products); + } +} + +@end diff --git a/Pods/Purchases/Purchases/Purchasing/RCStoreKitWrapper.h b/Pods/Purchases/Purchases/Purchasing/RCStoreKitWrapper.h new file mode 100644 index 0000000..aa6c02d --- /dev/null +++ b/Pods/Purchases/Purchases/Purchasing/RCStoreKitWrapper.h @@ -0,0 +1,42 @@ +// +// RCStoreKitWrapper.h +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol RCStoreKitWrapperDelegate; + +@interface RCStoreKitWrapper : NSObject + +- (nullable instancetype)initWithPaymentQueue:(SKPaymentQueue *)paymentQueue; + +@property (nonatomic, weak, nullable) id delegate; + +- (void)addPayment:(SKPayment *)payment; +- (void)finishTransaction:(SKPaymentTransaction *)transaction; +- (void)presentCodeRedemptionSheet API_AVAILABLE(ios(14.0)) API_UNAVAILABLE(tvos, macos, watchos); + +@end + +@protocol RCStoreKitWrapperDelegate + +- (void)storeKitWrapper:(RCStoreKitWrapper *)storeKitWrapper + updatedTransaction:(SKPaymentTransaction *)transaction; + +- (void)storeKitWrapper:(RCStoreKitWrapper *)storeKitWrapper + removedTransaction:(SKPaymentTransaction *)transaction; + +- (BOOL)storeKitWrapper:(RCStoreKitWrapper *)storeKitWrapper + shouldAddStorePayment:(SKPayment *)payment + forProduct:(SKProduct *)product; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/Purchasing/RCStoreKitWrapper.m b/Pods/Purchases/Purchases/Purchasing/RCStoreKitWrapper.m new file mode 100644 index 0000000..85e8415 --- /dev/null +++ b/Pods/Purchases/Purchases/Purchasing/RCStoreKitWrapper.m @@ -0,0 +1,101 @@ +// +// RCStoreKitWrapper.m +// Purchases +// +// Created by RevenueCat. +// Copyright © 2019 RevenueCat. All rights reserved. +// + +#import "RCStoreKitWrapper.h" +#import "RCCrossPlatformSupport.h" + +#import "RCLogUtils.h" + +@interface RCStoreKitWrapper () +@property (nonatomic) SKPaymentQueue *paymentQueue; +@end + +@implementation RCStoreKitWrapper + +@synthesize delegate = _delegate; + +- (instancetype)init +{ + return [self initWithPaymentQueue:SKPaymentQueue.defaultQueue]; +} + +- (nullable instancetype)initWithPaymentQueue:(SKPaymentQueue *)paymentQueue +{ + if (self = [super init]) { + self.paymentQueue = paymentQueue; + } + return self; +} + +- (void)dealloc +{ + [self.paymentQueue removeTransactionObserver:self]; +} + +- (void)setDelegate:(id)delegate +{ + _delegate = delegate; + + if (_delegate != nil) { + [self.paymentQueue addTransactionObserver:self]; + } else { + [self.paymentQueue removeTransactionObserver:self]; + } +} + +- (id)delegate +{ + return _delegate; +} + +- (void)addPayment:(SKPayment *)payment +{ + [self.paymentQueue addPayment:payment]; +} + +- (void)finishTransaction:(SKPaymentTransaction *)transaction +{ + RCDebugLog(@"Finishing %@ %@ (%@)", transaction.payment.productIdentifier, + transaction.transactionIdentifier, transaction.originalTransaction.transactionIdentifier); + + [self.paymentQueue finishTransaction:transaction]; +} + +- (void)presentCodeRedemptionSheet API_AVAILABLE(ios(14.0)) API_UNAVAILABLE(tvos, macos, watchos) { + if (@available(iOS 14.0, *)) { + [self.paymentQueue presentCodeRedemptionSheet]; + } else { + RCLog(@"Attempted to present code redemption sheet, but it's not available on this device."); + } +} + +- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions +{ + for (SKPaymentTransaction *transaction in transactions) { + RCDebugLog(@"PaymentQueue updatedTransaction: %@ %@ (%@) %@ - %d", transaction.payment.productIdentifier, transaction.transactionIdentifier, transaction.error, transaction.originalTransaction.transactionIdentifier, transaction.transactionState); + [self.delegate storeKitWrapper:self updatedTransaction:transaction]; + } +} + +// Sent when transactions are removed from the queue (via finishTransaction:). +- (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions +{ + for (SKPaymentTransaction *transaction in transactions) { + RCDebugLog(@"PaymentQueue removedTransaction: %@ %@ (%@ %@) %@ - %d", transaction.payment.productIdentifier, transaction.transactionIdentifier, transaction.originalTransaction.transactionIdentifier, transaction.error, transaction.error.userInfo, transaction.transactionState); + [self.delegate storeKitWrapper:self removedTransaction:transaction]; + } +} + +#if PURCHASES_INITIATED_FROM_APP_STORE_AVAILABLE +- (BOOL)paymentQueue:(SKPaymentQueue *)queue shouldAddStorePayment:(SKPayment *)payment forProduct:(SKProduct *)product +{ + return [self.delegate storeKitWrapper:self shouldAddStorePayment:payment forProduct:product]; +} +#endif + +@end diff --git a/Pods/Purchases/Purchases/SubscriberAttributes/RCPurchases+SubscriberAttributes.h b/Pods/Purchases/Purchases/SubscriberAttributes/RCPurchases+SubscriberAttributes.h new file mode 100644 index 0000000..1fd6c62 --- /dev/null +++ b/Pods/Purchases/Purchases/SubscriberAttributes/RCPurchases+SubscriberAttributes.h @@ -0,0 +1,33 @@ +// +// Created by Andrés Boedo on 2/21/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import +#import "RCPurchases.h" +#import "RCSubscriberAttribute.h" + +@class RCSubscriberAttribute, RCSubscriberAttributesManager, RCOperationDispatcher; + +NS_ASSUME_NONNULL_BEGIN + + +@interface RCPurchases (SubscriberAttributes) + +- (RCSubscriberAttributeDict)unsyncedAttributesByKey; +- (void)markAttributesAsSyncedIfNeeded:(nullable RCSubscriberAttributeDict)syncedAttributes + appUserID:(NSString *)appUserID + error:(nullable NSError *)error; +- (void)syncSubscriberAttributesIfNeeded; + +@end + +@interface RCPurchases () + +@property (nonatomic) RCSubscriberAttributesManager *subscriberAttributesManager; +@property (nonatomic) RCOperationDispatcher *operationDispatcher; + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/SubscriberAttributes/RCPurchases+SubscriberAttributes.m b/Pods/Purchases/Purchases/SubscriberAttributes/RCPurchases+SubscriberAttributes.m new file mode 100644 index 0000000..373416b --- /dev/null +++ b/Pods/Purchases/Purchases/SubscriberAttributes/RCPurchases+SubscriberAttributes.m @@ -0,0 +1,57 @@ +// +// Created by Andrés Boedo on 2/21/20. +// + +#import "RCPurchases.h" +#import "RCPurchases+Protected.h" +#import "RCPurchases+SubscriberAttributes.h" +#import "RCSubscriberAttributesManager.h" +#import "RCCrossPlatformSupport.h" +#import "RCLogUtils.h" +#import "NSError+RCExtensions.h" +#import "RCOffering.h" +#import "RCOfferings.h" +@import PurchasesCoreSwift; + +NS_ASSUME_NONNULL_BEGIN + + +@implementation RCPurchases (SubscriberAttributes) + +#pragma mark protected methods + +- (RCSubscriberAttributeDict)unsyncedAttributesByKey { + NSString *appUserID = self.appUserID; + RCSubscriberAttributeDict unsyncedAttributes = [self.subscriberAttributesManager + unsyncedAttributesByKeyForAppUserID:appUserID]; + RCLog(@"found %lu unsynced attributes for appUserID: %@", unsyncedAttributes.count, appUserID); + if (unsyncedAttributes.count > 0) { + RCLog(@"unsynced attributes: %@", unsyncedAttributes); + } + + return unsyncedAttributes; +} + +- (void)markAttributesAsSyncedIfNeeded:(nullable RCSubscriberAttributeDict)syncedAttributes + appUserID:(NSString *)appUserID + error:(nullable NSError *)error { + if (error && !error.successfullySynced) { + return; + } + + if (error.subscriberAttributesErrors) { + RCLog(@"Subscriber attributes errors: %@", error.subscriberAttributesErrors); + } + [self.subscriberAttributesManager markAttributesAsSynced:syncedAttributes appUserID:appUserID]; +} + +- (void)syncSubscriberAttributesIfNeeded { + [self.operationDispatcher dispatchOnWorkerThreadWithRandomDelay:NO block:^{ + [self.subscriberAttributesManager syncAttributesForAllUsersWithCurrentAppUserID:self.appUserID]; + }]; +} + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/SubscriberAttributes/RCSpecialSubscriberAttributes.h b/Pods/Purchases/Purchases/SubscriberAttributes/RCSpecialSubscriberAttributes.h new file mode 100644 index 0000000..8ed0abc --- /dev/null +++ b/Pods/Purchases/Purchases/SubscriberAttributes/RCSpecialSubscriberAttributes.h @@ -0,0 +1,32 @@ +// +// Created by RevenueCat on 2/7/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +#define SPECIAL_ATTRIBUTE_EMAIL @"$email" +#define SPECIAL_ATTRIBUTE_PHONE_NUMBER @"$phoneNumber" +#define SPECIAL_ATTRIBUTE_DISPLAY_NAME @"$displayName" +#define SPECIAL_ATTRIBUTE_PUSH_TOKEN @"$apnsTokens" + +#define SPECIAL_ATTRIBUTE_IDFA @"$idfa" +#define SPECIAL_ATTRIBUTE_IDFV @"$idfv" +#define SPECIAL_ATTRIBUTE_IP @"$ip" + +#define SPECIAL_ATTRIBUTE_ADJUST_ID @"$adjustId" +#define SPECIAL_ATTRIBUTE_APPSFLYER_ID @"$appsflyerId" +#define SPECIAL_ATTRIBUTE_FB_ANON_ID @"$fbAnonId" +#define SPECIAL_ATTRIBUTE_MPARTICLE_ID @"$mparticleId" +#define SPECIAL_ATTRIBUTE_ONESIGNAL_ID @"$onesignalId" + +#define SPECIAL_ATTRIBUTE_MEDIA_SOURCE @"$mediaSource" +#define SPECIAL_ATTRIBUTE_CAMPAIGN @"$campaign" +#define SPECIAL_ATTRIBUTE_AD_GROUP @"$adGroup" +#define SPECIAL_ATTRIBUTE_AD @"$ad" +#define SPECIAL_ATTRIBUTE_KEYWORD @"$keyword" +#define SPECIAL_ATTRIBUTE_CREATIVE @"$creative" + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/SubscriberAttributes/RCSubscriberAttribute.h b/Pods/Purchases/Purchases/SubscriberAttributes/RCSubscriberAttribute.h new file mode 100644 index 0000000..d7e62c7 --- /dev/null +++ b/Pods/Purchases/Purchases/SubscriberAttributes/RCSubscriberAttribute.h @@ -0,0 +1,35 @@ +// +// Created by RevenueCat on 2/17/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + + +@interface RCSubscriberAttribute : NSObject + +@property (nonatomic, copy, readonly) NSString *key; +@property (nonatomic, copy, readonly) NSString *value; +@property (nonatomic, readonly) NSDate *setTime; +@property (nonatomic, assign) BOOL isSynced; + +- (instancetype)initWithKey:(NSString *)key value:(NSString *)value; + +- (instancetype)initWithDictionary:(NSDictionary *)dict; + +- (instancetype)init NS_UNAVAILABLE; + +- (NSDictionary *)asDictionary; + +- (NSDictionary *)asBackendDictionary; + +- (BOOL)isEqual:(nullable RCSubscriberAttribute *)attribute; + +@end + +typedef NSMutableDictionary *RCSubscriberAttributeMutableDict; +typedef NSDictionary *RCSubscriberAttributeDict; + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/SubscriberAttributes/RCSubscriberAttribute.m b/Pods/Purchases/Purchases/SubscriberAttributes/RCSubscriberAttribute.m new file mode 100644 index 0000000..e15ba6b --- /dev/null +++ b/Pods/Purchases/Purchases/SubscriberAttributes/RCSubscriberAttribute.m @@ -0,0 +1,108 @@ +// +// Created by RevenueCat on 2/17/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import +#import "RCSubscriberAttribute.h" +#import "RCDateProvider.h" +#import "NSDate+RCExtensions.h" + +NS_ASSUME_NONNULL_BEGIN + +#define KEY_KEY @"key" +#define VALUE_KEY @"value" +#define SET_TIME_KEY @"setTime" +#define IS_SYNCED_KEY @"isSynced" + +#define BACKEND_VALUE_KEY @"value" +#define BACKEND_TIMESTAMP_KEY @"updated_at_ms" + + +@interface RCSubscriberAttribute () + +@property (nonatomic, copy) NSString *key; +@property (nonatomic, copy) NSString *value; +@property (nonatomic) NSDate *setTime; + +@end + + +NS_ASSUME_NONNULL_END + + +@implementation RCSubscriberAttribute + +- (instancetype)initWithKey:(NSString *)key value:(NSString *)value { + return [self initWithKey:key + value:value + dateProvider:[[RCDateProvider alloc] init]]; +} + +- (instancetype)initWithKey:(NSString *)key + value:(NSString *)value + isSynced:(BOOL)isSynced + setTime:(NSDate *)setTime { + if (self = [super init]) { + self.key = key; + self.value = value; + self.isSynced = isSynced; + self.setTime = setTime; + } + return self; +} + +- (instancetype)initWithKey:(NSString *)key + value:(NSString *)value + dateProvider:(RCDateProvider *)dateProvider { + return [self initWithKey:key + value:value + isSynced:NO + setTime:dateProvider.now]; +} + +- (instancetype)initWithDictionary:(NSDictionary *)dict { + return [self initWithKey:(NSString *) dict[KEY_KEY] + value:(NSString *) dict[VALUE_KEY] + isSynced:((NSNumber *) dict[IS_SYNCED_KEY]).boolValue + setTime:(NSDate *) dict[SET_TIME_KEY]]; +} + +- (NSDictionary *)asDictionary { + return @{ + KEY_KEY: self.key, + VALUE_KEY: self.value ?: @"", + IS_SYNCED_KEY: @(self.isSynced), + SET_TIME_KEY: self.setTime, + }; +} + +- (NSDictionary *)asBackendDictionary { + return @{ + BACKEND_VALUE_KEY: self.value ?: @"", + BACKEND_TIMESTAMP_KEY: @(self.setTime.millisecondsSince1970) + }; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"Subscriber attribute: key: %@ value: %@ setTime: %@", + self.key, self.value, self.setTime]; +} + +- (BOOL)isEqual:(nullable RCSubscriberAttribute *)attribute { + if (self == attribute) + return YES; + if (attribute == nil) + return NO; + if (self.key != attribute.key) + return NO; + if (self.value != attribute.value) + return NO; + if (self.setTime != attribute.setTime && ![self.setTime isEqualToDate:attribute.setTime]) + return NO; + if (self.isSynced != attribute.isSynced) + return NO; + return YES; +} + +@end diff --git a/Pods/Purchases/Purchases/SubscriberAttributes/RCSubscriberAttributesManager.h b/Pods/Purchases/Purchases/SubscriberAttributes/RCSubscriberAttributesManager.h new file mode 100644 index 0000000..2f88bed --- /dev/null +++ b/Pods/Purchases/Purchases/SubscriberAttributes/RCSubscriberAttributesManager.h @@ -0,0 +1,69 @@ +// +// Created by RevenueCat on 2/7/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import +#import "RCSubscriberAttribute.h" + +NS_ASSUME_NONNULL_BEGIN + +@class RCBackend; +@class RCDeviceCache; +@class RCSubscriberAttribute; +@class RCAttributionFetcher; + + +@interface RCSubscriberAttributesManager : NSObject + +- (instancetype)initWithBackend:(nullable RCBackend *)backend + deviceCache:(nullable RCDeviceCache *)deviceCache + attributionFetcher:(nullable RCAttributionFetcher *)attributionFetcher; + +- (void)setAttributes:(NSDictionary *)attributes appUserID:(NSString *)appUserID; + +- (void)setEmail:(nullable NSString *)email appUserID:(NSString *)appUserID; + +- (void)setPhoneNumber:(nullable NSString *)phoneNumber appUserID:(NSString *)appUserID; + +- (void)setDisplayName:(nullable NSString *)displayName appUserID:(NSString *)appUserID; + +- (void)setPushToken:(nullable NSData *)pushToken appUserID:(NSString *)appUserID; + +- (void)setPushTokenString:(nullable NSString *)pushToken appUserID:(NSString *)appUserID; + +- (void)setAdjustID:(nullable NSString *)adjustID appUserID:(NSString *)appUserID; + +- (void)setAppsflyerID:(nullable NSString *)appsflyerID appUserID:(NSString *)appUserID; + +- (void)setFBAnonymousID:(nullable NSString *)fbAnonymousID appUserID:(NSString *)appUserID; + +- (void)setMparticleID:(nullable NSString *)mparticleID appUserID:(NSString *)appUserID; + +- (void)setOnesignalID:(nullable NSString *)onesignalID appUserID:(NSString *)appUserID; + +- (void)setMediaSource:(nullable NSString *)mediaSource appUserID:(NSString *)appUserID; + +- (void)setCampaign:(nullable NSString *)campaign appUserID:(NSString *)appUserID; + +- (void)setAdGroup:(nullable NSString *)adGroup appUserID:(NSString *)appUserID; + +- (void)setAd:(nullable NSString *)ad appUserID:(NSString *)appUserID; + +- (void)setKeyword:(nullable NSString *)keyword appUserID:(NSString *)appUserID; + +- (void)setCreative:(nullable NSString *)creative appUserID:(NSString *)appUserID; + +- (void)syncAttributesForAllUsersWithCurrentAppUserID:(NSString *)currentAppUserID; + +- (RCSubscriberAttributeDict)unsyncedAttributesByKeyForAppUserID:(NSString *)appUserID; + +- (void)markAttributesAsSynced:(RCSubscriberAttributeDict)syncedAttributes + appUserID:(NSString *)appUserID; + +- (void)collectDeviceIdentifiersForAppUserID:(NSString *)appUserID; + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/Purchases/SubscriberAttributes/RCSubscriberAttributesManager.m b/Pods/Purchases/Purchases/SubscriberAttributes/RCSubscriberAttributesManager.m new file mode 100644 index 0000000..ffdb9a7 --- /dev/null +++ b/Pods/Purchases/Purchases/SubscriberAttributes/RCSubscriberAttributesManager.m @@ -0,0 +1,234 @@ +// +// Created by RevenueCat on 2/7/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +#import "RCSubscriberAttributesManager.h" +#import "RCSpecialSubscriberAttributes.h" +#import "RCBackend.h" +#import "RCDeviceCache.h" +#import "NSError+RCExtensions.h" +#import "NSData+RCExtensions.h" +#import "RCLogUtils.h" +#import "RCAttributionFetcher.h" + +NS_ASSUME_NONNULL_BEGIN + + +@interface RCSubscriberAttributesManager () + +@property (nonatomic) RCDeviceCache *deviceCache; +@property (nonatomic) RCBackend *backend; +@property (nonatomic) RCAttributionFetcher *attributionFetcher; + +@end + + +@implementation RCSubscriberAttributesManager + +#pragma MARK - Public methods + +- (instancetype)initWithBackend:(nullable RCBackend *)backend + deviceCache:(nullable RCDeviceCache *)deviceCache + attributionFetcher:(nullable RCAttributionFetcher *)attributionFetcher{ + if (self = [super init]) { + NSParameterAssert(backend); + NSParameterAssert(deviceCache); + NSParameterAssert(attributionFetcher); + self.backend = backend; + self.deviceCache = deviceCache; + self.attributionFetcher = attributionFetcher; + } + return self; +} + +- (void)setAttributes:(NSDictionary *)attributes appUserID:(NSString *)appUserID { + [attributes enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) { + [self setAttributeWithKey:key value:value appUserID:appUserID]; + }]; +} + +- (void)setEmail:(nullable NSString *)email appUserID:(NSString *)appUserID { + [self setAttributeWithKey:SPECIAL_ATTRIBUTE_EMAIL value:email appUserID:appUserID]; +} + +- (void)setPhoneNumber:(nullable NSString *)phoneNumber appUserID:(NSString *)appUserID { + [self setAttributeWithKey:SPECIAL_ATTRIBUTE_PHONE_NUMBER value:phoneNumber appUserID:appUserID]; +} + +- (void)setDisplayName:(nullable NSString *)displayName appUserID:(NSString *)appUserID { + [self setAttributeWithKey:SPECIAL_ATTRIBUTE_DISPLAY_NAME value:displayName appUserID:appUserID]; +} + +- (void)setPushToken:(nullable NSData *)pushToken appUserID:(NSString *)appUserID { + NSString *pushTokenString = pushToken ? pushToken.asString : nil; + [self setPushTokenString:pushTokenString appUserID:appUserID]; +} + +- (void)setPushTokenString:(nullable NSString *)pushTokenString appUserID:(NSString *)appUserID { + [self setAttributeWithKey:SPECIAL_ATTRIBUTE_PUSH_TOKEN value:pushTokenString appUserID:appUserID]; +} + +- (void)setAdjustID:(nullable NSString *)adjustID appUserID:(NSString *)appUserID { + [self setAttributionID:adjustID networkKey:SPECIAL_ATTRIBUTE_ADJUST_ID appUserID:appUserID]; +} + +- (void)setAppsflyerID:(nullable NSString *)appsflyerID appUserID:(NSString *)appUserID { + [self setAttributionID:appsflyerID networkKey:SPECIAL_ATTRIBUTE_APPSFLYER_ID appUserID:appUserID]; +} + +- (void)setFBAnonymousID:(nullable NSString *)fbAnonymousID appUserID:(NSString *)appUserID { + [self setAttributionID:fbAnonymousID networkKey:SPECIAL_ATTRIBUTE_FB_ANON_ID appUserID:appUserID]; +} + +- (void)setMparticleID:(nullable NSString *)mparticleID appUserID:(NSString *)appUserID { + [self setAttributionID:mparticleID networkKey:SPECIAL_ATTRIBUTE_MPARTICLE_ID appUserID:appUserID]; +} + +- (void)setOnesignalID:(nullable NSString *)onesignalID appUserID:(NSString *)appUserID { + [self setAttributionID:onesignalID networkKey:SPECIAL_ATTRIBUTE_ONESIGNAL_ID appUserID:appUserID]; +} + +- (void)setMediaSource:(nullable NSString *)mediaSource appUserID:(NSString *)appUserID { + [self setAttributeWithKey:SPECIAL_ATTRIBUTE_MEDIA_SOURCE value:mediaSource appUserID:appUserID]; +} + +- (void)setCampaign:(nullable NSString *)campaign appUserID:(NSString *)appUserID { + [self setAttributeWithKey:SPECIAL_ATTRIBUTE_CAMPAIGN value:campaign appUserID:appUserID]; +} + +- (void)setAdGroup:(nullable NSString *)adGroup appUserID:(NSString *)appUserID { + [self setAttributeWithKey:SPECIAL_ATTRIBUTE_AD_GROUP value:adGroup appUserID:appUserID]; +} + +- (void)setAd:(nullable NSString *)ad appUserID:(NSString *)appUserID { + [self setAttributeWithKey:SPECIAL_ATTRIBUTE_AD value:ad appUserID:appUserID]; +} + +- (void)setKeyword:(nullable NSString *)keyword appUserID:(NSString *)appUserID { + [self setAttributeWithKey:SPECIAL_ATTRIBUTE_KEYWORD value:keyword appUserID:appUserID]; +} + +- (void)setCreative:(nullable NSString *)creative appUserID:(NSString *)appUserID { + [self setAttributeWithKey:SPECIAL_ATTRIBUTE_CREATIVE value:creative appUserID:appUserID]; +} + +- (void)collectDeviceIdentifiersForAppUserID:(NSString *)appUserID { + NSString *identifierForAdvertisers = [self.attributionFetcher identifierForAdvertisers]; + NSString *identifierForVendor = [self.attributionFetcher identifierForVendor]; + [self setAttributeWithKey:SPECIAL_ATTRIBUTE_IDFA value:identifierForAdvertisers appUserID:appUserID]; + [self setAttributeWithKey:SPECIAL_ATTRIBUTE_IDFV value:identifierForVendor appUserID:appUserID]; + [self setAttributeWithKey:SPECIAL_ATTRIBUTE_IP value:@"true" appUserID:appUserID]; +} + +- (void)syncAttributesForAllUsersWithCurrentAppUserID:(NSString *)currentAppUserID { + NSDictionary *unsyncedAttributesForAllUsers = + [self unsyncedAttributesByKeyForAllUsers]; + + for (NSString *syncingAppUserID in unsyncedAttributesForAllUsers.allKeys) { + [self syncAttributes:unsyncedAttributesForAllUsers[syncingAppUserID] + forAppUserID:syncingAppUserID + completion:^(NSError *error) { + [self handleAttributesSyncedForAppUserID:syncingAppUserID + currentAppUserID:currentAppUserID + error:error]; + }]; + } +} + +- (void)handleAttributesSyncedForAppUserID:(NSString *)syncingAppUserID + currentAppUserID:(NSString *)currentAppUserID + error:(NSError *)error { + if (error == nil) { + RCLog(@"Subscriber attributes synced successfully for appUserID: %@", syncingAppUserID); + if (![syncingAppUserID isEqualToString:currentAppUserID]) { + [self.deviceCache deleteAttributesIfSyncedForAppUserID:syncingAppUserID]; + } + } else { + RCErrorLog(@"error when syncing subscriber attributes. Details: %@\n UserInfo:%@", + error.localizedDescription, + error.userInfo); + } +} + +- (RCSubscriberAttributeDict)unsyncedAttributesByKeyForAppUserID:(NSString *)appUserID { + return [self.deviceCache unsyncedAttributesByKeyForAppUserID:appUserID]; +} + +- (NSDictionary *)unsyncedAttributesByKeyForAllUsers { + return [self.deviceCache unsyncedAttributesForAllUsers]; +} + +#pragma MARK - Private methods + +- (void)setAttributeWithKey:(NSString *)key value:(nullable NSString *)value appUserID:(NSString *)appUserID { + [self storeAttributeLocallyIfNeededWithKey:key value:value appUserID:appUserID]; +} + +- (void)syncAttributes:(RCSubscriberAttributeDict)attributes + forAppUserID:(NSString *)appUserID + completion:(void (^)(NSError *))completion { + [self.backend postSubscriberAttributes:attributes appUserID:appUserID completion:^(NSError *error) { + BOOL didBackendReceiveValues = (error == nil || error.successfullySynced); + + if (didBackendReceiveValues) { + [self markAttributesAsSynced:attributes appUserID:appUserID]; + } + completion(error); + }]; +} + +- (void)markAttributesAsSynced:(RCSubscriberAttributeDict)syncedAttributes + appUserID:(NSString *)appUserID { + if (syncedAttributes == nil || syncedAttributes.count == 0) { + return; + } + + RCLog(@"marking the following attributes as synced for appUserID: %@: %@", appUserID, syncedAttributes); + @synchronized (self) { + RCSubscriberAttributeMutableDict + unsyncedAttributes = [self unsyncedAttributesByKeyForAppUserID:appUserID].mutableCopy; + + for (NSString *key in syncedAttributes) { + RCSubscriberAttribute *attribute = [unsyncedAttributes valueForKey:key]; + if (attribute != nil && [attribute.value isEqualToString:syncedAttributes[key].value]) { + attribute.isSynced = YES; + unsyncedAttributes[key] = attribute; + } + } + [self.deviceCache storeSubscriberAttributes:unsyncedAttributes appUserID:appUserID]; + } +} + +- (void)storeAttributeLocallyIfNeededWithKey:(NSString *)key + value:(nullable NSString *)value + appUserID:(NSString *)appUserID { + NSString *valueOrEmpty = value ?: @""; + NSString *_Nullable currentValue = [self currentValueForAttributeWithKey:key appUserID:appUserID]; + if (!currentValue || ![currentValue isEqualToString:valueOrEmpty]) { + [self storeAttributeLocallyWithKey:key value:valueOrEmpty appUserID:appUserID]; + } +} + +- (void)storeAttributeLocallyWithKey:(NSString *)key value:(NSString *)value appUserID:(NSString *)appUserID { + RCSubscriberAttribute *subscriberAttribute = [[RCSubscriberAttribute alloc] initWithKey:key + value:value]; + [self.deviceCache storeSubscriberAttribute:subscriberAttribute appUserID:appUserID]; +} + +- (nullable NSString *)currentValueForAttributeWithKey:(NSString *)key appUserID:(NSString *)appUserID { + RCSubscriberAttribute *attribute = [self.deviceCache subscriberAttributeWithKey:key appUserID:appUserID]; + return attribute ? attribute.value : nil; +} + +- (void)setAttributionID:(nullable NSString *)networkID + networkKey:(NSString *)networkKey + appUserID:(NSString *)appUserID { + [self collectDeviceIdentifiersForAppUserID:appUserID]; + [self setAttributeWithKey:networkKey value:networkID appUserID:appUserID]; +} + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Pods/Purchases/README.md b/Pods/Purchases/README.md new file mode 100644 index 0000000..c402ed4 --- /dev/null +++ b/Pods/Purchases/README.md @@ -0,0 +1,29 @@ +

+ RevenueCat +

+

😻 In-app Subscriptions Made Easy 😻

+ +[![License](https://img.shields.io/cocoapods/l/Purchases.svg?style=flat)](http://cocoapods.org/pods/Purchases) +[![Version](https://img.shields.io/cocoapods/v/Purchases.svg?style=flat)](https://cocoapods.org/pods/Purchases) +[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://docs.revenuecat.com/docs/ios#section-install-via-carthage) +[![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible-orange.svg)](https://docs.revenuecat.com/docs/ios#section-install-via-swift-package-manager) + +## Purchases.framework + +*Purchases* is a client for the [RevenueCat](https://www.revenuecat.com/) subscription and purchase tracking system. It is an open source framework that provides a wrapper around `StoreKit` and the RevenueCat backend to make implementing in-app subscriptions in `Swift` or `Objective-C` easy - receipt validation and status tracking included! + +## Features +| | RevenueCat | +| --- | --- | +✅ | Server-side receipt validation +➡️ | [Webhooks](https://docs.revenuecat.com/docs/webhooks) - enhanced server-to-server communication with events for purchases, renewals, cancellations, and more +🖥 | macOS support +🎯 | Subscription status tracking - know whether a user is subscribed whether they're on iOS, Android or web +📊 | Analytics - automatic calculation of metrics like conversion, mrr, and churn +📝 | [Online documentation](https://docs.revenuecat.com/docs) up to date +🔀 | [Integrations](https://www.revenuecat.com/integrations) - over a dozen integrations to easily send purchase data where you need it +💯 | Well maintained - [frequent releases](https://github.com/RevenueCat/purchases-ios/releases) +📮 | Great support - [Help Center](https://revenuecat.zendesk.com) + +## Getting Started +For more detailed information, you can view our complete documentation at [docs.revenuecat.com](https://docs.revenuecat.com/v3.0/docs). diff --git a/Pods/PurchasesCoreSwift/LICENSE b/Pods/PurchasesCoreSwift/LICENSE new file mode 100644 index 0000000..91dd957 --- /dev/null +++ b/Pods/PurchasesCoreSwift/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Jacob Eiting + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Pods/PurchasesCoreSwift/PurchasesCoreSwift/IntroEligibilityCalculator.swift b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/IntroEligibilityCalculator.swift new file mode 100644 index 0000000..f0d2439 --- /dev/null +++ b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/IntroEligibilityCalculator.swift @@ -0,0 +1,96 @@ +// +// IntroEligibilityCalculator.swift +// Purchases +// +// Created by Andrés Boedo on 7/14/20. +// Copyright © 2020 Purchases. All rights reserved. +// + +import Foundation +import StoreKit + +@objc(RCIntroEligibilityCalculator) public class IntroEligibilityCalculator: NSObject { + private let productsManager: ProductsManager + private let receiptParser: ReceiptParser + + @objc public override init() { + self.productsManager = ProductsManager() + self.receiptParser = ReceiptParser() + } + + internal init(productsManager: ProductsManager, + receiptParser: ReceiptParser) { + self.productsManager = productsManager + self.receiptParser = receiptParser + } + + @available(iOS 12.0, macOS 10.14, macCatalyst 13.0, tvOS 12.0, watchOS 6.2, *) + @objc public func checkTrialOrIntroductoryPriceEligibility(with receiptData: Data, + productIdentifiers candidateProductIdentifiers: Set, + completion: @escaping ([String: NSNumber], Error?) -> Void) { + guard candidateProductIdentifiers.count > 0 else { + completion([:], nil) + return + } + + var result: [String: NSNumber] = candidateProductIdentifiers.reduce(into: [:]) { resultDict, productId in + resultDict[productId] = IntroEligibilityStatus.unknown.toNSNumber() + } + do { + let receipt = try receiptParser.parse(from: receiptData) + let purchasedProductIdsWithIntroOffersOrFreeTrials = receipt.purchasedIntroOfferOrFreeTrialProductIdentifiers() + + let allProductIdentifiers = candidateProductIdentifiers.union(purchasedProductIdsWithIntroOffersOrFreeTrials) + + productsManager.products(withIdentifiers: allProductIdentifiers) { allProducts in + let purchasedProductsWithIntroOffersOrFreeTrials = allProducts.filter { + purchasedProductIdsWithIntroOffersOrFreeTrials.contains($0.productIdentifier) + } + let candidateProducts = allProducts.filter { candidateProductIdentifiers.contains($0.productIdentifier) } + + let eligibility: [String: NSNumber] = self.checkIntroEligibility(candidateProducts: candidateProducts, + purchasedProductsWithIntroOffers: purchasedProductsWithIntroOffersOrFreeTrials) + result.merge(eligibility) { (_, new) in new } + + completion(result, nil) + } + } + catch let error { + completion([:], error) + return + } + } +} + +@available(iOS 12.0, macOS 10.14, macCatalyst 13.0, tvOS 12.0, watchOS 6.2, *) +private extension IntroEligibilityCalculator { + + func checkIntroEligibility(candidateProducts: Set, + purchasedProductsWithIntroOffers: Set) -> [String: NSNumber] { + var result: [String: NSNumber] = [:] + for candidate in candidateProducts { + let usedIntroForProductIdentifier = purchasedProductsWithIntroOffers + .contains { purchased in + let foundByGroupId = (candidate.subscriptionGroupIdentifier != nil + && candidate.subscriptionGroupIdentifier == purchased.subscriptionGroupIdentifier) + return foundByGroupId + } + result[candidate.productIdentifier] = usedIntroForProductIdentifier + ? IntroEligibilityStatus.ineligible.toNSNumber() + : IntroEligibilityStatus.eligible.toNSNumber() + } + return result + } +} + +enum IntroEligibilityStatus: Int { + case unknown, + ineligible, + eligible +} + +extension IntroEligibilityStatus { + func toNSNumber() -> NSNumber { + return NSNumber(integerLiteral: self.rawValue) + } +} diff --git a/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/BasicTypes/ASN1Container.swift b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/BasicTypes/ASN1Container.swift new file mode 100644 index 0000000..ae9b1ad --- /dev/null +++ b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/BasicTypes/ASN1Container.swift @@ -0,0 +1,62 @@ +// +// Created by Andrés Boedo on 7/28/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +import Foundation + +enum ASN1Class: UInt8 { + case universal, application, contextSpecific, `private` +} + +enum ASN1Identifier: UInt8, CaseIterable { + case endOfContent = 0 + case boolean = 1 + case integer = 2 + case bitString = 3 + case octetString = 4 + case null = 5 + case objectIdentifier = 6 + case objectDescriptor = 7 + case external = 8 + case real = 9 + case enumerated = 10 + case embeddedPdv = 11 + case utf8String = 12 + case relativeOid = 13 + case sequence = 16 + case set = 17 + case numericString = 18 + case printableString = 19 + case t61String = 20 + case videotexString = 21 + case ia5String = 22 + case utcTime = 23 + case generalizedTime = 24 + case graphicString = 25 + case visibleString = 26 + case generalString = 27 + case universalString = 28 + case characterString = 29 + case bmpString = 30 +} + +enum ASN1EncodingType: UInt8 { + case primitive, constructed +} + +struct ASN1Length: Equatable { + let value: Int + let bytesUsedForLength: Int +} + +struct ASN1Container: Equatable { + let containerClass: ASN1Class + let containerIdentifier: ASN1Identifier + let encodingType: ASN1EncodingType + let length: ASN1Length + let internalPayload: ArraySlice + let bytesUsedForIdentifier = 1 + var totalBytesUsed: Int { return bytesUsedForIdentifier + length.value + length.bytesUsedForLength } + let internalContainers: [ASN1Container] +} diff --git a/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/BasicTypes/ASN1ObjectIdentifier.swift b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/BasicTypes/ASN1ObjectIdentifier.swift new file mode 100644 index 0000000..0859089 --- /dev/null +++ b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/BasicTypes/ASN1ObjectIdentifier.swift @@ -0,0 +1,16 @@ +// +// Created by Andrés Boedo on 7/29/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +import Foundation + +// http://www.umich.edu/~x509/ssleay/asn1-oids.html +enum ASN1ObjectIdentifier: String { + case data = "1.2.840.113549.1.7.1" + case signedData = "1.2.840.113549.1.7.2" + case envelopedData = "1.2.840.113549.1.7.3" + case signedAndEnvelopedData = "1.2.840.113549.1.7.4" + case digestedData = "1.2.840.113549.1.7.5" + case encryptedData = "1.2.840.113549.1.7.6" +} diff --git a/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/BasicTypes/AppleReceipt.swift b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/BasicTypes/AppleReceipt.swift new file mode 100644 index 0000000..40de480 --- /dev/null +++ b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/BasicTypes/AppleReceipt.swift @@ -0,0 +1,63 @@ +// +// AppleReceipt.swift +// Purchases +// +// Created by Andrés Boedo on 7/22/20. +// Copyright © 2020 Purchases. All rights reserved. +// + +import Foundation + +// https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html +struct ReceiptAttribute { + let type: ReceiptAttributeType + let version: Int + let value: String +} + +enum ReceiptAttributeType: Int { + case bundleId = 2, + applicationVersion = 3, + opaqueValue = 4, + sha1Hash = 5, + creationDate = 12, + inAppPurchase = 17, + originalApplicationVersion = 19, + expirationDate = 21 +} + +struct AppleReceipt: Equatable { + let bundleId: String + let applicationVersion: String + let originalApplicationVersion: String + let opaqueValue: Data + let sha1Hash: Data + let creationDate: Date + let expirationDate: Date? + let inAppPurchases: [InAppPurchase] + + func purchasedIntroOfferOrFreeTrialProductIdentifiers() -> Set { + let productIdentifiers = inAppPurchases + .filter { $0.isInIntroOfferPeriod || $0.isInTrialPeriod == true } + .map { $0.productId } + return Set(productIdentifiers) + } + + var asDict: [String: Any] { + return [ + "bundleId": bundleId, + "applicationVersion": applicationVersion, + "originalApplicationVersion": originalApplicationVersion, + "opaqueValue": opaqueValue, + "sha1Hash": sha1Hash, + "creationDate": creationDate, + "expirationDate": expirationDate ?? "", + "inAppPurchases": inAppPurchases.map { $0.asDict } + ] + } + + var description: String { + return String(describing: self.asDict) + } +} + diff --git a/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/BasicTypes/InAppPurchase.swift b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/BasicTypes/InAppPurchase.swift new file mode 100644 index 0000000..3f88582 --- /dev/null +++ b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/BasicTypes/InAppPurchase.swift @@ -0,0 +1,69 @@ +// +// Created by Andrés Boedo on 7/29/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +import Foundation + +// https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html +enum InAppPurchaseAttributeType: Int { + case quantity = 1701, + productId = 1702, + transactionId = 1703, + purchaseDate = 1704, + originalTransactionId = 1705, + originalPurchaseDate = 1706, + productType = 1707, + expiresDate = 1708, + webOrderLineItemId = 1711, + cancellationDate = 1712, + isInTrialPeriod = 1713, + isInIntroOfferPeriod = 1719, + promotionalOfferIdentifier = 1721 +} + +enum InAppPurchaseProductType: Int { + case unknown = -1, + nonConsumable, + consumable, + nonRenewingSubscription, + autoRenewableSubscription +} + +struct InAppPurchase: Equatable { + let quantity: Int + let productId: String + let transactionId: String + let originalTransactionId: String + let productType: InAppPurchaseProductType? + let purchaseDate: Date + let originalPurchaseDate: Date + let expiresDate: Date? + let cancellationDate: Date? + let isInTrialPeriod: Bool? + let isInIntroOfferPeriod: Bool + let webOrderLineItemId: Int64 + let promotionalOfferIdentifier: String? + + var asDict: [String: Any] { + return [ + "quantity": quantity, + "productId": productId, + "transactionId": transactionId, + "originalTransactionId": originalTransactionId, + "promotionalOfferIdentifier": promotionalOfferIdentifier ?? "", + "purchaseDate": purchaseDate, + "productType": productType?.rawValue ?? "", + "originalPurchaseDate": originalPurchaseDate, + "expiresDate": expiresDate ?? "", + "cancellationDate": cancellationDate ?? "", + "isInTrialPeriod": isInTrialPeriod ?? "", + "isInIntroOfferPeriod": isInIntroOfferPeriod, + "webOrderLineItemId": webOrderLineItemId + ] + } + + var description: String { + return String(describing: self.asDict) + } +} diff --git a/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/Builders/ASN1ContainerBuilder.swift b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/Builders/ASN1ContainerBuilder.swift new file mode 100644 index 0000000..346c8a9 --- /dev/null +++ b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/Builders/ASN1ContainerBuilder.swift @@ -0,0 +1,98 @@ +// +// Created by Andrés Boedo on 7/29/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +import Foundation + +class ASN1ContainerBuilder { + + func build(fromPayload payload: ArraySlice) throws -> ASN1Container { + guard payload.count >= 2, + let firstByte = payload.first else { + throw ReceiptReadingError.asn1ParsingError(description: "payload needs to be at least 2 bytes long") + } + let containerClass = try extractClass(byte: firstByte) + let encodingType = try extractEncodingType(byte: firstByte) + let containerIdentifier = try extractIdentifier(byte: firstByte) + let length = try extractLength(data: payload.dropFirst()) + let bytesUsedForIdentifier = 1 + let bytesUsedForMetadata = bytesUsedForIdentifier + length.bytesUsedForLength + + guard payload.count - bytesUsedForMetadata >= length.value else { + throw ReceiptReadingError.asn1ParsingError(description: "payload is shorter than length value") + } + + let internalPayload = payload.dropFirst(bytesUsedForMetadata).prefix(length.value) + var internalContainers: [ASN1Container] = [] + if encodingType == .constructed { + internalContainers = try buildInternalContainers(payload: internalPayload) + } + return ASN1Container(containerClass: containerClass, + containerIdentifier: containerIdentifier, + encodingType: encodingType, + length: length, + internalPayload: internalPayload, + internalContainers: internalContainers) + } +} + +private extension ASN1ContainerBuilder { + + func buildInternalContainers(payload: ArraySlice) throws -> [ASN1Container] { + var internalContainers = [ASN1Container]() + var currentPayload = payload + while (currentPayload.count > 0) { + let internalContainer = try build(fromPayload: currentPayload) + internalContainers.append(internalContainer) + currentPayload = currentPayload.dropFirst(internalContainer.totalBytesUsed) + } + return internalContainers + } + + func extractClass(byte: UInt8) throws -> ASN1Class { + let firstTwoBits = byte.valueInRange(from: 0, to: 1) + guard let asn1Class = ASN1Class(rawValue: firstTwoBits) else { + throw ReceiptReadingError.asn1ParsingError(description: "couldn't determine asn1 class") + } + return asn1Class + } + + func extractEncodingType(byte: UInt8) throws -> ASN1EncodingType { + let thirdBit = byte.bitAtIndex(2) + guard let encodingType = ASN1EncodingType(rawValue: thirdBit) else { + throw ReceiptReadingError.asn1ParsingError(description: "couldn't determine encoding type") + } + return encodingType + } + + func extractIdentifier(byte: UInt8) throws -> ASN1Identifier { + let lastFiveBits = byte.valueInRange(from: 3, to: 7) + guard let asn1Identifier = ASN1Identifier(rawValue: lastFiveBits) else { + throw ReceiptReadingError.asn1ParsingError(description: "couldn't determine identifier") + } + return asn1Identifier + } + + func extractLength(data: ArraySlice) throws -> ASN1Length { + guard let firstByte = data.first else { + throw ReceiptReadingError.asn1ParsingError(description: "length needs to be at least one byte") + } + + let lengthBit = firstByte.bitAtIndex(0) + let isShortLength = lengthBit == 0 + + let firstByteValue = Int(firstByte.valueInRange(from: 1, to: 7)) + + var bytesUsedForLength = 1 + if isShortLength { + return ASN1Length(value: firstByteValue, bytesUsedForLength: bytesUsedForLength) + } else { + let totalLengthBytes = firstByteValue + bytesUsedForLength += totalLengthBytes + let lengthBytes = data.dropFirst().prefix(totalLengthBytes) + let lengthValue = lengthBytes.toInt() + return ASN1Length(value: lengthValue, bytesUsedForLength: bytesUsedForLength) + } + } +} diff --git a/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/Builders/ASN1ObjectIdentifierBuilder.swift b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/Builders/ASN1ObjectIdentifierBuilder.swift new file mode 100644 index 0000000..322247b --- /dev/null +++ b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/Builders/ASN1ObjectIdentifierBuilder.swift @@ -0,0 +1,48 @@ +// +// Created by Andrés Boedo on 7/28/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +import Foundation + +class ASN1ObjectIdentifierBuilder { + + // info on the format: https://docs.microsoft.com/en-us/windows/win32/seccertenroll/about-object-identifier + func build(fromPayload payload: ArraySlice) -> ASN1ObjectIdentifier? { + guard let firstByte = payload.first else { return nil } + + var objectIdentifierNumbers: [UInt] = [] + objectIdentifierNumbers.append(UInt(firstByte / 40)) + objectIdentifierNumbers.append(UInt(firstByte % 40)) + + let trailingPayload = payload.dropFirst() + let variableLengthQuantityNumbers = decodeVariableLengthQuantity(payload: trailingPayload) + objectIdentifierNumbers += variableLengthQuantityNumbers + + let objectIdentifierString = objectIdentifierNumbers.map { String($0) } + .joined(separator: ".") + return ASN1ObjectIdentifier(rawValue: objectIdentifierString) + } +} + +private extension ASN1ObjectIdentifierBuilder { + + // https://en.wikipedia.org/wiki/Variable-length_quantity + func decodeVariableLengthQuantity(payload: ArraySlice) -> [UInt] { + var decodedNumbers = [UInt]() + + var currentBuffer: UInt = 0 + var isShortLength = false + for byte in payload { + isShortLength = byte.bitAtIndex(0) == 0 + let byteValue = UInt(byte.valueInRange(from: 1, to: 7)) + + currentBuffer = (currentBuffer << 7) | byteValue + if isShortLength { + decodedNumbers.append(currentBuffer) + currentBuffer = 0 + } + } + return decodedNumbers + } +} \ No newline at end of file diff --git a/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/Builders/AppleReceiptBuilder.swift b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/Builders/AppleReceiptBuilder.swift new file mode 100644 index 0000000..fe135ae --- /dev/null +++ b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/Builders/AppleReceiptBuilder.swift @@ -0,0 +1,97 @@ +// +// Created by Andrés Boedo on 7/29/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +import Foundation + +class AppleReceiptBuilder { + private let containerBuilder: ASN1ContainerBuilder + private let inAppPurchaseBuilder: InAppPurchaseBuilder + private let dateFormatter: ISO3601DateFormatter + + private let typeContainerIndex = 0 + private let versionContainerIndex = 1 // unused + private let attributeTypeContainerIndex = 2 + private let expectedInternalContainersCount = 3 // type + version + attribute + + init(containerBuilder: ASN1ContainerBuilder = ASN1ContainerBuilder(), + inAppPurchaseBuilder: InAppPurchaseBuilder = InAppPurchaseBuilder(), + dateFormatter: ISO3601DateFormatter = ISO3601DateFormatter.shared) { + self.containerBuilder = containerBuilder + self.inAppPurchaseBuilder = inAppPurchaseBuilder + self.dateFormatter = dateFormatter + } + + func build(fromContainer container: ASN1Container) throws -> AppleReceipt { + var bundleId: String? + var applicationVersion: String? + var originalApplicationVersion: String? + var opaqueValue: Data? + var sha1Hash: Data? + var creationDate: Date? + var expirationDate: Date? + var inAppPurchases: [InAppPurchase] = [] + + guard let internalContainer = container.internalContainers.first else { + throw ReceiptReadingError.receiptParsingError + } + let receiptContainer = try containerBuilder.build(fromPayload: internalContainer.internalPayload) + for receiptAttribute in receiptContainer.internalContainers { + guard receiptAttribute.internalContainers.count == expectedInternalContainersCount else { + throw ReceiptReadingError.receiptParsingError + } + let typeContainer = receiptAttribute.internalContainers[typeContainerIndex] + let valueContainer = receiptAttribute.internalContainers[attributeTypeContainerIndex] + let attributeType = ReceiptAttributeType(rawValue: typeContainer.internalPayload.toInt()) + guard let nonOptionalType = attributeType else { + continue + } + let payload = valueContainer.internalPayload + + switch nonOptionalType { + case .opaqueValue: + opaqueValue = payload.toData() + case .sha1Hash: + sha1Hash = payload.toData() + case .applicationVersion: + let internalContainer = try containerBuilder.build(fromPayload: payload) + applicationVersion = internalContainer.internalPayload.toString() + case .originalApplicationVersion: + let internalContainer = try containerBuilder.build(fromPayload: payload) + originalApplicationVersion = internalContainer.internalPayload.toString() + case .bundleId: + let internalContainer = try containerBuilder.build(fromPayload: payload) + bundleId = internalContainer.internalPayload.toString() + case .creationDate: + let internalContainer = try containerBuilder.build(fromPayload: payload) + creationDate = internalContainer.internalPayload.toDate(dateFormatter: dateFormatter) + case .expirationDate: + let internalContainer = try containerBuilder.build(fromPayload: payload) + expirationDate = internalContainer.internalPayload.toDate(dateFormatter: dateFormatter) + case .inAppPurchase: + let internalContainer = try containerBuilder.build(fromPayload: payload) + inAppPurchases.append(try inAppPurchaseBuilder.build(fromContainer: internalContainer)) + } + } + + guard let nonOptionalBundleId = bundleId, + let nonOptionalApplicationVersion = applicationVersion, + let nonOptionalOriginalApplicationVersion = originalApplicationVersion, + let nonOptionalOpaqueValue = opaqueValue, + let nonOptionalSha1Hash = sha1Hash, + let nonOptionalCreationDate = creationDate else { + throw ReceiptReadingError.receiptParsingError + } + + let receipt = AppleReceipt(bundleId: nonOptionalBundleId, + applicationVersion: nonOptionalApplicationVersion, + originalApplicationVersion: nonOptionalOriginalApplicationVersion, + opaqueValue: nonOptionalOpaqueValue, + sha1Hash: nonOptionalSha1Hash, + creationDate: nonOptionalCreationDate, + expirationDate: expirationDate, + inAppPurchases: inAppPurchases) + return receipt + } +} diff --git a/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/Builders/InAppPurchaseBuilder.swift b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/Builders/InAppPurchaseBuilder.swift new file mode 100644 index 0000000..3878e5c --- /dev/null +++ b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/Builders/InAppPurchaseBuilder.swift @@ -0,0 +1,105 @@ +// +// Created by Andrés Boedo on 7/29/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +import Foundation + +class InAppPurchaseBuilder { + private let containerBuilder: ASN1ContainerBuilder + private let dateFormatter: ISO3601DateFormatter + + private let typeContainerIndex = 0 + private let versionContainerIndex = 1 // unused + private let attributeTypeContainerIndex = 2 + private let expectedInternalContainersCount = 3 // type + version + attribute + + init() { + self.containerBuilder = ASN1ContainerBuilder() + self.dateFormatter = ISO3601DateFormatter.shared + } + + func build(fromContainer container: ASN1Container) throws -> InAppPurchase { + var quantity: Int? + var productId: String? + var transactionId: String? + var originalTransactionId: String? + var productType: InAppPurchaseProductType? + var purchaseDate: Date? + var originalPurchaseDate: Date? + var expiresDate: Date? + var cancellationDate: Date? + var isInTrialPeriod: Bool? + var isInIntroOfferPeriod: Bool? + var webOrderLineItemId: Int64? + var promotionalOfferIdentifier: String? + + for internalContainer in container.internalContainers { + guard internalContainer.internalContainers.count == expectedInternalContainersCount else { + throw ReceiptReadingError.inAppPurchaseParsingError + } + let typeContainer = internalContainer.internalContainers[typeContainerIndex] + let valueContainer = internalContainer.internalContainers[attributeTypeContainerIndex] + + guard let attributeType = InAppPurchaseAttributeType(rawValue: typeContainer.internalPayload.toInt()) + else { continue } + + let internalContainer = try containerBuilder.build(fromPayload: valueContainer.internalPayload) + guard internalContainer.length.value > 0 else { continue } + + switch attributeType { + case .quantity: + quantity = internalContainer.internalPayload.toInt() + case .webOrderLineItemId: + webOrderLineItemId = internalContainer.internalPayload.toInt64() + case .productType: + productType = InAppPurchaseProductType(rawValue: internalContainer.internalPayload.toInt()) + case .isInIntroOfferPeriod: + isInIntroOfferPeriod = internalContainer.internalPayload.toBool() + case .isInTrialPeriod: + isInTrialPeriod = internalContainer.internalPayload.toBool() + case .productId: + productId = internalContainer.internalPayload.toString() + case .transactionId: + transactionId = internalContainer.internalPayload.toString() + case .originalTransactionId: + originalTransactionId = internalContainer.internalPayload.toString() + case .promotionalOfferIdentifier: + promotionalOfferIdentifier = internalContainer.internalPayload.toString() + case .cancellationDate: + cancellationDate = internalContainer.internalPayload.toDate(dateFormatter: dateFormatter) + case .expiresDate: + expiresDate = internalContainer.internalPayload.toDate(dateFormatter: dateFormatter) + case .originalPurchaseDate: + originalPurchaseDate = internalContainer.internalPayload.toDate(dateFormatter: dateFormatter) + case .purchaseDate: + purchaseDate = internalContainer.internalPayload.toDate(dateFormatter: dateFormatter) + } + } + + guard let nonOptionalQuantity = quantity, + let nonOptionalProductId = productId, + let nonOptionalTransactionId = transactionId, + let nonOptionalOriginalTransactionId = originalTransactionId, + let nonOptionalPurchaseDate = purchaseDate, + let nonOptionalOriginalPurchaseDate = originalPurchaseDate, + let nonOptionalIsInIntroOfferPeriod = isInIntroOfferPeriod, + let nonOptionalWebOrderLineItemId = webOrderLineItemId else { + throw ReceiptReadingError.inAppPurchaseParsingError + } + + return InAppPurchase(quantity: nonOptionalQuantity, + productId: nonOptionalProductId, + transactionId: nonOptionalTransactionId, + originalTransactionId: nonOptionalOriginalTransactionId, + productType: productType, + purchaseDate: nonOptionalPurchaseDate, + originalPurchaseDate: nonOptionalOriginalPurchaseDate, + expiresDate: expiresDate, + cancellationDate: cancellationDate, + isInTrialPeriod: isInTrialPeriod, + isInIntroOfferPeriod: nonOptionalIsInIntroOfferPeriod, + webOrderLineItemId: nonOptionalWebOrderLineItemId, + promotionalOfferIdentifier: promotionalOfferIdentifier) + } +} diff --git a/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/DataConverters/ArraySlice_UInt8+Extensions.swift b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/DataConverters/ArraySlice_UInt8+Extensions.swift new file mode 100644 index 0000000..ccbdbde --- /dev/null +++ b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/DataConverters/ArraySlice_UInt8+Extensions.swift @@ -0,0 +1,42 @@ +// +// Created by Andrés Boedo on 7/29/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +import Foundation + +extension ArraySlice where Element == UInt8 { + func toUInt() -> UInt64 { + let array = Array(self) + var result: UInt64 = 0 + for idx in 0..<(array.count) { + let shiftAmount = UInt((array.count) - idx - 1) * 8 + result += UInt64(array[idx]) << shiftAmount + } + return result + } + + func toInt() -> Int { + return Int(self.toUInt()) + } + + func toInt64() -> Int64 { + return Int64(self.toUInt()) + } + + func toBool() -> Bool { + return self.toUInt() == 1 + } + + func toString() -> String? { + return String(bytes: self, encoding: .utf8) + } + + func toDate(dateFormatter: ISO3601DateFormatter) -> Date? { + return dateFormatter.date(fromBytes: self) + } + + func toData() -> Data { + return Data(self) + } +} diff --git a/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/DataConverters/ISO3601DateFormatter.swift b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/DataConverters/ISO3601DateFormatter.swift new file mode 100644 index 0000000..212392e --- /dev/null +++ b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/DataConverters/ISO3601DateFormatter.swift @@ -0,0 +1,24 @@ +// +// Created by Andrés Boedo on 7/29/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +import Foundation + +struct ISO3601DateFormatter { + static let shared = ISO3601DateFormatter() + + private let secondsDateFormatter = DateFormatter() + private let milisecondsDateFormatter = DateFormatter() + + private init() { + secondsDateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ" + milisecondsDateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSSZ" + } + + func date(fromBytes bytes: ArraySlice) -> Date? { + guard let dateString = String(bytes: Array(bytes), encoding: .ascii) else { return nil } + return (secondsDateFormatter.date(from: dateString) + ?? milisecondsDateFormatter.date(from: dateString)) + } +} diff --git a/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/DataConverters/UInt8+Extensions.swift b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/DataConverters/UInt8+Extensions.swift new file mode 100644 index 0000000..557226c --- /dev/null +++ b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/DataConverters/UInt8+Extensions.swift @@ -0,0 +1,45 @@ +// +// UInt8+Extensions.swift +// Purchases +// +// Created by Andrés Boedo on 7/24/20. +// Copyright © 2020 Purchases. All rights reserved. +// + +import Foundation + +extension UInt8 { + func bitAtIndex(_ index: UInt8) -> UInt8 { + guard index <= 7 else { fatalError("invalid index: \(index)") } + let shifted = self >> (7 - index) + return shifted & 0b1 + } + + func valueInRange(from: UInt8, to: UInt8) -> UInt8 { + guard to <= 7 else { fatalError("invalid index: \(to)") } + guard from <= to else { fatalError("from: \(from) can't be greater than to: \(to)") } + + let range: UInt8 = to - from + 1 + let shifted = self >> (7 - to) + let mask = maskForRange(range) + return shifted & mask + } +} + +private extension UInt8 { + func maskForRange(_ range: UInt8) -> UInt8 { + guard 0 <= range && range <= 8 else { fatalError("range must be between 1 and 8") } + switch range { + case 1: return 0b1 + case 2: return 0b11 + case 3: return 0b111 + case 4: return 0b1111 + case 5: return 0b11111 + case 6: return 0b111111 + case 7: return 0b1111111 + case 8: return 0b11111111 + default: + fatalError("unhandled range") + } + } +} diff --git a/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/ReceiptParser.swift b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/ReceiptParser.swift new file mode 100644 index 0000000..a9c124e --- /dev/null +++ b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/ReceiptParser.swift @@ -0,0 +1,73 @@ +// +// ReceiptParser.swift +// Purchases +// +// Created by Andrés Boedo on 7/22/20. +// Copyright © 2020 Purchases. All rights reserved. +// + +import Foundation + +@objc(RCReceiptParser) public class ReceiptParser: NSObject { + private let objectIdentifierBuilder: ASN1ObjectIdentifierBuilder + private let containerBuilder: ASN1ContainerBuilder + private let receiptBuilder: AppleReceiptBuilder + + @objc public convenience override init() { + self.init(objectIdentifierBuilder: ASN1ObjectIdentifierBuilder(), + containerBuilder: ASN1ContainerBuilder(), + receiptBuilder: AppleReceiptBuilder()) + } + + init(objectIdentifierBuilder: ASN1ObjectIdentifierBuilder, + containerBuilder: ASN1ContainerBuilder, + receiptBuilder: AppleReceiptBuilder) { + self.objectIdentifierBuilder = objectIdentifierBuilder + self.containerBuilder = containerBuilder + self.receiptBuilder = receiptBuilder + super.init() + } + + @objc public func receiptHasTransactions(receiptData: Data) -> Bool { + if let receipt = try? parse(from: receiptData) { + return receipt.inAppPurchases.count > 0 + } + // if the receipt can't be parsed, conservatively return true + return true + } + + func parse(from receiptData: Data) throws -> AppleReceipt { + let intData = [UInt8](receiptData) + + let asn1Container = try containerBuilder.build(fromPayload: ArraySlice(intData)) + guard let receiptASN1Container = try findASN1Container(withObjectId: ASN1ObjectIdentifier.data, + inContainer: asn1Container) else { + throw ReceiptReadingError.dataObjectIdentifierMissing + } + let receipt = try receiptBuilder.build(fromContainer: receiptASN1Container) + return receipt + } +} + +private extension ReceiptParser { + func findASN1Container(withObjectId objectId: ASN1ObjectIdentifier, + inContainer container: ASN1Container) throws -> ASN1Container? { + if container.encodingType == .constructed { + for (index, internalContainer) in container.internalContainers.enumerated() { + if internalContainer.containerIdentifier == .objectIdentifier { + let objectIdentifier = objectIdentifierBuilder.build(fromPayload: internalContainer.internalPayload) + if objectIdentifier == objectId && index < container.internalContainers.count - 1 { + // the container that holds the data comes right after the one with the object identifier + return container.internalContainers[index + 1] + } + } else { + let receipt = try findASN1Container(withObjectId: objectId, inContainer: internalContainer) + if receipt != nil { + return receipt + } + } + } + } + return nil + } +} diff --git a/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/ReceiptParsingError.swift b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/ReceiptParsingError.swift new file mode 100644 index 0000000..67116a3 --- /dev/null +++ b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/LocalReceiptParsing/ReceiptParsingError.swift @@ -0,0 +1,37 @@ +// +// ReceiptParsingError.swift +// Purchases +// +// Created by Andrés Boedo on 7/30/20. +// Copyright © 2020 Purchases. All rights reserved. +// + +import Foundation + +enum ReceiptReadingError: Error, Equatable { + case missingReceipt, + emptyReceipt, + dataObjectIdentifierMissing, + asn1ParsingError(description: String), + receiptParsingError, + inAppPurchaseParsingError +} + +extension ReceiptReadingError: LocalizedError { + public var errorDescription: String? { + switch self { + case .missingReceipt: + return "The receipt couldn't be found" + case .emptyReceipt: + return "The receipt is empty" + case .dataObjectIdentifierMissing: + return "Couldn't find an object identifier of type data in the receipt" + case .asn1ParsingError(let description): + return "Error while parsing, payload can't be interpreted as ASN1. details: \(description)" + case .receiptParsingError: + return "Error while parsing the receipt. One or more attributes are missing." + case .inAppPurchaseParsingError: + return "Error while parsing in-app purchase. One or more attributes are missing or in the wrong format." + } + } +} diff --git a/Pods/PurchasesCoreSwift/PurchasesCoreSwift/Logging/Strings/AttributionStrings.swift b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/Logging/Strings/AttributionStrings.swift new file mode 100644 index 0000000..028e606 --- /dev/null +++ b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/Logging/Strings/AttributionStrings.swift @@ -0,0 +1,14 @@ +// +// AttributionStrings.swift +// PurchasesCoreSwift +// +// Created by Andrés Boedo on 9/14/20. +// Copyright © 2020 Purchases. All rights reserved. +// + +import Foundation + +@objc(RCAttributionStrings) public class AttributionStrings: NSObject { + @objc public var instance_configured_posting_attribution: String { "There is an instance configured, posting attribution." } + @objc public var no_instance_configured_caching_attribution: String { "There is no instance configured, caching attribution." } +} diff --git a/Pods/PurchasesCoreSwift/PurchasesCoreSwift/Logging/Strings/Strings.swift b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/Logging/Strings/Strings.swift new file mode 100644 index 0000000..437439e --- /dev/null +++ b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/Logging/Strings/Strings.swift @@ -0,0 +1,10 @@ +// +// Created by Andrés Boedo on 9/14/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +import Foundation + +@objc(RCStrings) public class Strings: NSObject { + @objc public static let attribution = AttributionStrings() +} diff --git a/Pods/PurchasesCoreSwift/PurchasesCoreSwift/Misc/DateExtensions.swift b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/Misc/DateExtensions.swift new file mode 100644 index 0000000..5c07115 --- /dev/null +++ b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/Misc/DateExtensions.swift @@ -0,0 +1,22 @@ +// +// Created by Andrés Boedo on 8/7/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +import Foundation + +extension Date { + + static func from(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int) -> Date { + let calendar = Calendar(identifier: .gregorian) + var dateComponents = DateComponents() + dateComponents.year = year + dateComponents.month = month + dateComponents.day = day + dateComponents.hour = hour + dateComponents.minute = minute + dateComponents.second = second + guard let date = calendar.date(from: dateComponents) else { fatalError() } + return date + } +} diff --git a/Pods/PurchasesCoreSwift/PurchasesCoreSwift/Misc/OperationDispatcher.swift b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/Misc/OperationDispatcher.swift new file mode 100644 index 0000000..a6badfc --- /dev/null +++ b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/Misc/OperationDispatcher.swift @@ -0,0 +1,39 @@ +// +// OperationDispatcher.swift +// Purchases +// +// Created by Andrés Boedo on 8/5/20. +// Copyright © 2020 Purchases. All rights reserved. +// + +import Foundation + +@objc(RCOperationDispatcher) public class OperationDispatcher: NSObject { + + private let mainQueue: DispatchQueue + private let workerQueue: DispatchQueue + private let maxJitterInSeconds: Double = 5 + + @objc public override init() { + mainQueue = DispatchQueue.main + workerQueue = DispatchQueue(label: "OperationDispatcherWorkerQueue") + } + + @objc public func dispatchOnMainThread(_ block: @escaping () -> Void) { + if Thread.isMainThread { + block() + } else { + mainQueue.async { block() } + } + } + + @objc public func dispatchOnWorkerThread(withRandomDelay: Bool = false, + block: @escaping () -> ()) { + if withRandomDelay { + let delay = Double.random(in: 0..] = [:] + private var completionHandlers: [Set: [(Set) -> Void]] = [:] + + init(productsRequestFactory: ProductsRequestFactory = ProductsRequestFactory()) { + self.productsRequestFactory = productsRequestFactory + } + + func products(withIdentifiers identifiers: Set, completion: @escaping (Set) -> Void) { + queue.async { [self] in + let productsAlreadyCached = self.cachedProductsByIdentifier.filter { key, _ in identifiers.contains(key) } + if productsAlreadyCached.count == identifiers.count { + let productsAlreadyCachedSet = Set(productsAlreadyCached.values) + NSLog("skipping products request because products were already cached. products: \(identifiers)") + completion(productsAlreadyCachedSet) + return + } + + if let existingHandlers = self.completionHandlers[identifiers] { + NSLog("found an existing request for products: \(identifiers), appending to completion") + self.completionHandlers[identifiers] = existingHandlers + [completion] + return + } + + NSLog("no existing requests and products not cached, starting SKProducts request for: \(identifiers)") + let request = self.productsRequestFactory.request(productIdentifiers: identifiers) + request.delegate = self + self.completionHandlers[identifiers] = [completion] + self.productsByRequests[request] = identifiers + request.start() + } + } +} + +extension ProductsManager: SKProductsRequestDelegate { + + func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { + queue.async { [self] in + NSLog("products request received response") + guard let requestProducts = self.productsByRequests[request] else { fatalError("couldn't find request") } + guard let completionBlocks = self.completionHandlers[requestProducts] else { + fatalError("couldn't find completion") + } + self.completionHandlers.removeValue(forKey: requestProducts) + self.productsByRequests.removeValue(forKey: request) + + self.cacheProducts(response.products) + for completion in completionBlocks { + completion(Set(response.products)) + } + } + } + + func requestDidFinish(_ request: SKRequest) { + NSLog("request did finish") + } + + func request(_ request: SKRequest, didFailWithError error: Error) { + queue.async { [self] in + NSLog("products request failed! error: \(error.localizedDescription)") + guard let products = self.productsByRequests[request] else { fatalError("couldn't find request") } + guard let completionBlocks = self.completionHandlers[products] else { + fatalError("couldn't find completion") + } + + self.completionHandlers.removeValue(forKey: products) + self.productsByRequests.removeValue(forKey: request) + for completion in completionBlocks { + completion(Set()) + } + } + } +} + +private extension ProductsManager { + + func cacheProducts(_ products: [SKProduct]) { + let productsByIdentifier = products.reduce(into: [:]) { resultDict, product in + resultDict[product.productIdentifier] = product + } + + cachedProductsByIdentifier.merge(productsByIdentifier) { (_, new) in new } + } +} diff --git a/Pods/PurchasesCoreSwift/PurchasesCoreSwift/Purchasing/ProductsRequestFactory.swift b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/Purchasing/ProductsRequestFactory.swift new file mode 100644 index 0000000..af7dda5 --- /dev/null +++ b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/Purchasing/ProductsRequestFactory.swift @@ -0,0 +1,13 @@ +// +// Created by Andrés Boedo on 8/12/20. +// Copyright (c) 2020 Purchases. All rights reserved. +// + +import Foundation +import StoreKit + +class ProductsRequestFactory { + func request(productIdentifiers: Set) -> SKProductsRequest { + return SKProductsRequest(productIdentifiers: productIdentifiers) + } +} diff --git a/Pods/PurchasesCoreSwift/PurchasesCoreSwift/Purchasing/TransactionsFactory.swift b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/Purchasing/TransactionsFactory.swift new file mode 100644 index 0000000..e5ba66a --- /dev/null +++ b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/Purchasing/TransactionsFactory.swift @@ -0,0 +1,24 @@ +// +// PurchaserInfoHelper.swift +// Purchases +// +// Created by RevenueCat. +// Copyright © 2020 Purchases. All rights reserved. +// + +import Foundation + +@objc(RCTransactionsFactory) public class TransactionsFactory: NSObject { + + @objc public func nonSubscriptionTransactions(withSubscriptionsData subscriptionsData: [String: [[String: Any]]], + dateFormatter: DateFormatter) -> [Transaction] { + subscriptionsData.flatMap { (productId: String, transactionData: [[String: Any]]) -> [Transaction] in + transactionData.map { + Transaction(with: $0, productId: productId, dateFormatter: dateFormatter) + } + }.sorted { + $0.purchaseDate < $1.purchaseDate + } + } + +} diff --git a/Pods/PurchasesCoreSwift/PurchasesCoreSwift/Transaction.swift b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/Transaction.swift new file mode 100644 index 0000000..fba6ff0 --- /dev/null +++ b/Pods/PurchasesCoreSwift/PurchasesCoreSwift/Transaction.swift @@ -0,0 +1,40 @@ +// +// Transaction.swift +// Purchases +// +// Created by RevenueCat. +// Copyright © 2020 Purchases. All rights reserved. +// + +import Foundation + +@objc(RCTransaction) public class Transaction: NSObject { + + @objc public let revenueCatId: String + @objc public let productId: String + @objc public let purchaseDate: Date + + @objc public init(transactionId: String, productId: String, purchaseDate: Date) { + self.revenueCatId = transactionId + self.productId = productId + self.purchaseDate = purchaseDate + super.init() + } + + init(with serverResponse: [String: Any], productId: String, dateFormatter: DateFormatter) { + guard let revenueCatId = serverResponse["id"] as? String, + let dateString = serverResponse["purchase_date"] as? String, + let purchaseDate = dateFormatter.date(from: dateString) else { + fatalError(""" + Couldn't initialize Transaction from dictionary. + Reason: unexpected format. Dictionary: \(serverResponse). + """) + } + + self.revenueCatId = revenueCatId + self.purchaseDate = purchaseDate + self.productId = productId + super.init() + } + +} diff --git a/Pods/PurchasesCoreSwift/README.md b/Pods/PurchasesCoreSwift/README.md new file mode 100644 index 0000000..c402ed4 --- /dev/null +++ b/Pods/PurchasesCoreSwift/README.md @@ -0,0 +1,29 @@ +

+ RevenueCat +

+

😻 In-app Subscriptions Made Easy 😻

+ +[![License](https://img.shields.io/cocoapods/l/Purchases.svg?style=flat)](http://cocoapods.org/pods/Purchases) +[![Version](https://img.shields.io/cocoapods/v/Purchases.svg?style=flat)](https://cocoapods.org/pods/Purchases) +[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://docs.revenuecat.com/docs/ios#section-install-via-carthage) +[![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible-orange.svg)](https://docs.revenuecat.com/docs/ios#section-install-via-swift-package-manager) + +## Purchases.framework + +*Purchases* is a client for the [RevenueCat](https://www.revenuecat.com/) subscription and purchase tracking system. It is an open source framework that provides a wrapper around `StoreKit` and the RevenueCat backend to make implementing in-app subscriptions in `Swift` or `Objective-C` easy - receipt validation and status tracking included! + +## Features +| | RevenueCat | +| --- | --- | +✅ | Server-side receipt validation +➡️ | [Webhooks](https://docs.revenuecat.com/docs/webhooks) - enhanced server-to-server communication with events for purchases, renewals, cancellations, and more +🖥 | macOS support +🎯 | Subscription status tracking - know whether a user is subscribed whether they're on iOS, Android or web +📊 | Analytics - automatic calculation of metrics like conversion, mrr, and churn +📝 | [Online documentation](https://docs.revenuecat.com/docs) up to date +🔀 | [Integrations](https://www.revenuecat.com/integrations) - over a dozen integrations to easily send purchase data where you need it +💯 | Well maintained - [frequent releases](https://github.com/RevenueCat/purchases-ios/releases) +📮 | Great support - [Help Center](https://revenuecat.zendesk.com) + +## Getting Started +For more detailed information, you can view our complete documentation at [docs.revenuecat.com](https://docs.revenuecat.com/v3.0/docs). diff --git a/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-Info.plist b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-Info.plist new file mode 100644 index 0000000..2243fe6 --- /dev/null +++ b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-acknowledgements.markdown b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-acknowledgements.markdown new file mode 100644 index 0000000..7d53ccb --- /dev/null +++ b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-acknowledgements.markdown @@ -0,0 +1,53 @@ +# Acknowledgements +This application makes use of the following third party libraries: + +## Purchases + +MIT License + +Copyright (c) 2017 Jacob Eiting + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +## PurchasesCoreSwift + +MIT License + +Copyright (c) 2017 Jacob Eiting + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +Generated by CocoaPods - https://cocoapods.org diff --git a/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-acknowledgements.plist b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-acknowledgements.plist new file mode 100644 index 0000000..359b789 --- /dev/null +++ b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-acknowledgements.plist @@ -0,0 +1,91 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + MIT License + +Copyright (c) 2017 Jacob Eiting + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + License + MIT + Title + Purchases + Type + PSGroupSpecifier + + + FooterText + MIT License + +Copyright (c) 2017 Jacob Eiting + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + License + MIT + Title + PurchasesCoreSwift + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-dummy.m b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-dummy.m new file mode 100644 index 0000000..14e5c7b --- /dev/null +++ b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_calm_mind : NSObject +@end +@implementation PodsDummy_Pods_calm_mind +@end diff --git a/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-frameworks-Debug-input-files.xcfilelist b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-frameworks-Debug-input-files.xcfilelist new file mode 100644 index 0000000..72ce417 --- /dev/null +++ b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-frameworks-Debug-input-files.xcfilelist @@ -0,0 +1,3 @@ +${PODS_ROOT}/Target Support Files/Pods-calm-mind/Pods-calm-mind-frameworks.sh +${BUILT_PRODUCTS_DIR}/Purchases/Purchases.framework +${BUILT_PRODUCTS_DIR}/PurchasesCoreSwift/PurchasesCoreSwift.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-frameworks-Debug-output-files.xcfilelist b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-frameworks-Debug-output-files.xcfilelist new file mode 100644 index 0000000..713f291 --- /dev/null +++ b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-frameworks-Debug-output-files.xcfilelist @@ -0,0 +1,2 @@ +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Purchases.framework +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PurchasesCoreSwift.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-frameworks-Release-input-files.xcfilelist b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-frameworks-Release-input-files.xcfilelist new file mode 100644 index 0000000..72ce417 --- /dev/null +++ b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-frameworks-Release-input-files.xcfilelist @@ -0,0 +1,3 @@ +${PODS_ROOT}/Target Support Files/Pods-calm-mind/Pods-calm-mind-frameworks.sh +${BUILT_PRODUCTS_DIR}/Purchases/Purchases.framework +${BUILT_PRODUCTS_DIR}/PurchasesCoreSwift/PurchasesCoreSwift.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-frameworks-Release-output-files.xcfilelist b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-frameworks-Release-output-files.xcfilelist new file mode 100644 index 0000000..713f291 --- /dev/null +++ b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-frameworks-Release-output-files.xcfilelist @@ -0,0 +1,2 @@ +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Purchases.framework +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PurchasesCoreSwift.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-frameworks.sh b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-frameworks.sh new file mode 100755 index 0000000..fe12ed3 --- /dev/null +++ b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-frameworks.sh @@ -0,0 +1,187 @@ +#!/bin/sh +set -e +set -u +set -o pipefail + +function on_error { + echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" +} +trap 'on_error $LINENO' ERR + +if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then + # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy + # frameworks to, so exit 0 (signalling the script phase was successful). + exit 0 +fi + +echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" +mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + +COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" +SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" +BCSYMBOLMAP_DIR="BCSymbolMaps" + + +# This protects against multiple targets copying the same framework dependency at the same time. The solution +# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html +RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") + +# Copies and strips a vendored framework +install_framework() +{ + if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then + local source="${BUILT_PRODUCTS_DIR}/$1" + elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then + local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" + elif [ -r "$1" ]; then + local source="$1" + fi + + local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + + if [ -L "${source}" ]; then + echo "Symlinked..." + source="$(readlink "${source}")" + fi + + if [ -d "${source}/${BCSYMBOLMAP_DIR}" ]; then + # Locate and install any .bcsymbolmaps if present, and remove them from the .framework before the framework is copied + find "${source}/${BCSYMBOLMAP_DIR}" -name "*.bcsymbolmap"|while read f; do + echo "Installing $f" + install_bcsymbolmap "$f" "$destination" + rm "$f" + done + rmdir "${source}/${BCSYMBOLMAP_DIR}" + fi + + # Use filter instead of exclude so missing patterns don't throw errors. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" + + local basename + basename="$(basename -s .framework "$1")" + binary="${destination}/${basename}.framework/${basename}" + + if ! [ -r "$binary" ]; then + binary="${destination}/${basename}" + elif [ -L "${binary}" ]; then + echo "Destination binary is symlinked..." + dirname="$(dirname "${binary}")" + binary="${dirname}/$(readlink "${binary}")" + fi + + # Strip invalid architectures so "fat" simulator / device frameworks work on device + if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then + strip_invalid_archs "$binary" + fi + + # Resign the code if required by the build settings to avoid unstable apps + code_sign_if_enabled "${destination}/$(basename "$1")" + + # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. + if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then + local swift_runtime_libs + swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) + for lib in $swift_runtime_libs; do + echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" + rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" + code_sign_if_enabled "${destination}/${lib}" + done + fi +} +# Copies and strips a vendored dSYM +install_dsym() { + local source="$1" + warn_missing_arch=${2:-true} + if [ -r "$source" ]; then + # Copy the dSYM into the targets temp dir. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" + + local basename + basename="$(basename -s .dSYM "$source")" + binary_name="$(ls "$source/Contents/Resources/DWARF")" + binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}" + + # Strip invalid architectures from the dSYM. + if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then + strip_invalid_archs "$binary" "$warn_missing_arch" + fi + if [[ $STRIP_BINARY_RETVAL == 0 ]]; then + # Move the stripped file into its final destination. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}" + else + # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. + touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.dSYM" + fi + fi +} + +# Used as a return value for each invocation of `strip_invalid_archs` function. +STRIP_BINARY_RETVAL=0 + +# Strip invalid architectures +strip_invalid_archs() { + binary="$1" + warn_missing_arch=${2:-true} + # Get architectures for current target binary + binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" + # Intersect them with the architectures we are building for + intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" + # If there are no archs supported by this binary then warn the user + if [[ -z "$intersected_archs" ]]; then + if [[ "$warn_missing_arch" == "true" ]]; then + echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." + fi + STRIP_BINARY_RETVAL=1 + return + fi + stripped="" + for arch in $binary_archs; do + if ! [[ "${ARCHS}" == *"$arch"* ]]; then + # Strip non-valid architectures in-place + lipo -remove "$arch" -output "$binary" "$binary" + stripped="$stripped $arch" + fi + done + if [[ "$stripped" ]]; then + echo "Stripped $binary of architectures:$stripped" + fi + STRIP_BINARY_RETVAL=0 +} + +# Copies the bcsymbolmap files of a vendored framework +install_bcsymbolmap() { + local bcsymbolmap_path="$1" + local destination="${BUILT_PRODUCTS_DIR}" + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" +} + +# Signs a framework with the provided identity +code_sign_if_enabled() { + if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then + # Use the current code_sign_identity + echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" + local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" + + if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then + code_sign_cmd="$code_sign_cmd &" + fi + echo "$code_sign_cmd" + eval "$code_sign_cmd" + fi +} + +if [[ "$CONFIGURATION" == "Debug" ]]; then + install_framework "${BUILT_PRODUCTS_DIR}/Purchases/Purchases.framework" + install_framework "${BUILT_PRODUCTS_DIR}/PurchasesCoreSwift/PurchasesCoreSwift.framework" +fi +if [[ "$CONFIGURATION" == "Release" ]]; then + install_framework "${BUILT_PRODUCTS_DIR}/Purchases/Purchases.framework" + install_framework "${BUILT_PRODUCTS_DIR}/PurchasesCoreSwift/PurchasesCoreSwift.framework" +fi +if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then + wait +fi diff --git a/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-umbrella.h b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-umbrella.h new file mode 100644 index 0000000..d6db547 --- /dev/null +++ b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind-umbrella.h @@ -0,0 +1,16 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + + +FOUNDATION_EXPORT double Pods_calm_mindVersionNumber; +FOUNDATION_EXPORT const unsigned char Pods_calm_mindVersionString[]; + diff --git a/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind.debug.xcconfig b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind.debug.xcconfig new file mode 100644 index 0000000..b3fbead --- /dev/null +++ b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind.debug.xcconfig @@ -0,0 +1,14 @@ +ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Purchases" "${PODS_CONFIGURATION_BUILD_DIR}/PurchasesCoreSwift" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Purchases/Purchases.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/PurchasesCoreSwift/PurchasesCoreSwift.framework/Headers" +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' +OTHER_LDFLAGS = $(inherited) -framework "Purchases" -framework "PurchasesCoreSwift" -framework "StoreKit" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind.modulemap b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind.modulemap new file mode 100644 index 0000000..3317113 --- /dev/null +++ b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind.modulemap @@ -0,0 +1,6 @@ +framework module Pods_calm_mind { + umbrella header "Pods-calm-mind-umbrella.h" + + export * + module * { export * } +} diff --git a/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind.release.xcconfig b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind.release.xcconfig new file mode 100644 index 0000000..b3fbead --- /dev/null +++ b/Pods/Target Support Files/Pods-calm-mind/Pods-calm-mind.release.xcconfig @@ -0,0 +1,14 @@ +ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Purchases" "${PODS_CONFIGURATION_BUILD_DIR}/PurchasesCoreSwift" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Purchases/Purchases.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/PurchasesCoreSwift/PurchasesCoreSwift.framework/Headers" +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' +OTHER_LDFLAGS = $(inherited) -framework "Purchases" -framework "PurchasesCoreSwift" -framework "StoreKit" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/Purchases/Purchases-Info.plist b/Pods/Target Support Files/Purchases/Purchases-Info.plist new file mode 100644 index 0000000..532c55b --- /dev/null +++ b/Pods/Target Support Files/Purchases/Purchases-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 3.8.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Pods/Target Support Files/Purchases/Purchases-dummy.m b/Pods/Target Support Files/Purchases/Purchases-dummy.m new file mode 100644 index 0000000..11a63f9 --- /dev/null +++ b/Pods/Target Support Files/Purchases/Purchases-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Purchases : NSObject +@end +@implementation PodsDummy_Purchases +@end diff --git a/Pods/Target Support Files/Purchases/Purchases-prefix.pch b/Pods/Target Support Files/Purchases/Purchases-prefix.pch new file mode 100644 index 0000000..beb2a24 --- /dev/null +++ b/Pods/Target Support Files/Purchases/Purchases-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/Pods/Target Support Files/Purchases/Purchases-umbrella.h b/Pods/Target Support Files/Purchases/Purchases-umbrella.h new file mode 100644 index 0000000..2b5e6fc --- /dev/null +++ b/Pods/Target Support Files/Purchases/Purchases-umbrella.h @@ -0,0 +1,29 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + +#import "Purchases.h" +#import "RCAttributionNetwork.h" +#import "RCEntitlementInfo.h" +#import "RCEntitlementInfos.h" +#import "RCIntroEligibility.h" +#import "RCOffering.h" +#import "RCOfferings.h" +#import "RCPackage.h" +#import "RCPurchaserInfo.h" +#import "RCPurchases.h" +#import "RCPurchasesErrors.h" +#import "RCPurchasesErrorUtils.h" +#import "RCTransaction.h" + +FOUNDATION_EXPORT double PurchasesVersionNumber; +FOUNDATION_EXPORT const unsigned char PurchasesVersionString[]; + diff --git a/Pods/Target Support Files/Purchases/Purchases.debug.xcconfig b/Pods/Target Support Files/Purchases/Purchases.debug.xcconfig new file mode 100644 index 0000000..65065ba --- /dev/null +++ b/Pods/Target Support Files/Purchases/Purchases.debug.xcconfig @@ -0,0 +1,14 @@ +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Purchases +DEFINES_MODULE = YES +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/PurchasesCoreSwift" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "StoreKit" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/Purchases +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/Purchases/Purchases.modulemap b/Pods/Target Support Files/Purchases/Purchases.modulemap new file mode 100644 index 0000000..a4a66fa --- /dev/null +++ b/Pods/Target Support Files/Purchases/Purchases.modulemap @@ -0,0 +1,6 @@ +framework module Purchases { + umbrella header "Purchases-umbrella.h" + + export * + module * { export * } +} diff --git a/Pods/Target Support Files/Purchases/Purchases.release.xcconfig b/Pods/Target Support Files/Purchases/Purchases.release.xcconfig new file mode 100644 index 0000000..65065ba --- /dev/null +++ b/Pods/Target Support Files/Purchases/Purchases.release.xcconfig @@ -0,0 +1,14 @@ +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Purchases +DEFINES_MODULE = YES +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/PurchasesCoreSwift" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "StoreKit" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/Purchases +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift-Info.plist b/Pods/Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift-Info.plist new file mode 100644 index 0000000..532c55b --- /dev/null +++ b/Pods/Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 3.8.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Pods/Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift-dummy.m b/Pods/Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift-dummy.m new file mode 100644 index 0000000..d9f8cf7 --- /dev/null +++ b/Pods/Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_PurchasesCoreSwift : NSObject +@end +@implementation PodsDummy_PurchasesCoreSwift +@end diff --git a/Pods/Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift-prefix.pch b/Pods/Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift-prefix.pch new file mode 100644 index 0000000..beb2a24 --- /dev/null +++ b/Pods/Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/Pods/Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift-umbrella.h b/Pods/Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift-umbrella.h new file mode 100644 index 0000000..0998c06 --- /dev/null +++ b/Pods/Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift-umbrella.h @@ -0,0 +1,16 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + + +FOUNDATION_EXPORT double PurchasesCoreSwiftVersionNumber; +FOUNDATION_EXPORT const unsigned char PurchasesCoreSwiftVersionString[]; + diff --git a/Pods/Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift.debug.xcconfig b/Pods/Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift.debug.xcconfig new file mode 100644 index 0000000..3976ba6 --- /dev/null +++ b/Pods/Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift.debug.xcconfig @@ -0,0 +1,14 @@ +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/PurchasesCoreSwift +DEFINES_MODULE = YES +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "StoreKit" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/PurchasesCoreSwift +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift.modulemap b/Pods/Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift.modulemap new file mode 100644 index 0000000..56f570a --- /dev/null +++ b/Pods/Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift.modulemap @@ -0,0 +1,6 @@ +framework module PurchasesCoreSwift { + umbrella header "PurchasesCoreSwift-umbrella.h" + + export * + module * { export * } +} diff --git a/Pods/Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift.release.xcconfig b/Pods/Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift.release.xcconfig new file mode 100644 index 0000000..3976ba6 --- /dev/null +++ b/Pods/Target Support Files/PurchasesCoreSwift/PurchasesCoreSwift.release.xcconfig @@ -0,0 +1,14 @@ +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/PurchasesCoreSwift +DEFINES_MODULE = YES +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "StoreKit" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/PurchasesCoreSwift +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/calm-mind.xcodeproj/project.pbxproj b/calm-mind.xcodeproj/project.pbxproj new file mode 100644 index 0000000..c8decb7 --- /dev/null +++ b/calm-mind.xcodeproj/project.pbxproj @@ -0,0 +1,471 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXBuildFile section */ + 8314328A257596300034FE3A /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83143289257596300034FE3A /* StoreKit.framework */; }; + 83549B5D25893B2700C739A1 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83549B5C25893B2700C739A1 /* Constants.swift */; }; + 83B8F2D3256C5C050047B8AB /* calm_mindApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83B8F2D2256C5C050047B8AB /* calm_mindApp.swift */; }; + 83B8F2D5256C5C050047B8AB /* FeaturedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83B8F2D4256C5C050047B8AB /* FeaturedView.swift */; }; + 83B8F2D7256C5C080047B8AB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 83B8F2D6256C5C080047B8AB /* Assets.xcassets */; }; + 83B8F2DA256C5C080047B8AB /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 83B8F2D9256C5C080047B8AB /* Preview Assets.xcassets */; }; + 83B8F2E3256D875E0047B8AB /* Meditation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83B8F2E2256D875E0047B8AB /* Meditation.swift */; }; + 83B8F2EA256D8E2B0047B8AB /* Card.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83B8F2E9256D8E2B0047B8AB /* Card.swift */; }; + 83DAFD07256D9E7800DCBB0D /* DetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DAFD06256D9E7800DCBB0D /* DetailView.swift */; }; + 83DAFD0B256DB70A00DCBB0D /* AVKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83DAFD0A256DB70A00DCBB0D /* AVKit.framework */; }; + 83DAFD0E256DB7CA00DCBB0D /* AudioService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DAFD0D256DB7CA00DCBB0D /* AudioService.swift */; }; + 83DAFD19256EB20800DCBB0D /* PurchaseService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DAFD18256EB20800DCBB0D /* PurchaseService.swift */; }; + 83ECE3C92571502A00CFE50F /* MedidationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83ECE3C82571502A00CFE50F /* MedidationModel.swift */; }; + 8636E72912072B534BB35897 /* Pods_calm_mind.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32139D220445AF9A5887F44C /* Pods_calm_mind.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 14489AE3477EC945870B7A9C /* Pods-calm-mind.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-calm-mind.debug.xcconfig"; path = "Target Support Files/Pods-calm-mind/Pods-calm-mind.debug.xcconfig"; sourceTree = ""; }; + 32139D220445AF9A5887F44C /* Pods_calm_mind.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_calm_mind.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 83143289257596300034FE3A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; + 83549B5C25893B2700C739A1 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + 83B8F2CF256C5C050047B8AB /* calm-mind.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "calm-mind.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 83B8F2D2256C5C050047B8AB /* calm_mindApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = calm_mindApp.swift; sourceTree = ""; }; + 83B8F2D4256C5C050047B8AB /* FeaturedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedView.swift; sourceTree = ""; }; + 83B8F2D6256C5C080047B8AB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 83B8F2D9256C5C080047B8AB /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 83B8F2DB256C5C080047B8AB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 83B8F2E2256D875E0047B8AB /* Meditation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Meditation.swift; sourceTree = ""; }; + 83B8F2E9256D8E2B0047B8AB /* Card.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Card.swift; sourceTree = ""; }; + 83DAFD06256D9E7800DCBB0D /* DetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailView.swift; sourceTree = ""; }; + 83DAFD0A256DB70A00DCBB0D /* AVKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVKit.framework; path = System/Library/Frameworks/AVKit.framework; sourceTree = SDKROOT; }; + 83DAFD0D256DB7CA00DCBB0D /* AudioService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioService.swift; sourceTree = ""; }; + 83DAFD18256EB20800DCBB0D /* PurchaseService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseService.swift; sourceTree = ""; }; + 83ECE3C82571502A00CFE50F /* MedidationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MedidationModel.swift; sourceTree = ""; }; + A510901CCF618DA7EDD28138 /* Pods-calm-mind.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-calm-mind.release.xcconfig"; path = "Target Support Files/Pods-calm-mind/Pods-calm-mind.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 83B8F2CC256C5C050047B8AB /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8314328A257596300034FE3A /* StoreKit.framework in Frameworks */, + 83DAFD0B256DB70A00DCBB0D /* AVKit.framework in Frameworks */, + 8636E72912072B534BB35897 /* Pods_calm_mind.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 78A768055F5B056A3590146D /* Pods */ = { + isa = PBXGroup; + children = ( + 14489AE3477EC945870B7A9C /* Pods-calm-mind.debug.xcconfig */, + A510901CCF618DA7EDD28138 /* Pods-calm-mind.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 83B8F2C6256C5C050047B8AB = { + isa = PBXGroup; + children = ( + 83B8F2D1256C5C050047B8AB /* calm-mind */, + 83B8F2D0256C5C050047B8AB /* Products */, + 83DAFD09256DB70900DCBB0D /* Frameworks */, + 78A768055F5B056A3590146D /* Pods */, + ); + sourceTree = ""; + }; + 83B8F2D0256C5C050047B8AB /* Products */ = { + isa = PBXGroup; + children = ( + 83B8F2CF256C5C050047B8AB /* calm-mind.app */, + ); + name = Products; + sourceTree = ""; + }; + 83B8F2D1256C5C050047B8AB /* calm-mind */ = { + isa = PBXGroup; + children = ( + 83B8F2D2256C5C050047B8AB /* calm_mindApp.swift */, + 83B8F2EC256D90A10047B8AB /* Views */, + 83ECE3C725714FF800CFE50F /* ViewModels */, + 83B8F2ED256D90AC0047B8AB /* Models */, + 83DAFD0C256DB7B800DCBB0D /* Helper */, + 83B8F2D6256C5C080047B8AB /* Assets.xcassets */, + 83B8F2DB256C5C080047B8AB /* Info.plist */, + 83B8F2D8256C5C080047B8AB /* Preview Content */, + ); + path = "calm-mind"; + sourceTree = ""; + }; + 83B8F2D8256C5C080047B8AB /* Preview Content */ = { + isa = PBXGroup; + children = ( + 83B8F2D9256C5C080047B8AB /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 83B8F2EC256D90A10047B8AB /* Views */ = { + isa = PBXGroup; + children = ( + 83B8F2D4256C5C050047B8AB /* FeaturedView.swift */, + 83B8F2E9256D8E2B0047B8AB /* Card.swift */, + 83DAFD06256D9E7800DCBB0D /* DetailView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 83B8F2ED256D90AC0047B8AB /* Models */ = { + isa = PBXGroup; + children = ( + 83B8F2E2256D875E0047B8AB /* Meditation.swift */, + ); + path = Models; + sourceTree = ""; + }; + 83DAFD09256DB70900DCBB0D /* Frameworks */ = { + isa = PBXGroup; + children = ( + 83143289257596300034FE3A /* StoreKit.framework */, + 83DAFD0A256DB70A00DCBB0D /* AVKit.framework */, + 32139D220445AF9A5887F44C /* Pods_calm_mind.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 83DAFD0C256DB7B800DCBB0D /* Helper */ = { + isa = PBXGroup; + children = ( + 83DAFD0D256DB7CA00DCBB0D /* AudioService.swift */, + 83DAFD18256EB20800DCBB0D /* PurchaseService.swift */, + 83549B5C25893B2700C739A1 /* Constants.swift */, + ); + path = Helper; + sourceTree = ""; + }; + 83ECE3C725714FF800CFE50F /* ViewModels */ = { + isa = PBXGroup; + children = ( + 83ECE3C82571502A00CFE50F /* MedidationModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 83B8F2CE256C5C050047B8AB /* calm-mind */ = { + isa = PBXNativeTarget; + buildConfigurationList = 83B8F2DE256C5C080047B8AB /* Build configuration list for PBXNativeTarget "calm-mind" */; + buildPhases = ( + 2B51AD19419C6AAE5E4CCE23 /* [CP] Check Pods Manifest.lock */, + 83B8F2CB256C5C050047B8AB /* Sources */, + 83B8F2CC256C5C050047B8AB /* Frameworks */, + 83B8F2CD256C5C050047B8AB /* Resources */, + 5D7B9D58696F3A4A05380CD9 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "calm-mind"; + productName = "calm-mind"; + productReference = 83B8F2CF256C5C050047B8AB /* calm-mind.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 83B8F2C7256C5C050047B8AB /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1210; + LastUpgradeCheck = 1210; + TargetAttributes = { + 83B8F2CE256C5C050047B8AB = { + CreatedOnToolsVersion = 12.1; + }; + }; + }; + buildConfigurationList = 83B8F2CA256C5C050047B8AB /* Build configuration list for PBXProject "calm-mind" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 83B8F2C6256C5C050047B8AB; + productRefGroup = 83B8F2D0256C5C050047B8AB /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 83B8F2CE256C5C050047B8AB /* calm-mind */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 83B8F2CD256C5C050047B8AB /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 83B8F2DA256C5C080047B8AB /* Preview Assets.xcassets in Resources */, + 83B8F2D7256C5C080047B8AB /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 2B51AD19419C6AAE5E4CCE23 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-calm-mind-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 5D7B9D58696F3A4A05380CD9 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-calm-mind/Pods-calm-mind-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-calm-mind/Pods-calm-mind-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-calm-mind/Pods-calm-mind-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 83B8F2CB256C5C050047B8AB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 83DAFD0E256DB7CA00DCBB0D /* AudioService.swift in Sources */, + 83DAFD19256EB20800DCBB0D /* PurchaseService.swift in Sources */, + 83B8F2E3256D875E0047B8AB /* Meditation.swift in Sources */, + 83B8F2D5256C5C050047B8AB /* FeaturedView.swift in Sources */, + 83B8F2D3256C5C050047B8AB /* calm_mindApp.swift in Sources */, + 83DAFD07256D9E7800DCBB0D /* DetailView.swift in Sources */, + 83B8F2EA256D8E2B0047B8AB /* Card.swift in Sources */, + 83ECE3C92571502A00CFE50F /* MedidationModel.swift in Sources */, + 83549B5D25893B2700C739A1 /* Constants.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 83B8F2DC256C5C080047B8AB /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.1; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 83B8F2DD256C5C080047B8AB /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.1; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 83B8F2DF256C5C080047B8AB /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 14489AE3477EC945870B7A9C /* Pods-calm-mind.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"calm-mind/Preview Content\""; + DEVELOPMENT_TEAM = RYRJQ978K4; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = "calm-mind/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.codewithchris.calm-mind2"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 83B8F2E0256C5C080047B8AB /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A510901CCF618DA7EDD28138 /* Pods-calm-mind.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"calm-mind/Preview Content\""; + DEVELOPMENT_TEAM = RYRJQ978K4; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = "calm-mind/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.codewithchris.calm-mind2"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 83B8F2CA256C5C050047B8AB /* Build configuration list for PBXProject "calm-mind" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 83B8F2DC256C5C080047B8AB /* Debug */, + 83B8F2DD256C5C080047B8AB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 83B8F2DE256C5C080047B8AB /* Build configuration list for PBXNativeTarget "calm-mind" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 83B8F2DF256C5C080047B8AB /* Debug */, + 83B8F2E0256C5C080047B8AB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 83B8F2C7256C5C050047B8AB /* Project object */; +} diff --git a/calm-mind.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/calm-mind.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/calm-mind.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/calm-mind.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/calm-mind.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/calm-mind.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/calm-mind.xcodeproj/project.xcworkspace/xcuserdata/Chris.xcuserdatad/UserInterfaceState.xcuserstate b/calm-mind.xcodeproj/project.xcworkspace/xcuserdata/Chris.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..6c640ec Binary files /dev/null and b/calm-mind.xcodeproj/project.xcworkspace/xcuserdata/Chris.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/calm-mind.xcodeproj/xcuserdata/Chris.xcuserdatad/xcschemes/xcschememanagement.plist b/calm-mind.xcodeproj/xcuserdata/Chris.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..66f440c --- /dev/null +++ b/calm-mind.xcodeproj/xcuserdata/Chris.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + calm-mind.xcscheme_^#shared#^_ + + orderHint + 3 + + + + diff --git a/calm-mind.xcworkspace/contents.xcworkspacedata b/calm-mind.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..41e5114 --- /dev/null +++ b/calm-mind.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/calm-mind.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/calm-mind.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/calm-mind.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/calm-mind.xcworkspace/xcuserdata/Chris.xcuserdatad/UserInterfaceState.xcuserstate b/calm-mind.xcworkspace/xcuserdata/Chris.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..97da252 Binary files /dev/null and b/calm-mind.xcworkspace/xcuserdata/Chris.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/calm-mind.xcworkspace/xcuserdata/Chris.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/calm-mind.xcworkspace/xcuserdata/Chris.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..e12bce9 --- /dev/null +++ b/calm-mind.xcworkspace/xcuserdata/Chris.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/calm-mind/Assets.xcassets/AccentColor.colorset/Contents.json b/calm-mind/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/calm-mind/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/calm-mind/Assets.xcassets/AppIcon.appiconset/Contents.json b/calm-mind/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..9221b9b --- /dev/null +++ b/calm-mind/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/calm-mind/Assets.xcassets/Contents.json b/calm-mind/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/calm-mind/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/calm-mind/Assets.xcassets/ambition.imageset/Contents.json b/calm-mind/Assets.xcassets/ambition.imageset/Contents.json new file mode 100644 index 0000000..d57820f --- /dev/null +++ b/calm-mind/Assets.xcassets/ambition.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "ambition.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/calm-mind/Assets.xcassets/ambition.imageset/ambition.jpg b/calm-mind/Assets.xcassets/ambition.imageset/ambition.jpg new file mode 100644 index 0000000..1218773 Binary files /dev/null and b/calm-mind/Assets.xcassets/ambition.imageset/ambition.jpg differ diff --git a/calm-mind/Assets.xcassets/calm.imageset/Contents.json b/calm-mind/Assets.xcassets/calm.imageset/Contents.json new file mode 100644 index 0000000..d03a011 --- /dev/null +++ b/calm-mind/Assets.xcassets/calm.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "calm.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/calm-mind/Assets.xcassets/calm.imageset/calm.jpg b/calm-mind/Assets.xcassets/calm.imageset/calm.jpg new file mode 100644 index 0000000..a1f8b02 Binary files /dev/null and b/calm-mind/Assets.xcassets/calm.imageset/calm.jpg differ diff --git a/calm-mind/Assets.xcassets/focus.imageset/Contents.json b/calm-mind/Assets.xcassets/focus.imageset/Contents.json new file mode 100644 index 0000000..20b5582 --- /dev/null +++ b/calm-mind/Assets.xcassets/focus.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "focus.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/calm-mind/Assets.xcassets/focus.imageset/focus.jpg b/calm-mind/Assets.xcassets/focus.imageset/focus.jpg new file mode 100644 index 0000000..514056b Binary files /dev/null and b/calm-mind/Assets.xcassets/focus.imageset/focus.jpg differ diff --git a/calm-mind/Assets.xcassets/reflect.imageset/Contents.json b/calm-mind/Assets.xcassets/reflect.imageset/Contents.json new file mode 100644 index 0000000..9cbeba0 --- /dev/null +++ b/calm-mind/Assets.xcassets/reflect.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "reflect.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/calm-mind/Assets.xcassets/reflect.imageset/reflect.jpg b/calm-mind/Assets.xcassets/reflect.imageset/reflect.jpg new file mode 100644 index 0000000..79cf6b9 Binary files /dev/null and b/calm-mind/Assets.xcassets/reflect.imageset/reflect.jpg differ diff --git a/calm-mind/Assets.xcassets/reflect2.imageset/Contents.json b/calm-mind/Assets.xcassets/reflect2.imageset/Contents.json new file mode 100644 index 0000000..1b8303e --- /dev/null +++ b/calm-mind/Assets.xcassets/reflect2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "reflect2.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/calm-mind/Assets.xcassets/reflect2.imageset/reflect2.jpg b/calm-mind/Assets.xcassets/reflect2.imageset/reflect2.jpg new file mode 100644 index 0000000..2a9ccd3 Binary files /dev/null and b/calm-mind/Assets.xcassets/reflect2.imageset/reflect2.jpg differ diff --git a/calm-mind/Helper/AudioService.swift b/calm-mind/Helper/AudioService.swift new file mode 100644 index 0000000..028a403 --- /dev/null +++ b/calm-mind/Helper/AudioService.swift @@ -0,0 +1,34 @@ +// +// AudioManager.swift +// calm-mind +// +// Created by Christopher Ching on 2020-11-24. +// + +import Foundation +import AVKit + +class AudioService { + + var audioPlayer:AVAudioPlayer? + + func playSample() { + + do { + let path = Bundle.main.path(forResource: "sample", ofType: "mp3") + + if let path = path { + let url = URL(fileURLWithPath: path) + + audioPlayer = try AVAudioPlayer(contentsOf: url) + audioPlayer?.play() + } + } + catch { + + } + } + func stopSample() { + audioPlayer?.stop() + } +} diff --git a/calm-mind/Helper/Constants.swift b/calm-mind/Helper/Constants.swift new file mode 100644 index 0000000..d3bea04 --- /dev/null +++ b/calm-mind/Helper/Constants.swift @@ -0,0 +1,13 @@ +// +// Constants.swift +// calm-mind +// +// Created by Christopher Ching on 2020-12-15. +// + +import Foundation + +struct Constants { + + static var subscriptionProductId = "cm_1499_1m" +} diff --git a/calm-mind/Helper/PurchaseService.swift b/calm-mind/Helper/PurchaseService.swift new file mode 100644 index 0000000..e896d99 --- /dev/null +++ b/calm-mind/Helper/PurchaseService.swift @@ -0,0 +1,41 @@ +// +// PurchaseManager.swift +// calm-mind +// +// Created by Christopher Ching on 2020-11-25. +// + +import Foundation +import Purchases + +class PurchaseService { + + + static func purchase(productId:String?, successfulPurchase:@escaping () -> Void) { + + guard productId != nil else { + return + } + + var skProduct:SKProduct? + + // Find product based on Id + Purchases.shared.products([productId!]) { products in + + if !products.isEmpty { + skProduct = products[0] + + // Purchase it + Purchases.shared.purchaseProduct(skProduct!) { (transaction, purchaseInfo, error, userCancelled) in + + // If successful purchase... + if error == nil && !userCancelled { + successfulPurchase() + } + + } + } + } + } + +} diff --git a/calm-mind/Info.plist b/calm-mind/Info.plist new file mode 100644 index 0000000..efc211a --- /dev/null +++ b/calm-mind/Info.plist @@ -0,0 +1,50 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + UIApplicationSupportsIndirectInputEvents + + UILaunchScreen + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/calm-mind/Models/Meditation.swift b/calm-mind/Models/Meditation.swift new file mode 100644 index 0000000..ab9803c --- /dev/null +++ b/calm-mind/Models/Meditation.swift @@ -0,0 +1,53 @@ +// +// Meditation.swift +// calm-mind +// +// Created by Christopher Ching on 2020-11-24. +// + +import Foundation + +struct Meditation: Identifiable { + + var id = UUID() + var title:String = "" + var desc:String = "" + var imageName = "" + var productId:String? + + static func TestData() -> [Meditation] { + + var array = [Meditation]() + + var m1 = Meditation() + m1.title = "Ambition" + m1.desc = "Channel your inert energy and set a goal that you're scared of achieving." + m1.imageName = "ambition" + m1.productId = "ambition" + array.append(m1) + + var m2 = Meditation() + m2.title = "Reflection" + m2.desc = "Channel your inert energy and set a goal that you're scared of achieving." + m2.imageName = "reflect" + m2.productId = "reflect" + array.append(m2) + + var m3 = Meditation() + m3.title = "Calm" + m3.desc = "Channel your inert energy and set a goal that you're scared of achieving." + m3.imageName = "calm" + m3.productId = "calm" + array.append(m3) + + var m4 = Meditation() + m4.title = "Focus" + m4.desc = "Channel your inert energy and set a goal that you're scared of achieving." + m4.imageName = "focus" + m4.productId = "focus" + array.append(m4) + + return array + } +} + diff --git a/calm-mind/Preview Content/Preview Assets.xcassets/Contents.json b/calm-mind/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/calm-mind/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/calm-mind/ViewModels/MedidationModel.swift b/calm-mind/ViewModels/MedidationModel.swift new file mode 100644 index 0000000..77362cb --- /dev/null +++ b/calm-mind/ViewModels/MedidationModel.swift @@ -0,0 +1,61 @@ +// +// MedidationModel.swift +// calm-mind +// +// Created by Christopher Ching on 2020-11-27. +// + +import Foundation +import SwiftUI +import Purchases + +class MeditationModel: ObservableObject { + + @Published var meditations = [Meditation]() + + // Note: For this demo, we're keeping track of purchases with a dictionary and bool flag. + // This won't fly in a production app. + // In a production app, you would likely persist this data in a database linked to their account + // and then fetch that information from the database. + @Published var userPurchases = [String:Bool]() + @Published var allAccess = false + + init() { + + // Get meditations + self.meditations = Meditation.TestData() + + // Check if the user has an active subscription + Purchases.shared.purchaserInfo { (info, error) in + + // Check the info parameter for active entitlements + if info?.entitlements["allaccess"]?.isActive == true { + + // Unlock all access for the user + self.allAccess = true + } + } + } + + func makeSubscriptionPurchase() { + + // Perform purchase + PurchaseService.purchase(productId: Constants.subscriptionProductId) { + + // Upon successful purchase, set the all access flag + self.allAccess = true + } + } + + func makePurchase(meditation:Meditation) { + + // Perform purchase + PurchaseService.purchase(productId: meditation.productId) { + + // Upon successful purchase, set the purchase status of the meditation + if meditation.productId != nil { + self.userPurchases[meditation.productId!] = true + } + } + } +} diff --git a/calm-mind/Views/Card.swift b/calm-mind/Views/Card.swift new file mode 100644 index 0000000..c4490e1 --- /dev/null +++ b/calm-mind/Views/Card.swift @@ -0,0 +1,58 @@ +// +// Card.swift +// calm-mind +// +// Created by Christopher Ching on 2020-11-24. +// + +import SwiftUI + +struct Card: View { + + var meditation:Meditation + + var body: some View { + + GeometryReader { reader in + + // Background Image + Image(meditation.imageName) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: reader.size.width, height: 400, alignment: .center) + .cornerRadius(15) + + // Text Stack + VStack(alignment: .leading, spacing: 10.0) { + + // Title + Text(meditation.title) + .font(.largeTitle) + .fontWeight(.bold) + + // Description + Text(meditation.desc) + } + .padding([.top, .leading], 20.0) + .shadow(color: .black, radius: 10, x: 2, y: 2) + + // Chevron + Image(systemName: "chevron.right") + .font(.largeTitle) + .position(x: reader.size.width - 40, y: reader.size.height - 50) + } + .foregroundColor(Color.white) + .frame(width: .infinity, height: 400, alignment: .center) + .clipped() + .cornerRadius(15) + .padding([.leading, .trailing]) + + } +} + +struct Card_Previews: PreviewProvider { + static var previews: some View { + + Card(meditation: Meditation.TestData()[0]) + } +} diff --git a/calm-mind/Views/DetailView.swift b/calm-mind/Views/DetailView.swift new file mode 100644 index 0000000..8f659e9 --- /dev/null +++ b/calm-mind/Views/DetailView.swift @@ -0,0 +1,121 @@ +// +// DetailView.swift +// calm-mind +// +// Created by Christopher Ching on 2020-11-24. +// + +import SwiftUI +import AVKit + +struct DetailView: View { + + @EnvironmentObject var model: MeditationModel + @State var isPlaying = false + @Binding var meditation: Meditation? + + var audioPlayer = AudioService() + + var body: some View { + + // If no meditation set, can't display detail + if let meditation = meditation { + + VStack(alignment: .leading, spacing: 20.0) { + + // Meditation Image + Image(meditation.imageName) + .resizable() + .cornerRadius(10) + + // Title + Text(meditation.title) + .font(.largeTitle) + .fontWeight(.bold) + + // Description + Text(meditation.desc) + + // GeometryReader for button width + GeometryReader { + reader in + + // Button stack + VStack { + + // Play button + Button(action: { + if !isPlaying { + audioPlayer.playSample() + } + else { + audioPlayer.stopSample() + } + isPlaying.toggle() + }, label: { + + if !isPlaying { + Text(model.userPurchases[meditation.productId!] == nil && model.allAccess == false ? "Play Sample" : "Play") + } + else { + Text("Stop") + } + }) + .frame(width: reader.size.width - 40, height: 30, alignment: .center) + .foregroundColor(.white) + .padding() + .background(Color.blue) + .cornerRadius(15) + + // If user hasn't purchased, show Buy button + if model.userPurchases[meditation.productId!] == nil && model.allAccess == false { + Button(action: { + + model.makePurchase(meditation: meditation) + + }, label: { + + Text("Buy") + + }) + .frame(width: reader.size.width - 40, height: 30, alignment: .center) + .foregroundColor(.white) + .padding() + .background(Color.blue) + .cornerRadius(15) + } + + if model.allAccess == false { + Button(action: { + + // Buy subscription + model.makeSubscriptionPurchase() + + }, label: { + + Text("Buy Subscription") + + }) + .frame(width: reader.size.width - 40, height: 30, alignment: .center) + .foregroundColor(.white) + .padding() + .background(Color.blue) + .cornerRadius(15) + } + } + } + + + }.padding() + } + } + + +} + +struct DetailView_Previews: PreviewProvider { + static var previews: some View { + DetailView(meditation: Binding.constant(Meditation.TestData()[0])) + .environmentObject(MeditationModel()) + } +} diff --git a/calm-mind/Views/FeaturedView.swift b/calm-mind/Views/FeaturedView.swift new file mode 100644 index 0000000..151db3a --- /dev/null +++ b/calm-mind/Views/FeaturedView.swift @@ -0,0 +1,62 @@ +// +// ContentView.swift +// calm-mind +// +// Created by Christopher Ching on 2020-11-23. +// + +import SwiftUI + +struct FeaturedView: View { + + @EnvironmentObject var model: MeditationModel + @State var showDetail = false + @State var selectedMeditation: Meditation? = nil + + var body: some View { + + // Header text + VStack(alignment: .leading) { + Text("Meditations") + .font(.largeTitle).fontWeight(.heavy).padding([.leading]) + + ScrollView { + + LazyVStack(alignment: .leading, spacing: 20, pinnedViews: []) { + + ForEach(model.meditations) { m in + + Button(action: { + + // Set selected meditation for the detail + self.selectedMeditation = m + + // Toggle flag to show detail + self.showDetail.toggle() + + }) { + + // Each meditation card in the scrollview + Card(meditation: m) + } + .sheet(isPresented: $showDetail, content: { + + // Shows the detail + DetailView(meditation: $selectedMeditation) + }) + + } + } + + } + } + + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + FeaturedView() + .environmentObject(MeditationModel()) + } +} diff --git a/calm-mind/calm_mindApp.swift b/calm-mind/calm_mindApp.swift new file mode 100644 index 0000000..a93b7a3 --- /dev/null +++ b/calm-mind/calm_mindApp.swift @@ -0,0 +1,31 @@ +// +// calm_mindApp.swift +// calm-mind +// +// Created by Christopher Ching on 2020-11-23. +// + +import SwiftUI +import Purchases + +@main +struct calm_mindApp: App { + + init() { + setupRevenueCat() + } + + var body: some Scene { + WindowGroup { + FeaturedView() + .environmentObject(MeditationModel()) + } + } + + func setupRevenueCat() { + + Purchases.debugLogsEnabled = true + Purchases.configure(withAPIKey: "Your_Own_RevenueCat_ApiKey") + // Sign up for a free account at https://codewithchris.com/rcat2 + } +}