Commit cb99e28a authored by Mazy's avatar Mazy

mrege

parent 2bdc4370
File added
PODS:
- AFNetworking (4.0.1):
- AFNetworking/NSURLSession (= 4.0.1)
- AFNetworking/Reachability (= 4.0.1)
- AFNetworking/Security (= 4.0.1)
- AFNetworking/Serialization (= 4.0.1)
- AFNetworking/UIKit (= 4.0.1)
- AFNetworking/NSURLSession (4.0.1):
- AFNetworking/Reachability
- AFNetworking/Security
- AFNetworking/Serialization
- AFNetworking/Reachability (4.0.1)
- AFNetworking/Security (4.0.1)
- AFNetworking/Serialization (4.0.1)
- AFNetworking/UIKit (4.0.1):
- AFNetworking/NSURLSession
- Masonry (1.1.0)
- MBProgressHUD (1.2.0)
- SwiftyStoreKit (0.16.1)
- UMCCommon (7.1.1)
DEPENDENCIES:
- AFNetworking
- Masonry
- MBProgressHUD
- SwiftyStoreKit
- UMCCommon
SPEC REPOS:
trunk:
- AFNetworking
- Masonry
- MBProgressHUD
- SwiftyStoreKit
- UMCCommon
SPEC CHECKSUMS:
AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce
Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
MBProgressHUD: 3ee5efcc380f6a79a7cc9b363dd669c5e1ae7406
SwiftyStoreKit: 6b9c08810269f030586dac1fae8e75871a82e84a
UMCCommon: f455f92c125342e360d6ad9eafe484945351eeb5
PODFILE CHECKSUM: 4f2749c25022ff63f90562fa6a7ba216444b845e
COCOAPODS: 1.9.3
PODS:
- AFNetworking (4.0.1):
- AFNetworking/NSURLSession (= 4.0.1)
- AFNetworking/Reachability (= 4.0.1)
- AFNetworking/Security (= 4.0.1)
- AFNetworking/Serialization (= 4.0.1)
- AFNetworking/UIKit (= 4.0.1)
- AFNetworking/NSURLSession (4.0.1):
- AFNetworking/Reachability
- AFNetworking/Security
- AFNetworking/Serialization
- AFNetworking/Reachability (4.0.1)
- AFNetworking/Security (4.0.1)
- AFNetworking/Serialization (4.0.1)
- AFNetworking/UIKit (4.0.1):
- AFNetworking/NSURLSession
- Masonry (1.1.0)
- MBProgressHUD (1.2.0)
- SwiftyStoreKit (0.16.1)
- UMCCommon (7.1.1)
DEPENDENCIES:
- AFNetworking
- Masonry
- MBProgressHUD
- SwiftyStoreKit
- UMCCommon
SPEC REPOS:
trunk:
- AFNetworking
- Masonry
- MBProgressHUD
- SwiftyStoreKit
- UMCCommon
SPEC CHECKSUMS:
AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce
Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
MBProgressHUD: 3ee5efcc380f6a79a7cc9b363dd669c5e1ae7406
SwiftyStoreKit: 6b9c08810269f030586dac1fae8e75871a82e84a
UMCCommon: f455f92c125342e360d6ad9eafe484945351eeb5
PODFILE CHECKSUM: 4f2749c25022ff63f90562fa6a7ba216444b845e
COCOAPODS: 1.9.3
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1100"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForAnalyzing = "YES"
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0130B3724283586C0E9D2A112D4F2AA1"
BuildableName = "AFNetworking.framework"
BlueprintName = "AFNetworking"
ReferencedContainer = "container:Pods.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
buildConfiguration = "Debug"
allowLocationSimulation = "YES">
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1100"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForAnalyzing = "YES"
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "82B0A41D3031FF27D78E17B0A9A46FB0"
BuildableName = "MBProgressHUD.framework"
BlueprintName = "MBProgressHUD"
ReferencedContainer = "container:Pods.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
buildConfiguration = "Debug"
allowLocationSimulation = "YES">
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1100"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForAnalyzing = "YES"
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "55AF53E6C77A10ED4985E04D74A8878E"
BuildableName = "Masonry.framework"
BlueprintName = "Masonry"
ReferencedContainer = "container:Pods.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
buildConfiguration = "Debug"
allowLocationSimulation = "YES">
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1100"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "596A560B22E98CCD9E2F4E063F10C485"
BuildableName = "Pods_superCleaner.framework"
BlueprintName = "Pods-superCleaner"
ReferencedContainer = "container:Pods.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1100"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForAnalyzing = "YES"
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FCD4F1901DD86FEB184BFDD6673F4A7B"
BuildableName = "SwiftyStoreKit.framework"
BlueprintName = "SwiftyStoreKit"
ReferencedContainer = "container:Pods.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
buildConfiguration = "Debug"
allowLocationSimulation = "YES">
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1100"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForAnalyzing = "YES"
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6F6B630FA5213AB083E7CEF1F986FE44"
BuildableName = "UMCCommon"
BlueprintName = "UMCCommon"
ReferencedContainer = "container:Pods.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
buildConfiguration = "Debug"
allowLocationSimulation = "YES">
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>AFNetworking.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>MBProgressHUD.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>Masonry.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>Pods-superCleaner.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>SwiftyStoreKit.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
<key>UMCCommon.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict/>
</dict>
</plist>
//
// InAppReceipt.swift
// SwiftyStoreKit
//
// Created by phimage on 22/12/15.
// Copyright (c) 2015 Andrea Bizzotto (bizz84@gmail.com)
//
// 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.
import Foundation
// https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html
public class AppleReceiptValidator: ReceiptValidator {
public enum VerifyReceiptURLType: String {
case production = "https://buy.itunes.apple.com/verifyReceipt"
case sandbox = "https://sandbox.itunes.apple.com/verifyReceipt"
}
/// You should always verify your receipt first with the `production` service
/// Note: will auto change to `.sandbox` and validate again if received a 21007 status code from Apple
public var service: VerifyReceiptURLType
private let sharedSecret: String?
/**
* Reference Apple Receipt Validator
* - Parameter service: Either .production or .sandbox
* - Parameter sharedSecret: Only used for receipts that contain auto-renewable subscriptions. Your app’s shared secret (a hexadecimal string).
*/
public init(service: VerifyReceiptURLType = .production, sharedSecret: String? = nil) {
self.service = service
self.sharedSecret = sharedSecret
}
public func validate(receiptData: Data, completion: @escaping (VerifyReceiptResult) -> Void) {
let storeURL = URL(string: service.rawValue)! // safe (until no more)
let storeRequest = NSMutableURLRequest(url: storeURL)
storeRequest.httpMethod = "POST"
let receipt = receiptData.base64EncodedString(options: [])
let requestContents: NSMutableDictionary = [ "receipt-data": receipt ]
// password if defined
if let password = sharedSecret {
requestContents.setValue(password, forKey: "password")
}
// Encore request body
do {
storeRequest.httpBody = try JSONSerialization.data(withJSONObject: requestContents, options: [])
} catch let e {
completion(.error(error: .requestBodyEncodeError(error: e)))
return
}
// Remote task
let task = URLSession.shared.dataTask(with: storeRequest as URLRequest) { data, _, error -> Void in
// there is an error
if let networkError = error {
completion(.error(error: .networkError(error: networkError)))
return
}
// there is no data
guard let safeData = data else {
completion(.error(error: .noRemoteData))
return
}
// cannot decode data
guard let receiptInfo = try? JSONSerialization.jsonObject(with: safeData, options: .mutableLeaves) as? ReceiptInfo ?? [:] else {
let jsonStr = String(data: safeData, encoding: String.Encoding.utf8)
completion(.error(error: .jsonDecodeError(string: jsonStr)))
return
}
// get status from info
if let status = receiptInfo["status"] as? Int {
/*
* http://stackoverflow.com/questions/16187231/how-do-i-know-if-an-in-app-purchase-receipt-comes-from-the-sandbox
* How do I verify my receipt (iOS)?
* Always verify your receipt first with the production URL; proceed to verify
* with the sandbox URL if you receive a 21007 status code. Following this
* approach ensures that you do not have to switch between URLs while your
* application is being tested or reviewed in the sandbox or is live in the
* App Store.
* Note: The 21007 status code indicates that this receipt is a sandbox receipt,
* but it was sent to the production service for verification.
*/
let receiptStatus = ReceiptStatus(rawValue: status) ?? ReceiptStatus.unknown
if case .testReceipt = receiptStatus {
self.service = .sandbox
self.validate(receiptData: receiptData, completion: completion)
} else {
if receiptStatus.isValid {
completion(.success(receipt: receiptInfo))
} else {
completion(.error(error: .receiptInvalid(receipt: receiptInfo, status: receiptStatus)))
}
}
} else {
completion(.error(error: .receiptInvalid(receipt: receiptInfo, status: ReceiptStatus.none)))
}
}
task.resume()
}
}
//
// CompleteTransactionsController.swift
// SwiftyStoreKit
//
// Copyright (c) 2017 Andrea Bizzotto (bizz84@gmail.com)
//
// 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.
import Foundation
import StoreKit
struct CompleteTransactions {
let atomically: Bool
let callback: ([Purchase]) -> Void
init(atomically: Bool, callback: @escaping ([Purchase]) -> Void) {
self.atomically = atomically
self.callback = callback
}
}
class CompleteTransactionsController: TransactionController {
var completeTransactions: CompleteTransactions?
func processTransactions(_ transactions: [SKPaymentTransaction], on paymentQueue: PaymentQueue) -> [SKPaymentTransaction] {
guard let completeTransactions = completeTransactions else {
print("SwiftyStoreKit.completeTransactions() should be called once when the app launches.")
return transactions
}
var unhandledTransactions: [SKPaymentTransaction] = []
var purchases: [Purchase] = []
for transaction in transactions {
let transactionState = transaction.transactionState
if transactionState != .purchasing {
let willFinishTransaction = completeTransactions.atomically || transactionState == .failed
let purchase = Purchase(productId: transaction.payment.productIdentifier, quantity: transaction.payment.quantity, transaction: transaction, originalTransaction: transaction.original, needsFinishTransaction: !willFinishTransaction)
purchases.append(purchase)
if willFinishTransaction {
print("Finishing transaction for payment \"\(transaction.payment.productIdentifier)\" with state: \(transactionState.debugDescription)")
paymentQueue.finishTransaction(transaction)
}
} else {
unhandledTransactions.append(transaction)
}
}
if purchases.count > 0 {
completeTransactions.callback(purchases)
}
return unhandledTransactions
}
}
//
// InAppPurchaseProductRequest.swift
// SwiftyStoreKit
//
// Copyright (c) 2015 Andrea Bizzotto (bizz84@gmail.com)
//
// 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.
import StoreKit
typealias InAppProductRequestCallback = (RetrieveResults) -> Void
public protocol InAppRequest: class {
func start()
func cancel()
}
protocol InAppProductRequest: InAppRequest { }
class InAppProductQueryRequest: NSObject, InAppProductRequest, SKProductsRequestDelegate {
private let callback: InAppProductRequestCallback
private let request: SKProductsRequest
deinit {
request.delegate = nil
}
init(productIds: Set<String>, callback: @escaping InAppProductRequestCallback) {
self.callback = callback
request = SKProductsRequest(productIdentifiers: productIds)
super.init()
request.delegate = self
}
func start() {
request.start()
}
func cancel() {
request.cancel()
}
// MARK: SKProductsRequestDelegate
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
let retrievedProducts = Set<SKProduct>(response.products)
let invalidProductIDs = Set<String>(response.invalidProductIdentifiers)
performCallback(RetrieveResults(retrievedProducts: retrievedProducts,
invalidProductIDs: invalidProductIDs, error: nil))
}
func requestDidFinish(_ request: SKRequest) {
}
func request(_ request: SKRequest, didFailWithError error: Error) {
performCallback(RetrieveResults(retrievedProducts: Set<SKProduct>(), invalidProductIDs: Set<String>(), error: error))
}
private func performCallback(_ results: RetrieveResults) {
DispatchQueue.main.async {
self.callback(results)
}
}
}
This diff is collapsed.
//
// InAppReceiptRefreshRequest.swift
// SwiftyStoreKit
//
// Created by phimage on 23/12/15.
// Copyright (c) 2015 Andrea Bizzotto (bizz84@gmail.com)
//
// 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.
import StoreKit
import Foundation
class InAppReceiptRefreshRequest: NSObject, SKRequestDelegate, InAppRequest {
enum ResultType {
case success
case error(e: Error)
}
typealias RequestCallback = (ResultType) -> Void
typealias ReceiptRefresh = (_ receiptProperties: [String: Any]?, _ callback: @escaping RequestCallback) -> InAppReceiptRefreshRequest
class func refresh(_ receiptProperties: [String: Any]? = nil, callback: @escaping RequestCallback) -> InAppReceiptRefreshRequest {
let request = InAppReceiptRefreshRequest(receiptProperties: receiptProperties, callback: callback)
request.start()
return request
}
let refreshReceiptRequest: SKReceiptRefreshRequest
let callback: RequestCallback
deinit {
refreshReceiptRequest.delegate = nil
}
init(receiptProperties: [String: Any]? = nil, callback: @escaping RequestCallback) {
self.callback = callback
self.refreshReceiptRequest = SKReceiptRefreshRequest(receiptProperties: receiptProperties)
super.init()
self.refreshReceiptRequest.delegate = self
}
func start() {
self.refreshReceiptRequest.start()
}
func cancel() {
self.refreshReceiptRequest.cancel()
}
func requestDidFinish(_ request: SKRequest) {
/*if let resoreRequest = request as? SKReceiptRefreshRequest {
let receiptProperties = resoreRequest.receiptProperties ?? [:]
for (k, v) in receiptProperties {
print("\(k): \(v)")
}
}*/
performCallback(.success)
}
func request(_ request: SKRequest, didFailWithError error: Error) {
// XXX could here check domain and error code to return typed exception
performCallback(.error(e: error))
}
private func performCallback(_ result: ResultType) {
DispatchQueue.main.async {
self.callback(result)
}
}
}
//
// InAppReceiptVerificator.swift
// SwiftyStoreKit
//
// Created by Andrea Bizzotto on 16/05/2017.
// Copyright (c) 2017 Andrea Bizzotto (bizz84@gmail.com)
//
// 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.
import Foundation
class InAppReceiptVerificator: NSObject {
let appStoreReceiptURL: URL?
init(appStoreReceiptURL: URL? = Bundle.main.appStoreReceiptURL) {
self.appStoreReceiptURL = appStoreReceiptURL
}
var appStoreReceiptData: Data? {
guard let receiptDataURL = appStoreReceiptURL,
let data = try? Data(contentsOf: receiptDataURL) else {
return nil
}
return data
}
private var receiptRefreshRequest: InAppReceiptRefreshRequest?
/**
* Verify application receipt.
* - Parameter validator: Validator to check the encrypted receipt and return the receipt in readable format
* - Parameter forceRefresh: If true, refreshes the receipt even if one already exists.
* - Parameter refresh: closure to perform receipt refresh (this is made explicit for testability)
* - Parameter completion: handler for result
*/
@discardableResult
public func verifyReceipt(using validator: ReceiptValidator,
forceRefresh: Bool,
refresh: InAppReceiptRefreshRequest.ReceiptRefresh = InAppReceiptRefreshRequest.refresh,
completion: @escaping (VerifyReceiptResult) -> Void) -> InAppRequest? {
return fetchReceipt(forceRefresh: forceRefresh, refresh: refresh) { result in
switch result {
case .success(let receiptData):
self.verify(receiptData: receiptData, using: validator, completion: completion)
case .error(let error):
completion(.error(error: error))
}
}
}
/**
* Fetch application receipt. This method does two things:
* * If the receipt is missing, refresh it
* * If the receipt is available or is refreshed, validate it
* - Parameter forceRefresh: If true, refreshes the receipt even if one already exists.
* - Parameter refresh: closure to perform receipt refresh (this is made explicit for testability)
* - Parameter completion: handler for result
*/
@discardableResult
public func fetchReceipt(forceRefresh: Bool,
refresh: InAppReceiptRefreshRequest.ReceiptRefresh = InAppReceiptRefreshRequest.refresh,
completion: @escaping (FetchReceiptResult) -> Void) -> InAppRequest? {
if let receiptData = appStoreReceiptData, forceRefresh == false {
completion(.success(receiptData: receiptData))
return nil
} else {
receiptRefreshRequest = refresh(nil) { result in
self.receiptRefreshRequest = nil
switch result {
case .success:
if let receiptData = self.appStoreReceiptData {
completion(.success(receiptData: receiptData))
} else {
completion(.error(error: .noReceiptData))
}
case .error(let e):
completion(.error(error: .networkError(error: e)))
}
}
return receiptRefreshRequest
}
}
/**
* - Parameter receiptData: encrypted receipt data
* - Parameter validator: Validator to check the encrypted receipt and return the receipt in readable format
* - Parameter completion: handler for result
*/
private func verify(receiptData: Data, using validator: ReceiptValidator, completion: @escaping (VerifyReceiptResult) -> Void) {
validator.validate(receiptData: receiptData) { result in
DispatchQueue.main.async {
completion(result)
}
}
}
}
//
// OS.swift
// SwiftyStoreKit
//
// Copyright (c) 2020 Andrea Bizzotto (bizz84@gmail.com)
//
// 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.
import StoreKit
// MARK: - Missing SKMutablePayment init with product on macOS
#if os(OSX)
extension SKMutablePayment {
convenience init(product: SKProduct) {
self.init()
self.productIdentifier = product.productIdentifier
}
}
#endif
// MARK: - Missing SKError on watchOS
#if os(watchOS) && swift(<5.3)
public struct SKError: Error {
public typealias Code = SKErrorCode
let _nsError: NSError
init(_nsError: NSError) {
self._nsError = _nsError
}
var code: Code {
return Code(rawValue: _nsError.code) ?? .unknown
}
static var unknown: Code = .unknown
static var paymentInvalid: Code = .paymentInvalid
}
#endif
//
// PaymentsController.swift
// SwiftyStoreKit
//
// Copyright (c) 2017 Andrea Bizzotto (bizz84@gmail.com)
//
// 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.
import Foundation
import StoreKit
struct Payment: Hashable {
let product: SKProduct
let paymentDiscount: PaymentDiscount?
let quantity: Int
let atomically: Bool
let applicationUsername: String
let simulatesAskToBuyInSandbox: Bool
let callback: (TransactionResult) -> Void
func hash(into hasher: inout Hasher) {
hasher.combine(product)
hasher.combine(quantity)
hasher.combine(atomically)
hasher.combine(applicationUsername)
hasher.combine(simulatesAskToBuyInSandbox)
}
static func == (lhs: Payment, rhs: Payment) -> Bool {
return lhs.product.productIdentifier == rhs.product.productIdentifier
}
}
public struct PaymentDiscount {
let discount: AnyObject?
@available(iOS 12.2, tvOS 12.2, OSX 10.14.4, watchOS 6.2, macCatalyst 13.0, *)
public init(discount: SKPaymentDiscount) {
self.discount = discount
}
private init() {
self.discount = nil
}
}
class PaymentsController: TransactionController {
private var payments: [Payment] = []
private func findPaymentIndex(withProductIdentifier identifier: String) -> Int? {
for payment in payments where payment.product.productIdentifier == identifier {
return payments.firstIndex(of: payment)
}
return nil
}
func hasPayment(_ payment: Payment) -> Bool {
return findPaymentIndex(withProductIdentifier: payment.product.productIdentifier) != nil
}
func append(_ payment: Payment) {
payments.append(payment)
}
func processTransaction(_ transaction: SKPaymentTransaction, on paymentQueue: PaymentQueue) -> Bool {
let transactionProductIdentifier = transaction.payment.productIdentifier
guard let paymentIndex = findPaymentIndex(withProductIdentifier: transactionProductIdentifier) else {
return false
}
let payment = payments[paymentIndex]
let transactionState = transaction.transactionState
if transactionState == .purchased {
let purchase = PurchaseDetails(productId: transactionProductIdentifier, quantity: transaction.payment.quantity, product: payment.product, transaction: transaction, originalTransaction: transaction.original, needsFinishTransaction: !payment.atomically)
payment.callback(.purchased(purchase: purchase))
if payment.atomically {
paymentQueue.finishTransaction(transaction)
}
payments.remove(at: paymentIndex)
return true
}
if transactionState == .restored {
print("Unexpected restored transaction for payment \(transactionProductIdentifier)")
let purchase = PurchaseDetails(productId: transactionProductIdentifier, quantity: transaction.payment.quantity, product: payment.product, transaction: transaction, originalTransaction: transaction.original, needsFinishTransaction: !payment.atomically)
payment.callback(.purchased(purchase: purchase))
if payment.atomically {
paymentQueue.finishTransaction(transaction)
}
payments.remove(at: paymentIndex)
return true
}
if transactionState == .failed {
payment.callback(.failed(error: transactionError(for: transaction.error as NSError?)))
paymentQueue.finishTransaction(transaction)
payments.remove(at: paymentIndex)
return true
}
return false
}
func transactionError(for error: NSError?) -> SKError {
let message = "Unknown error"
let altError = NSError(domain: SKErrorDomain, code: SKError.unknown.rawValue, userInfo: [ NSLocalizedDescriptionKey: message ])
let nsError = error ?? altError
return SKError(_nsError: nsError)
}
func processTransactions(_ transactions: [SKPaymentTransaction], on paymentQueue: PaymentQueue) -> [SKPaymentTransaction] {
return transactions.filter { !processTransaction($0, on: paymentQueue) }
}
}
//
// ProductsInfoController.swift
// SwiftyStoreKit
//
// Copyright (c) 2015 Andrea Bizzotto (bizz84@gmail.com)
//
// 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.
import Foundation
import StoreKit
protocol InAppProductRequestBuilder: class {
func request(productIds: Set<String>, callback: @escaping InAppProductRequestCallback) -> InAppProductRequest
}
class InAppProductQueryRequestBuilder: InAppProductRequestBuilder {
func request(productIds: Set<String>, callback: @escaping InAppProductRequestCallback) -> InAppProductRequest {
return InAppProductQueryRequest(productIds: productIds, callback: callback)
}
}
class ProductsInfoController: NSObject {
struct InAppProductQuery {
let request: InAppProductRequest
var completionHandlers: [InAppProductRequestCallback]
}
let inAppProductRequestBuilder: InAppProductRequestBuilder
init(inAppProductRequestBuilder: InAppProductRequestBuilder = InAppProductQueryRequestBuilder()) {
self.inAppProductRequestBuilder = inAppProductRequestBuilder
}
// As we can have multiple inflight requests, we store them in a dictionary by product ids
private var inflightRequests: [Set<String>: InAppProductQuery] = [:]
@discardableResult
func retrieveProductsInfo(_ productIds: Set<String>, completion: @escaping (RetrieveResults) -> Void) -> InAppProductRequest {
if inflightRequests[productIds] == nil {
let request = inAppProductRequestBuilder.request(productIds: productIds) { results in
if let query = self.inflightRequests[productIds] {
for completion in query.completionHandlers {
completion(results)
}
self.inflightRequests[productIds] = nil
} else {
// should not get here, but if it does it seems reasonable to call the outer completion block
completion(results)
}
}
inflightRequests[productIds] = InAppProductQuery(request: request, completionHandlers: [completion])
request.start()
return request
} else {
inflightRequests[productIds]!.completionHandlers.append(completion)
return inflightRequests[productIds]!.request
}
}
}
//
// RestorePurchasesController.swift
// SwiftyStoreKit
//
// Copyright (c) 2017 Andrea Bizzotto (bizz84@gmail.com)
//
// 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.
import Foundation
import StoreKit
struct RestorePurchases {
let atomically: Bool
let applicationUsername: String?
let callback: ([TransactionResult]) -> Void
init(atomically: Bool, applicationUsername: String? = nil, callback: @escaping ([TransactionResult]) -> Void) {
self.atomically = atomically
self.applicationUsername = applicationUsername
self.callback = callback
}
}
class RestorePurchasesController: TransactionController {
public var restorePurchases: RestorePurchases?
private var restoredPurchases: [TransactionResult] = []
func processTransaction(_ transaction: SKPaymentTransaction, atomically: Bool, on paymentQueue: PaymentQueue) -> Purchase? {
let transactionState = transaction.transactionState
if transactionState == .restored {
let transactionProductIdentifier = transaction.payment.productIdentifier
let purchase = Purchase(productId: transactionProductIdentifier, quantity: transaction.payment.quantity, transaction: transaction, originalTransaction: transaction.original, needsFinishTransaction: !atomically)
if atomically {
paymentQueue.finishTransaction(transaction)
}
return purchase
}
return nil
}
func processTransactions(_ transactions: [SKPaymentTransaction], on paymentQueue: PaymentQueue) -> [SKPaymentTransaction] {
guard let restorePurchases = restorePurchases else {
return transactions
}
var unhandledTransactions: [SKPaymentTransaction] = []
for transaction in transactions {
if let restoredPurchase = processTransaction(transaction, atomically: restorePurchases.atomically, on: paymentQueue) {
restoredPurchases.append(.restored(purchase: restoredPurchase))
} else {
unhandledTransactions.append(transaction)
}
}
return unhandledTransactions
}
func restoreCompletedTransactionsFailed(withError error: Error) {
guard let restorePurchases = restorePurchases else {
print("Callback already called. Returning")
return
}
restoredPurchases.append(.failed(error: SKError(_nsError: error as NSError)))
restorePurchases.callback(restoredPurchases)
// Reset state after error received
restoredPurchases = []
self.restorePurchases = nil
}
func restoreCompletedTransactionsFinished() {
guard let restorePurchases = restorePurchases else {
print("Callback already called. Returning")
return
}
restorePurchases.callback(restoredPurchases)
// Reset state after error transactions finished
restoredPurchases = []
self.restorePurchases = nil
}
}
//
// SKProduct+LocalizedPrice.swift
// SwiftyStoreKit
//
// Created by Andrea Bizzotto on 19/10/2016.
// Copyright (c) 2015 Andrea Bizzotto (bizz84@gmail.com)
//
// 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.
import StoreKit
public extension SKProduct {
var localizedPrice: String? {
return priceFormatter(locale: priceLocale).string(from: price)
}
private func priceFormatter(locale: Locale) -> NumberFormatter {
let formatter = NumberFormatter()
formatter.locale = locale
formatter.numberStyle = .currency
return formatter
}
@available(iOSApplicationExtension 11.2, iOS 11.2, OSX 10.13.2, tvOS 11.2, watchOS 6.2, macCatalyst 13.0, *)
var localizedSubscriptionPeriod: String {
guard let subscriptionPeriod = self.subscriptionPeriod else { return "" }
let dateComponents: DateComponents
switch subscriptionPeriod.unit {
case .day: dateComponents = DateComponents(day: subscriptionPeriod.numberOfUnits)
case .week: dateComponents = DateComponents(weekOfMonth: subscriptionPeriod.numberOfUnits)
case .month: dateComponents = DateComponents(month: subscriptionPeriod.numberOfUnits)
case .year: dateComponents = DateComponents(year: subscriptionPeriod.numberOfUnits)
@unknown default:
print("WARNING: SwiftyStoreKit localizedSubscriptionPeriod does not handle all SKProduct.PeriodUnit cases.")
// Default to month units in the unlikely event a different unit type is added to a future OS version
dateComponents = DateComponents(month: subscriptionPeriod.numberOfUnits)
}
return DateComponentsFormatter.localizedString(from: dateComponents, unitsStyle: .short) ?? ""
}
}
//
// SKProductDiscount+LocalizedPrice.swift
// SwiftyStoreKit
//
// Created by Sam Spencer on 5/29/20.
// Copyright © 2020 Sam Spencer. All rights reserved.
//
// 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.
import StoreKit
@available(iOSApplicationExtension 11.2, iOS 11.2, OSX 10.13.2, tvOS 11.2, watchOS 4.2, macCatalyst 13.0, *)
public extension SKProductDiscount {
/// The formatted discount price of the product using the local currency.
var localizedPrice: String? {
return priceFormatter(locale: priceLocale).string(from: price)
}
private func priceFormatter(locale: Locale) -> NumberFormatter {
let formatter = NumberFormatter()
formatter.locale = locale
formatter.numberStyle = .currency
return formatter
}
/// The formatted, localized period / date for the product discount.
/// - note: The subscription period for the discount is independent of the product's regular subscription period, and does not have to match in units or duration.
var localizedSubscriptionPeriod: String {
let dateComponents: DateComponents
switch subscriptionPeriod.unit {
case .day: dateComponents = DateComponents(day: subscriptionPeriod.numberOfUnits)
case .week: dateComponents = DateComponents(weekOfMonth: subscriptionPeriod.numberOfUnits)
case .month: dateComponents = DateComponents(month: subscriptionPeriod.numberOfUnits)
case .year: dateComponents = DateComponents(year: subscriptionPeriod.numberOfUnits)
@unknown default:
print("WARNING: SwiftyStoreKit localizedSubscriptionPeriod does not handle all SKProduct.PeriodUnit cases.")
// Default to month units in the unlikely event a different unit type is added to a future OS version
dateComponents = DateComponents(month: subscriptionPeriod.numberOfUnits)
}
return DateComponentsFormatter.localizedString(from: dateComponents, unitsStyle: .full) ?? ""
}
}
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:superCleaner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment