1.3.2 launch业务调整
This commit is contained in:
parent
1a06d6346b
commit
4c632a0f61
3
Podfile
3
Podfile
@ -31,7 +31,8 @@ pod 'Tiercel'
|
||||
pod 'MarqueeLabel'
|
||||
#HTML解析
|
||||
pod 'Kanna', '5.3.0'
|
||||
|
||||
#内购管理
|
||||
pod 'SwiftyStoreKit'
|
||||
#广告组
|
||||
pod 'GoogleUserMessagingPlatform', '2.4.0'
|
||||
pod 'Google-Mobile-Ads-SDK', '11.4.0'
|
||||
|
||||
@ -96,6 +96,7 @@ PODS:
|
||||
- SVProgressHUD/Core (= 2.3.1)
|
||||
- SVProgressHUD/Core (2.3.1)
|
||||
- SwiftDate (6.3.1)
|
||||
- SwiftyStoreKit (0.16.1)
|
||||
- Tiercel (3.2.5)
|
||||
- VungleAds (7.4.2)
|
||||
|
||||
@ -120,6 +121,7 @@ DEPENDENCIES:
|
||||
- SnapKit
|
||||
- SVProgressHUD
|
||||
- SwiftDate
|
||||
- SwiftyStoreKit
|
||||
- Tiercel
|
||||
|
||||
SPEC REPOS:
|
||||
@ -150,6 +152,7 @@ SPEC REPOS:
|
||||
- SnapKit
|
||||
- SVProgressHUD
|
||||
- SwiftDate
|
||||
- SwiftyStoreKit
|
||||
- Tiercel
|
||||
- VungleAds
|
||||
|
||||
@ -180,9 +183,10 @@ SPEC CHECKSUMS:
|
||||
SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a
|
||||
SVProgressHUD: 4837c74bdfe2e51e8821c397825996a8d7de6e22
|
||||
SwiftDate: 72d28954e8e1c6c1c0f917ccc8005e4f83c7d4b2
|
||||
SwiftyStoreKit: 6b9c08810269f030586dac1fae8e75871a82e84a
|
||||
Tiercel: c0a73f876a72800333b15f4e7e48791f4ad21e90
|
||||
VungleAds: d7cf66a6dfd390a583ea2b2cfb9af1043b686e58
|
||||
|
||||
PODFILE CHECKSUM: e326003760d5d9820a607d3e116c0c70f7c9b241
|
||||
PODFILE CHECKSUM: f35fee0f7703367babb46f1ea46d4b684a5a1b8a
|
||||
|
||||
COCOAPODS: 1.15.2
|
||||
|
||||
6
Pods/Manifest.lock
generated
6
Pods/Manifest.lock
generated
@ -96,6 +96,7 @@ PODS:
|
||||
- SVProgressHUD/Core (= 2.3.1)
|
||||
- SVProgressHUD/Core (2.3.1)
|
||||
- SwiftDate (6.3.1)
|
||||
- SwiftyStoreKit (0.16.1)
|
||||
- Tiercel (3.2.5)
|
||||
- VungleAds (7.4.2)
|
||||
|
||||
@ -120,6 +121,7 @@ DEPENDENCIES:
|
||||
- SnapKit
|
||||
- SVProgressHUD
|
||||
- SwiftDate
|
||||
- SwiftyStoreKit
|
||||
- Tiercel
|
||||
|
||||
SPEC REPOS:
|
||||
@ -150,6 +152,7 @@ SPEC REPOS:
|
||||
- SnapKit
|
||||
- SVProgressHUD
|
||||
- SwiftDate
|
||||
- SwiftyStoreKit
|
||||
- Tiercel
|
||||
- VungleAds
|
||||
|
||||
@ -180,9 +183,10 @@ SPEC CHECKSUMS:
|
||||
SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a
|
||||
SVProgressHUD: 4837c74bdfe2e51e8821c397825996a8d7de6e22
|
||||
SwiftDate: 72d28954e8e1c6c1c0f917ccc8005e4f83c7d4b2
|
||||
SwiftyStoreKit: 6b9c08810269f030586dac1fae8e75871a82e84a
|
||||
Tiercel: c0a73f876a72800333b15f4e7e48791f4ad21e90
|
||||
VungleAds: d7cf66a6dfd390a583ea2b2cfb9af1043b686e58
|
||||
|
||||
PODFILE CHECKSUM: e326003760d5d9820a607d3e116c0c70f7c9b241
|
||||
PODFILE CHECKSUM: f35fee0f7703367babb46f1ea46d4b684a5a1b8a
|
||||
|
||||
COCOAPODS: 1.15.2
|
||||
|
||||
11665
Pods/Pods.xcodeproj/project.pbxproj
generated
11665
Pods/Pods.xcodeproj/project.pbxproj
generated
File diff suppressed because it is too large
Load Diff
58
Pods/Pods.xcodeproj/xcuserdata/zhou.xcuserdatad/xcschemes/SwiftyStoreKit.xcscheme
generated
Normal file
58
Pods/Pods.xcodeproj/xcuserdata/zhou.xcuserdatad/xcschemes/SwiftyStoreKit.xcscheme
generated
Normal file
@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "FCD4F1901DD86FEB184BFDD6673F4A7B"
|
||||
BuildableName = "SwiftyStoreKit.framework"
|
||||
BlueprintName = "SwiftyStoreKit"
|
||||
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>
|
||||
@ -209,6 +209,11 @@
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>SwiftyStoreKit.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>Tiercel.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
|
||||
7
Pods/SwiftyStoreKit/LICENSE.md
generated
Normal file
7
Pods/SwiftyStoreKit/LICENSE.md
generated
Normal file
@ -0,0 +1,7 @@
|
||||
Copyright (c) 2015-2016 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.
|
||||
812
Pods/SwiftyStoreKit/README.md
generated
Normal file
812
Pods/SwiftyStoreKit/README.md
generated
Normal file
@ -0,0 +1,812 @@
|
||||
<img src="https://github.com/bizz84/SwiftyStoreKit/raw/master/SwiftyStoreKit-logo.png" width=100%>
|
||||
|
||||
[](http://mit-license.org)
|
||||
[](https://developer.apple.com/resources/)
|
||||
[](https://developer.apple.com/swift)
|
||||
[](https://travis-ci.org/bizz84/SwiftyStoreKit)
|
||||
[](https://github.com/bizz84/SwiftyStoreKit/issues)
|
||||
[](https://join.slack.com/t/swiftystorekit/shared_invite/enQtODY3OTYxOTExMzE5LWVkNGY4MzcwY2VjNGM4MGU4NDFhMGE5YmUxMGM3ZTQ4NjVjNTRkNTJhNDAyMWZmY2M5OWE5MDE0ODc3OGJjMmM)
|
||||
|
||||
SwiftyStoreKit is a lightweight In App Purchases framework for iOS, tvOS, watchOS, macOS, and Mac Catalyst.
|
||||
|
||||
## Contributions Wanted
|
||||
SwiftyStoreKit makes it easy for an incredible number of developers to seemlessly integrate in-App Purchases. This project, however, is now **community-led**. We need help building out features and writing tests (see [issue #550](https://github.com/bizz84/SwiftyStoreKit/issues/550)).
|
||||
|
||||
### Maintainers Wanted
|
||||
|
||||
- The author is no longer maintaining this project actively. If you'd like to become a maintainer, [join the Slack workspace](https://join.slack.com/t/swiftystorekit/shared_invite/enQtODY3OTYxOTExMzE5LWVkNGY4MzcwY2VjNGM4MGU4NDFhMGE5YmUxMGM3ZTQ4NjVjNTRkNTJhNDAyMWZmY2M5OWE5MDE0ODc3OGJjMmM) and enter the [#maintainers](https://app.slack.com/client/TL2JYQ458/CLG62K26A/details/) channel.
|
||||
- Going forward, SwiftyStoreKit should be made for the community, by the community.
|
||||
|
||||
More info here:
|
||||
|
||||
- [The Future of SwiftyStoreKit: Maintainers Wanted](https://medium.com/@biz84/the-future-of-swiftystorekit-maintainers-needed-f60d01572c91)
|
||||
|
||||
### Join on Slack
|
||||
|
||||
SwiftyStoreKit is on Slack. [Join here](https://join.slack.com/t/swiftystorekit/shared_invite/enQtODY3OTYxOTExMzE5LWVkNGY4MzcwY2VjNGM4MGU4NDFhMGE5YmUxMGM3ZTQ4NjVjNTRkNTJhNDAyMWZmY2M5OWE5MDE0ODc3OGJjMmM).
|
||||
|
||||
## Content
|
||||
|
||||
- [Requirements](#requirements)
|
||||
- [Installation](#installation)
|
||||
- [Swift Package Manager](#swift-package-manager)
|
||||
- [Carthage](#carthage)
|
||||
- [CocoaPods](#cocoapods)
|
||||
- [Features](#features)
|
||||
- [Contributing](#contributing)
|
||||
- [App startup](#app-startup)
|
||||
- [Complete Transactions](#complete-transactions)
|
||||
- [Purchases](#purchases)
|
||||
- [Retrieve products info](#retrieve-products-info)
|
||||
- [Purchase a product (given a product id)](#purchase-a-product-given-a-product-id)
|
||||
- [Purchase a product (given a SKProduct)](#purchase-a-product-given-a-skproduct)
|
||||
- [Handle purchases started on the App Store (iOS 11)](#handle-purchases-started-on-the-app-store-ios-11)
|
||||
- [Restore previous purchases](#restore-previous-purchases)
|
||||
- [Downloading content hosted with Apple](#downloading-content-hosted-with-apple)
|
||||
- [Receipt verification](#receipt-verification)
|
||||
- [Retrieve local receipt (encrypted)](#retrieve-local-receipt-encrypted)
|
||||
- [Fetch receipt (encrypted)](#fetch-receipt-encrypted)
|
||||
- [Verify Receipt](#verify-receipt)
|
||||
- [Verifying purchases and subscriptions](#verifying-purchases-and-subscriptions)
|
||||
- [Verify Purchase](#verify-purchase)
|
||||
- [Verify Subscription](#verify-subscription)
|
||||
- [Subscription Groups](#subscription-groups)
|
||||
- [Get distinct purchase identifiers](#get-distinct-purchase-identifiers)
|
||||
- [Notes](#notes)
|
||||
- [Change Log](#change-log)
|
||||
- [Sample Code](#sample-code)
|
||||
- [Essential Reading](#essential-reading)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Video Tutorials](#video-tutorials)
|
||||
- [Payment flows: implementation details](#payment-flows-implementation-details)
|
||||
- [Credits](#credits)
|
||||
- [Apps using SwiftyStoreKit](#apps-using-swiftystorekit)
|
||||
- [License](#license)
|
||||
|
||||
## Requirements
|
||||
If you've shipped an app in the last five years, you're probably good to go. Some features (like discounts) are only available on new OS versions, but most features are available as far back as:
|
||||
|
||||
| iOS | watchOS | tvOS | macOS | Mac Catalyst |
|
||||
|-----|---------|------|-------|--------------|
|
||||
| 8.0 | 6.2 | 9.0 | 10.10 | 13.0 |
|
||||
|
||||
## Installation
|
||||
There are a number of ways to install SwiftyStoreKit for your project. Swift Package Manager and Carthage integrations are the preferred and recommended approaches. Unfortunately, CocoaPods is currently not supported / outdated (see below for details).
|
||||
|
||||
Regardless, make sure to import the project wherever you may use it:
|
||||
|
||||
```swift
|
||||
import SwiftyStoreKit
|
||||
```
|
||||
|
||||
### Swift Package Manager
|
||||
|
||||
The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into Xcode and the Swift compiler. **This is the recommended installation method.** Updates to SwiftyStoreKit will always be available immediately to projects with SPM. SPM is also integrated directly with Xcode.
|
||||
|
||||
If you are using Xcode 11 or later:
|
||||
1. Click `File`
|
||||
2. `Swift Packages`
|
||||
3. `Add Package Dependency...`
|
||||
4. Specify the git URL for SwiftyStoreKit.
|
||||
|
||||
```swift
|
||||
https://github.com/bizz84/SwiftyStoreKit.git
|
||||
```
|
||||
|
||||
### Carthage
|
||||
|
||||
To integrate SwiftyStoreKit into your Xcode project using [Carthage](https://github.com/Carthage/Carthage), specify it in your Cartfile:
|
||||
|
||||
```ogdl
|
||||
github "bizz84/SwiftyStoreKit"
|
||||
```
|
||||
|
||||
**NOTE**: Please ensure that you have the [latest](https://github.com/Carthage/Carthage/releases) Carthage installed.
|
||||
|
||||
### CocoaPods
|
||||
SwiftyStoreKit can be installed as a [CocoaPod](https://cocoapods.org/) and builds as a Swift framework. To install, include this in your Podfile.
|
||||
|
||||
```ruby
|
||||
use_frameworks!
|
||||
|
||||
pod 'SwiftyStoreKit'
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Super easy-to-use block-based API
|
||||
- Support for consumable and non-consumable in-app purchases
|
||||
- Support for free, auto-renewable and non-renewing subscriptions
|
||||
- Support for in-app purchases started in the App Store (iOS 11)
|
||||
- Support for subscription discounts and offers
|
||||
- Remote receipt verification
|
||||
- Verify purchases, subscriptions, subscription groups
|
||||
- Downloading content hosted with Apple
|
||||
- iOS, tvOS, watchOS, macOS, and Catalyst compatible
|
||||
|
||||
## Contributing
|
||||
|
||||
#### Got issues / pull requests / want to contribute? [Read here](CONTRIBUTING.md).
|
||||
|
||||
|
||||
## App startup
|
||||
|
||||
### Complete Transactions
|
||||
|
||||
Apple recommends to register a transaction observer [as soon as the app starts](https://developer.apple.com/library/ios/technotes/tn2387/_index.html):
|
||||
> Adding your app's observer at launch ensures that it will persist during all launches of your app, thus allowing your app to receive all the payment queue notifications.
|
||||
|
||||
SwiftyStoreKit supports this by calling `completeTransactions()` when the app starts:
|
||||
|
||||
```swift
|
||||
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
|
||||
// see notes below for the meaning of Atomic / Non-Atomic
|
||||
SwiftyStoreKit.completeTransactions(atomically: true) { purchases in
|
||||
for purchase in purchases {
|
||||
switch purchase.transaction.transactionState {
|
||||
case .purchased, .restored:
|
||||
if purchase.needsFinishTransaction {
|
||||
// Deliver content from server, then:
|
||||
SwiftyStoreKit.finishTransaction(purchase.transaction)
|
||||
}
|
||||
// Unlock content
|
||||
case .failed, .purchasing, .deferred:
|
||||
break // do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
If there are any pending transactions at this point, these will be reported by the completion block so that the app state and UI can be updated.
|
||||
|
||||
If there are no pending transactions, the completion block will **not** be called.
|
||||
|
||||
Note that `completeTransactions()` **should only be called once** in your code, in `application(:didFinishLaunchingWithOptions:)`.
|
||||
|
||||
## Purchases
|
||||
|
||||
### Retrieve products info
|
||||
```swift
|
||||
SwiftyStoreKit.retrieveProductsInfo(["com.musevisions.SwiftyStoreKit.Purchase1"]) { result in
|
||||
if let product = result.retrievedProducts.first {
|
||||
let priceString = product.localizedPrice!
|
||||
print("Product: \(product.localizedDescription), price: \(priceString)")
|
||||
}
|
||||
else if let invalidProductId = result.invalidProductIDs.first {
|
||||
print("Invalid product identifier: \(invalidProductId)")
|
||||
}
|
||||
else {
|
||||
print("Error: \(result.error)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Purchase a product (given a product id)
|
||||
|
||||
* **Atomic**: to be used when the content is delivered immediately.
|
||||
|
||||
```swift
|
||||
SwiftyStoreKit.purchaseProduct("com.musevisions.SwiftyStoreKit.Purchase1", quantity: 1, atomically: true) { result in
|
||||
switch result {
|
||||
case .success(let purchase):
|
||||
print("Purchase Success: \(purchase.productId)")
|
||||
case .error(let error):
|
||||
switch error.code {
|
||||
case .unknown: print("Unknown error. Please contact support")
|
||||
case .clientInvalid: print("Not allowed to make the payment")
|
||||
case .paymentCancelled: break
|
||||
case .paymentInvalid: print("The purchase identifier was invalid")
|
||||
case .paymentNotAllowed: print("The device is not allowed to make the payment")
|
||||
case .storeProductNotAvailable: print("The product is not available in the current storefront")
|
||||
case .cloudServicePermissionDenied: print("Access to cloud service information is not allowed")
|
||||
case .cloudServiceNetworkConnectionFailed: print("Could not connect to the network")
|
||||
case .cloudServiceRevoked: print("User has revoked permission to use this cloud service")
|
||||
default: print((error as NSError).localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* **Non-Atomic**: to be used when the content is delivered by the server.
|
||||
|
||||
```swift
|
||||
SwiftyStoreKit.purchaseProduct("com.musevisions.SwiftyStoreKit.Purchase1", quantity: 1, atomically: false) { result in
|
||||
switch result {
|
||||
case .success(let product):
|
||||
// fetch content from your server, then:
|
||||
if product.needsFinishTransaction {
|
||||
SwiftyStoreKit.finishTransaction(product.transaction)
|
||||
}
|
||||
print("Purchase Success: \(product.productId)")
|
||||
case .error(let error):
|
||||
switch error.code {
|
||||
case .unknown: print("Unknown error. Please contact support")
|
||||
case .clientInvalid: print("Not allowed to make the payment")
|
||||
case .paymentCancelled: break
|
||||
case .paymentInvalid: print("The purchase identifier was invalid")
|
||||
case .paymentNotAllowed: print("The device is not allowed to make the payment")
|
||||
case .storeProductNotAvailable: print("The product is not available in the current storefront")
|
||||
case .cloudServicePermissionDenied: print("Access to cloud service information is not allowed")
|
||||
case .cloudServiceNetworkConnectionFailed: print("Could not connect to the network")
|
||||
case .cloudServiceRevoked: print("User has revoked permission to use this cloud service")
|
||||
default: print((error as NSError).localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Purchase a product (given a SKProduct)
|
||||
|
||||
This is a variant of the method above that can be used to purchase a product when the corresponding `SKProduct` has already been retrieved with `retrieveProductsInfo`:
|
||||
|
||||
```swift
|
||||
SwiftyStoreKit.retrieveProductsInfo(["com.musevisions.SwiftyStoreKit.Purchase1"]) { result in
|
||||
if let product = result.retrievedProducts.first {
|
||||
SwiftyStoreKit.purchaseProduct(product, quantity: 1, atomically: true) { result in
|
||||
// handle result (same as above)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Using this `purchaseProduct` method guarantees that only one network call is made to StoreKit to perform the purchase, as opposed to one call to get the product and another to perform the purchase.
|
||||
|
||||
### Handle purchases started on the App Store (iOS 11)
|
||||
|
||||
iOS 11 adds a new delegate method on `SKPaymentTransactionObserver`:
|
||||
|
||||
```swift
|
||||
@available(iOS 11.0, *)
|
||||
optional public func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool
|
||||
```
|
||||
|
||||
From [Apple Docs](https://developer.apple.com/documentation/storekit/skpaymenttransactionobserver/2877502-paymentqueue):
|
||||
|
||||
> This delegate method is called when the user has started an in-app purchase in the App Store, and is continuing the transaction in your app. Specifically, if your app is already installed, the method is called automatically.
|
||||
If your app is not yet installed when the user starts the in-app purchase in the App Store, the user gets a notification when the app installation is complete. This method is called when the user taps the notification. Otherwise, if the user opens the app manually, this method is called only if the app is opened soon after the purchase was started.
|
||||
|
||||
SwiftyStoreKit supports this with a new handler, called like this:
|
||||
|
||||
```swift
|
||||
SwiftyStoreKit.shouldAddStorePaymentHandler = { payment, product in
|
||||
// return true if the content can be delivered by your app
|
||||
// return false otherwise
|
||||
}
|
||||
```
|
||||
|
||||
To test this in sandbox mode, open this URL in Safari:
|
||||
|
||||
```
|
||||
itms-services://?action=purchaseIntent&bundleId=com.example.app&productIdentifier=product_name
|
||||
```
|
||||
|
||||
More information on the [WWDC17 session What's New in StoreKit](https://developer.apple.com/videos/play/wwdc2017/303)
|
||||
([slide number 165](https://devstreaming-cdn.apple.com/videos/wwdc/2017/303f0u5froddl13/303/303_whats_new_in_storekit.pdf) shows the link above).
|
||||
|
||||
### Restore previous purchases
|
||||
|
||||
According to [Apple - Restoring Purchased Products](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Restoring.html#//apple_ref/doc/uid/TP40008267-CH8-SW9):
|
||||
|
||||
> In most cases, all your app needs to do is refresh its receipt and deliver the products in its receipt. The refreshed receipt contains a record of the user’s purchases in this app, on this device or any other device.
|
||||
|
||||
> Restoring completed transactions creates a new transaction for every completed transaction the user made, essentially replaying history for your transaction queue observer.
|
||||
|
||||
See the **Receipt Verification** section below for how to restore previous purchases using the receipt.
|
||||
|
||||
This section shows how to restore completed transactions with the `restorePurchases` method instead. When successful, the method returns all non-consumable purchases, as well as all auto-renewable subscription purchases, **regardless of whether they are expired or not**.
|
||||
|
||||
* **Atomic**: to be used when the content is delivered immediately.
|
||||
|
||||
```swift
|
||||
SwiftyStoreKit.restorePurchases(atomically: true) { results in
|
||||
if results.restoreFailedPurchases.count > 0 {
|
||||
print("Restore Failed: \(results.restoreFailedPurchases)")
|
||||
}
|
||||
else if results.restoredPurchases.count > 0 {
|
||||
print("Restore Success: \(results.restoredPurchases)")
|
||||
}
|
||||
else {
|
||||
print("Nothing to Restore")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* **Non-Atomic**: to be used when the content is delivered by the server.
|
||||
|
||||
```swift
|
||||
SwiftyStoreKit.restorePurchases(atomically: false) { results in
|
||||
if results.restoreFailedPurchases.count > 0 {
|
||||
print("Restore Failed: \(results.restoreFailedPurchases)")
|
||||
}
|
||||
else if results.restoredPurchases.count > 0 {
|
||||
for purchase in results.restoredPurchases {
|
||||
// fetch content from your server, then:
|
||||
if purchase.needsFinishTransaction {
|
||||
SwiftyStoreKit.finishTransaction(purchase.transaction)
|
||||
}
|
||||
}
|
||||
print("Restore Success: \(results.restoredPurchases)")
|
||||
}
|
||||
else {
|
||||
print("Nothing to Restore")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### What does atomic / non-atomic mean?
|
||||
|
||||
When you purchase a product the following things happen:
|
||||
|
||||
* A payment is added to the payment queue for your IAP.
|
||||
* When the payment has been processed with Apple, the payment queue is updated so that the appropriate transaction can be handled.
|
||||
* If the transaction state is **purchased** or **restored**, the app can unlock the functionality purchased by the user.
|
||||
* The app should call `finishTransaction(_:)` to complete the purchase.
|
||||
|
||||
This is what is [recommended by Apple](https://developer.apple.com/reference/storekit/skpaymentqueue/1506003-finishtransaction):
|
||||
|
||||
> Your application should call `finishTransaction(_:)` only after it has successfully processed the transaction and unlocked the functionality purchased by the user.
|
||||
|
||||
* A purchase is **atomic** when the app unlocks the functionality purchased by the user immediately and call `finishTransaction(_:)` at the same time. This is desirable if you're unlocking functionality that is already inside the app.
|
||||
|
||||
* In cases when you need to make a request to your own server in order to unlock the functionality, you can use a **non-atomic** purchase instead.
|
||||
|
||||
* **Note**: SwiftyStoreKit doesn't yet support downloading content hosted by Apple for non-consumable products. See [this feature request](https://github.com/bizz84/SwiftyStoreKit/issues/128).
|
||||
|
||||
SwiftyStoreKit provides three operations that can be performed **atomically** or **non-atomically**:
|
||||
|
||||
* Making a purchase
|
||||
* Restoring purchases
|
||||
* Completing transactions on app launch
|
||||
|
||||
### Downloading content hosted with Apple
|
||||
|
||||
Quoting Apple Docs:
|
||||
|
||||
> When you create a product in iTunes Connect, you can associate one or more pieces of downloadable content with it. At runtime, when a product is purchased by a user, your app uses SKDownload objects to download the content from the App Store.
|
||||
|
||||
> Your app never directly creates a SKDownload object. Instead, after a payment is processed, your app reads the transaction object’s downloads property to retrieve an array of SKDownload objects associated with the transaction.
|
||||
|
||||
> To download the content, you queue a download object on the payment queue and wait for the content to be downloaded. After a download completes, read the download object’s contentURL property to get a URL to the downloaded content. Your app must process the downloaded file before completing the transaction. For example, it might copy the file into a directory whose contents are persistent. When all downloads are complete, you finish the transaction. After the transaction is finished, the download objects cannot be queued to the payment queue and any URLs to the downloaded content are invalid.
|
||||
|
||||
To start the downloads (this can be done in `purchaseProduct()`, `completeTransactions()` or `restorePurchases()`):
|
||||
|
||||
```swift
|
||||
SwiftyStoreKit.purchaseProduct("com.musevisions.SwiftyStoreKit.Purchase1", quantity: 1, atomically: false) { result in
|
||||
switch result {
|
||||
case .success(let product):
|
||||
let downloads = purchase.transaction.downloads
|
||||
if !downloads.isEmpty {
|
||||
SwiftyStoreKit.start(downloads)
|
||||
}
|
||||
case .error(let error):
|
||||
print("\(error)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To check the updated downloads, setup a `updatedDownloadsHandler` block in your AppDelegate:
|
||||
|
||||
```swift
|
||||
SwiftyStoreKit.updatedDownloadsHandler = { downloads in
|
||||
|
||||
// contentURL is not nil if downloadState == .finished
|
||||
let contentURLs = downloads.flatMap { $0.contentURL }
|
||||
if contentURLs.count == downloads.count {
|
||||
// process all downloaded files, then finish the transaction
|
||||
SwiftyStoreKit.finishTransaction(downloads[0].transaction)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To control the state of the downloads, SwiftyStoreKit offers `start()`, `pause()`, `resume()`, `cancel()` methods.
|
||||
|
||||
## Receipt verification
|
||||
|
||||
According to [Apple - Delivering Products](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/DeliverProduct.html#//apple_ref/doc/uid/TP40008267-CH5-SW4):
|
||||
|
||||
> The app receipt contains a record of the user’s purchases, cryptographically signed by Apple. For more information, see [Receipt Validation Programming Guide](https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Introduction.html#//apple_ref/doc/uid/TP40010573).
|
||||
|
||||
> Information about consumable products is added to the receipt when they’re paid for and remains in the receipt until you finish the transaction. After you finish the transaction, this information is removed the next time the receipt is updated—for example, the next time the user makes a purchase.
|
||||
|
||||
> Information about all other kinds of purchases is added to the receipt when they’re paid for and remains in the receipt indefinitely.
|
||||
|
||||
When an app is first installed, the app receipt is missing.
|
||||
|
||||
As soon as a user completes a purchase or restores purchases, StoreKit creates and stores the receipt locally as a file, located by `Bundle.main.appStoreReceiptURL`.
|
||||
|
||||
### Retrieve local receipt (encrypted)
|
||||
|
||||
This helper can be used to retrieve the (encrypted) local receipt data:
|
||||
|
||||
```swift
|
||||
let receiptData = SwiftyStoreKit.localReceiptData
|
||||
let receiptString = receiptData.base64EncodedString(options: [])
|
||||
// do your receipt validation here
|
||||
```
|
||||
|
||||
However, the receipt file may be missing or outdated.
|
||||
|
||||
### Fetch receipt (encrypted)
|
||||
|
||||
Use this method to get the updated receipt:
|
||||
|
||||
```swift
|
||||
SwiftyStoreKit.fetchReceipt(forceRefresh: true) { result in
|
||||
switch result {
|
||||
case .success(let receiptData):
|
||||
let encryptedReceipt = receiptData.base64EncodedString(options: [])
|
||||
print("Fetch receipt success:\n\(encryptedReceipt)")
|
||||
case .error(let error):
|
||||
print("Fetch receipt failed: \(error)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This method works as follows:
|
||||
|
||||
* If `forceRefresh = false`, it returns the local receipt from file, or refreshes it if missing.
|
||||
* If `forceRefresh = true`, it always refreshes the receipt regardless.
|
||||
|
||||
**Notes**
|
||||
|
||||
* If the local receipt is missing or `forceRefresh = true` when calling `fetchReceipt`, a network call is made to refresh it.
|
||||
* If the user is not logged to the App Store, StoreKit will present a popup asking to **Sign In to the iTunes Store**.
|
||||
* If the user enters valid credentials, the receipt will be refreshed.
|
||||
* If the user cancels, receipt refresh will fail with a **Cannot connect to iTunes Store** error.
|
||||
|
||||
If `fetchReceipt` is successful, it will return the **encrypted** receipt as a string. For this reason, a **validation** step is needed to get all the receipt fields in readable form. This can be done in various ways:
|
||||
|
||||
1. Validate with Apple via the `AppleReceiptValidator` (see [`verifyReceipt`](#verify-receipt) below).
|
||||
2. Perform local receipt validation (see [#101](https://github.com/bizz84/SwiftyStoreKit/issues/101)).
|
||||
3. Post the receipt data and validate on server.
|
||||
|
||||
### Verify Receipt
|
||||
|
||||
Use this method to (optionally) refresh the receipt and perform validation in one step.
|
||||
|
||||
```swift
|
||||
let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
|
||||
SwiftyStoreKit.verifyReceipt(using: appleValidator, forceRefresh: false) { result in
|
||||
switch result {
|
||||
case .success(let receipt):
|
||||
print("Verify receipt success: \(receipt)")
|
||||
case .error(let error):
|
||||
print("Verify receipt failed: \(error)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Notes**
|
||||
|
||||
* This method is based on `fetchReceipt`, and the same refresh logic discussed above applies.
|
||||
* `AppleReceiptValidator` is a **reference implementation** that validates the receipt with Apple and results in a network call. _This is prone to man-in-the-middle attacks._
|
||||
* You should implement your secure logic by validating your receipt locally, or sending the encrypted receipt data and validating it in your server.
|
||||
* Local receipt validation is not implemented (see [issue #101](https://github.com/bizz84/SwiftyStoreKit/issues/101) for details).
|
||||
* You can implement your own receipt validator by conforming to the `ReceiptValidator` protocol and passing it to `verifyReceipt`.
|
||||
|
||||
## Verifying purchases and subscriptions
|
||||
|
||||
Once you have retrieved the receipt using the `verifyReceipt` method, you can verify your purchases and subscriptions by product identifier.
|
||||
|
||||
Verifying multiple purchases and subscriptions in one call is not yet supported (see [issue #194](https://github.com/bizz84/SwiftyStoreKit/issues/194) for more details).
|
||||
|
||||
If you need to verify multiple purchases / subscriptions, you can either:
|
||||
|
||||
* manually parse the receipt dictionary returned by `verifyReceipt`
|
||||
* call `verifyPurchase` or `verifySubscription` multiple times with different product identifiers
|
||||
|
||||
### Verify Purchase
|
||||
|
||||
```swift
|
||||
let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
|
||||
SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
|
||||
switch result {
|
||||
case .success(let receipt):
|
||||
let productId = "com.musevisions.SwiftyStoreKit.Purchase1"
|
||||
// Verify the purchase of Consumable or NonConsumable
|
||||
let purchaseResult = SwiftyStoreKit.verifyPurchase(
|
||||
productId: productId,
|
||||
inReceipt: receipt)
|
||||
|
||||
switch purchaseResult {
|
||||
case .purchased(let receiptItem):
|
||||
print("\(productId) is purchased: \(receiptItem)")
|
||||
case .notPurchased:
|
||||
print("The user has never purchased \(productId)")
|
||||
}
|
||||
case .error(let error):
|
||||
print("Receipt verification failed: \(error)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that for consumable products, the receipt will only include the information for a couple of minutes after the purchase.
|
||||
|
||||
### Verify Subscription
|
||||
|
||||
This can be used to check if a subscription was previously purchased, and whether it is still active or if it's expired.
|
||||
|
||||
From [Apple - Working with Subscriptions](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Subscriptions.html#//apple_ref/doc/uid/TP40008267-CH7-SW6):
|
||||
|
||||
> keep a record of the date that each piece of content is published. Read the Original Purchase Date and Subscription Expiration Date field from each receipt entry to determine the start and end dates of the subscription.
|
||||
|
||||
When one or more subscriptions are found for a given product id, they are returned as a `ReceiptItem` array ordered by `expiryDate`, with the first one being the newest.
|
||||
|
||||
```swift
|
||||
let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
|
||||
SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
|
||||
switch result {
|
||||
case .success(let receipt):
|
||||
let productId = "com.musevisions.SwiftyStoreKit.Subscription"
|
||||
// Verify the purchase of a Subscription
|
||||
let purchaseResult = SwiftyStoreKit.verifySubscription(
|
||||
ofType: .autoRenewable, // or .nonRenewing (see below)
|
||||
productId: productId,
|
||||
inReceipt: receipt)
|
||||
|
||||
switch purchaseResult {
|
||||
case .purchased(let expiryDate, let items):
|
||||
print("\(productId) is valid until \(expiryDate)\n\(items)\n")
|
||||
case .expired(let expiryDate, let items):
|
||||
print("\(productId) is expired since \(expiryDate)\n\(items)\n")
|
||||
case .notPurchased:
|
||||
print("The user has never purchased \(productId)")
|
||||
}
|
||||
|
||||
case .error(let error):
|
||||
print("Receipt verification failed: \(error)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Auto-Renewable
|
||||
```swift
|
||||
let purchaseResult = SwiftyStoreKit.verifySubscription(
|
||||
ofType: .autoRenewable,
|
||||
productId: "com.musevisions.SwiftyStoreKit.Subscription",
|
||||
inReceipt: receipt)
|
||||
```
|
||||
|
||||
#### Non-Renewing
|
||||
```swift
|
||||
// validDuration: time interval in seconds
|
||||
let purchaseResult = SwiftyStoreKit.verifySubscription(
|
||||
ofType: .nonRenewing(validDuration: 3600 * 24 * 30),
|
||||
productId: "com.musevisions.SwiftyStoreKit.Subscription",
|
||||
inReceipt: receipt)
|
||||
```
|
||||
|
||||
**Notes**
|
||||
|
||||
* The expiration dates are calculated against the receipt date. This is the date of the last successful call to `verifyReceipt`.
|
||||
* When purchasing subscriptions in sandbox mode, the expiry dates are set just minutes after the purchase date for testing purposes.
|
||||
|
||||
#### Purchasing and verifying a subscription
|
||||
|
||||
The `verifySubscription` method can be used together with the `purchaseProduct` method to purchase a subscription and check its expiration date, like so:
|
||||
|
||||
```swift
|
||||
let productId = "your-product-id"
|
||||
SwiftyStoreKit.purchaseProduct(productId, atomically: true) { result in
|
||||
|
||||
if case .success(let purchase) = result {
|
||||
// Deliver content from server, then:
|
||||
if purchase.needsFinishTransaction {
|
||||
SwiftyStoreKit.finishTransaction(purchase.transaction)
|
||||
}
|
||||
|
||||
let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
|
||||
SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
|
||||
|
||||
if case .success(let receipt) = result {
|
||||
let purchaseResult = SwiftyStoreKit.verifySubscription(
|
||||
ofType: .autoRenewable,
|
||||
productId: productId,
|
||||
inReceipt: receipt)
|
||||
|
||||
switch purchaseResult {
|
||||
case .purchased(let expiryDate, let receiptItems):
|
||||
print("Product is valid until \(expiryDate)")
|
||||
case .expired(let expiryDate, let receiptItems):
|
||||
print("Product is expired since \(expiryDate)")
|
||||
case .notPurchased:
|
||||
print("This product has never been purchased")
|
||||
}
|
||||
|
||||
} else {
|
||||
// receipt verification error
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// purchase error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Subscription Groups
|
||||
|
||||
From [Apple Docs - Offering Subscriptions](https://developer.apple.com/app-store/subscriptions/):
|
||||
|
||||
> A subscription group is a set of in-app purchases that you can create to provide users with a range of content offerings, service levels, or durations to best meet their needs. Users can only buy one subscription within a subscription group at a time. If users would want to buy more that one type of subscription — for example, to subscribe to more than one channel in a streaming app — you can put these in-app purchases in different subscription groups.
|
||||
|
||||
You can verify all subscriptions within the same group with the `verifySubscriptions` method:
|
||||
|
||||
```swift
|
||||
let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
|
||||
SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
|
||||
switch result {
|
||||
case .success(let receipt):
|
||||
let productIds = Set([ "com.musevisions.SwiftyStoreKit.Weekly",
|
||||
"com.musevisions.SwiftyStoreKit.Monthly",
|
||||
"com.musevisions.SwiftyStoreKit.Yearly" ])
|
||||
let purchaseResult = SwiftyStoreKit.verifySubscriptions(productIds: productIds, inReceipt: receipt)
|
||||
switch purchaseResult {
|
||||
case .purchased(let expiryDate, let items):
|
||||
print("\(productIds) are valid until \(expiryDate)\n\(items)\n")
|
||||
case .expired(let expiryDate, let items):
|
||||
print("\(productIds) are expired since \(expiryDate)\n\(items)\n")
|
||||
case .notPurchased:
|
||||
print("The user has never purchased \(productIds)")
|
||||
}
|
||||
case .error(let error):
|
||||
print("Receipt verification failed: \(error)")
|
||||
}
|
||||
}
|
||||
```
|
||||
#### Get distinct purchase identifiers
|
||||
|
||||
You can retrieve all product identifiers with the `getDistinctPurchaseIds` method:
|
||||
|
||||
```swift
|
||||
let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
|
||||
SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
|
||||
switch result {
|
||||
case .success(let receipt):
|
||||
let productIds = SwiftyStoreKit.getDistinctPurchaseIds(inReceipt receipt: ReceiptInfo)
|
||||
let purchaseResult = SwiftyStoreKit.verifySubscriptions(productIds: productIds, inReceipt: receipt)
|
||||
switch purchaseResult {
|
||||
case .purchased(let expiryDate, let items):
|
||||
print("\(productIds) are valid until \(expiryDate)\n\(items)\n")
|
||||
case .expired(let expiryDate, let items):
|
||||
print("\(productIds) are expired since \(expiryDate)\n\(items)\n")
|
||||
case .notPurchased:
|
||||
print("The user has never purchased \(productIds)")
|
||||
}
|
||||
case .error(let error):
|
||||
print("Receipt verification failed: \(error)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Notes
|
||||
The framework provides a simple block based API with robust error handling on top of the existing StoreKit framework. It does **NOT** persist in app purchases data locally. It is up to clients to do this with a storage solution of choice (i.e. NSUserDefaults, CoreData, Keychain).
|
||||
|
||||
#### Swift 2.x / 3.x / 4.x / 5.x
|
||||
|
||||
| Language | Branch | Pod version | Xcode version |
|
||||
| --------- | ------ | ----------- | ------------- |
|
||||
| Swift 5.x | [master](https://github.com/bizz84/SwiftyStoreKit/tree/master) | >= 0.15.0 | Xcode 10.2 or greater|
|
||||
| Swift 4.x | [master](https://github.com/bizz84/SwiftyStoreKit/tree/master) | >= 0.10.4 | Xcode 9 or greater|
|
||||
| Swift 3.x | [master](https://github.com/bizz84/SwiftyStoreKit/tree/master) | >= 0.5.x | Xcode 8.x |
|
||||
| Swift 2.3 | [swift-2.3](https://github.com/bizz84/SwiftyStoreKit/tree/swift-2.3) | 0.4.x | Xcode 8, Xcode 7.3.x |
|
||||
| Swift 2.2 | [swift-2.2](https://github.com/bizz84/SwiftyStoreKit/tree/swift-2.2) | 0.3.x | Xcode 7.3.x |
|
||||
|
||||
|
||||
## Change Log
|
||||
|
||||
See the [Releases Page](https://github.com/bizz84/SwiftyStoreKit/releases).
|
||||
|
||||
## Sample Code
|
||||
The project includes demo apps [for iOS](https://github.com/bizz84/SwiftyStoreKit/blob/master/SwiftyStoreKit-iOS-Demo/ViewController.swift) [and macOS](https://github.com/bizz84/SwiftyStoreKit/blob/master/SwiftyStoreKit-macOS-Demo/ViewController.swift) showing how to use SwiftyStoreKit.
|
||||
Note that the pre-registered in app purchases in the demo apps are for illustration purposes only and may not work as iTunes Connect may invalidate them.
|
||||
|
||||
## Essential Reading
|
||||
* [Apple - WWDC16, Session 702: Using Store Kit for In-app Purchases with Swift 3](https://developer.apple.com/videos/play/wwdc2016/702/)
|
||||
* [Apple - TN2387: In-App Purchase Best Practices](https://developer.apple.com/library/content/technotes/tn2387/_index.html)
|
||||
* [Apple - TN2413: In-App Purchase FAQ](https://developer.apple.com/library/content/technotes/tn2413/_index.html) (also see [Cannot connect to iTunes Store](https://developer.apple.com/library/content/technotes/tn2413/_index.html#//apple_ref/doc/uid/DTS40016228-CH1-ERROR_MESSAGES-CANNOT_CONNECT_TO_ITUNES_STORE))
|
||||
* [Apple - TN2259: Adding In-App Purchase to Your Applications](https://developer.apple.com/library/content/technotes/tn2259/_index.html)
|
||||
* [iTunes Connect Developer Help - Workflow for configuring in-app purchases](https://help.apple.com/itunes-connect/developer/#/devb57be10e7)
|
||||
* [Apple - About Receipt Validation](https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Introduction.html)
|
||||
* [Apple - Receipt Validation Programming Guide](https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW1)
|
||||
* [Apple - Validating Receipts Locally](https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html)
|
||||
* [Apple - Working with Subscriptions](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Subscriptions.html#//apple_ref/doc/uid/TP40008267-CH7-SW6)
|
||||
* [Apple - Offering Subscriptions](https://developer.apple.com/app-store/subscriptions/)
|
||||
* [Apple - Restoring Purchased Products](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Restoring.html#//apple_ref/doc/uid/TP40008267-CH8-SW9)
|
||||
* [Apple - Testing In-App Purchase Products](https://developer.apple.com/library/content/documentation/LanguagesUtilities/Conceptual/iTunesConnectInAppPurchase_Guide/Chapters/TestingInAppPurchases.html): includes info on duration of subscriptions in sandbox mode
|
||||
* [objc.io - Receipt Validation](https://www.objc.io/issues/17-security/receipt-validation/)
|
||||
|
||||
I have also written about building SwiftyStoreKit on Medium:
|
||||
|
||||
* [How I got 1000 ⭐️ on my GitHub Project](https://medium.com/ios-os-x-development/how-i-got-1000-%EF%B8%8F-on-my-github-project-654d3d394ca6#.1idp27olf)
|
||||
* [Maintaining a Growing Open Source Project](https://medium.com/@biz84/maintaining-a-growing-open-source-project-1d385ca84c5#.4cv2g7tdc)
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
* [Apple TN 2413 - Why are my product identifiers being returned in the invalidProductIdentifiers array?](https://developer.apple.com/library/content/technotes/tn2413/_index.html#//apple_ref/doc/uid/DTS40016228-CH1-TROUBLESHOOTING-WHY_ARE_MY_PRODUCT_IDENTIFIERS_BEING_RETURNED_IN_THE_INVALIDPRODUCTIDENTIFIERS_ARRAY_)
|
||||
* [Invalid Product IDs](http://troybrant.net/blog/2010/01/invalid-product-ids/): Checklist of common mistakes
|
||||
* [Testing Auto-Renewable Subscriptions on iOS](http://davidbarnard.com/post/164337147440/testing-auto-renewable-subscriptions-on-ios)
|
||||
* [Apple forums - iOS 11 beta sandbox - cannot connect to App Store](https://forums.developer.apple.com/message/261428#261428)
|
||||
|
||||
## Video Tutorials
|
||||
|
||||
#### Jared Davidson: In App Purchases! (Swift 3 in Xcode : Swifty Store Kit)
|
||||
|
||||
<a href="https://www.youtube.com/watch?v=dwPFtwDJ7tcb"><img src="https://raw.githubusercontent.com/bizz84/SwiftyStoreKit/master/Screenshots/VideoTutorial-JaredDavidson.jpg" width="854" /></a>
|
||||
|
||||
#### [@rebeloper](https://github.com/rebeloper): Ultimate In-app Purchases Guide
|
||||
|
||||
<a href="https://www.youtube.com/watch?v=MP-U5gQylHc"><img src="https://user-images.githubusercontent.com/2488011/65576278-55cccc80-df7a-11e9-8db5-244e2afa3e46.png" width="100%" /></a>
|
||||
|
||||
## Payment flows: implementation details
|
||||
In order to make a purchase, two operations are needed:
|
||||
|
||||
- Perform a `SKProductRequest` to obtain the `SKProduct` corresponding to the product identifier.
|
||||
|
||||
- Submit the payment and listen for updated transactions on the `SKPaymentQueue`.
|
||||
|
||||
The framework takes care of caching SKProducts so that future requests for the same `SKProduct` don't need to perform a new `SKProductRequest`.
|
||||
|
||||
#### Payment queue
|
||||
|
||||
The following list outlines how requests are processed by SwiftyStoreKit.
|
||||
|
||||
* `SKPaymentQueue` is used to queue payments or restore purchases requests.
|
||||
* Payments are processed serially and in-order and require user interaction.
|
||||
* Restore purchases requests don't require user interaction and can jump ahead of the queue.
|
||||
* `SKPaymentQueue` rejects multiple restore purchases calls.
|
||||
* Failed transactions only ever belong to queued payment requests.
|
||||
* `restoreCompletedTransactionsFailedWithError` is always called when a restore purchases request fails.
|
||||
* `paymentQueueRestoreCompletedTransactionsFinished` is always called following 0 or more update transactions when a restore purchases request succeeds.
|
||||
* A complete transactions handler is require to catch any transactions that are updated when the app is not running.
|
||||
* Registering a complete transactions handler when the app launches ensures that any pending transactions can be cleared.
|
||||
* If a complete transactions handler is missing, pending transactions can be mis-attributed to any new incoming payments or restore purchases.
|
||||
|
||||
The order in which transaction updates are processed is:
|
||||
|
||||
1. payments (transactionState: `.purchased` and `.failed` for matching product identifiers)
|
||||
2. restore purchases (transactionState: `.restored`, or `restoreCompletedTransactionsFailedWithError`, or `paymentQueueRestoreCompletedTransactionsFinished`)
|
||||
3. complete transactions (transactionState: `.purchased`, `.failed`, `.restored`, `.deferred`)
|
||||
|
||||
Any transactions where state is `.purchasing` are ignored.
|
||||
|
||||
See [this pull request](https://github.com/bizz84/SwiftyStoreKit/pull/131) for full details about how the payment flows have been implemented.
|
||||
|
||||
## Credits
|
||||
Many thanks to [phimage](https://github.com/phimage) for adding macOS support and receipt verification.
|
||||
|
||||
## Apps using SwiftyStoreKit
|
||||
|
||||
It would be great to showcase apps using SwiftyStoreKit here. Pull requests welcome :)
|
||||
|
||||
* [Every Plant, Ever](https://itunes.apple.com/us/app/every-plant-ever/id1433967019) - The sticker pack of every plant, ever.
|
||||
* [Countdown](https://countdowns.download/ssk) - Countdown the days until your next vacation, deadline, or event
|
||||
* [MDacne](https://itunes.apple.com/app/id1044050208) - Acne analysis and treatment
|
||||
* [Pixel Picker](https://itunes.apple.com/app/id930804327) - Image Color Picker
|
||||
* [KType](https://itunes.apple.com/app/id1037000234) - Space shooter game
|
||||
* [iPic](https://itunes.apple.com/app/id1101244278) - Automatically upload images and save Markdown links
|
||||
* [iHosts](https://itunes.apple.com/app/id1102004240) - Perfect for editing /etc/hosts
|
||||
* [Arise](http://www.abnehm-app.de/) - Calorie counter
|
||||
* [Truth Truth Lie](https://itunes.apple.com/app/id1130832864) - iMessage game, featured by Apple
|
||||
* [Tactus Music Player](https://itunes.apple.com/app/id557446352) - Alternative music player app
|
||||
* [Drops](https://itunes.apple.com/app/id939540371) - Language learning app
|
||||
* [Fresh Snow](https://itunes.apple.com/app/id1063000470) - Colorado Ski Report
|
||||
* [Zmeu Grand Canyon](http://grandcanyon.zmeu.guide/) - Interactive hiking map & planner
|
||||
* [OB Monitor](https://itunes.apple.com/app/id1073398446) - The app for Texas Longhorns athletics fans
|
||||
* [Talk Dim Sum](https://itunes.apple.com/us/app/talk-dim-sum/id953929066) - Your dim sum companion
|
||||
* [Sluggard](https://itunes.apple.com/app/id1160131071) - Perform simple exercises to reduce the risks of sedentary lifestyle
|
||||
* [Debts iOS](https://debts.ivanvorobei.by/ios) & [Debts macOS](https://debts.ivanvorobei.by/macos) - Track amounts owed
|
||||
|
||||
A full list of apps is published [on AppSight](https://www.appsight.io/sdk/574154).
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2015-2018 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.
|
||||
127
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/AppleReceiptValidator.swift
generated
Normal file
127
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/AppleReceiptValidator.swift
generated
Normal file
@ -0,0 +1,127 @@
|
||||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
77
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/CompleteTransactionsController.swift
generated
Normal file
77
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/CompleteTransactionsController.swift
generated
Normal file
@ -0,0 +1,77 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
81
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/InAppProductQueryRequest.swift
generated
Normal file
81
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/InAppProductQueryRequest.swift
generated
Normal file
@ -0,0 +1,81 @@
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
285
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/InAppReceipt.swift
generated
Normal file
285
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/InAppReceipt.swift
generated
Normal file
@ -0,0 +1,285 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
extension Date {
|
||||
|
||||
init?(millisecondsSince1970: String) {
|
||||
guard let millisecondsNumber = Double(millisecondsSince1970) else {
|
||||
return nil
|
||||
}
|
||||
self = Date(timeIntervalSince1970: millisecondsNumber / 1000)
|
||||
}
|
||||
}
|
||||
|
||||
extension ReceiptItem {
|
||||
|
||||
public init?(receiptInfo: ReceiptInfo) {
|
||||
guard
|
||||
let productId = receiptInfo["product_id"] as? String,
|
||||
let quantityString = receiptInfo["quantity"] as? String,
|
||||
let quantity = Int(quantityString),
|
||||
let transactionId = receiptInfo["transaction_id"] as? String,
|
||||
let originalTransactionId = receiptInfo["original_transaction_id"] as? String,
|
||||
let purchaseDate = ReceiptItem.parseDate(from: receiptInfo, key: "purchase_date_ms"),
|
||||
let originalPurchaseDate = ReceiptItem.parseDate(from: receiptInfo, key: "original_purchase_date_ms")
|
||||
else {
|
||||
print("could not parse receipt item: \(receiptInfo). Skipping...")
|
||||
return nil
|
||||
}
|
||||
self.productId = productId
|
||||
self.quantity = quantity
|
||||
self.transactionId = transactionId
|
||||
self.originalTransactionId = originalTransactionId
|
||||
self.purchaseDate = purchaseDate
|
||||
self.originalPurchaseDate = originalPurchaseDate
|
||||
self.webOrderLineItemId = receiptInfo["web_order_line_item_id"] as? String
|
||||
self.subscriptionExpirationDate = ReceiptItem.parseDate(from: receiptInfo, key: "expires_date_ms")
|
||||
self.cancellationDate = ReceiptItem.parseDate(from: receiptInfo, key: "cancellation_date_ms")
|
||||
if let isTrialPeriod = receiptInfo["is_trial_period"] as? String {
|
||||
self.isTrialPeriod = Bool(isTrialPeriod) ?? false
|
||||
} else {
|
||||
self.isTrialPeriod = false
|
||||
}
|
||||
if let isInIntroOfferPeriod = receiptInfo["is_in_intro_offer_period"] as? String {
|
||||
self.isInIntroOfferPeriod = Bool(isInIntroOfferPeriod) ?? false
|
||||
} else {
|
||||
self.isInIntroOfferPeriod = false
|
||||
}
|
||||
}
|
||||
|
||||
private static func parseDate(from receiptInfo: ReceiptInfo, key: String) -> Date? {
|
||||
|
||||
guard
|
||||
let requestDateString = receiptInfo[key] as? String,
|
||||
let requestDateMs = Double(requestDateString) else {
|
||||
return nil
|
||||
}
|
||||
return Date(timeIntervalSince1970: requestDateMs / 1000)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - receipt mangement
|
||||
internal class InAppReceipt {
|
||||
|
||||
/**
|
||||
* Verify the purchase of a Consumable or NonConsumable product in a receipt
|
||||
* - Parameter productId: the product id of the purchase to verify
|
||||
* - Parameter inReceipt: the receipt to use for looking up the purchase
|
||||
* - return: either notPurchased or purchased
|
||||
*/
|
||||
class func verifyPurchase(
|
||||
productId: String,
|
||||
inReceipt receipt: ReceiptInfo
|
||||
) -> VerifyPurchaseResult {
|
||||
|
||||
// Get receipts info for the product
|
||||
let receipts = getInAppReceipts(receipt: receipt)
|
||||
let filteredReceiptsInfo = filterReceiptsInfo(receipts: receipts, withProductIds: [productId])
|
||||
let nonCancelledReceiptsInfo = filteredReceiptsInfo.filter { receipt in receipt["cancellation_date"] == nil }
|
||||
|
||||
#if swift(>=4.1)
|
||||
let receiptItems = nonCancelledReceiptsInfo.compactMap { ReceiptItem(receiptInfo: $0) }
|
||||
#else
|
||||
let receiptItems = nonCancelledReceiptsInfo.flatMap { ReceiptItem(receiptInfo: $0) }
|
||||
#endif
|
||||
|
||||
// Verify that at least one receipt has the right product id
|
||||
if let firstItem = receiptItems.first {
|
||||
return .purchased(item: firstItem)
|
||||
}
|
||||
return .notPurchased
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the validity of a set of subscriptions in a receipt.
|
||||
*
|
||||
* This method extracts all transactions matching the given productIds and sorts them by date in descending order. It then compares the first transaction expiry date against the receipt date, to determine its validity.
|
||||
* - Note: You can use this method to check the validity of (mutually exclusive) subscriptions in a subscription group.
|
||||
* - Remark: The type parameter determines how the expiration dates are calculated for all subscriptions. Make sure all productIds match the specified subscription type to avoid incorrect results.
|
||||
* - Parameter type: .autoRenewable or .nonRenewing.
|
||||
* - Parameter productIds: The product ids of the subscriptions to verify.
|
||||
* - Parameter receipt: The receipt to use for looking up the subscriptions
|
||||
* - Parameter validUntil: Date to check against the expiry date of the subscriptions. This is only used if a date is not found in the receipt.
|
||||
* - return: Either .notPurchased or .purchased / .expired with the expiry date found in the receipt.
|
||||
*/
|
||||
class func verifySubscriptions(
|
||||
ofType type: SubscriptionType,
|
||||
productIds: Set<String>,
|
||||
inReceipt receipt: ReceiptInfo,
|
||||
validUntil date: Date = Date()
|
||||
) -> VerifySubscriptionResult {
|
||||
|
||||
// The values of the latest_receipt and latest_receipt_info keys are useful when checking whether an auto-renewable subscription is currently active. By providing any transaction receipt for the subscription and checking these values, you can get information about the currently-active subscription period. If the receipt being validated is for the latest renewal, the value for latest_receipt is the same as receipt-data (in the request) and the value for latest_receipt_info is the same as receipt.
|
||||
let (receipts, duration) = getReceiptsAndDuration(for: type, inReceipt: receipt)
|
||||
let receiptsInfo = filterReceiptsInfo(receipts: receipts, withProductIds: productIds)
|
||||
let nonCancelledReceiptsInfo = receiptsInfo.filter { receipt in receipt["cancellation_date"] == nil }
|
||||
if nonCancelledReceiptsInfo.count == 0 {
|
||||
return .notPurchased
|
||||
}
|
||||
|
||||
let receiptDate = getReceiptRequestDate(inReceipt: receipt) ?? date
|
||||
|
||||
#if swift(>=4.1)
|
||||
let receiptItems = nonCancelledReceiptsInfo.compactMap { ReceiptItem(receiptInfo: $0) }
|
||||
#else
|
||||
let receiptItems = nonCancelledReceiptsInfo.flatMap { ReceiptItem(receiptInfo: $0) }
|
||||
#endif
|
||||
|
||||
if nonCancelledReceiptsInfo.count > receiptItems.count {
|
||||
print("receipt has \(nonCancelledReceiptsInfo.count) items, but only \(receiptItems.count) were parsed")
|
||||
}
|
||||
|
||||
let sortedExpiryDatesAndItems = expiryDatesAndItems(receiptItems: receiptItems, duration: duration).sorted { a, b in
|
||||
return a.0 > b.0
|
||||
}
|
||||
|
||||
guard let firstExpiryDateItemPair = sortedExpiryDatesAndItems.first else {
|
||||
return .notPurchased
|
||||
}
|
||||
|
||||
let sortedReceiptItems = sortedExpiryDatesAndItems.map { $0.1 }
|
||||
if firstExpiryDateItemPair.0 > receiptDate {
|
||||
return .purchased(expiryDate: firstExpiryDateItemPair.0, items: sortedReceiptItems)
|
||||
} else {
|
||||
return .expired(expiryDate: firstExpiryDateItemPair.0, items: sortedReceiptItems)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the distinct product identifiers from receipt.
|
||||
*
|
||||
* This Method extracts all product identifiers. (Including cancelled ones).
|
||||
* - Note: You can use this method to get all unique product identifiers from receipt.
|
||||
* - Parameter type: .autoRenewable or .nonRenewing.
|
||||
* - Parameter receipt: The receipt to use for looking up the product identifiers.
|
||||
* - return: Either Set<String> or nil.
|
||||
*/
|
||||
class func getDistinctPurchaseIds(
|
||||
ofType type: SubscriptionType,
|
||||
inReceipt receipt: ReceiptInfo
|
||||
) -> Set<String>? {
|
||||
|
||||
// Get receipts array from receipt
|
||||
guard let receipts = getReceipts(for: type, inReceipt: receipt) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
#if swift(>=4.1)
|
||||
let receiptIds = receipts.compactMap { ReceiptItem(receiptInfo: $0)?.productId }
|
||||
#else
|
||||
let receiptIds = receipts.flatMap { ReceiptItem(receiptInfo: $0)?.productId }
|
||||
#endif
|
||||
|
||||
if receiptIds.isEmpty {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Set(receiptIds)
|
||||
}
|
||||
|
||||
private class func expiryDatesAndItems(receiptItems: [ReceiptItem], duration: TimeInterval?) -> [(Date, ReceiptItem)] {
|
||||
|
||||
if let duration = duration {
|
||||
return receiptItems.map {
|
||||
let expirationDate = Date(timeIntervalSince1970: $0.originalPurchaseDate.timeIntervalSince1970 + duration)
|
||||
return (expirationDate, $0)
|
||||
}
|
||||
} else {
|
||||
#if swift(>=4.1)
|
||||
return receiptItems.compactMap {
|
||||
if let expirationDate = $0.subscriptionExpirationDate {
|
||||
return (expirationDate, $0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
#else
|
||||
return receiptItems.flatMap {
|
||||
if let expirationDate = $0.subscriptionExpirationDate {
|
||||
return (expirationDate, $0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private class func getReceipts(for subscriptionType: SubscriptionType, inReceipt receipt: ReceiptInfo) -> [ReceiptInfo]? {
|
||||
switch subscriptionType {
|
||||
case .autoRenewable:
|
||||
return receipt["latest_receipt_info"] as? [ReceiptInfo]
|
||||
case .nonRenewing:
|
||||
return getInAppReceipts(receipt: receipt)
|
||||
}
|
||||
}
|
||||
|
||||
private class func getReceiptsAndDuration(for subscriptionType: SubscriptionType, inReceipt receipt: ReceiptInfo) -> ([ReceiptInfo]?, TimeInterval?) {
|
||||
switch subscriptionType {
|
||||
case .autoRenewable:
|
||||
return (receipt["latest_receipt_info"] as? [ReceiptInfo], nil)
|
||||
case .nonRenewing(let duration):
|
||||
return (getInAppReceipts(receipt: receipt), duration)
|
||||
}
|
||||
}
|
||||
|
||||
private class func getReceiptRequestDate(inReceipt receipt: ReceiptInfo) -> Date? {
|
||||
|
||||
guard let receiptInfo = receipt["receipt"] as? ReceiptInfo,
|
||||
let requestDateString = receiptInfo["request_date_ms"] as? String else {
|
||||
return nil
|
||||
}
|
||||
return Date(millisecondsSince1970: requestDateString)
|
||||
}
|
||||
|
||||
private class func getInAppReceipts(receipt: ReceiptInfo) -> [ReceiptInfo]? {
|
||||
|
||||
let appReceipt = receipt["receipt"] as? ReceiptInfo
|
||||
return appReceipt?["in_app"] as? [ReceiptInfo]
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the receipts info for a specific product
|
||||
* - Parameter receipts: the receipts array to grab info from
|
||||
* - Parameter productId: the product id
|
||||
*/
|
||||
private class func filterReceiptsInfo(receipts: [ReceiptInfo]?, withProductIds productIds: Set<String>) -> [ReceiptInfo] {
|
||||
|
||||
guard let receipts = receipts else {
|
||||
return []
|
||||
}
|
||||
|
||||
// Filter receipts with matching product ids
|
||||
let receiptsMatchingProductIds = receipts
|
||||
.filter { (receipt) -> Bool in
|
||||
if let productId = receipt["product_id"] as? String {
|
||||
return productIds.contains(productId)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return receiptsMatchingProductIds
|
||||
}
|
||||
}
|
||||
85
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/InAppReceiptRefreshRequest.swift
generated
Normal file
85
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/InAppReceiptRefreshRequest.swift
generated
Normal file
@ -0,0 +1,85 @@
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
119
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/InAppReceiptVerificator.swift
generated
Normal file
119
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/InAppReceiptVerificator.swift
generated
Normal file
@ -0,0 +1,119 @@
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
56
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/OS.swift
generated
Normal file
56
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/OS.swift
generated
Normal file
@ -0,0 +1,56 @@
|
||||
//
|
||||
// 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
|
||||
261
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/PaymentQueueController.swift
generated
Normal file
261
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/PaymentQueueController.swift
generated
Normal file
@ -0,0 +1,261 @@
|
||||
//
|
||||
// PaymentQueueController.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
|
||||
|
||||
protocol TransactionController {
|
||||
|
||||
/// Process the supplied transactions on a given queue.
|
||||
/// - parameter transactions: transactions to process
|
||||
/// - parameter paymentQueue: payment queue for finishing transactions
|
||||
/// - returns: array of unhandled transactions
|
||||
func processTransactions(_ transactions: [SKPaymentTransaction], on paymentQueue: PaymentQueue) -> [SKPaymentTransaction]
|
||||
}
|
||||
|
||||
public enum TransactionResult {
|
||||
case purchased(purchase: PurchaseDetails)
|
||||
case restored(purchase: Purchase)
|
||||
case failed(error: SKError)
|
||||
}
|
||||
|
||||
public protocol PaymentQueue: class {
|
||||
|
||||
func add(_ observer: SKPaymentTransactionObserver)
|
||||
func remove(_ observer: SKPaymentTransactionObserver)
|
||||
|
||||
func add(_ payment: SKPayment)
|
||||
|
||||
func start(_ downloads: [SKDownload])
|
||||
func pause(_ downloads: [SKDownload])
|
||||
func resume(_ downloads: [SKDownload])
|
||||
func cancel(_ downloads: [SKDownload])
|
||||
|
||||
func restoreCompletedTransactions(withApplicationUsername username: String?)
|
||||
|
||||
func finishTransaction(_ transaction: SKPaymentTransaction)
|
||||
}
|
||||
|
||||
extension SKPaymentQueue: PaymentQueue {
|
||||
#if os(watchOS) && swift(<5.3)
|
||||
public func resume(_ downloads: [SKDownload]) {
|
||||
resumeDownloads(downloads)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
extension SKPaymentTransaction {
|
||||
|
||||
open override var debugDescription: String {
|
||||
let transactionId = transactionIdentifier ?? "null"
|
||||
return "productId: \(payment.productIdentifier), transactionId: \(transactionId), state: \(transactionState), date: \(String(describing: transactionDate))"
|
||||
}
|
||||
}
|
||||
|
||||
extension SKPaymentTransactionState: CustomDebugStringConvertible {
|
||||
|
||||
public var debugDescription: String {
|
||||
|
||||
switch self {
|
||||
case .purchasing: return "purchasing"
|
||||
case .purchased: return "purchased"
|
||||
case .failed: return "failed"
|
||||
case .restored: return "restored"
|
||||
case .deferred: return "deferred"
|
||||
@unknown default: return "default"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PaymentQueueController: NSObject, SKPaymentTransactionObserver {
|
||||
|
||||
private let paymentsController: PaymentsController
|
||||
|
||||
private let restorePurchasesController: RestorePurchasesController
|
||||
|
||||
private let completeTransactionsController: CompleteTransactionsController
|
||||
|
||||
unowned let paymentQueue: PaymentQueue
|
||||
|
||||
deinit {
|
||||
paymentQueue.remove(self)
|
||||
}
|
||||
|
||||
init(paymentQueue: PaymentQueue = SKPaymentQueue.default(),
|
||||
paymentsController: PaymentsController = PaymentsController(),
|
||||
restorePurchasesController: RestorePurchasesController = RestorePurchasesController(),
|
||||
completeTransactionsController: CompleteTransactionsController = CompleteTransactionsController()) {
|
||||
|
||||
self.paymentQueue = paymentQueue
|
||||
self.paymentsController = paymentsController
|
||||
self.restorePurchasesController = restorePurchasesController
|
||||
self.completeTransactionsController = completeTransactionsController
|
||||
super.init()
|
||||
paymentQueue.add(self)
|
||||
}
|
||||
|
||||
private func assertCompleteTransactionsWasCalled() {
|
||||
|
||||
let message = "SwiftyStoreKit.completeTransactions() must be called when the app launches."
|
||||
assert(completeTransactionsController.completeTransactions != nil, message)
|
||||
}
|
||||
|
||||
func startPayment(_ payment: Payment) {
|
||||
assertCompleteTransactionsWasCalled()
|
||||
|
||||
let skPayment = SKMutablePayment(product: payment.product)
|
||||
skPayment.applicationUsername = payment.applicationUsername
|
||||
skPayment.quantity = payment.quantity
|
||||
|
||||
if #available(iOS 12.2, tvOS 12.2, OSX 10.14.4, watchOS 6.2, *) {
|
||||
if let discount = payment.paymentDiscount?.discount as? SKPaymentDiscount {
|
||||
skPayment.paymentDiscount = discount
|
||||
}
|
||||
}
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
if #available(iOS 8.3, watchOS 6.2, *) {
|
||||
skPayment.simulatesAskToBuyInSandbox = payment.simulatesAskToBuyInSandbox
|
||||
}
|
||||
#endif
|
||||
|
||||
paymentQueue.add(skPayment)
|
||||
|
||||
paymentsController.append(payment)
|
||||
}
|
||||
|
||||
func restorePurchases(_ restorePurchases: RestorePurchases) {
|
||||
assertCompleteTransactionsWasCalled()
|
||||
|
||||
if restorePurchasesController.restorePurchases != nil {
|
||||
return
|
||||
}
|
||||
|
||||
paymentQueue.restoreCompletedTransactions(withApplicationUsername: restorePurchases.applicationUsername)
|
||||
|
||||
restorePurchasesController.restorePurchases = restorePurchases
|
||||
}
|
||||
|
||||
func completeTransactions(_ completeTransactions: CompleteTransactions) {
|
||||
|
||||
guard completeTransactionsController.completeTransactions == nil else {
|
||||
print("SwiftyStoreKit.completeTransactions() should only be called once when the app launches. Ignoring this call")
|
||||
return
|
||||
}
|
||||
|
||||
completeTransactionsController.completeTransactions = completeTransactions
|
||||
}
|
||||
|
||||
func finishTransaction(_ transaction: PaymentTransaction) {
|
||||
guard let skTransaction = transaction as? SKPaymentTransaction else {
|
||||
print("Object is not a SKPaymentTransaction: \(transaction)")
|
||||
return
|
||||
}
|
||||
paymentQueue.finishTransaction(skTransaction)
|
||||
}
|
||||
|
||||
func start(_ downloads: [SKDownload]) {
|
||||
paymentQueue.start(downloads)
|
||||
}
|
||||
func pause(_ downloads: [SKDownload]) {
|
||||
paymentQueue.pause(downloads)
|
||||
}
|
||||
|
||||
func resume(_ downloads: [SKDownload]) {
|
||||
paymentQueue.resume(downloads)
|
||||
}
|
||||
func cancel(_ downloads: [SKDownload]) {
|
||||
paymentQueue.cancel(downloads)
|
||||
}
|
||||
|
||||
var shouldAddStorePaymentHandler: ShouldAddStorePaymentHandler?
|
||||
var updatedDownloadsHandler: UpdatedDownloadsHandler?
|
||||
|
||||
// MARK: SKPaymentTransactionObserver
|
||||
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
|
||||
|
||||
/*
|
||||
* Some notes about how requests are processed by SKPaymentQueue:
|
||||
*
|
||||
* SKPaymentQueue is used to queue payments or restore purchases requests.
|
||||
* Payments are processed serially and in-order and require user interaction.
|
||||
* Restore purchases requests don't require user interaction and can jump ahead of the queue.
|
||||
* SKPaymentQueue rejects multiple restore purchases calls.
|
||||
* Having one payment queue observer for each request causes extra processing
|
||||
* Failed transactions only ever belong to queued payment requests.
|
||||
* restoreCompletedTransactionsFailedWithError is always called when a restore purchases request fails.
|
||||
* paymentQueueRestoreCompletedTransactionsFinished is always called following 0 or more update transactions when a restore purchases request succeeds.
|
||||
* A complete transactions handler is require to catch any transactions that are updated when the app is not running.
|
||||
* Registering a complete transactions handler when the app launches ensures that any pending transactions can be cleared.
|
||||
* If a complete transactions handler is missing, pending transactions can be mis-attributed to any new incoming payments or restore purchases.
|
||||
*
|
||||
* The order in which transaction updates are processed is:
|
||||
* 1. payments (transactionState: .purchased and .failed for matching product identifiers)
|
||||
* 2. restore purchases (transactionState: .restored, or restoreCompletedTransactionsFailedWithError, or paymentQueueRestoreCompletedTransactionsFinished)
|
||||
* 3. complete transactions (transactionState: .purchased, .failed, .restored, .deferred)
|
||||
* Any transactions where state == .purchasing are ignored.
|
||||
*/
|
||||
var unhandledTransactions = transactions.filter { $0.transactionState != .purchasing }
|
||||
|
||||
if unhandledTransactions.count > 0 {
|
||||
|
||||
unhandledTransactions = paymentsController.processTransactions(transactions, on: paymentQueue)
|
||||
|
||||
unhandledTransactions = restorePurchasesController.processTransactions(unhandledTransactions, on: paymentQueue)
|
||||
|
||||
unhandledTransactions = completeTransactionsController.processTransactions(unhandledTransactions, on: paymentQueue)
|
||||
|
||||
if unhandledTransactions.count > 0 {
|
||||
let strings = unhandledTransactions.map { $0.debugDescription }.joined(separator: "\n")
|
||||
print("unhandledTransactions:\n\(strings)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func paymentQueue(_ queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
|
||||
|
||||
}
|
||||
|
||||
func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
|
||||
|
||||
restorePurchasesController.restoreCompletedTransactionsFailed(withError: error)
|
||||
}
|
||||
|
||||
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
|
||||
|
||||
restorePurchasesController.restoreCompletedTransactionsFinished()
|
||||
}
|
||||
|
||||
func paymentQueue(_ queue: SKPaymentQueue, updatedDownloads downloads: [SKDownload]) {
|
||||
|
||||
updatedDownloadsHandler?(downloads)
|
||||
}
|
||||
|
||||
#if os(iOS) && !targetEnvironment(macCatalyst)
|
||||
func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool {
|
||||
|
||||
return shouldAddStorePaymentHandler?(payment, product) ?? false
|
||||
}
|
||||
#endif
|
||||
}
|
||||
144
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/PaymentsController.swift
generated
Normal file
144
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/PaymentsController.swift
generated
Normal file
@ -0,0 +1,144 @@
|
||||
//
|
||||
// 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) }
|
||||
}
|
||||
}
|
||||
78
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/ProductsInfoController.swift
generated
Normal file
78
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/ProductsInfoController.swift
generated
Normal file
@ -0,0 +1,78 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
108
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/RestorePurchasesController.swift
generated
Normal file
108
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/RestorePurchasesController.swift
generated
Normal file
@ -0,0 +1,108 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
61
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/SKProduct+LocalizedPrice.swift
generated
Normal file
61
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/SKProduct+LocalizedPrice.swift
generated
Normal file
@ -0,0 +1,61 @@
|
||||
//
|
||||
// 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) ?? ""
|
||||
}
|
||||
|
||||
}
|
||||
63
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/SKProductDiscount+LocalizedPrice.swift
generated
Normal file
63
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/SKProductDiscount+LocalizedPrice.swift
generated
Normal file
@ -0,0 +1,63 @@
|
||||
//
|
||||
// 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) ?? ""
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
332
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/SwiftyStoreKit+Types.swift
generated
Normal file
332
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/SwiftyStoreKit+Types.swift
generated
Normal file
@ -0,0 +1,332 @@
|
||||
//
|
||||
// SwiftyStoreKit+Types.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
|
||||
|
||||
// MARK: Purchases
|
||||
|
||||
/// The Purchased protocol allows different purchase flows to be handled by common code in simple cases
|
||||
///
|
||||
/// For example you could route through to
|
||||
///
|
||||
/// func didPurchase<P:Purchased>(item:P) { ... }
|
||||
///
|
||||
/// for example
|
||||
/// - SwiftyStoreKit.completeTransactions (in .purchased and .restored cases)
|
||||
/// - SwiftyStoreKit.restorePurchases (for results.restoredPurchases)
|
||||
/// - SwiftyStoreKit.purchaseProducd (in .success case)
|
||||
public protocol Purchased {
|
||||
var productId: String { get }
|
||||
var quantity: Int { get }
|
||||
var originalPurchaseDate: Date { get }
|
||||
}
|
||||
|
||||
extension Purchase: Purchased {
|
||||
public var originalPurchaseDate: Date {
|
||||
guard let date = originalTransaction?.transactionDate ?? transaction.transactionDate else {
|
||||
fatalError("there should always be a transaction date, so this should not happen...")
|
||||
}
|
||||
return date
|
||||
}
|
||||
}
|
||||
|
||||
extension PurchaseDetails: Purchased {
|
||||
public var originalPurchaseDate: Date {
|
||||
guard let date = originalTransaction?.transactionDate ?? transaction.transactionDate else {
|
||||
fatalError("there should always be a transaction date, so this should not happen...")
|
||||
}
|
||||
return date
|
||||
}
|
||||
}
|
||||
|
||||
// Restored product
|
||||
public struct Purchase {
|
||||
public let productId: String
|
||||
public let quantity: Int
|
||||
public let transaction: PaymentTransaction
|
||||
public let originalTransaction: PaymentTransaction?
|
||||
public let needsFinishTransaction: Bool
|
||||
|
||||
public init(productId: String, quantity: Int, transaction: PaymentTransaction, originalTransaction: PaymentTransaction?, needsFinishTransaction: Bool) {
|
||||
self.productId = productId
|
||||
self.quantity = quantity
|
||||
self.transaction = transaction
|
||||
self.originalTransaction = originalTransaction
|
||||
self.needsFinishTransaction = needsFinishTransaction
|
||||
}
|
||||
}
|
||||
|
||||
/// Purchased product
|
||||
public struct PurchaseDetails {
|
||||
public let productId: String
|
||||
public let quantity: Int
|
||||
public let product: SKProduct
|
||||
public let transaction: PaymentTransaction
|
||||
public let originalTransaction: PaymentTransaction?
|
||||
public let needsFinishTransaction: Bool
|
||||
|
||||
public init(productId: String, quantity: Int, product: SKProduct, transaction: PaymentTransaction, originalTransaction: PaymentTransaction?, needsFinishTransaction: Bool) {
|
||||
self.productId = productId
|
||||
self.quantity = quantity
|
||||
self.product = product
|
||||
self.transaction = transaction
|
||||
self.originalTransaction = originalTransaction
|
||||
self.needsFinishTransaction = needsFinishTransaction
|
||||
}
|
||||
}
|
||||
|
||||
/// Conform to this protocol to provide custom receipt validator
|
||||
public protocol ReceiptValidator {
|
||||
func validate(receiptData: Data, completion: @escaping (VerifyReceiptResult) -> Void)
|
||||
}
|
||||
|
||||
/// Payment transaction
|
||||
public protocol PaymentTransaction {
|
||||
var transactionDate: Date? { get }
|
||||
var transactionState: SKPaymentTransactionState { get }
|
||||
var transactionIdentifier: String? { get }
|
||||
var downloads: [SKDownload] { get }
|
||||
}
|
||||
|
||||
/// Add PaymentTransaction conformance to SKPaymentTransaction
|
||||
extension SKPaymentTransaction: PaymentTransaction { }
|
||||
|
||||
/// Products information
|
||||
public struct RetrieveResults {
|
||||
public let retrievedProducts: Set<SKProduct>
|
||||
public let invalidProductIDs: Set<String>
|
||||
public let error: Error?
|
||||
|
||||
public init(retrievedProducts: Set<SKProduct>, invalidProductIDs: Set<String>, error: Error?) {
|
||||
self.retrievedProducts = retrievedProducts
|
||||
self.invalidProductIDs = invalidProductIDs
|
||||
self.error = error
|
||||
}
|
||||
}
|
||||
|
||||
/// Purchase result
|
||||
public enum PurchaseResult {
|
||||
case success(purchase: PurchaseDetails)
|
||||
case error(error: SKError)
|
||||
}
|
||||
|
||||
/// Restore purchase results
|
||||
public struct RestoreResults {
|
||||
public let restoredPurchases: [Purchase]
|
||||
public let restoreFailedPurchases: [(SKError, String?)]
|
||||
|
||||
public init(restoredPurchases: [Purchase], restoreFailedPurchases: [(SKError, String?)]) {
|
||||
self.restoredPurchases = restoredPurchases
|
||||
self.restoreFailedPurchases = restoreFailedPurchases
|
||||
}
|
||||
}
|
||||
|
||||
public typealias ShouldAddStorePaymentHandler = (_ payment: SKPayment, _ product: SKProduct) -> Bool
|
||||
public typealias UpdatedDownloadsHandler = (_ downloads: [SKDownload]) -> Void
|
||||
|
||||
// MARK: Receipt verification
|
||||
|
||||
/// Info for receipt returned by server
|
||||
public typealias ReceiptInfo = [String: AnyObject]
|
||||
|
||||
/// Fetch receipt result
|
||||
public enum FetchReceiptResult {
|
||||
case success(receiptData: Data)
|
||||
case error(error: ReceiptError)
|
||||
}
|
||||
|
||||
/// Verify receipt result
|
||||
public enum VerifyReceiptResult {
|
||||
case success(receipt: ReceiptInfo)
|
||||
case error(error: ReceiptError)
|
||||
}
|
||||
|
||||
/// Result for Consumable and NonConsumable
|
||||
public enum VerifyPurchaseResult {
|
||||
case purchased(item: ReceiptItem)
|
||||
case notPurchased
|
||||
}
|
||||
|
||||
/// Verify subscription result
|
||||
public enum VerifySubscriptionResult {
|
||||
case purchased(expiryDate: Date, items: [ReceiptItem])
|
||||
case expired(expiryDate: Date, items: [ReceiptItem])
|
||||
case notPurchased
|
||||
}
|
||||
|
||||
public enum SubscriptionType {
|
||||
case autoRenewable
|
||||
case nonRenewing(validDuration: TimeInterval)
|
||||
}
|
||||
|
||||
public struct ReceiptItem: Purchased, Codable {
|
||||
|
||||
/// The product identifier of the item that was purchased. This value corresponds to the `productIdentifier` property of the `SKPayment` object stored in the transaction’s payment property.
|
||||
public var productId: String
|
||||
|
||||
/// The number of items purchased. This value corresponds to the `quantity` property of the `SKPayment` object stored in the transaction’s payment property.
|
||||
public var quantity: Int
|
||||
|
||||
/// The transaction identifier of the item that was purchased. This value corresponds to the transaction’s `transactionIdentifier` property.
|
||||
public var transactionId: String
|
||||
|
||||
/// For a transaction that restores a previous transaction, the transaction identifier of the original transaction.
|
||||
///
|
||||
/// Otherwise, identical to the transaction identifier. This value corresponds to the original transaction’s `transactionIdentifier` property. All receipts in a chain of renewals for an auto-renewable subscription have the same value for this field.
|
||||
public var originalTransactionId: String
|
||||
|
||||
/// The date and time that the item was purchased. This value corresponds to the transaction’s `transactionDate` property.
|
||||
public var purchaseDate: Date
|
||||
|
||||
/// For a transaction that restores a previous transaction, the date of the original transaction. This value corresponds to the original transaction’s `transactionDate` property. In an auto-renewable subscription receipt, this indicates the beginning of the subscription period, even if the subscription has been renewed.
|
||||
public var originalPurchaseDate: Date
|
||||
|
||||
/// The primary key for identifying subscription purchases.
|
||||
public var webOrderLineItemId: String?
|
||||
|
||||
/// The expiration date for the subscription, expressed as the number of milliseconds since January 1, 1970, 00:00:00 GMT. This key is **only** present for **auto-renewable** subscription receipts.
|
||||
public var subscriptionExpirationDate: Date?
|
||||
|
||||
/// For a transaction that was canceled by Apple customer support, the time and date of the cancellation.
|
||||
///
|
||||
/// Treat a canceled receipt the same as if no purchase had ever been made.
|
||||
public var cancellationDate: Date?
|
||||
|
||||
/// Indicates whether or not the subscription item is currently within a given trial period.
|
||||
public var isTrialPeriod: Bool
|
||||
|
||||
/// Indicates whether or not the subscription item is currently within an intro offer period.
|
||||
public var isInIntroOfferPeriod: Bool
|
||||
|
||||
public init(productId: String, quantity: Int, transactionId: String, originalTransactionId: String, purchaseDate: Date, originalPurchaseDate: Date, webOrderLineItemId: String?, subscriptionExpirationDate: Date?, cancellationDate: Date?, isTrialPeriod: Bool, isInIntroOfferPeriod: Bool) {
|
||||
self.productId = productId
|
||||
self.quantity = quantity
|
||||
self.transactionId = transactionId
|
||||
self.originalTransactionId = originalTransactionId
|
||||
self.purchaseDate = purchaseDate
|
||||
self.originalPurchaseDate = originalPurchaseDate
|
||||
self.webOrderLineItemId = webOrderLineItemId
|
||||
self.subscriptionExpirationDate = subscriptionExpirationDate
|
||||
self.cancellationDate = cancellationDate
|
||||
self.isTrialPeriod = isTrialPeriod
|
||||
self.isInIntroOfferPeriod = isInIntroOfferPeriod
|
||||
}
|
||||
}
|
||||
|
||||
/// Error when managing receipt
|
||||
public enum ReceiptError: Swift.Error {
|
||||
/// No receipt data
|
||||
case noReceiptData
|
||||
/// No data received
|
||||
case noRemoteData
|
||||
/// Error when encoding HTTP body into JSON
|
||||
case requestBodyEncodeError(error: Swift.Error)
|
||||
/// Error when proceeding request
|
||||
case networkError(error: Swift.Error)
|
||||
/// Error when decoding response
|
||||
case jsonDecodeError(string: String?)
|
||||
/// Receive invalid - bad status returned
|
||||
case receiptInvalid(receipt: ReceiptInfo, status: ReceiptStatus)
|
||||
}
|
||||
|
||||
/// Status code returned by remote server
|
||||
///
|
||||
/// See Table 2-1 Status codes
|
||||
public enum ReceiptStatus: Int {
|
||||
/// Not decodable status
|
||||
case unknown = -2
|
||||
/// No status returned
|
||||
case none = -1
|
||||
/// valid statua
|
||||
case valid = 0
|
||||
/// The App Store could not read the JSON object you provided.
|
||||
case jsonNotReadable = 21000
|
||||
/// The data in the receipt-data property was malformed or missing.
|
||||
case malformedOrMissingData = 21002
|
||||
/// The receipt could not be authenticated.
|
||||
case receiptCouldNotBeAuthenticated = 21003
|
||||
/// The shared secret you provided does not match the shared secret on file for your account.
|
||||
case secretNotMatching = 21004
|
||||
/// The receipt server is not currently available.
|
||||
case receiptServerUnavailable = 21005
|
||||
/// This receipt is valid but the subscription has expired. When this status code is returned to your server, the receipt data is also decoded and returned as part of the response.
|
||||
case subscriptionExpired = 21006
|
||||
/// This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead.
|
||||
case testReceipt = 21007
|
||||
/// This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead.
|
||||
case productionEnvironment = 21008
|
||||
|
||||
var isValid: Bool { return self == .valid}
|
||||
}
|
||||
|
||||
// Receipt field as defined in : https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW1
|
||||
public enum ReceiptInfoField: String {
|
||||
/// Bundle Identifier. This corresponds to the value of CFBundleIdentifier in the Info.plist file.
|
||||
case bundle_id
|
||||
/// The app’s version number.This corresponds to the value of CFBundleVersion (in iOS) or CFBundleShortVersionString (in OS X) in the Info.plist.
|
||||
case application_version
|
||||
/// The version of the app that was originally purchased. This corresponds to the value of CFBundleVersion (in iOS) or CFBundleShortVersionString (in OS X) in the Info.plist file when the purchase was originally made.
|
||||
case original_application_version
|
||||
/// The date when the app receipt was created.
|
||||
case creation_date
|
||||
/// The date that the app receipt expires. This key is present only for apps purchased through the Volume Purchase Program.
|
||||
case expiration_date
|
||||
|
||||
/// The receipt for an in-app purchase.
|
||||
case in_app
|
||||
|
||||
public enum InApp: String {
|
||||
/// The number of items purchased. This value corresponds to the quantity property of the SKPayment object stored in the transaction’s payment property.
|
||||
case quantity
|
||||
/// The product identifier of the item that was purchased. This value corresponds to the productIdentifier property of the SKPayment object stored in the transaction’s payment property.
|
||||
case product_id
|
||||
/// The transaction identifier of the item that was purchased. This value corresponds to the transaction’s transactionIdentifier property.
|
||||
case transaction_id
|
||||
/// For a transaction that restores a previous transaction, the transaction identifier of the original transaction. Otherwise, identical to the transaction identifier. This value corresponds to the original transaction’s transactionIdentifier property. All receipts in a chain of renewals for an auto-renewable subscription have the same value for this field.
|
||||
case original_transaction_id
|
||||
/// The date and time that the item was purchased. This value corresponds to the transaction’s transactionDate property.
|
||||
case purchase_date
|
||||
/// For a transaction that restores a previous transaction, the date of the original transaction. This value corresponds to the original transaction’s transactionDate property. In an auto-renewable subscription receipt, this indicates the beginning of the subscription period, even if the subscription has been renewed.
|
||||
case original_purchase_date
|
||||
/// The expiration date for the subscription, expressed as the number of milliseconds since January 1, 1970, 00:00:00 GMT. This key is only present for auto-renewable subscription receipts.
|
||||
case expires_date
|
||||
/// For a transaction that was canceled by Apple customer support, the time and date of the cancellation. Treat a canceled receipt the same as if no purchase had ever been made.
|
||||
case cancellation_date
|
||||
#if os(iOS) || os(tvOS)
|
||||
/// A string that the App Store uses to uniquely identify the application that created the transaction. If your server supports multiple applications, you can use this value to differentiate between them. Apps are assigned an identifier only in the production environment, so this key is not present for receipts created in the test environment. This field is not present for Mac apps. See also Bundle Identifier.
|
||||
case app_item_id
|
||||
#endif
|
||||
/// An arbitrary number that uniquely identifies a revision of your application. This key is not present for receipts created in the test environment.
|
||||
case version_external_identifier
|
||||
/// The primary key for identifying subscription purchases.
|
||||
case web_order_line_item_id
|
||||
}
|
||||
}
|
||||
|
||||
#if os(OSX)
|
||||
public enum ReceiptExitCode: Int32 {
|
||||
/// If validation fails in OS X, call exit with a status of 173. This exit status notifies the system that your application has determined that its receipt is invalid. At this point, the system attempts to obtain a valid receipt and may prompt for the user’s iTunes credentials
|
||||
case notValid = 173
|
||||
}
|
||||
#endif
|
||||
312
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/SwiftyStoreKit.swift
generated
Normal file
312
Pods/SwiftyStoreKit/Sources/SwiftyStoreKit/SwiftyStoreKit.swift
generated
Normal file
@ -0,0 +1,312 @@
|
||||
//
|
||||
// SwiftyStoreKit.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
|
||||
|
||||
public class SwiftyStoreKit {
|
||||
|
||||
private let productsInfoController: ProductsInfoController
|
||||
|
||||
fileprivate let paymentQueueController: PaymentQueueController
|
||||
|
||||
fileprivate let receiptVerificator: InAppReceiptVerificator
|
||||
|
||||
init(productsInfoController: ProductsInfoController = ProductsInfoController(),
|
||||
paymentQueueController: PaymentQueueController = PaymentQueueController(paymentQueue: SKPaymentQueue.default()),
|
||||
receiptVerificator: InAppReceiptVerificator = InAppReceiptVerificator()) {
|
||||
|
||||
self.productsInfoController = productsInfoController
|
||||
self.paymentQueueController = paymentQueueController
|
||||
self.receiptVerificator = receiptVerificator
|
||||
}
|
||||
|
||||
// MARK: private methods
|
||||
fileprivate func retrieveProductsInfo(_ productIds: Set<String>, completion: @escaping (RetrieveResults) -> Void) -> InAppProductRequest {
|
||||
return productsInfoController.retrieveProductsInfo(productIds, completion: completion)
|
||||
}
|
||||
|
||||
fileprivate func purchaseProduct(_ productId: String, quantity: Int = 1, atomically: Bool = true, applicationUsername: String = "", simulatesAskToBuyInSandbox: Bool = false, completion: @escaping ( PurchaseResult) -> Void) -> InAppProductRequest {
|
||||
|
||||
return retrieveProductsInfo(Set([productId])) { result -> Void in
|
||||
if let product = result.retrievedProducts.first {
|
||||
self.purchase(product: product, quantity: quantity, atomically: atomically, applicationUsername: applicationUsername, simulatesAskToBuyInSandbox: simulatesAskToBuyInSandbox, completion: completion)
|
||||
} else if let error = result.error {
|
||||
completion(.error(error: SKError(_nsError: error as NSError)))
|
||||
} else if let invalidProductId = result.invalidProductIDs.first {
|
||||
let userInfo = [ NSLocalizedDescriptionKey: "Invalid product id: \(invalidProductId)" ]
|
||||
let error = NSError(domain: SKErrorDomain, code: SKError.paymentInvalid.rawValue, userInfo: userInfo)
|
||||
completion(.error(error: SKError(_nsError: error)))
|
||||
} else {
|
||||
let error = NSError(domain: SKErrorDomain, code: SKError.unknown.rawValue, userInfo: nil)
|
||||
completion(.error(error: SKError(_nsError: error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func purchase(product: SKProduct, quantity: Int, atomically: Bool, applicationUsername: String = "", simulatesAskToBuyInSandbox: Bool = false, paymentDiscount: PaymentDiscount? = nil, completion: @escaping (PurchaseResult) -> Void) {
|
||||
paymentQueueController.startPayment(Payment(product: product, paymentDiscount: paymentDiscount, quantity: quantity, atomically: atomically, applicationUsername: applicationUsername, simulatesAskToBuyInSandbox: simulatesAskToBuyInSandbox) { result in
|
||||
|
||||
completion(self.processPurchaseResult(result))
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func restorePurchases(atomically: Bool = true, applicationUsername: String = "", completion: @escaping (RestoreResults) -> Void) {
|
||||
|
||||
paymentQueueController.restorePurchases(RestorePurchases(atomically: atomically, applicationUsername: applicationUsername) { results in
|
||||
|
||||
let results = self.processRestoreResults(results)
|
||||
completion(results)
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func completeTransactions(atomically: Bool = true, completion: @escaping ([Purchase]) -> Void) {
|
||||
|
||||
paymentQueueController.completeTransactions(CompleteTransactions(atomically: atomically, callback: completion))
|
||||
}
|
||||
|
||||
fileprivate func finishTransaction(_ transaction: PaymentTransaction) {
|
||||
|
||||
paymentQueueController.finishTransaction(transaction)
|
||||
}
|
||||
|
||||
private func processPurchaseResult(_ result: TransactionResult) -> PurchaseResult {
|
||||
switch result {
|
||||
case .purchased(let purchase):
|
||||
return .success(purchase: purchase)
|
||||
case .failed(let error):
|
||||
return .error(error: error)
|
||||
case .restored(let purchase):
|
||||
return .error(error: storeInternalError(description: "Cannot restore product \(purchase.productId) from purchase path"))
|
||||
}
|
||||
}
|
||||
|
||||
private func processRestoreResults(_ results: [TransactionResult]) -> RestoreResults {
|
||||
var restoredPurchases: [Purchase] = []
|
||||
var restoreFailedPurchases: [(SKError, String?)] = []
|
||||
for result in results {
|
||||
switch result {
|
||||
case .purchased(let purchase):
|
||||
let error = storeInternalError(description: "Cannot purchase product \(purchase.productId) from restore purchases path")
|
||||
restoreFailedPurchases.append((error, purchase.productId))
|
||||
case .failed(let error):
|
||||
restoreFailedPurchases.append((error, nil))
|
||||
case .restored(let purchase):
|
||||
restoredPurchases.append(purchase)
|
||||
}
|
||||
}
|
||||
return RestoreResults(restoredPurchases: restoredPurchases, restoreFailedPurchases: restoreFailedPurchases)
|
||||
}
|
||||
|
||||
private func storeInternalError(code: SKError.Code = SKError.unknown, description: String = "") -> SKError {
|
||||
let error = NSError(domain: SKErrorDomain, code: code.rawValue, userInfo: [ NSLocalizedDescriptionKey: description ])
|
||||
return SKError(_nsError: error)
|
||||
}
|
||||
}
|
||||
|
||||
extension SwiftyStoreKit {
|
||||
|
||||
// MARK: Singleton
|
||||
fileprivate static let sharedInstance = SwiftyStoreKit()
|
||||
|
||||
// MARK: Public methods - Purchases
|
||||
|
||||
/// Check if the current device can make payments.
|
||||
/// - returns: `false` if this device is not able or allowed to make payments
|
||||
public class var canMakePayments: Bool {
|
||||
return SKPaymentQueue.canMakePayments()
|
||||
}
|
||||
|
||||
/// Retrieve products information
|
||||
/// - Parameter productIds: The set of product identifiers to retrieve corresponding products for
|
||||
/// - Parameter completion: handler for result
|
||||
/// - returns: A cancellable `InAppRequest` object
|
||||
@discardableResult
|
||||
public class func retrieveProductsInfo(_ productIds: Set<String>, completion: @escaping (RetrieveResults) -> Void) -> InAppRequest {
|
||||
return sharedInstance.retrieveProductsInfo(productIds, completion: completion)
|
||||
}
|
||||
|
||||
/// Purchase a product
|
||||
/// - Parameter productId: productId as specified in App Store Connect
|
||||
/// - Parameter quantity: quantity of the product to be purchased
|
||||
/// - Parameter atomically: whether the product is purchased atomically (e.g. `finishTransaction` is called immediately)
|
||||
/// - Parameter applicationUsername: an opaque identifier for the user’s account on your system
|
||||
/// - Parameter completion: handler for result
|
||||
/// - returns: A cancellable `InAppRequest` object
|
||||
@discardableResult
|
||||
public class func purchaseProduct(_ productId: String, quantity: Int = 1, atomically: Bool = true, applicationUsername: String = "", simulatesAskToBuyInSandbox: Bool = false, completion: @escaping (PurchaseResult) -> Void) -> InAppRequest {
|
||||
|
||||
return sharedInstance.purchaseProduct(productId, quantity: quantity, atomically: atomically, applicationUsername: applicationUsername, simulatesAskToBuyInSandbox: simulatesAskToBuyInSandbox, completion: completion)
|
||||
}
|
||||
|
||||
/// Purchase a product
|
||||
/// - Parameter product: product to be purchased
|
||||
/// - Parameter quantity: quantity of the product to be purchased
|
||||
/// - Parameter atomically: whether the product is purchased atomically (e.g. `finishTransaction` is called immediately)
|
||||
/// - Parameter applicationUsername: an opaque identifier for the user’s account on your system
|
||||
/// - Parameter product: optional discount to be applied. Must be of `SKProductPayment` type
|
||||
/// - Parameter completion: handler for result
|
||||
public class func purchaseProduct(_ product: SKProduct, quantity: Int = 1, atomically: Bool = true, applicationUsername: String = "", simulatesAskToBuyInSandbox: Bool = false, paymentDiscount: PaymentDiscount? = nil, completion: @escaping ( PurchaseResult) -> Void) {
|
||||
|
||||
sharedInstance.purchase(product: product, quantity: quantity, atomically: atomically, applicationUsername: applicationUsername, simulatesAskToBuyInSandbox: simulatesAskToBuyInSandbox, paymentDiscount: paymentDiscount, completion: completion)
|
||||
}
|
||||
|
||||
/// Restore purchases
|
||||
/// - Parameter atomically: whether the product is purchased atomically (e.g. `finishTransaction` is called immediately)
|
||||
/// - Parameter applicationUsername: an opaque identifier for the user’s account on your system
|
||||
/// - Parameter completion: handler for result
|
||||
public class func restorePurchases(atomically: Bool = true, applicationUsername: String = "", completion: @escaping (RestoreResults) -> Void) {
|
||||
|
||||
sharedInstance.restorePurchases(atomically: atomically, applicationUsername: applicationUsername, completion: completion)
|
||||
}
|
||||
|
||||
/// Complete transactions
|
||||
/// - Parameter atomically: whether the product is purchased atomically (e.g. `finishTransaction` is called immediately)
|
||||
/// - Parameter completion: handler for result
|
||||
public class func completeTransactions(atomically: Bool = true, completion: @escaping ([Purchase]) -> Void) {
|
||||
|
||||
sharedInstance.completeTransactions(atomically: atomically, completion: completion)
|
||||
}
|
||||
|
||||
/// Finish a transaction
|
||||
///
|
||||
/// Once the content has been delivered, call this method to finish a transaction that was performed non-atomically
|
||||
/// - Parameter transaction: transaction to finish
|
||||
public class func finishTransaction(_ transaction: PaymentTransaction) {
|
||||
|
||||
sharedInstance.finishTransaction(transaction)
|
||||
}
|
||||
|
||||
/// Register a handler for `SKPaymentQueue.shouldAddStorePayment` delegate method.
|
||||
/// - requires: iOS 11.0+
|
||||
public static var shouldAddStorePaymentHandler: ShouldAddStorePaymentHandler? {
|
||||
didSet {
|
||||
sharedInstance.paymentQueueController.shouldAddStorePaymentHandler = shouldAddStorePaymentHandler
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a handler for `paymentQueue(_:updatedDownloads:)`
|
||||
/// - seealso: `paymentQueue(_:updatedDownloads:)`
|
||||
public static var updatedDownloadsHandler: UpdatedDownloadsHandler? {
|
||||
didSet {
|
||||
sharedInstance.paymentQueueController.updatedDownloadsHandler = updatedDownloadsHandler
|
||||
}
|
||||
}
|
||||
|
||||
public class func start(_ downloads: [SKDownload]) {
|
||||
sharedInstance.paymentQueueController.start(downloads)
|
||||
}
|
||||
public class func pause(_ downloads: [SKDownload]) {
|
||||
sharedInstance.paymentQueueController.pause(downloads)
|
||||
}
|
||||
public class func resume(_ downloads: [SKDownload]) {
|
||||
sharedInstance.paymentQueueController.resume(downloads)
|
||||
}
|
||||
public class func cancel(_ downloads: [SKDownload]) {
|
||||
sharedInstance.paymentQueueController.cancel(downloads)
|
||||
}
|
||||
}
|
||||
|
||||
extension SwiftyStoreKit {
|
||||
|
||||
// MARK: Public methods - Receipt verification
|
||||
|
||||
/// Return receipt data from the application bundle. This is read from `Bundle.main.appStoreReceiptURL`.
|
||||
public static var localReceiptData: Data? {
|
||||
return sharedInstance.receiptVerificator.appStoreReceiptData
|
||||
}
|
||||
|
||||
/// Verify application receipt
|
||||
/// - Parameter validator: receipt validator to use
|
||||
/// - Parameter forceRefresh: If `true`, refreshes the receipt even if one already exists.
|
||||
/// - Parameter completion: handler for result
|
||||
/// - returns: A cancellable `InAppRequest` object
|
||||
@discardableResult
|
||||
public class func verifyReceipt(using validator: ReceiptValidator, forceRefresh: Bool = false, completion: @escaping (VerifyReceiptResult) -> Void) -> InAppRequest? {
|
||||
|
||||
return sharedInstance.receiptVerificator.verifyReceipt(using: validator, forceRefresh: forceRefresh, completion: completion)
|
||||
}
|
||||
|
||||
/// Fetch application receipt
|
||||
/// - Parameter forceRefresh: If true, refreshes the receipt even if one already exists.
|
||||
/// - Parameter completion: handler for result
|
||||
/// - returns: A cancellable `InAppRequest` object
|
||||
@discardableResult
|
||||
public class func fetchReceipt(forceRefresh: Bool, completion: @escaping (FetchReceiptResult) -> Void) -> InAppRequest? {
|
||||
|
||||
return sharedInstance.receiptVerificator.fetchReceipt(forceRefresh: forceRefresh, completion: completion)
|
||||
}
|
||||
|
||||
/// Verify the purchase of a Consumable or NonConsumable product in a receipt
|
||||
/// - Parameter productId: the product id of the purchase to verify
|
||||
/// - Parameter inReceipt: the receipt to use for looking up the purchase
|
||||
/// - returns: A `VerifyPurchaseResult`, which may be either `notPurchased` or `purchased`.
|
||||
public class func verifyPurchase(productId: String, inReceipt receipt: ReceiptInfo) -> VerifyPurchaseResult {
|
||||
|
||||
return InAppReceipt.verifyPurchase(productId: productId, inReceipt: receipt)
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the validity of a subscription (auto-renewable, free or non-renewing) in a receipt.
|
||||
*
|
||||
* This method extracts all transactions matching the given productId and sorts them by date in descending order. It then compares the first transaction expiry date against the receipt date to determine its validity.
|
||||
* - Parameter type: `.autoRenewable` or `.nonRenewing`.
|
||||
* - Parameter productId: The product id of the subscription to verify.
|
||||
* - Parameter receipt: The receipt to use for looking up the subscription.
|
||||
* - Parameter validUntil: Date to check against the expiry date of the subscription. This is only used if a date is not found in the receipt.
|
||||
* - returns: Either `.notPurchased` or `.purchased` / `.expired` with the expiry date found in the receipt.
|
||||
*/
|
||||
public class func verifySubscription(ofType type: SubscriptionType, productId: String, inReceipt receipt: ReceiptInfo, validUntil date: Date = Date()) -> VerifySubscriptionResult {
|
||||
|
||||
return InAppReceipt.verifySubscriptions(ofType: type, productIds: [productId], inReceipt: receipt, validUntil: date)
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the validity of a set of subscriptions in a receipt.
|
||||
*
|
||||
* This method extracts all transactions matching the given productIds and sorts them by date in descending order. It then compares the first transaction expiry date against the receipt date, to determine its validity.
|
||||
* - Note: You can use this method to check the validity of (mutually exclusive) subscriptions in a subscription group.
|
||||
* - Remark: The type parameter determines how the expiration dates are calculated for all subscriptions. Make sure all productIds match the specified subscription type to avoid incorrect results.
|
||||
* - Parameter type: `.autoRenewable` or `.nonRenewing`.
|
||||
* - Parameter productIds: The product IDs of the subscriptions to verify.
|
||||
* - Parameter receipt: The receipt to use for looking up the subscriptions
|
||||
* - Parameter validUntil: Date to check against the expiry date of the subscriptions. This is only used if a date is not found in the receipt.
|
||||
* - returns: Either `.notPurchased` or `.purchased` / `.expired` with the expiry date found in the receipt.
|
||||
*/
|
||||
public class func verifySubscriptions(ofType type: SubscriptionType = .autoRenewable, productIds: Set<String>, inReceipt receipt: ReceiptInfo, validUntil date: Date = Date()) -> VerifySubscriptionResult {
|
||||
|
||||
return InAppReceipt.verifySubscriptions(ofType: type, productIds: productIds, inReceipt: receipt, validUntil: date)
|
||||
}
|
||||
|
||||
/// Get the distinct product identifiers from receipt.
|
||||
///
|
||||
/// This Method extracts all product identifiers. (Including cancelled ones).
|
||||
/// - Note: You can use this method to get all unique product identifiers from receipt.
|
||||
/// - Parameter type: `.autoRenewable` or `.nonRenewing`.
|
||||
/// - Parameter receipt: The receipt to use for looking up product identifiers.
|
||||
/// - returns: Either `Set<String>` or `nil`.
|
||||
public class func getDistinctPurchaseIds(ofType type: SubscriptionType = .autoRenewable, inReceipt receipt: ReceiptInfo) -> Set<String>? {
|
||||
|
||||
return InAppReceipt.getDistinctPurchaseIds(ofType: type, inReceipt: receipt)
|
||||
}
|
||||
}
|
||||
@ -618,6 +618,17 @@ SOFTWARE.
|
||||
|
||||
|
||||
|
||||
## SwiftyStoreKit
|
||||
|
||||
Copyright (c) 2015-2016 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.
|
||||
|
||||
|
||||
## Tiercel
|
||||
|
||||
Copyright (c) 2018 176516837@qq.com <176516837@qq.com>
|
||||
|
||||
@ -779,6 +779,23 @@ SOFTWARE.
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>Copyright (c) 2015-2016 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.
|
||||
</string>
|
||||
<key>License</key>
|
||||
<string>MIT</string>
|
||||
<key>Title</key>
|
||||
<string>SwiftyStoreKit</string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>Copyright (c) 2018 176516837@qq.com <176516837@qq.com>
|
||||
|
||||
@ -11,5 +11,6 @@ ${BUILT_PRODUCTS_DIR}/MarqueeLabel/MarqueeLabel.framework
|
||||
${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework
|
||||
${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework
|
||||
${BUILT_PRODUCTS_DIR}/SwiftDate/SwiftDate.framework
|
||||
${BUILT_PRODUCTS_DIR}/SwiftyStoreKit/SwiftyStoreKit.framework
|
||||
${BUILT_PRODUCTS_DIR}/Tiercel/Tiercel.framework
|
||||
${PODS_XCFRAMEWORKS_BUILD_DIR}/AppLovinSDK/AppLovinSDK.framework/AppLovinSDK
|
||||
@ -10,5 +10,6 @@ ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MarqueeLabel.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftDate.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyStoreKit.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Tiercel.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AppLovinSDK.framework
|
||||
@ -11,5 +11,6 @@ ${BUILT_PRODUCTS_DIR}/MarqueeLabel/MarqueeLabel.framework
|
||||
${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework
|
||||
${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework
|
||||
${BUILT_PRODUCTS_DIR}/SwiftDate/SwiftDate.framework
|
||||
${BUILT_PRODUCTS_DIR}/SwiftyStoreKit/SwiftyStoreKit.framework
|
||||
${BUILT_PRODUCTS_DIR}/Tiercel/Tiercel.framework
|
||||
${PODS_XCFRAMEWORKS_BUILD_DIR}/AppLovinSDK/AppLovinSDK.framework/AppLovinSDK
|
||||
@ -10,5 +10,6 @@ ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MarqueeLabel.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftDate.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyStoreKit.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Tiercel.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AppLovinSDK.framework
|
||||
@ -188,6 +188,7 @@ if [[ "$CONFIGURATION" == "Debug" ]]; then
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/SwiftDate/SwiftDate.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/SwiftyStoreKit/SwiftyStoreKit.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/Tiercel/Tiercel.framework"
|
||||
install_framework "${PODS_XCFRAMEWORKS_BUILD_DIR}/AppLovinSDK/AppLovinSDK.framework"
|
||||
fi
|
||||
@ -204,6 +205,7 @@ if [[ "$CONFIGURATION" == "Release" ]]; then
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/SwiftDate/SwiftDate.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/SwiftyStoreKit/SwiftyStoreKit.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/Tiercel/Tiercel.framework"
|
||||
install_framework "${PODS_XCFRAMEWORKS_BUILD_DIR}/AppLovinSDK/AppLovinSDK.framework"
|
||||
fi
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/DownloadButton" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView" "${PODS_CONFIGURATION_BUILD_DIR}/Kanna" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/MarqueeLabel" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate" "${PODS_CONFIGURATION_BUILD_DIR}/Tiercel" "${PODS_ROOT}/Ads-Global/SDK" "${PODS_ROOT}/AppLovinSDK/applovin-ios-sdk-13.0.0" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalAFN" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalAPM" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalFoundation" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalGecko" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalHeader" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalNETWork" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalSDWebImage" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalYYModel" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalZFPlayer" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalZip" "${PODS_ROOT}/ChartboostSDK" "${PODS_ROOT}/Google-Mobile-Ads-SDK/Frameworks/GoogleMobileAdsFramework" "${PODS_ROOT}/GoogleMobileAdsMediationAppLovin/AppLovinAdapter-13.0.0.1" "${PODS_ROOT}/GoogleMobileAdsMediationChartboost/ChartboostAdapter-9.7.0.1" "${PODS_ROOT}/GoogleMobileAdsMediationIronSource/IronSourceAdapter-8.4.0.0.0" "${PODS_ROOT}/GoogleMobileAdsMediationMintegral/MintegralAdapter-7.7.3.0" "${PODS_ROOT}/GoogleMobileAdsMediationPangle/PangleAdapter-6.2.0.9.0" "${PODS_ROOT}/GoogleMobileAdsMediationVungle/LiftoffMonetizeAdapter-7.4.2.0" "${PODS_ROOT}/GoogleUserMessagingPlatform/Frameworks/Release" "${PODS_ROOT}/IronSourceSDK/IronSource" "${PODS_ROOT}/MintegralAdSDK/Fmk" "${PODS_ROOT}/VungleAds/static" "${PODS_XCFRAMEWORKS_BUILD_DIR}/Ads-Global/BUAdSDK" "${PODS_XCFRAMEWORKS_BUILD_DIR}/AppLovinSDK" "${PODS_XCFRAMEWORKS_BUILD_DIR}/ChartboostSDK" "${PODS_XCFRAMEWORKS_BUILD_DIR}/Google-Mobile-Ads-SDK" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationAppLovin" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationChartboost" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationIronSource" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationMintegral" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationPangle" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationVungle" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleUserMessagingPlatform" "${PODS_XCFRAMEWORKS_BUILD_DIR}/IronSourceSDK" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/BannerAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/BidNativeAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/InterstitialVideoAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/NativeAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/NativeAdvancedAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/NewInterstitialAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/RewardVideoAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/SplashAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/VungleAds"
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/DownloadButton" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView" "${PODS_CONFIGURATION_BUILD_DIR}/Kanna" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/MarqueeLabel" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyStoreKit" "${PODS_CONFIGURATION_BUILD_DIR}/Tiercel" "${PODS_ROOT}/Ads-Global/SDK" "${PODS_ROOT}/AppLovinSDK/applovin-ios-sdk-13.0.0" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalAFN" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalAPM" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalFoundation" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalGecko" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalHeader" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalNETWork" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalSDWebImage" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalYYModel" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalZFPlayer" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalZip" "${PODS_ROOT}/ChartboostSDK" "${PODS_ROOT}/Google-Mobile-Ads-SDK/Frameworks/GoogleMobileAdsFramework" "${PODS_ROOT}/GoogleMobileAdsMediationAppLovin/AppLovinAdapter-13.0.0.1" "${PODS_ROOT}/GoogleMobileAdsMediationChartboost/ChartboostAdapter-9.7.0.1" "${PODS_ROOT}/GoogleMobileAdsMediationIronSource/IronSourceAdapter-8.4.0.0.0" "${PODS_ROOT}/GoogleMobileAdsMediationMintegral/MintegralAdapter-7.7.3.0" "${PODS_ROOT}/GoogleMobileAdsMediationPangle/PangleAdapter-6.2.0.9.0" "${PODS_ROOT}/GoogleMobileAdsMediationVungle/LiftoffMonetizeAdapter-7.4.2.0" "${PODS_ROOT}/GoogleUserMessagingPlatform/Frameworks/Release" "${PODS_ROOT}/IronSourceSDK/IronSource" "${PODS_ROOT}/MintegralAdSDK/Fmk" "${PODS_ROOT}/VungleAds/static" "${PODS_XCFRAMEWORKS_BUILD_DIR}/Ads-Global/BUAdSDK" "${PODS_XCFRAMEWORKS_BUILD_DIR}/AppLovinSDK" "${PODS_XCFRAMEWORKS_BUILD_DIR}/ChartboostSDK" "${PODS_XCFRAMEWORKS_BUILD_DIR}/Google-Mobile-Ads-SDK" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationAppLovin" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationChartboost" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationIronSource" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationMintegral" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationPangle" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationVungle" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleUserMessagingPlatform" "${PODS_XCFRAMEWORKS_BUILD_DIR}/IronSourceSDK" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/BannerAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/BidNativeAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/InterstitialVideoAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/NativeAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/NativeAdvancedAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/NewInterstitialAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/RewardVideoAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/SplashAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/VungleAds"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DownloadButton/DownloadButton.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView/JXPagingView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView/JXSegmentedView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Kanna/Kanna.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher/Kingfisher.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MarqueeLabel/MarqueeLabel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD/SVProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate/SwiftDate.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Tiercel/Tiercel.framework/Headers" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/BURelyFoundation_Global" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/AFNetworking/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/APM/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Foundation/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Gecko/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Header/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/NETWork/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/SDWebImage/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/YYModel/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/ZFPlayer/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Zip/Headers" $(SDKROOT)/usr/include/libxml2
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DownloadButton/DownloadButton.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView/JXPagingView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView/JXSegmentedView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Kanna/Kanna.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher/Kingfisher.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MarqueeLabel/MarqueeLabel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD/SVProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate/SwiftDate.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyStoreKit/SwiftyStoreKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Tiercel/Tiercel.framework/Headers" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/BURelyFoundation_Global" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/AFNetworking/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/APM/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Foundation/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Gecko/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Header/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/NETWork/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/SDWebImage/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/YYModel/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/ZFPlayer/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Zip/Headers" $(SDKROOT)/usr/include/libxml2
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/AFNetworking" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/APM" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Foundation" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Gecko" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Header" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/NETWork" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/SDWebImage" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/YYModel" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/ZFPlayer" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Zip" "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift $(SDKROOT)/usr/lib/swift
|
||||
OTHER_LDFLAGS = $(inherited) -ObjC -l"BURelyFoundationGlobalAFN" -l"BURelyFoundationGlobalAPM" -l"BURelyFoundationGlobalFoundation" -l"BURelyFoundationGlobalGecko" -l"BURelyFoundationGlobalHeader" -l"BURelyFoundationGlobalNETWork" -l"BURelyFoundationGlobalSDWebImage" -l"BURelyFoundationGlobalYYModel" -l"BURelyFoundationGlobalZFPlayer" -l"BURelyFoundationGlobalZip" -l"bz2" -l"c++" -l"c++abi" -l"iconv" -l"resolv" -l"resolv.9" -l"sqlite3" -l"swiftCoreGraphics" -l"xml2" -l"z" -framework "AVFoundation" -framework "Accelerate" -framework "AdSupport" -framework "Alamofire" -framework "AppLovinAdapter" -framework "AppLovinSDK" -framework "AppTrackingTransparency" -framework "AudioToolbox" -framework "CFNetwork" -framework "ChartboostAdapter" -framework "ChartboostSDK" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreImage" -framework "CoreMedia" -framework "CoreMotion" -framework "CoreServices" -framework "CoreTelephony" -framework "CoreText" -framework "CoreVideo" -framework "DownloadButton" -framework "Foundation" -framework "GoogleMobileAds" -framework "IQKeyboardManagerSwift" -framework "ImageIO" -framework "IronSource" -framework "IronSourceAdapter" -framework "JXPagingView" -framework "JXSegmentedView" -framework "JavaScriptCore" -framework "Kanna" -framework "Kingfisher" -framework "LiftoffMonetizeAdapter" -framework "MJRefresh" -framework "MTGSDK" -framework "MTGSDKBanner" -framework "MTGSDKBidding" -framework "MTGSDKInterstitialVideo" -framework "MTGSDKNativeAdvanced" -framework "MTGSDKNewInterstitial" -framework "MTGSDKReward" -framework "MTGSDKSplash" -framework "MarqueeLabel" -framework "MediaPlayer" -framework "MessageUI" -framework "MintegralAdapter" -framework "MobileCoreServices" -framework "PAGAdSDK" -framework "PangleAdapter" -framework "QuartzCore" -framework "SVProgressHUD" -framework "SafariServices" -framework "Security" -framework "SnapKit" -framework "StoreKit" -framework "SwiftDate" -framework "SystemConfiguration" -framework "Tiercel" -framework "UIKit" -framework "UserMessagingPlatform" -framework "VungleAdsSDK" -framework "WebKit" -weak_framework "AdSupport" -weak_framework "AppTrackingTransparency" -weak_framework "Combine" -weak_framework "CoreML" -weak_framework "DeviceCheck" -weak_framework "Foundation" -weak_framework "JavaScriptCore" -weak_framework "SafariServices" -weak_framework "SwiftUI" -weak_framework "UIKit" -weak_framework "WebKit"
|
||||
OTHER_LDFLAGS = $(inherited) -ObjC -l"BURelyFoundationGlobalAFN" -l"BURelyFoundationGlobalAPM" -l"BURelyFoundationGlobalFoundation" -l"BURelyFoundationGlobalGecko" -l"BURelyFoundationGlobalHeader" -l"BURelyFoundationGlobalNETWork" -l"BURelyFoundationGlobalSDWebImage" -l"BURelyFoundationGlobalYYModel" -l"BURelyFoundationGlobalZFPlayer" -l"BURelyFoundationGlobalZip" -l"bz2" -l"c++" -l"c++abi" -l"iconv" -l"resolv" -l"resolv.9" -l"sqlite3" -l"swiftCoreGraphics" -l"xml2" -l"z" -framework "AVFoundation" -framework "Accelerate" -framework "AdSupport" -framework "Alamofire" -framework "AppLovinAdapter" -framework "AppLovinSDK" -framework "AppTrackingTransparency" -framework "AudioToolbox" -framework "CFNetwork" -framework "ChartboostAdapter" -framework "ChartboostSDK" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreImage" -framework "CoreMedia" -framework "CoreMotion" -framework "CoreServices" -framework "CoreTelephony" -framework "CoreText" -framework "CoreVideo" -framework "DownloadButton" -framework "Foundation" -framework "GoogleMobileAds" -framework "IQKeyboardManagerSwift" -framework "ImageIO" -framework "IronSource" -framework "IronSourceAdapter" -framework "JXPagingView" -framework "JXSegmentedView" -framework "JavaScriptCore" -framework "Kanna" -framework "Kingfisher" -framework "LiftoffMonetizeAdapter" -framework "MJRefresh" -framework "MTGSDK" -framework "MTGSDKBanner" -framework "MTGSDKBidding" -framework "MTGSDKInterstitialVideo" -framework "MTGSDKNativeAdvanced" -framework "MTGSDKNewInterstitial" -framework "MTGSDKReward" -framework "MTGSDKSplash" -framework "MarqueeLabel" -framework "MediaPlayer" -framework "MessageUI" -framework "MintegralAdapter" -framework "MobileCoreServices" -framework "PAGAdSDK" -framework "PangleAdapter" -framework "QuartzCore" -framework "SVProgressHUD" -framework "SafariServices" -framework "Security" -framework "SnapKit" -framework "StoreKit" -framework "SwiftDate" -framework "SwiftyStoreKit" -framework "SystemConfiguration" -framework "Tiercel" -framework "UIKit" -framework "UserMessagingPlatform" -framework "VungleAdsSDK" -framework "WebKit" -weak_framework "AdSupport" -weak_framework "AppTrackingTransparency" -weak_framework "Combine" -weak_framework "CoreML" -weak_framework "DeviceCheck" -weak_framework "Foundation" -weak_framework "JavaScriptCore" -weak_framework "SafariServices" -weak_framework "SwiftUI" -weak_framework "UIKit" -weak_framework "WebKit"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/DownloadButton" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView" "${PODS_CONFIGURATION_BUILD_DIR}/Kanna" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/MarqueeLabel" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate" "${PODS_CONFIGURATION_BUILD_DIR}/Tiercel" "${PODS_ROOT}/Ads-Global/SDK" "${PODS_ROOT}/AppLovinSDK/applovin-ios-sdk-13.0.0" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalAFN" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalAPM" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalFoundation" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalGecko" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalHeader" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalNETWork" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalSDWebImage" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalYYModel" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalZFPlayer" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalZip" "${PODS_ROOT}/ChartboostSDK" "${PODS_ROOT}/Google-Mobile-Ads-SDK/Frameworks/GoogleMobileAdsFramework" "${PODS_ROOT}/GoogleMobileAdsMediationAppLovin/AppLovinAdapter-13.0.0.1" "${PODS_ROOT}/GoogleMobileAdsMediationChartboost/ChartboostAdapter-9.7.0.1" "${PODS_ROOT}/GoogleMobileAdsMediationIronSource/IronSourceAdapter-8.4.0.0.0" "${PODS_ROOT}/GoogleMobileAdsMediationMintegral/MintegralAdapter-7.7.3.0" "${PODS_ROOT}/GoogleMobileAdsMediationPangle/PangleAdapter-6.2.0.9.0" "${PODS_ROOT}/GoogleMobileAdsMediationVungle/LiftoffMonetizeAdapter-7.4.2.0" "${PODS_ROOT}/GoogleUserMessagingPlatform/Frameworks/Release" "${PODS_ROOT}/IronSourceSDK/IronSource" "${PODS_ROOT}/MintegralAdSDK/Fmk" "${PODS_ROOT}/VungleAds/static" "${PODS_XCFRAMEWORKS_BUILD_DIR}/Ads-Global/BUAdSDK" "${PODS_XCFRAMEWORKS_BUILD_DIR}/AppLovinSDK" "${PODS_XCFRAMEWORKS_BUILD_DIR}/ChartboostSDK" "${PODS_XCFRAMEWORKS_BUILD_DIR}/Google-Mobile-Ads-SDK" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationAppLovin" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationChartboost" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationIronSource" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationMintegral" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationPangle" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationVungle" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleUserMessagingPlatform" "${PODS_XCFRAMEWORKS_BUILD_DIR}/IronSourceSDK" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/BannerAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/BidNativeAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/InterstitialVideoAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/NativeAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/NativeAdvancedAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/NewInterstitialAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/RewardVideoAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/SplashAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/VungleAds"
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/DownloadButton" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView" "${PODS_CONFIGURATION_BUILD_DIR}/Kanna" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/MarqueeLabel" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyStoreKit" "${PODS_CONFIGURATION_BUILD_DIR}/Tiercel" "${PODS_ROOT}/Ads-Global/SDK" "${PODS_ROOT}/AppLovinSDK/applovin-ios-sdk-13.0.0" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalAFN" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalAPM" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalFoundation" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalGecko" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalHeader" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalNETWork" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalSDWebImage" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalYYModel" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalZFPlayer" "${PODS_ROOT}/BURelyFoundation_Global/Frameworks/SDK/BURelyFoundationGlobalZip" "${PODS_ROOT}/ChartboostSDK" "${PODS_ROOT}/Google-Mobile-Ads-SDK/Frameworks/GoogleMobileAdsFramework" "${PODS_ROOT}/GoogleMobileAdsMediationAppLovin/AppLovinAdapter-13.0.0.1" "${PODS_ROOT}/GoogleMobileAdsMediationChartboost/ChartboostAdapter-9.7.0.1" "${PODS_ROOT}/GoogleMobileAdsMediationIronSource/IronSourceAdapter-8.4.0.0.0" "${PODS_ROOT}/GoogleMobileAdsMediationMintegral/MintegralAdapter-7.7.3.0" "${PODS_ROOT}/GoogleMobileAdsMediationPangle/PangleAdapter-6.2.0.9.0" "${PODS_ROOT}/GoogleMobileAdsMediationVungle/LiftoffMonetizeAdapter-7.4.2.0" "${PODS_ROOT}/GoogleUserMessagingPlatform/Frameworks/Release" "${PODS_ROOT}/IronSourceSDK/IronSource" "${PODS_ROOT}/MintegralAdSDK/Fmk" "${PODS_ROOT}/VungleAds/static" "${PODS_XCFRAMEWORKS_BUILD_DIR}/Ads-Global/BUAdSDK" "${PODS_XCFRAMEWORKS_BUILD_DIR}/AppLovinSDK" "${PODS_XCFRAMEWORKS_BUILD_DIR}/ChartboostSDK" "${PODS_XCFRAMEWORKS_BUILD_DIR}/Google-Mobile-Ads-SDK" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationAppLovin" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationChartboost" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationIronSource" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationMintegral" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationPangle" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleMobileAdsMediationVungle" "${PODS_XCFRAMEWORKS_BUILD_DIR}/GoogleUserMessagingPlatform" "${PODS_XCFRAMEWORKS_BUILD_DIR}/IronSourceSDK" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/BannerAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/BidNativeAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/InterstitialVideoAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/NativeAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/NativeAdvancedAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/NewInterstitialAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/RewardVideoAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/MintegralAdSDK/SplashAd" "${PODS_XCFRAMEWORKS_BUILD_DIR}/VungleAds"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DownloadButton/DownloadButton.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView/JXPagingView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView/JXSegmentedView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Kanna/Kanna.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher/Kingfisher.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MarqueeLabel/MarqueeLabel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD/SVProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate/SwiftDate.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Tiercel/Tiercel.framework/Headers" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/BURelyFoundation_Global" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/AFNetworking/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/APM/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Foundation/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Gecko/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Header/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/NETWork/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/SDWebImage/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/YYModel/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/ZFPlayer/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Zip/Headers" $(SDKROOT)/usr/include/libxml2
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DownloadButton/DownloadButton.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView/JXPagingView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView/JXSegmentedView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Kanna/Kanna.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher/Kingfisher.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MarqueeLabel/MarqueeLabel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD/SVProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate/SwiftDate.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyStoreKit/SwiftyStoreKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Tiercel/Tiercel.framework/Headers" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/BURelyFoundation_Global" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/AFNetworking/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/APM/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Foundation/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Gecko/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Header/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/NETWork/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/SDWebImage/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/YYModel/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/ZFPlayer/Headers" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Zip/Headers" $(SDKROOT)/usr/include/libxml2
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/AFNetworking" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/APM" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Foundation" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Gecko" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Header" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/NETWork" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/SDWebImage" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/YYModel" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/ZFPlayer" "${PODS_XCFRAMEWORKS_BUILD_DIR}/BURelyFoundation_Global/Zip" "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift $(SDKROOT)/usr/lib/swift
|
||||
OTHER_LDFLAGS = $(inherited) -ObjC -l"BURelyFoundationGlobalAFN" -l"BURelyFoundationGlobalAPM" -l"BURelyFoundationGlobalFoundation" -l"BURelyFoundationGlobalGecko" -l"BURelyFoundationGlobalHeader" -l"BURelyFoundationGlobalNETWork" -l"BURelyFoundationGlobalSDWebImage" -l"BURelyFoundationGlobalYYModel" -l"BURelyFoundationGlobalZFPlayer" -l"BURelyFoundationGlobalZip" -l"bz2" -l"c++" -l"c++abi" -l"iconv" -l"resolv" -l"resolv.9" -l"sqlite3" -l"swiftCoreGraphics" -l"xml2" -l"z" -framework "AVFoundation" -framework "Accelerate" -framework "AdSupport" -framework "Alamofire" -framework "AppLovinAdapter" -framework "AppLovinSDK" -framework "AppTrackingTransparency" -framework "AudioToolbox" -framework "CFNetwork" -framework "ChartboostAdapter" -framework "ChartboostSDK" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreImage" -framework "CoreMedia" -framework "CoreMotion" -framework "CoreServices" -framework "CoreTelephony" -framework "CoreText" -framework "CoreVideo" -framework "DownloadButton" -framework "Foundation" -framework "GoogleMobileAds" -framework "IQKeyboardManagerSwift" -framework "ImageIO" -framework "IronSource" -framework "IronSourceAdapter" -framework "JXPagingView" -framework "JXSegmentedView" -framework "JavaScriptCore" -framework "Kanna" -framework "Kingfisher" -framework "LiftoffMonetizeAdapter" -framework "MJRefresh" -framework "MTGSDK" -framework "MTGSDKBanner" -framework "MTGSDKBidding" -framework "MTGSDKInterstitialVideo" -framework "MTGSDKNativeAdvanced" -framework "MTGSDKNewInterstitial" -framework "MTGSDKReward" -framework "MTGSDKSplash" -framework "MarqueeLabel" -framework "MediaPlayer" -framework "MessageUI" -framework "MintegralAdapter" -framework "MobileCoreServices" -framework "PAGAdSDK" -framework "PangleAdapter" -framework "QuartzCore" -framework "SVProgressHUD" -framework "SafariServices" -framework "Security" -framework "SnapKit" -framework "StoreKit" -framework "SwiftDate" -framework "SystemConfiguration" -framework "Tiercel" -framework "UIKit" -framework "UserMessagingPlatform" -framework "VungleAdsSDK" -framework "WebKit" -weak_framework "AdSupport" -weak_framework "AppTrackingTransparency" -weak_framework "Combine" -weak_framework "CoreML" -weak_framework "DeviceCheck" -weak_framework "Foundation" -weak_framework "JavaScriptCore" -weak_framework "SafariServices" -weak_framework "SwiftUI" -weak_framework "UIKit" -weak_framework "WebKit"
|
||||
OTHER_LDFLAGS = $(inherited) -ObjC -l"BURelyFoundationGlobalAFN" -l"BURelyFoundationGlobalAPM" -l"BURelyFoundationGlobalFoundation" -l"BURelyFoundationGlobalGecko" -l"BURelyFoundationGlobalHeader" -l"BURelyFoundationGlobalNETWork" -l"BURelyFoundationGlobalSDWebImage" -l"BURelyFoundationGlobalYYModel" -l"BURelyFoundationGlobalZFPlayer" -l"BURelyFoundationGlobalZip" -l"bz2" -l"c++" -l"c++abi" -l"iconv" -l"resolv" -l"resolv.9" -l"sqlite3" -l"swiftCoreGraphics" -l"xml2" -l"z" -framework "AVFoundation" -framework "Accelerate" -framework "AdSupport" -framework "Alamofire" -framework "AppLovinAdapter" -framework "AppLovinSDK" -framework "AppTrackingTransparency" -framework "AudioToolbox" -framework "CFNetwork" -framework "ChartboostAdapter" -framework "ChartboostSDK" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreImage" -framework "CoreMedia" -framework "CoreMotion" -framework "CoreServices" -framework "CoreTelephony" -framework "CoreText" -framework "CoreVideo" -framework "DownloadButton" -framework "Foundation" -framework "GoogleMobileAds" -framework "IQKeyboardManagerSwift" -framework "ImageIO" -framework "IronSource" -framework "IronSourceAdapter" -framework "JXPagingView" -framework "JXSegmentedView" -framework "JavaScriptCore" -framework "Kanna" -framework "Kingfisher" -framework "LiftoffMonetizeAdapter" -framework "MJRefresh" -framework "MTGSDK" -framework "MTGSDKBanner" -framework "MTGSDKBidding" -framework "MTGSDKInterstitialVideo" -framework "MTGSDKNativeAdvanced" -framework "MTGSDKNewInterstitial" -framework "MTGSDKReward" -framework "MTGSDKSplash" -framework "MarqueeLabel" -framework "MediaPlayer" -framework "MessageUI" -framework "MintegralAdapter" -framework "MobileCoreServices" -framework "PAGAdSDK" -framework "PangleAdapter" -framework "QuartzCore" -framework "SVProgressHUD" -framework "SafariServices" -framework "Security" -framework "SnapKit" -framework "StoreKit" -framework "SwiftDate" -framework "SwiftyStoreKit" -framework "SystemConfiguration" -framework "Tiercel" -framework "UIKit" -framework "UserMessagingPlatform" -framework "VungleAdsSDK" -framework "WebKit" -weak_framework "AdSupport" -weak_framework "AppTrackingTransparency" -weak_framework "Combine" -weak_framework "CoreML" -weak_framework "DeviceCheck" -weak_framework "Foundation" -weak_framework "JavaScriptCore" -weak_framework "SafariServices" -weak_framework "SwiftUI" -weak_framework "UIKit" -weak_framework "WebKit"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
|
||||
26
Pods/Target Support Files/SwiftyStoreKit/SwiftyStoreKit-Info.plist
generated
Normal file
26
Pods/Target Support Files/SwiftyStoreKit/SwiftyStoreKit-Info.plist
generated
Normal file
@ -0,0 +1,26 @@
|
||||
<?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>CFBundleDevelopmentRegion</key>
|
||||
<string>${PODS_DEVELOPMENT_LANGUAGE}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.16.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${CURRENT_PROJECT_VERSION}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
5
Pods/Target Support Files/SwiftyStoreKit/SwiftyStoreKit-dummy.m
generated
Normal file
5
Pods/Target Support Files/SwiftyStoreKit/SwiftyStoreKit-dummy.m
generated
Normal file
@ -0,0 +1,5 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
@interface PodsDummy_SwiftyStoreKit : NSObject
|
||||
@end
|
||||
@implementation PodsDummy_SwiftyStoreKit
|
||||
@end
|
||||
12
Pods/Target Support Files/SwiftyStoreKit/SwiftyStoreKit-prefix.pch
generated
Normal file
12
Pods/Target Support Files/SwiftyStoreKit/SwiftyStoreKit-prefix.pch
generated
Normal file
@ -0,0 +1,12 @@
|
||||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
#ifndef FOUNDATION_EXPORT
|
||||
#if defined(__cplusplus)
|
||||
#define FOUNDATION_EXPORT extern "C"
|
||||
#else
|
||||
#define FOUNDATION_EXPORT extern
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
16
Pods/Target Support Files/SwiftyStoreKit/SwiftyStoreKit-umbrella.h
generated
Normal file
16
Pods/Target Support Files/SwiftyStoreKit/SwiftyStoreKit-umbrella.h
generated
Normal file
@ -0,0 +1,16 @@
|
||||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
#ifndef FOUNDATION_EXPORT
|
||||
#if defined(__cplusplus)
|
||||
#define FOUNDATION_EXPORT extern "C"
|
||||
#else
|
||||
#define FOUNDATION_EXPORT extern
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
FOUNDATION_EXPORT double SwiftyStoreKitVersionNumber;
|
||||
FOUNDATION_EXPORT const unsigned char SwiftyStoreKitVersionString[];
|
||||
|
||||
14
Pods/Target Support Files/SwiftyStoreKit/SwiftyStoreKit.debug.xcconfig
generated
Normal file
14
Pods/Target Support Files/SwiftyStoreKit/SwiftyStoreKit.debug.xcconfig
generated
Normal file
@ -0,0 +1,14 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftyStoreKit
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftyStoreKit
|
||||
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
|
||||
6
Pods/Target Support Files/SwiftyStoreKit/SwiftyStoreKit.modulemap
generated
Normal file
6
Pods/Target Support Files/SwiftyStoreKit/SwiftyStoreKit.modulemap
generated
Normal file
@ -0,0 +1,6 @@
|
||||
framework module SwiftyStoreKit {
|
||||
umbrella header "SwiftyStoreKit-umbrella.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
}
|
||||
14
Pods/Target Support Files/SwiftyStoreKit/SwiftyStoreKit.release.xcconfig
generated
Normal file
14
Pods/Target Support Files/SwiftyStoreKit/SwiftyStoreKit.release.xcconfig
generated
Normal file
@ -0,0 +1,14 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftyStoreKit
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftyStoreKit
|
||||
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
|
||||
@ -36,9 +36,6 @@
|
||||
CB2CAAD82C5A1AC500EF691D /* MP_IAPViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2CAAD52C5A1AC500EF691D /* MP_IAPViewController.swift */; };
|
||||
CB51340E2C9C1E4800833AD5 /* MP_ADSimpleManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB51340D2C9C1E4800833AD5 /* MP_ADSimpleManager.swift */; };
|
||||
CB6EEB8E2C5DFE6100AEC414 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB6EEB8D2C5DFE6100AEC414 /* StoreKit.framework */; };
|
||||
CB7FC5422C2AA01F00292A43 /* FacebookAEM in Frameworks */ = {isa = PBXBuildFile; productRef = CB7FC5412C2AA01F00292A43 /* FacebookAEM */; };
|
||||
CB7FC5442C2AA01F00292A43 /* FacebookBasics in Frameworks */ = {isa = PBXBuildFile; productRef = CB7FC5432C2AA01F00292A43 /* FacebookBasics */; };
|
||||
CB7FC5462C2AA01F00292A43 /* FacebookCore in Frameworks */ = {isa = PBXBuildFile; productRef = CB7FC5452C2AA01F00292A43 /* FacebookCore */; };
|
||||
CB7FC5482C2AC25C00292A43 /* MPPositive_CenterListSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB7FC5472C2AC25C00292A43 /* MPPositive_CenterListSearchView.swift */; };
|
||||
CBAFCAE62C0A10500054500E /* MP_BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBAFC9F22C0A10500054500E /* MP_BaseViewController.swift */; };
|
||||
CBAFCAE72C0A10500054500E /* MP_LunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBAFC9F32C0A10500054500E /* MP_LunchViewController.swift */; };
|
||||
@ -82,7 +79,7 @@
|
||||
CBAFCB102C0A10500054500E /* MP_NetWorkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBAFCA272C0A10500054500E /* MP_NetWorkManager.swift */; };
|
||||
CBAFCB112C0A10500054500E /* MP_PlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBAFCA282C0A10500054500E /* MP_PlayerManager.swift */; };
|
||||
CBAFCB122C0A10500054500E /* MP_PlayerSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBAFCA292C0A10500054500E /* MP_PlayerSlider.swift */; };
|
||||
CBAFCB132C0A10500054500E /* MP_WebWork.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBAFCA2A2C0A10500054500E /* MP_WebWork.swift */; };
|
||||
CBAFCB132C0A10500054500E /* MP_WebDecryptionWork.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBAFCA2A2C0A10500054500E /* MP_WebDecryptionWork.swift */; };
|
||||
CBAFCB142C0A10500054500E /* MPPositive_Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBAFCA2B2C0A10500054500E /* MPPositive_Debouncer.swift */; };
|
||||
CBAFCB152C0A10500054500E /* MPSideA_MediaCenterManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBAFCA2C2C0A10500054500E /* MPSideA_MediaCenterManager.swift */; };
|
||||
CBAFCB162C0A10500054500E /* MPSideA_VolumeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBAFCA2D2C0A10500054500E /* MPSideA_VolumeManager.swift */; };
|
||||
@ -241,6 +238,9 @@
|
||||
CBB720542C6A040000D1B504 /* MP_IAPViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = CBB720532C6A040000D1B504 /* MP_IAPViewController.xib */; };
|
||||
CBB75FDD2C4F7AA60041665D /* UIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBB75FDC2C4F7AA60041665D /* UIImageView.swift */; };
|
||||
CBBAF8CD2C339CF200B3C838 /* MPPositive_JsonCharts.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBAF8CC2C339CF200B3C838 /* MPPositive_JsonCharts.swift */; };
|
||||
CBBE53382CF065800036D2D9 /* FacebookAEM in Frameworks */ = {isa = PBXBuildFile; productRef = CBBE53372CF065800036D2D9 /* FacebookAEM */; };
|
||||
CBBE533A2CF065800036D2D9 /* FacebookBasics in Frameworks */ = {isa = PBXBuildFile; productRef = CBBE53392CF065800036D2D9 /* FacebookBasics */; };
|
||||
CBBE533C2CF065800036D2D9 /* FacebookCore in Frameworks */ = {isa = PBXBuildFile; productRef = CBBE533B2CF065800036D2D9 /* FacebookCore */; };
|
||||
CBBFD8212C8018D100BD67BC /* Reload_Animation.json in Resources */ = {isa = PBXBuildFile; fileRef = CBBFD8202C8018D100BD67BC /* Reload_Animation.json */; };
|
||||
CBC1FB7A2C50999800AC0633 /* MPPositive_LibraryItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBC1FB792C50999800AC0633 /* MPPositive_LibraryItemModel.swift */; };
|
||||
CBC1FB7C2C509B7300AC0633 /* MPPositive_LibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBC1FB7B2C509B7300AC0633 /* MPPositive_LibraryViewModel.swift */; };
|
||||
@ -362,7 +362,7 @@
|
||||
CBAFCA272C0A10500054500E /* MP_NetWorkManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MP_NetWorkManager.swift; sourceTree = "<group>"; };
|
||||
CBAFCA282C0A10500054500E /* MP_PlayerManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MP_PlayerManager.swift; sourceTree = "<group>"; };
|
||||
CBAFCA292C0A10500054500E /* MP_PlayerSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MP_PlayerSlider.swift; sourceTree = "<group>"; };
|
||||
CBAFCA2A2C0A10500054500E /* MP_WebWork.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MP_WebWork.swift; sourceTree = "<group>"; };
|
||||
CBAFCA2A2C0A10500054500E /* MP_WebDecryptionWork.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MP_WebDecryptionWork.swift; sourceTree = "<group>"; };
|
||||
CBAFCA2B2C0A10500054500E /* MPPositive_Debouncer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MPPositive_Debouncer.swift; sourceTree = "<group>"; };
|
||||
CBAFCA2C2C0A10500054500E /* MPSideA_MediaCenterManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MPSideA_MediaCenterManager.swift; sourceTree = "<group>"; };
|
||||
CBAFCA2D2C0A10500054500E /* MPSideA_VolumeManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MPSideA_VolumeManager.swift; sourceTree = "<group>"; };
|
||||
@ -583,15 +583,15 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
CB7FC5442C2AA01F00292A43 /* FacebookBasics in Frameworks */,
|
||||
CB7FC5422C2AA01F00292A43 /* FacebookAEM in Frameworks */,
|
||||
CBBE533A2CF065800036D2D9 /* FacebookBasics in Frameworks */,
|
||||
CBBE533C2CF065800036D2D9 /* FacebookCore in Frameworks */,
|
||||
CBBE53382CF065800036D2D9 /* FacebookAEM in Frameworks */,
|
||||
CBAFCBAD2C0A10DA0054500E /* FirebaseCrashlytics in Frameworks */,
|
||||
CBD4570D2C2EC38400CE766D /* AppTrackingTransparency.framework in Frameworks */,
|
||||
CBAFCBAB2C0A10DA0054500E /* FirebaseAnalytics in Frameworks */,
|
||||
CB0B368B2C65AE3A004036E2 /* Lottie in Frameworks */,
|
||||
CB6EEB8E2C5DFE6100AEC414 /* StoreKit.framework in Frameworks */,
|
||||
CBAFCBAF2C0A10DA0054500E /* FirebaseRemoteConfig in Frameworks */,
|
||||
CB7FC5462C2AA01F00292A43 /* FacebookCore in Frameworks */,
|
||||
82F33EC0D64393B7AAD72A45 /* Pods_relax_offline_mp3_music.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -782,7 +782,7 @@
|
||||
CBAFCA252C0A10500054500E /* MP_HUD.swift */,
|
||||
CBAFCA262C0A10500054500E /* MP_LocationManager.swift */,
|
||||
CBAFCA292C0A10500054500E /* MP_PlayerSlider.swift */,
|
||||
CBAFCA2A2C0A10500054500E /* MP_WebWork.swift */,
|
||||
CBAFCA2A2C0A10500054500E /* MP_WebDecryptionWork.swift */,
|
||||
CB20A06F2C53BDBF00FC5AFC /* MP_WebVisitorDataManager.swift */,
|
||||
CBD1E19E2C57650F00DF20E5 /* MP_IAPManager.swift */,
|
||||
CBAFCA2B2C0A10500054500E /* MPPositive_Debouncer.swift */,
|
||||
@ -1322,10 +1322,10 @@
|
||||
CBAFCBAA2C0A10DA0054500E /* FirebaseAnalytics */,
|
||||
CBAFCBAC2C0A10DA0054500E /* FirebaseCrashlytics */,
|
||||
CBAFCBAE2C0A10DA0054500E /* FirebaseRemoteConfig */,
|
||||
CB7FC5412C2AA01F00292A43 /* FacebookAEM */,
|
||||
CB7FC5432C2AA01F00292A43 /* FacebookBasics */,
|
||||
CB7FC5452C2AA01F00292A43 /* FacebookCore */,
|
||||
CB0B368A2C65AE3A004036E2 /* Lottie */,
|
||||
CBBE53372CF065800036D2D9 /* FacebookAEM */,
|
||||
CBBE53392CF065800036D2D9 /* FacebookBasics */,
|
||||
CBBE533B2CF065800036D2D9 /* FacebookCore */,
|
||||
);
|
||||
productName = relax.offline.mp3.music;
|
||||
productReference = CBC2D6E82BFDF3D700E17703 /* relax.offline.mp3.music.app */;
|
||||
@ -1364,8 +1364,8 @@
|
||||
mainGroup = CBC2D6DF2BFDF3D700E17703;
|
||||
packageReferences = (
|
||||
CBAFCBA92C0A10DA0054500E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
|
||||
CB7FC5402C2AA01F00292A43 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */,
|
||||
CB0B36892C65AE3A004036E2 /* XCRemoteSwiftPackageReference "lottie-spm" */,
|
||||
CBBE53362CF065800036D2D9 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */,
|
||||
);
|
||||
productRefGroup = CBC2D6E92BFDF3D700E17703 /* Products */;
|
||||
projectDirPath = "";
|
||||
@ -1574,7 +1574,7 @@
|
||||
CBAFCB1B2C0A10500054500E /* MPPositive_JsonLyrics.swift in Sources */,
|
||||
CBAFCB572C0A10500054500E /* MPPositive_MoreOperationDownLoadTableViewCell.swift in Sources */,
|
||||
CBAFCB402C0A10500054500E /* MPPositive_SearchResultsLoadViewModel.swift in Sources */,
|
||||
CBAFCB132C0A10500054500E /* MP_WebWork.swift in Sources */,
|
||||
CBAFCB132C0A10500054500E /* MP_WebDecryptionWork.swift in Sources */,
|
||||
CBAFCB3A2C0A10500054500E /* MPPositive_SearchSuggestionItemListModel.swift in Sources */,
|
||||
CB0B36912C65EBFC004036E2 /* MPPositive_BaseShowView.swift in Sources */,
|
||||
CBAFCB122C0A10500054500E /* MP_PlayerSlider.swift in Sources */,
|
||||
@ -1927,7 +1927,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1.3.1;
|
||||
CURRENT_PROJECT_VERSION = 1.3.2;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = RAQJ4FNZUH;
|
||||
@ -1948,7 +1948,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.3.1;
|
||||
MARKETING_VERSION = 1.3.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = relax.offline.mp3.music;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -1973,7 +1973,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1.3.1;
|
||||
CURRENT_PROJECT_VERSION = 1.3.2;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = RAQJ4FNZUH;
|
||||
@ -1994,7 +1994,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.3.1;
|
||||
MARKETING_VERSION = 1.3.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = relax.offline.mp3.music;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -2042,14 +2042,6 @@
|
||||
minimumVersion = 4.5.0;
|
||||
};
|
||||
};
|
||||
CB7FC5402C2AA01F00292A43 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/facebook/facebook-ios-sdk";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 14.1.0;
|
||||
};
|
||||
};
|
||||
CBAFCBA92C0A10DA0054500E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/firebase/firebase-ios-sdk";
|
||||
@ -2058,6 +2050,14 @@
|
||||
minimumVersion = 10.27.0;
|
||||
};
|
||||
};
|
||||
CBBE53362CF065800036D2D9 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/facebook/facebook-ios-sdk";
|
||||
requirement = {
|
||||
kind = exactVersion;
|
||||
version = 17.1.0;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
@ -2066,21 +2066,6 @@
|
||||
package = CB0B36892C65AE3A004036E2 /* XCRemoteSwiftPackageReference "lottie-spm" */;
|
||||
productName = Lottie;
|
||||
};
|
||||
CB7FC5412C2AA01F00292A43 /* FacebookAEM */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = CB7FC5402C2AA01F00292A43 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */;
|
||||
productName = FacebookAEM;
|
||||
};
|
||||
CB7FC5432C2AA01F00292A43 /* FacebookBasics */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = CB7FC5402C2AA01F00292A43 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */;
|
||||
productName = FacebookBasics;
|
||||
};
|
||||
CB7FC5452C2AA01F00292A43 /* FacebookCore */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = CB7FC5402C2AA01F00292A43 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */;
|
||||
productName = FacebookCore;
|
||||
};
|
||||
CBAFCBAA2C0A10DA0054500E /* FirebaseAnalytics */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = CBAFCBA92C0A10DA0054500E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
|
||||
@ -2096,6 +2081,21 @@
|
||||
package = CBAFCBA92C0A10DA0054500E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
|
||||
productName = FirebaseRemoteConfig;
|
||||
};
|
||||
CBBE53372CF065800036D2D9 /* FacebookAEM */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = CBBE53362CF065800036D2D9 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */;
|
||||
productName = FacebookAEM;
|
||||
};
|
||||
CBBE53392CF065800036D2D9 /* FacebookBasics */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = CBBE53362CF065800036D2D9 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */;
|
||||
productName = FacebookBasics;
|
||||
};
|
||||
CBBE533B2CF065800036D2D9 /* FacebookCore */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = CBBE53362CF065800036D2D9 /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */;
|
||||
productName = FacebookCore;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
|
||||
/* Begin XCVersionGroup section */
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"originHash" : "faf6001fdda5daa8e1887ad5537209b76d1294d394c19dacdef18322dc72fcd3",
|
||||
"originHash" : "9e71afad327e34c7f8c73ead6293e6c190487271646bec0e9be664fa055d5064",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "abseil-cpp-binary",
|
||||
@ -24,8 +24,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/facebook/facebook-ios-sdk",
|
||||
"state" : {
|
||||
"revision" : "c19607d535864533523d1f437c84035e5fb101cf",
|
||||
"version" : "14.1.0"
|
||||
"revision" : "3cebc3b1d13dbe85868cc04f5bed63648e8c410c",
|
||||
"version" : "17.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
Binary file not shown.
@ -56,10 +56,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
MP_NetWorkManager.shared.requestStatusToYouTube()
|
||||
IQKeyboardManager.shared.enable = true
|
||||
IQKeyboardManager.shared.shouldResignOnTouchOutside = true
|
||||
//启用内购项
|
||||
MP_IAPManager.shared.observeVIPStoreKit()
|
||||
MP_IAPManager.shared.getVIPAllProducts()
|
||||
window = UIWindow(frame: UIScreen.main.bounds)
|
||||
window?.backgroundColor = .init(hex: "#161616")
|
||||
//关联faceBook
|
||||
ApplicationDelegate.shared.application(application,didFinishLaunchingWithOptions: launchOptions)
|
||||
Settings.shared.isAdvertiserIDCollectionEnabled = true
|
||||
switch_lunch()
|
||||
//执行用户启动事件日志
|
||||
MP_AnalyticsManager.shared.user_launchAction()
|
||||
|
||||
@ -15,6 +15,8 @@
|
||||
<string>515287044709010</string>
|
||||
<key>FacebookAutoLogAppEventsEnabled</key>
|
||||
<true/>
|
||||
<key>FacebookAdvertiserIDCollectionEnabled</key>
|
||||
<true/>
|
||||
<key>FacebookClientToken</key>
|
||||
<string>f7ec7d15be9315aee02f6deb37b52e15</string>
|
||||
<key>FacebookDisplayName</key>
|
||||
|
||||
@ -82,7 +82,6 @@ class MP_IAPViewController: UIViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
scrollView.contentInsetAdjustmentBehavior = .never
|
||||
MP_IAPManager.shared.requestProducts()
|
||||
MP_AnalyticsManager.shared.VIP_page_impAction()
|
||||
//根据isType调整约束(A/B面样式存在差距)
|
||||
if isType {
|
||||
@ -177,7 +176,11 @@ class MP_IAPViewController: UIViewController {
|
||||
MP_HUD.onlytext("Bad connection~".localizableString(), delay: 1.0, completion: nil)
|
||||
return
|
||||
}
|
||||
MP_IAPManager.shared.purchaseProduct(level)
|
||||
guard let productId = MP_IAPManager.shared.productIdentifiers.safeObject(at: level) else {
|
||||
MP_HUD.onlytext("Bad connection~".localizableString(), delay: 1.0, completion: nil)
|
||||
return
|
||||
}
|
||||
MP_IAPManager.shared.purchaseProduct(with: productId)
|
||||
}
|
||||
//刷新商店
|
||||
@IBAction func reStoreClick(_ sender: UIButton) {
|
||||
|
||||
@ -63,8 +63,6 @@ class MP_LunchViewController: UIViewController {
|
||||
}
|
||||
}
|
||||
}
|
||||
// //检查并清理多余缓存文件
|
||||
// MP_CacheAndArchiverManager.shared.zhoujunfeng_checkAndCleanCacheFolder()
|
||||
}
|
||||
deinit {
|
||||
timer.isPaused = true
|
||||
@ -104,11 +102,12 @@ class MP_LunchViewController: UIViewController {
|
||||
}else {
|
||||
//停止计时器
|
||||
timer.isPaused = true
|
||||
MP_AnalyticsManager.shared.launch_progress_endAction()
|
||||
//判断是否具备广告
|
||||
if adShowBlock != nil {
|
||||
MP_AnalyticsManager.shared.launch_progress_endAction("Yes")
|
||||
adShowBlock!()
|
||||
}else if switchBlock != nil {
|
||||
MP_AnalyticsManager.shared.launch_progress_endAction("No")
|
||||
switchBlock!()
|
||||
}
|
||||
}
|
||||
@ -117,23 +116,20 @@ class MP_LunchViewController: UIViewController {
|
||||
private func switchAOrBAction() {
|
||||
//首先检测之前是否进入过B面
|
||||
guard UserDefaults.standard.bool(forKey: "MP_Into_B") != true else {
|
||||
MP_IAPManager.shared.startLunchStatus()
|
||||
MPPositive_BrowseLoadViewModel.shared.getRecentlyData()
|
||||
MP_LuxServerManager.shared.upDateOpenActiveEventTask()
|
||||
loadAds()
|
||||
//更新开关以及响应的数据
|
||||
MP_AnalyticsManager.shared.getOpenStatus { [weak self] open in
|
||||
MP_AnalyticsManager.shared.getOpenStatus(0) { open in
|
||||
if open {
|
||||
print("允许进入B面")
|
||||
}else {
|
||||
print("不允许进入B面,但是之前进入过,所以能进入B面")
|
||||
}
|
||||
}
|
||||
//更新地区信息
|
||||
MP_NetWorkManager.shared.requestIPInfo { statu in
|
||||
MP_NetWorkManager.shared.requestIPInfo()
|
||||
//获取B面首页数据
|
||||
MPPositive_BrowseLoadViewModel.shared.getRecentlyData()
|
||||
MPPositive_BrowseLoadViewModel.shared.reloadBrowseLists()
|
||||
}
|
||||
}
|
||||
loadAds()
|
||||
//进入过B面
|
||||
self.completionBlock = {
|
||||
//允许进入b面
|
||||
@ -157,18 +153,14 @@ class MP_LunchViewController: UIViewController {
|
||||
MP_NetWorkManager.shared.performTaskNetWrokAvailable {
|
||||
[weak self] in
|
||||
guard let self = self else {return}
|
||||
MP_IAPManager.shared.startLunchStatus()
|
||||
MP_LuxServerManager.shared.upDateOpenActiveEventTask()
|
||||
//广告加载
|
||||
loadAds()
|
||||
//进行开关检测
|
||||
MP_AnalyticsManager.shared.getOpenStatus { [weak self] open in
|
||||
guard let self = self else {return}
|
||||
if open {
|
||||
loadAds()
|
||||
//根据ip值确定进入那个页面
|
||||
MP_NetWorkManager.shared.requestIPInfo { statu in
|
||||
if statu == true {
|
||||
//允许进入b面
|
||||
if open {//允许进入b面
|
||||
//获取B面首页数据
|
||||
MP_NetWorkManager.shared.requestIPInfo()
|
||||
MPPositive_BrowseLoadViewModel.shared.reloadBrowseLists()
|
||||
MPPositive_BrowseLoadViewModel.shared.getRecentlyData()
|
||||
print("BLog")
|
||||
@ -219,39 +211,6 @@ class MP_LunchViewController: UIViewController {
|
||||
}
|
||||
}
|
||||
}
|
||||
}else {
|
||||
//地区限制不允许进入B面
|
||||
print("ALog")
|
||||
//打开A面
|
||||
if self.maxTimes > self.currentTimes {
|
||||
//未超时,完成包处理
|
||||
self.completionBlock = {
|
||||
DispatchQueue.main.async {
|
||||
[weak self] in
|
||||
//printCallStack()
|
||||
guard let self = self else {return}
|
||||
//停止计时器
|
||||
timer.isPaused = true
|
||||
MP_AnalyticsManager.shared.jump_eventAction("jump to A", reason: "Switch test passed, IP detection failed")
|
||||
//加载完毕,判断并跳转
|
||||
accessAppdelegate.switch_aSide()
|
||||
}
|
||||
}
|
||||
}else {
|
||||
//已超时,直接跳转
|
||||
DispatchQueue.main.async {
|
||||
[weak self] in
|
||||
//printCallStack()
|
||||
guard let self = self else {return}
|
||||
//停止计时器
|
||||
timer.isPaused = true
|
||||
MP_AnalyticsManager.shared.jump_eventAction("jump to A", reason: "Switch test passed, IP detection failed")
|
||||
//加载完毕,判断并跳转
|
||||
accessAppdelegate.switch_aSide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}else {
|
||||
loadAds()
|
||||
print("ALog")
|
||||
|
||||
@ -102,9 +102,9 @@ func coreDefaultValues() {
|
||||
if UserDefaults.standard.object(forKey: "OpenICEID") == nil {
|
||||
print("第一次启动,添加广告ID")
|
||||
if let array = coreAdModelforJson([.init(level: 3, identifier: "ca-app-pub-1371732277241593/2126815630", ad: "AdMob", type: .Insert),
|
||||
.init(level: 2, identifier: "ca-app-pub-1371732277241593/8500652294", ad: "AdMob", type: .Open),
|
||||
.init(level: 2, identifier: "ca-app-pub-1371732277241593/4622083009", ad: "AdMob", type: .Open),
|
||||
.init(level: 1, identifier: "ca-app-pub-1371732277241593/4561407280", ad: "AdMob", type: .Insert),
|
||||
.init(level: 0, identifier: "ca-app-pub-1371732277241593/1926543650", ad: "AdMob", type: .Open)]) {
|
||||
.init(level: 0, identifier: "ca-app-pub-1371732277241593/9009655231", ad: "AdMob", type: .Open)]) {
|
||||
//存入默认开屏冷启动广告ID
|
||||
UserDefaults.standard.set(array, forKey: "OpenICEID")
|
||||
}
|
||||
@ -114,9 +114,9 @@ func coreDefaultValues() {
|
||||
}
|
||||
if UserDefaults.standard.object(forKey: "OpenHOSTID") == nil {
|
||||
if let array = coreAdModelforJson([.init(level: 3, identifier: "ca-app-pub-1371732277241593/9262752398", ad: "AdMob", type: .Insert),
|
||||
.init(level: 2, identifier: "ca-app-pub-1371732277241593/6536516707", ad: "AdMob", type: .Open),
|
||||
.init(level: 2, identifier: "ca-app-pub-1371732277241593/7616784144", ad: "AdMob", type: .Open),
|
||||
.init(level: 1, identifier: "ca-app-pub-1371732277241593/9239018894", ad: "AdMob", type: .Insert),
|
||||
.init(level: 0, identifier: "ca-app-pub-1371732277241593/3299335073", ad: "AdMob", type: .Open)]) {
|
||||
.init(level: 0, identifier: "ca-app-pub-1371732277241593/3541807764", ad: "AdMob", type: .Open)]) {
|
||||
//存入默认开屏热启动广告ID
|
||||
UserDefaults.standard.set(array, forKey: "OpenHOSTID")
|
||||
}
|
||||
@ -304,6 +304,12 @@ func improveDataforResouceAndCover(_ song:MPPositive_SongItemModel, completion:@
|
||||
} failure: {statu in
|
||||
failure?(statu)
|
||||
}
|
||||
// MP_NetWorkManager.shared.requestPlayer(song.videoId ?? "", playlistId: "") { resourceUrls, coverUrls in
|
||||
// completion(resourceUrls,coverUrls)
|
||||
// }failure: {statu in
|
||||
// failure?(statu)
|
||||
// }
|
||||
|
||||
}
|
||||
///转时分值
|
||||
func setTimesToMinSeconds(_ time:TimeInterval) -> String {
|
||||
|
||||
@ -48,11 +48,7 @@ class MP_ADSimpleManager: NSObject {
|
||||
}
|
||||
///广告总开关
|
||||
var openAdStatus:Bool{
|
||||
if let bool = UserDefaults.standard.object(forKey: "UserVIPLevels") as? Bool{
|
||||
return bool
|
||||
}else {
|
||||
return true
|
||||
}
|
||||
return !MP_IAPManager.shared.isVIP
|
||||
}
|
||||
///是否启用广告静音设置(音量改动)默认不静音
|
||||
private var isMuted:Bool {
|
||||
|
||||
@ -91,12 +91,12 @@ class MP_AVURLAsset: AVURLAsset {
|
||||
}
|
||||
}
|
||||
///媒体资源加载代理
|
||||
protocol MP_PlayerResourceLoaderDelegate: NSObject {
|
||||
///检测当前加载器是否缓存完毕
|
||||
func loader(_ loader:MP_PlayerResourceLoader, isCached:Bool)
|
||||
///检测当前加载器是否请求错误
|
||||
func loader(_ loader:MP_PlayerResourceLoader, requestError errorCode:Int)
|
||||
}
|
||||
//protocol MP_PlayerResourceLoaderDelegate: NSObject {
|
||||
// ///检测当前加载器是否缓存完毕
|
||||
// func loader(_ loader:MP_PlayerResourceLoader, isCached:Bool)
|
||||
// ///检测当前加载器是否请求错误
|
||||
// func loader(_ loader:MP_PlayerResourceLoader, requestError errorCode:Int)
|
||||
//}
|
||||
typealias CheckStatusBlock = (Int) -> Void
|
||||
|
||||
///媒体资源加载器
|
||||
@ -584,43 +584,43 @@ class MP_PlayerTaskManager:NSObject, URLSessionDataDelegate{
|
||||
[weak self] in
|
||||
guard let self = self else {return}
|
||||
task.cancel()
|
||||
MP_NetWorkManager.shared.requestAndroidPlayer(videoId, playlistId: "", clickTrackingParams: nil) { [weak self] results, cs in
|
||||
//只需要获取results第一位,也就是新的资源路径
|
||||
guard let self = self, let first = results?.0.first else {return}
|
||||
//通知更新
|
||||
NotificationCenter.notificationKey.post(notificationName: .player_asset_403, object: first)
|
||||
guard let url = URL(string: first) else {return}
|
||||
accessQueue.async {
|
||||
[weak self] in
|
||||
guard let self = self else {return}
|
||||
//取消原来的任务
|
||||
if dataTasks[videoId] != nil {
|
||||
dataTasks[videoId] = nil
|
||||
}
|
||||
//成功创建了新的资源路径,创建新的请求
|
||||
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: 50)
|
||||
//假如cacheLength有值,那说明是断点传续
|
||||
if let cacheLength = cacheLengths[videoId] {
|
||||
var range:String!
|
||||
if let filePathLength = fileLengths[videoId] {
|
||||
if cacheLength < filePathLength {
|
||||
//计算最后的值
|
||||
range = "bytes=\(cacheLength)-\(filePathLength-1)"
|
||||
request.setValue(range, forHTTPHeaderField: "Range")
|
||||
}
|
||||
}else {
|
||||
range = "bytes=\(cacheLength)-"
|
||||
//创建任务
|
||||
request.setValue(range, forHTTPHeaderField: "Range")
|
||||
}
|
||||
}
|
||||
//创建新的任务
|
||||
let dataTask = session.dataTask(with: request)
|
||||
requests[videoId] = request
|
||||
dataTasks[videoId] = dataTask
|
||||
dataTask.resume()
|
||||
}
|
||||
}
|
||||
// MP_NetWorkManager.shared.requestAndroidPlayer(videoId, playlistId: "", clickTrackingParams: nil) { [weak self] results, cs in
|
||||
// //只需要获取results第一位,也就是新的资源路径
|
||||
// guard let self = self, let first = results?.0.first else {return}
|
||||
// //通知更新
|
||||
// NotificationCenter.notificationKey.post(notificationName: .player_asset_403, object: first)
|
||||
// guard let url = URL(string: first) else {return}
|
||||
// accessQueue.async {
|
||||
// [weak self] in
|
||||
// guard let self = self else {return}
|
||||
// //取消原来的任务
|
||||
// if dataTasks[videoId] != nil {
|
||||
// dataTasks[videoId] = nil
|
||||
// }
|
||||
// //成功创建了新的资源路径,创建新的请求
|
||||
// var request = URLRequest(url: url, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: 50)
|
||||
// //假如cacheLength有值,那说明是断点传续
|
||||
// if let cacheLength = cacheLengths[videoId] {
|
||||
// var range:String!
|
||||
// if let filePathLength = fileLengths[videoId] {
|
||||
// if cacheLength < filePathLength {
|
||||
// //计算最后的值
|
||||
// range = "bytes=\(cacheLength)-\(filePathLength-1)"
|
||||
// request.setValue(range, forHTTPHeaderField: "Range")
|
||||
// }
|
||||
// }else {
|
||||
// range = "bytes=\(cacheLength)-"
|
||||
// //创建任务
|
||||
// request.setValue(range, forHTTPHeaderField: "Range")
|
||||
// }
|
||||
// }
|
||||
// //创建新的任务
|
||||
// let dataTask = session.dataTask(with: request)
|
||||
// requests[videoId] = request
|
||||
// dataTasks[videoId] = dataTask
|
||||
// dataTask.resume()
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
//下载任务移除
|
||||
|
||||
@ -316,27 +316,27 @@ class MP_AdMobManager: NSObject, GADAudioVideoManagerDelegate, GADFullScreenCont
|
||||
}
|
||||
///加载更多广告
|
||||
func loadMoreAdMobs() {
|
||||
loadPlayInterstitialAd{status in
|
||||
loadPlayInterstitialAd{ [weak self] status in
|
||||
guard let self = self else {return}
|
||||
if status {
|
||||
print("成功加载播放插页广告")
|
||||
}else {
|
||||
print("播放插页广告加载失败")
|
||||
}
|
||||
}
|
||||
loadSearchInterstitialAd { status in
|
||||
if status {
|
||||
print("成功加载搜索插页广告")
|
||||
}else {
|
||||
print("搜索插页广告加载失败")
|
||||
}
|
||||
}
|
||||
loadLibraryInterstitialAd { status in
|
||||
self.loadLibraryInterstitialAd { status in
|
||||
if status {
|
||||
print("成功加载曲库插页广告")
|
||||
}else {
|
||||
print("曲库插页广告加载失败")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//网络可用时触发
|
||||
|
||||
@ -323,46 +323,46 @@ class MP_AnalyticsManager: NSObject {
|
||||
//MARK: - 事件日志
|
||||
///执行用户启动日志
|
||||
func user_launchAction(){
|
||||
Analytics.logEvent(user_launch, parameters: nil)
|
||||
Analytics.logEvent(user_launch, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///启动页曝光
|
||||
func launch_pvAction(){
|
||||
Analytics.logEvent(launch_pv, parameters: nil)
|
||||
Analytics.logEvent(launch_pv, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///配置获取成功
|
||||
func config_success_eventAction() {
|
||||
Analytics.logEvent(config_success_event, parameters: nil)
|
||||
Analytics.logEvent(config_success_event, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///启动页进度结束时间
|
||||
func launch_progress_endAction() {
|
||||
Analytics.logEvent(launch_progress_end, parameters: nil)
|
||||
func launch_progress_endAction(_ showAd:String) {
|
||||
Analytics.logEvent(launch_progress_end, parameters: ["USER_STATU":isOLD ? "Old":"New","showAd":showAd])
|
||||
}
|
||||
///跳转事件
|
||||
func jump_eventAction(_ side:String, reason:String) {
|
||||
Analytics.logEvent(jump_event, parameters: ["side":side,
|
||||
Analytics.logEvent(jump_event, parameters: ["USER_STATU":isOLD ? "Old":"New","side":side,
|
||||
"reason":reason])
|
||||
}
|
||||
///A面首页曝光
|
||||
func home_a_pvAction(){
|
||||
Analytics.logEvent(home_a_pv, parameters: nil)
|
||||
Analytics.logEvent(home_a_pv, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///B面首页曝光
|
||||
func home_b_pvAction(){
|
||||
Analytics.logEvent(home_b_pv, parameters: nil)
|
||||
Analytics.logEvent(home_b_pv, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///首页资源曝光失败
|
||||
func home_b_module_showfailure_errorAction(_ error:String) {
|
||||
Analytics.logEvent(home_b_module_showfailure_error, parameters: ["error":error])
|
||||
Analytics.logEvent(home_b_module_showfailure_error, parameters: ["USER_STATU":isOLD ? "Old":"New","error":error])
|
||||
}
|
||||
///首页资源曝光成功
|
||||
func home_b_module_showsucces_actionAction(){
|
||||
Analytics.logEvent(home_b_module_showsucces_action, parameters: nil)
|
||||
Analytics.logEvent(home_b_module_showsucces_action, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
|
||||
/// 点击首页模块
|
||||
/// - Parameter modulename: 模块名
|
||||
func home_b_module_clickAction(_ modulename:String){
|
||||
Analytics.logEvent(home_b_module_click, parameters: ["modulename":modulename])
|
||||
Analytics.logEvent(home_b_module_click, parameters: ["USER_STATU":isOLD ? "Old":"New","modulename":modulename])
|
||||
}
|
||||
///B面我的曝光
|
||||
func me_b_pvAction(){
|
||||
@ -370,16 +370,16 @@ class MP_AnalyticsManager: NSObject {
|
||||
}
|
||||
///需要播放器弹出
|
||||
func song_clickAction(_ songfrom:String) {
|
||||
Analytics.logEvent(song_click, parameters: ["songfrom":songfrom])
|
||||
Analytics.logEvent(song_click, parameters: ["USER_STATU":isOLD ? "Old":"New","songfrom":songfrom])
|
||||
}
|
||||
|
||||
///B面播放器曝光
|
||||
func player_b_impAction() {
|
||||
Analytics.logEvent(player_b_imp, parameters: nil)
|
||||
Analytics.logEvent(player_b_imp, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///B面列表资源加载成功
|
||||
func player_b_listAction() {
|
||||
Analytics.logEvent(player_b_list, parameters: nil)
|
||||
Analytics.logEvent(player_b_list, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
|
||||
/// B面播放器切歌
|
||||
@ -389,6 +389,7 @@ class MP_AnalyticsManager: NSObject {
|
||||
/// - artistname: 歌手
|
||||
func player_b_pvAction(_ videoid:String, videoname:String, artistname:String){
|
||||
Analytics.logEvent(player_b_pv, parameters: [
|
||||
"USER_STATU":isOLD ? "Old":"New",
|
||||
"videoid":videoid,
|
||||
"videoname":videoname,
|
||||
"artistname":artistname
|
||||
@ -402,6 +403,7 @@ class MP_AnalyticsManager: NSObject {
|
||||
/// - artistname: 歌手
|
||||
func player_b_delay_actionAction(_ videoid:String, videoname:String, artistname:String, delay:String){
|
||||
Analytics.logEvent(player_b_delay_action, parameters: [
|
||||
"USER_STATU":isOLD ? "Old":"New",
|
||||
"videoid":videoid,
|
||||
"videoname":videoname,
|
||||
"artistname":artistname,
|
||||
@ -415,6 +417,7 @@ class MP_AnalyticsManager: NSObject {
|
||||
/// - artistname: 歌手
|
||||
func player_b_success_actionAction(_ videoid:String, videoname:String, artistname:String){
|
||||
Analytics.logEvent(player_b_success_action, parameters: [
|
||||
"USER_STATU":isOLD ? "Old":"New",
|
||||
"videoid":videoid,
|
||||
"videoname":videoname,
|
||||
"artistname":artistname
|
||||
@ -423,6 +426,7 @@ class MP_AnalyticsManager: NSObject {
|
||||
///播放失败
|
||||
func player_b_failure_errorAction(_ videoid:String, videoname:String, artistname:String, error:String) {
|
||||
Analytics.logEvent(player_b_failure_error, parameters: [
|
||||
"USER_STATU":isOLD ? "Old":"New",
|
||||
"videoid":videoid,
|
||||
"videoname":videoname,
|
||||
"artistname":artistname,
|
||||
@ -431,11 +435,11 @@ class MP_AnalyticsManager: NSObject {
|
||||
}
|
||||
///资源获取失败
|
||||
func player_resource_failureAction(_ code:String) {
|
||||
Analytics.logEvent(player_resource_failure, parameters: ["area_code":code])
|
||||
Analytics.logEvent(player_resource_failure, parameters: ["USER_STATU":isOLD ? "Old":"New","area_code":code])
|
||||
}
|
||||
///IP被拉黑
|
||||
func resource_IP_blackAction() {
|
||||
var parameters:[String:String] = [:]
|
||||
var parameters:[String:String] = ["USER_STATU":isOLD ? "Old":"New"]
|
||||
if let ipInfo = UserDefaults.standard.object(forKey: "IP_Info") as? String {
|
||||
parameters["ip"] = ipInfo
|
||||
}else {
|
||||
@ -451,6 +455,7 @@ class MP_AnalyticsManager: NSObject {
|
||||
/// - artistname: 歌手
|
||||
func player_b_love_clickAction(_ videoid:String, videoname:String, artistname:String){
|
||||
Analytics.logEvent(player_b_love_click, parameters: [
|
||||
"USER_STATU":isOLD ? "Old":"New",
|
||||
"videoid":videoid,
|
||||
"videoname":videoname,
|
||||
"artistname":artistname
|
||||
@ -463,6 +468,7 @@ class MP_AnalyticsManager: NSObject {
|
||||
/// - artistname: 歌手
|
||||
func player_b_unlove_clickAction(_ videoid:String, videoname:String, artistname:String){
|
||||
Analytics.logEvent(player_b_unlove_click, parameters: [
|
||||
"USER_STATU":isOLD ? "Old":"New",
|
||||
"videoid":videoid,
|
||||
"videoname":videoname,
|
||||
"artistname":artistname
|
||||
@ -475,6 +481,7 @@ class MP_AnalyticsManager: NSObject {
|
||||
/// - artistname: 歌手
|
||||
func player_b_download_clickAction(_ videoid:String, videoname:String, artistname:String){
|
||||
Analytics.logEvent(player_b_download_click, parameters: [
|
||||
"USER_STATU":isOLD ? "Old":"New",
|
||||
"videoid":videoid,
|
||||
"videoname":videoname,
|
||||
"artistname":artistname
|
||||
@ -487,6 +494,7 @@ class MP_AnalyticsManager: NSObject {
|
||||
/// - artistname: 歌手
|
||||
func player_b_downloadsuccess_actionAction(_ videoid:String, videoname:String, artistname:String){
|
||||
Analytics.logEvent(player_b_downloadsuccess_action, parameters: [
|
||||
"USER_STATU":isOLD ? "Old":"New",
|
||||
"videoid":videoid,
|
||||
"videoname":videoname,
|
||||
"artistname":artistname
|
||||
@ -495,6 +503,7 @@ class MP_AnalyticsManager: NSObject {
|
||||
///下载失败
|
||||
func player_b_downloadfailure_errorAction(_ videoid:String, videoname:String, artistname:String, error:String) {
|
||||
Analytics.logEvent(player_b_downloadfailure_error, parameters: [
|
||||
"USER_STATU":isOLD ? "Old":"New",
|
||||
"videoid":videoid,
|
||||
"videoname":videoname,
|
||||
"artistname":artistname,
|
||||
@ -503,81 +512,81 @@ class MP_AnalyticsManager: NSObject {
|
||||
}
|
||||
///B面搜索曝光
|
||||
func search_pvAction(){
|
||||
Analytics.logEvent(search_pv, parameters: nil)
|
||||
Analytics.logEvent(search_pv, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///B面搜索心情流派模块点击
|
||||
func grid_mood_clickAction(_ mood:String) {
|
||||
Analytics.logEvent(grid_mood_click, parameters: ["mood":mood])
|
||||
Analytics.logEvent(grid_mood_click, parameters: ["USER_STATU":isOLD ? "Old":"New","mood":mood])
|
||||
}
|
||||
///如何进入搜索结果页
|
||||
func search_from_actionAction(_ from:String) {
|
||||
Analytics.logEvent(search_from_action, parameters: ["search_from":from])
|
||||
Analytics.logEvent(search_from_action, parameters: ["USER_STATU":isOLD ? "Old":"New","search_from":from])
|
||||
}
|
||||
|
||||
///搜索SUG曝光
|
||||
func search_sug_showAction(){
|
||||
Analytics.logEvent(search_sug_show, parameters: nil)
|
||||
Analytics.logEvent(search_sug_show, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
|
||||
/// 点击sug结果
|
||||
/// - Parameter sugname: 搜索标签名
|
||||
func search_sug_clickAction(_ sugname:String){
|
||||
Analytics.logEvent(search_sug_click, parameters: ["sugname":sugname])
|
||||
Analytics.logEvent(search_sug_click, parameters: ["USER_STATU":isOLD ? "Old":"New","sugname":sugname])
|
||||
}
|
||||
///搜索结果曝光
|
||||
func search_result_pvAction(){
|
||||
Analytics.logEvent(search_result_pv, parameters: nil)
|
||||
Analytics.logEvent(search_result_pv, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///搜索有结果
|
||||
func search_resultsuccess_actionAction(){
|
||||
Analytics.logEvent(search_resultsuccess_action, parameters: nil)
|
||||
Analytics.logEvent(search_resultsuccess_action, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///创建自定义歌单
|
||||
func create_list_actionAction() {
|
||||
Analytics.logEvent(create_list_action, parameters: nil)
|
||||
Analytics.logEvent(create_list_action, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
|
||||
///好评引导结果
|
||||
func guide_clickAction(_ result:String) {
|
||||
Analytics.logEvent(guide_click, parameters: ["result":result])
|
||||
Analytics.logEvent(guide_click, parameters: ["USER_STATU":isOLD ? "Old":"New","result":result])
|
||||
}
|
||||
///曲库点击事件
|
||||
func library_clickAction(_ click:String, folder:String) {
|
||||
Analytics.logEvent(library_click, parameters: ["click_from":click,
|
||||
Analytics.logEvent(library_click, parameters: ["USER_STATU":isOLD ? "Old":"New","click_from":click,
|
||||
"folder_click":folder])
|
||||
}
|
||||
///点击付费入口
|
||||
func VIP_clickAction() {
|
||||
Analytics.logEvent(VIP_click, parameters: nil)
|
||||
Analytics.logEvent(VIP_click, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///付费页面展示
|
||||
func VIP_page_impAction() {
|
||||
Analytics.logEvent(VIP_page_imp, parameters: nil)
|
||||
Analytics.logEvent(VIP_page_imp, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///购买付费套餐
|
||||
func VIP_continue_clickAction(_ productID:String) {
|
||||
Analytics.logEvent(VIP_continue_click, parameters: ["productID":productID])
|
||||
Analytics.logEvent(VIP_continue_click, parameters: ["USER_STATU":isOLD ? "Old":"New","productID":productID])
|
||||
}
|
||||
///购买成功
|
||||
func VIP_buy_successAction(_ productID:String) {
|
||||
Analytics.logEvent(VIP_buy_success, parameters: ["productID":productID])
|
||||
Analytics.logEvent(VIP_buy_success, parameters: ["USER_STATU":isOLD ? "Old":"New","productID":productID])
|
||||
}
|
||||
///购买失败
|
||||
func VIP_buy_failureAction(_ productID:String, error:String) {
|
||||
Analytics.logEvent(VIP_buy_failure, parameters: ["productID":productID,
|
||||
Analytics.logEvent(VIP_buy_failure, parameters: ["USER_STATU":isOLD ? "Old":"New","productID":productID,
|
||||
"buy_error":error])
|
||||
}
|
||||
///弹出更新框
|
||||
func update_reminder_showAction() {
|
||||
Analytics.logEvent(update_reminder_show, parameters: nil)
|
||||
Analytics.logEvent(update_reminder_show, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///确认更新
|
||||
func update_reminder_sureAction() {
|
||||
Analytics.logEvent(update_reminder_sure, parameters: nil)
|
||||
Analytics.logEvent(update_reminder_sure, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///取消更新
|
||||
func update_reminder_cancelAction() {
|
||||
Analytics.logEvent(update_reminder_cancel, parameters: nil)
|
||||
Analytics.logEvent(update_reminder_cancel, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
//MARK: - 广告埋点事件
|
||||
//冷启动展示机会
|
||||
@ -714,7 +723,7 @@ class MP_AnalyticsManager: NSObject {
|
||||
"CS_UNIT_NAME":mediation,
|
||||
"CS_CURRENCY":adValue.currencyCode,
|
||||
"CS_VALUE":priceString,
|
||||
"CS_STATUS":isOLD ? "Old":"New"]
|
||||
"USER_STATU":isOLD ? "Old":"New"]
|
||||
return parameters
|
||||
}
|
||||
///检索广告来源
|
||||
@ -736,11 +745,11 @@ class MP_AnalyticsManager: NSObject {
|
||||
//MARK: - 广告事件日志
|
||||
///冷启动展示机会
|
||||
func cold_ads_chanceAction() {
|
||||
Analytics.logEvent(cold_ads_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"])
|
||||
Analytics.logEvent(cold_ads_chance, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///冷启动加载失败
|
||||
func cold_ads_loadFailureAction(_ error:String) {
|
||||
let parameters:[String:String] = ["CS_STATUS":isOLD ? "Old":"New",
|
||||
let parameters:[String:String] = ["USER_STATU":isOLD ? "Old":"New",
|
||||
"CS_ERROR":error]
|
||||
Analytics.logEvent(cold_ads_loadFailure, parameters: parameters)
|
||||
}
|
||||
@ -752,23 +761,23 @@ class MP_AnalyticsManager: NSObject {
|
||||
}
|
||||
///冷启动展示失败
|
||||
func cold_ads_showFailureAction(_ error:String) {
|
||||
let parameters:[String:String] = ["CS_STATUS":isOLD ? "Old":"New",
|
||||
let parameters:[String:String] = ["USER_STATU":isOLD ? "Old":"New",
|
||||
"CS_ERROR":error]
|
||||
print("冷启动失败展示信息--\(parameters)")
|
||||
Analytics.logEvent(cold_ads_showFailure, parameters: parameters)
|
||||
}
|
||||
///冷启动关闭
|
||||
func cold_ads_closeAction() {
|
||||
let parameters:[String:String] = ["CS_STATUS":isOLD ? "Old":"New"]
|
||||
let parameters:[String:String] = ["USER_STATU":isOLD ? "Old":"New"]
|
||||
Analytics.logEvent(cold_ads_close, parameters: parameters)
|
||||
}
|
||||
///热启动展示机会
|
||||
func hot_ads_chanceAction() {
|
||||
Analytics.logEvent(hot_ads_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"])
|
||||
Analytics.logEvent(hot_ads_chance, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///热启动加载失败
|
||||
func hot_ads_loadFailureAction(_ error:String) {
|
||||
let parameters:[String:String] = ["CS_STATUS":isOLD ? "Old":"New",
|
||||
let parameters:[String:String] = ["USER_STATU":isOLD ? "Old":"New",
|
||||
"CS_ERROR":error]
|
||||
Analytics.logEvent(hot_ads_loadFailure, parameters: parameters)
|
||||
}
|
||||
@ -780,7 +789,7 @@ class MP_AnalyticsManager: NSObject {
|
||||
}
|
||||
///热启动展示失败
|
||||
func hot_ads_showFailureAction(_ error:String) {
|
||||
let parameters:[String:String] = ["CS_STATUS":isOLD ? "Old":"New",
|
||||
let parameters:[String:String] = ["USER_STATU":isOLD ? "Old":"New",
|
||||
"CS_ERROR":error]
|
||||
print("热启动失败展示信息--\(parameters)")
|
||||
Analytics.logEvent(hot_ads_showFailure, parameters: parameters)
|
||||
@ -788,11 +797,11 @@ class MP_AnalyticsManager: NSObject {
|
||||
|
||||
///搜索展示机会
|
||||
func search_ads_chanceAction() {
|
||||
Analytics.logEvent(search_ads_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"])
|
||||
Analytics.logEvent(search_ads_chance, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///搜索加载失败
|
||||
func search_ads_loadFailureAction(_ error:String) {
|
||||
let parameters:[String:String] = ["CS_STATUS":isOLD ? "Old":"New",
|
||||
let parameters:[String:String] = ["USER_STATU":isOLD ? "Old":"New",
|
||||
"CS_ERROR":error]
|
||||
Analytics.logEvent(search_ads_loadFailure, parameters: parameters)
|
||||
}
|
||||
@ -800,11 +809,11 @@ class MP_AnalyticsManager: NSObject {
|
||||
func search_ads_showSuccessAction(_ responseInfo:GADResponseInfo, adValue:GADAdValue) {
|
||||
let parameters:[String:Any] = infoToParameters(responseInfo, adValue: adValue)
|
||||
print("搜索插页成功展示信息--\(parameters)")
|
||||
Analytics.logEvent(search_ads_showSuccess, parameters: ["CS_STATUS":isOLD ? "Old":"New"])
|
||||
Analytics.logEvent(search_ads_showSuccess, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///搜索展示失败
|
||||
func search_ads_showFailureAction(_ error:String) {
|
||||
let parameters:[String:String] = ["CS_STATUS":isOLD ? "Old":"New",
|
||||
let parameters:[String:String] = ["USER_STATU":isOLD ? "Old":"New",
|
||||
"CS_ERROR":error]
|
||||
print("搜索插页失败展示信息--\(parameters)")
|
||||
Analytics.logEvent(search_ads_showFailure, parameters: parameters)
|
||||
@ -812,7 +821,7 @@ class MP_AnalyticsManager: NSObject {
|
||||
|
||||
///搜索结果页展示机会
|
||||
func result_ads_chanceAction() {
|
||||
Analytics.logEvent(result_ads_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"])
|
||||
Analytics.logEvent(result_ads_chance, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///搜索结果页成功
|
||||
func result_ads_showAction(_ responseInfo:GADResponseInfo, adValue:GADAdValue) {
|
||||
@ -823,11 +832,11 @@ class MP_AnalyticsManager: NSObject {
|
||||
|
||||
///播放展示机会
|
||||
func play_ads_chanceAction() {
|
||||
Analytics.logEvent(play_ads_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"])
|
||||
Analytics.logEvent(play_ads_chance, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///播放加载失败
|
||||
func play_ads_loadFailureAction(_ error:String) {
|
||||
let parameters:[String:String] = ["CS_STATUS":isOLD ? "Old":"New",
|
||||
let parameters:[String:String] = ["USER_STATU":isOLD ? "Old":"New",
|
||||
"CS_ERROR":error]
|
||||
Analytics.logEvent(play_ads_loadFailure, parameters: parameters)
|
||||
}
|
||||
@ -839,7 +848,7 @@ class MP_AnalyticsManager: NSObject {
|
||||
}
|
||||
///播放展示失败
|
||||
func play_ads_showFailureAction(_ error:String) {
|
||||
let parameters:[String:String] = ["CS_STATUS":isOLD ? "Old":"New",
|
||||
let parameters:[String:String] = ["USER_STATU":isOLD ? "Old":"New",
|
||||
"CS_ERROR":error]
|
||||
print("播放插页失败展示信息--\(parameters)")
|
||||
Analytics.logEvent(play_ads_showFailure, parameters: parameters)
|
||||
@ -847,11 +856,11 @@ class MP_AnalyticsManager: NSObject {
|
||||
|
||||
///下载展示机会
|
||||
func dl_ads_chanceAction() {
|
||||
Analytics.logEvent(dl_ads_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"])
|
||||
Analytics.logEvent(dl_ads_chance, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///下载加载失败
|
||||
func dl_ads_loadFailureAction(_ error:String) {
|
||||
let parameters:[String:String] = ["CS_STATUS":isOLD ? "Old":"New",
|
||||
let parameters:[String:String] = ["USER_STATU":isOLD ? "Old":"New",
|
||||
"CS_ERROR":error]
|
||||
Analytics.logEvent(dl_ads_loadFailure, parameters: parameters)
|
||||
}
|
||||
@ -863,7 +872,7 @@ class MP_AnalyticsManager: NSObject {
|
||||
}
|
||||
///下载展示失败
|
||||
func dl_ads_showFailureAction(_ error:String) {
|
||||
let parameters:[String:String] = ["CS_STATUS":isOLD ? "Old":"New",
|
||||
let parameters:[String:String] = ["USER_STATU":isOLD ? "Old":"New",
|
||||
"CS_ERROR":error]
|
||||
print("下载插页失败展示信息--\(parameters)")
|
||||
Analytics.logEvent(dl_ads_showFailure, parameters: parameters)
|
||||
@ -871,11 +880,11 @@ class MP_AnalyticsManager: NSObject {
|
||||
|
||||
///切歌展示机会
|
||||
func cut_ads_chanceAction() {
|
||||
Analytics.logEvent(cut_ads_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"])
|
||||
Analytics.logEvent(cut_ads_chance, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///切歌加载失败
|
||||
func cut_ads_loadFailureAction(_ error:String) {
|
||||
let parameters:[String:String] = ["CS_STATUS":isOLD ? "Old":"New",
|
||||
let parameters:[String:String] = ["USER_STATU":isOLD ? "Old":"New",
|
||||
"CS_ERROR":error]
|
||||
Analytics.logEvent(cut_ads_loadFailure, parameters: parameters)
|
||||
}
|
||||
@ -887,7 +896,7 @@ class MP_AnalyticsManager: NSObject {
|
||||
}
|
||||
///切歌展示失败
|
||||
func cut_ads_showFailureAction(_ error:String) {
|
||||
let parameters:[String:String] = ["CS_STATUS":isOLD ? "Old":"New",
|
||||
let parameters:[String:String] = ["USER_STATU":isOLD ? "Old":"New",
|
||||
"CS_ERROR":error]
|
||||
print("切歌插页失败展示信息--\(parameters)")
|
||||
Analytics.logEvent(cut_ads_showFailure, parameters: parameters)
|
||||
@ -895,11 +904,11 @@ class MP_AnalyticsManager: NSObject {
|
||||
|
||||
///列表进入机会
|
||||
func listclk_ads_chanceAction() {
|
||||
Analytics.logEvent(listclk_ads_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"])
|
||||
Analytics.logEvent(listclk_ads_chance, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///列表加载失败
|
||||
func listclk_ads_loadFailureAction(_ error:String) {
|
||||
let parameters:[String:String] = ["CS_STATUS":isOLD ? "Old":"New",
|
||||
let parameters:[String:String] = ["USER_STATU":isOLD ? "Old":"New",
|
||||
"CS_ERROR":error]
|
||||
Analytics.logEvent(listclk_ads_loadFailure, parameters: parameters)
|
||||
}
|
||||
@ -911,7 +920,7 @@ class MP_AnalyticsManager: NSObject {
|
||||
}
|
||||
///列表进入失败
|
||||
func listclk_ads_showFailureAction(_ error:String) {
|
||||
let parameters:[String:String] = ["CS_STATUS":isOLD ? "Old":"New",
|
||||
let parameters:[String:String] = ["USER_STATU":isOLD ? "Old":"New",
|
||||
"CS_ERROR":error]
|
||||
print("列表插页失败展示信息--\(parameters)")
|
||||
Analytics.logEvent(listclk_ads_showFailure, parameters: parameters)
|
||||
@ -919,7 +928,7 @@ class MP_AnalyticsManager: NSObject {
|
||||
|
||||
///曲库原生机会
|
||||
func lbr_ads_chanceAction() {
|
||||
Analytics.logEvent(lbr_ads_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"])
|
||||
Analytics.logEvent(lbr_ads_chance, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///曲库原生次数
|
||||
func lbr_ads_showAction(_ responseInfo:GADResponseInfo, adValue:GADAdValue) {
|
||||
@ -929,7 +938,7 @@ class MP_AnalyticsManager: NSObject {
|
||||
}
|
||||
///列表原生机会
|
||||
func list_ads_chanceAction() {
|
||||
Analytics.logEvent(list_ads_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"])
|
||||
Analytics.logEvent(list_ads_chance, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///列表原生次数
|
||||
func list_ads_showAction(_ responseInfo:GADResponseInfo, adValue:GADAdValue) {
|
||||
@ -960,74 +969,74 @@ class MP_AnalyticsManager: NSObject {
|
||||
|
||||
///AppLovin开屏广告展示机会
|
||||
func max_lunch_chanceAction() {
|
||||
Analytics.logEvent(max_lunch_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"])
|
||||
Analytics.logEvent(max_lunch_chance, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///AppLovin开屏广告加载失败
|
||||
func max_lunch_loadFailureAction(_ error:String) {
|
||||
Analytics.logEvent(max_lunch_loadFailure, parameters: ["CS_STATUS":isOLD ? "Old":"New",
|
||||
Analytics.logEvent(max_lunch_loadFailure, parameters: ["USER_STATU":isOLD ? "Old":"New",
|
||||
"CS_ERROR":error])
|
||||
}
|
||||
///AppLovin开屏广告展示成功
|
||||
func max_lunch_showSuccessAction() {
|
||||
Analytics.logEvent(max_lunch_showSuccess, parameters: ["CS_STATUS":isOLD ? "Old":"New"])
|
||||
Analytics.logEvent(max_lunch_showSuccess, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///AppLovin开屏广告展示失败
|
||||
func max_lunch_showFailureAction(_ error:String) {
|
||||
Analytics.logEvent(max_lunch_showFailure, parameters: ["CS_STATUS":isOLD ? "Old":"New",
|
||||
Analytics.logEvent(max_lunch_showFailure, parameters: ["USER_STATU":isOLD ? "Old":"New",
|
||||
"CS_ERROR":error])
|
||||
}
|
||||
///AppLovin搜索广告展示机会
|
||||
func max_search_chanceAction() {
|
||||
Analytics.logEvent(max_search_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"])
|
||||
Analytics.logEvent(max_search_chance, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///AppLovin搜索广告加载失败
|
||||
func max_search_loadFailureAction(_ error:String) {
|
||||
Analytics.logEvent(max_search_loadFailure, parameters: ["CS_STATUS":isOLD ? "Old":"New",
|
||||
Analytics.logEvent(max_search_loadFailure, parameters: ["USER_STATU":isOLD ? "Old":"New",
|
||||
"CS_ERROR":error])
|
||||
}
|
||||
///AppLovin搜索广告展示成功
|
||||
func max_search_showSuccessAction() {
|
||||
Analytics.logEvent(max_search_showSuccess, parameters: ["CS_STATUS":isOLD ? "Old":"New"])
|
||||
Analytics.logEvent(max_search_showSuccess, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///AppLovin搜索广告展示失败
|
||||
func max_search_showFailureAction(_ error:String) {
|
||||
Analytics.logEvent(max_search_showFailure, parameters: ["CS_STATUS":isOLD ? "Old":"New",
|
||||
Analytics.logEvent(max_search_showFailure, parameters: ["USER_STATU":isOLD ? "Old":"New",
|
||||
"CS_ERROR":error])
|
||||
}
|
||||
///AppLovin播放广告展示机会
|
||||
func max_play_chanceACtion() {
|
||||
Analytics.logEvent(max_play_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"])
|
||||
Analytics.logEvent(max_play_chance, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///AppLovin播放广告加载失败
|
||||
func max_play_loadFailureAction(_ error:String) {
|
||||
Analytics.logEvent(max_play_loadFailure, parameters: ["CS_STATUS":isOLD ? "Old":"New",
|
||||
Analytics.logEvent(max_play_loadFailure, parameters: ["USER_STATU":isOLD ? "Old":"New",
|
||||
"CS_ERROR":error])
|
||||
}
|
||||
///AppLovin播放广告展示成功
|
||||
func max_play_showSuccessAction() {
|
||||
Analytics.logEvent(max_play_showSuccess, parameters: ["CS_STATUS":isOLD ? "Old":"New"])
|
||||
Analytics.logEvent(max_play_showSuccess, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///AppLovin播放广告展示失败
|
||||
func max_play_showFailureAction(_ error:String) {
|
||||
Analytics.logEvent(max_play_showFailure, parameters: ["CS_STATUS":isOLD ? "Old":"New",
|
||||
Analytics.logEvent(max_play_showFailure, parameters: ["USER_STATU":isOLD ? "Old":"New",
|
||||
"CS_ERROR":error])
|
||||
}
|
||||
///AppLovin曲库广告展示机会
|
||||
func max_library_chanceAction() {
|
||||
Analytics.logEvent(max_library_chance, parameters: ["CS_STATUS":isOLD ? "Old":"New"])
|
||||
Analytics.logEvent(max_library_chance, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///AppLovin曲库广告加载失败
|
||||
func max_library_loadFailureAction(_ error:String) {
|
||||
Analytics.logEvent(max_library_loadFailure, parameters: ["CS_STATUS":isOLD ? "Old":"New",
|
||||
Analytics.logEvent(max_library_loadFailure, parameters: ["USER_STATU":isOLD ? "Old":"New",
|
||||
"CS_ERROR":error])
|
||||
}
|
||||
///AppLovin曲库广告展示成功
|
||||
func max_library_showSuccessAction() {
|
||||
Analytics.logEvent(max_library_showSuccess, parameters: ["CS_STATUS":isOLD ? "Old":"New"])
|
||||
Analytics.logEvent(max_library_showSuccess, parameters: ["USER_STATU":isOLD ? "Old":"New"])
|
||||
}
|
||||
///AppLovin曲库广告展示失败
|
||||
func max_library_showFailureAction(_ error:String) {
|
||||
Analytics.logEvent(max_library_showFailure, parameters: ["CS_STATUS":isOLD ? "Old":"New",
|
||||
Analytics.logEvent(max_library_showFailure, parameters: ["USER_STATU":isOLD ? "Old":"New",
|
||||
"CS_ERROR":error])
|
||||
}
|
||||
}
|
||||
|
||||
@ -218,21 +218,20 @@ class MP_AppLovinManager: NSObject {
|
||||
}
|
||||
///加载广告
|
||||
func loadMoreAds(){
|
||||
loadPlayInterstitialAd{status in
|
||||
loadPlayInterstitialAd{ [weak self] status in
|
||||
guard let self = self else {return}
|
||||
if status {
|
||||
print("成功加载播放插页广告")
|
||||
}else {
|
||||
print("播放插页广告加载失败")
|
||||
}
|
||||
}
|
||||
loadSearchInterstitialAd { status in
|
||||
if status {
|
||||
print("成功加载搜索插页广告")
|
||||
}else {
|
||||
print("搜索插页广告加载失败")
|
||||
}
|
||||
}
|
||||
loadLibraryInterstitialAd { status in
|
||||
self.loadLibraryInterstitialAd { status in
|
||||
if status {
|
||||
print("成功加载曲库插页广告")
|
||||
}else {
|
||||
@ -241,6 +240,8 @@ class MP_AppLovinManager: NSObject {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - 开屏广告
|
||||
///开屏广告ID组
|
||||
|
||||
@ -6,357 +6,527 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftyStoreKit
|
||||
import StoreKit
|
||||
///内部购买项目管理器
|
||||
class MP_IAPManager: NSObject {
|
||||
///单例
|
||||
static let shared = MP_IAPManager()
|
||||
///产品ID(根据开关获取)
|
||||
private var productIdentifiers:[String] = ["1weekvip","1yearvip","lifetimevip"]
|
||||
var productIdentifiers:[String] = ["1weekvip","1yearvip","lifetimevip"]
|
||||
///产品交易信息组
|
||||
private var transactions:[PaymentTransaction] = []
|
||||
///产品请求(根据ID获取产品信息)
|
||||
private var productsRequest:SKProductsRequest?
|
||||
///可用产品组
|
||||
private var availableProducts:[SKProduct] = []
|
||||
private var availableProducts:Set<SKProduct> = []
|
||||
///调用方案
|
||||
private var showHUD:Bool = false
|
||||
///是否是VIP
|
||||
var isVIP:Bool {
|
||||
//获取VIP过期时间
|
||||
guard let lastDate = UserDefaults.standard.object(forKey: "PurchaseVIPDate") as? Date, lastDate.timeIntervalSince(Date()) > 0 else { return false }
|
||||
return true
|
||||
}
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
SKPaymentQueue.default().add(self)
|
||||
|
||||
}
|
||||
deinit {
|
||||
SKPaymentQueue.default().remove(self)
|
||||
|
||||
}
|
||||
///执行产品请求
|
||||
func requestProducts() {
|
||||
guard MP_NetWorkManager.shared.netWorkStatu == .reachable else {
|
||||
//无网络
|
||||
print("无网络状况,无法获得产品")
|
||||
return
|
||||
///校正内购产品交易情况
|
||||
func observeVIPStoreKit() {
|
||||
//清理原有的交易信息组
|
||||
transactions.removeAll()
|
||||
//执行交易信息组校正处理
|
||||
SwiftyStoreKit.completeTransactions { [weak self] purchases in
|
||||
guard let self = self else { return }
|
||||
var productId = ""
|
||||
//检查交易情况
|
||||
purchases.forEach { item in
|
||||
switch item.transaction.transactionState {
|
||||
case .purchased, .restored://交易完成/处理中
|
||||
//检查交易是否需要强制终结
|
||||
if item.needsFinishTransaction {
|
||||
SwiftyStoreKit.finishTransaction(item.transaction)
|
||||
}
|
||||
//清空可用产品
|
||||
availableProducts = []
|
||||
//将产品ID转为Set集合
|
||||
let setStrings:Set<String> = Set(productIdentifiers)
|
||||
//初始化产品请求
|
||||
productsRequest = SKProductsRequest(productIdentifiers: setStrings)
|
||||
//实现请求代理
|
||||
productsRequest?.delegate = self
|
||||
//开始执行请求
|
||||
productsRequest?.start()
|
||||
//更新产品ID
|
||||
productId = item.productId
|
||||
//添加交易信息组
|
||||
self.transactions.append(item.transaction)
|
||||
case .failed://交易失败
|
||||
//检查交易是否需要强制终结
|
||||
if item.needsFinishTransaction {
|
||||
SwiftyStoreKit.finishTransaction(item.transaction)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
//交易检索完毕,查看是否有订阅的产品
|
||||
if !productId.isEmpty {
|
||||
//存在交易产品,重新验证一下流程
|
||||
print("当前用户有VIP记录,进行验证")
|
||||
self.showHUD = false
|
||||
self.purchaseVerificationProductVIP(with: productId)
|
||||
}else {
|
||||
//无交易内容
|
||||
print("当前用户非VIP")
|
||||
}
|
||||
}
|
||||
}
|
||||
///获取内购产品信息
|
||||
func getVIPAllProducts() {
|
||||
let productIds:Set<String> = Set(productIdentifiers)
|
||||
SwiftyStoreKit.retrieveProductsInfo(productIds) { [weak self] results in
|
||||
guard let self = self else { return }
|
||||
//检索产品信息
|
||||
if !results.retrievedProducts.isEmpty {
|
||||
self.availableProducts = results.retrievedProducts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ///执行产品请求
|
||||
// func requestProducts() {
|
||||
// guard MP_NetWorkManager.shared.netWorkStatu == .reachable else {
|
||||
// //无网络
|
||||
// print("无网络状况,无法获得产品")
|
||||
// return
|
||||
// }
|
||||
// //清空可用产品
|
||||
// availableProducts = []
|
||||
// //将产品ID转为Set集合
|
||||
// let setStrings:Set<String> = Set(productIdentifiers)
|
||||
// //初始化产品请求
|
||||
// productsRequest = SKProductsRequest(productIdentifiers: setStrings)
|
||||
// //实现请求代理
|
||||
// productsRequest?.delegate = self
|
||||
// //开始执行请求
|
||||
// productsRequest?.start()
|
||||
// }
|
||||
///对指定产品进行购买
|
||||
func purchaseProduct(_ index:Int) {
|
||||
//检索用户选择的产品
|
||||
guard availableProducts.indices.contains(index) else {
|
||||
//越界了,说明广告总数出了问题,重新请求
|
||||
MP_HUD.onlytext("Try again".localizableString(), delay: 1.0, completion: nil)
|
||||
MP_AnalyticsManager.shared.VIP_buy_failureAction(productIdentifiers[index], error: "Product data loading failed")
|
||||
requestProducts()
|
||||
return
|
||||
}
|
||||
let id = productIdentifiers[index]
|
||||
if let product = availableProducts.first(where: {$0.productIdentifier == id}) {
|
||||
MP_AnalyticsManager.shared.VIP_continue_clickAction(id)
|
||||
//将产品转为交易项
|
||||
let payment = SKPayment(product: product)
|
||||
//将交易项添加进购买线程中
|
||||
SKPaymentQueue.default().add(payment)
|
||||
func purchaseProduct(with productId:String) {
|
||||
showHUD = true
|
||||
MP_HUD.loading()
|
||||
//判断当前的购买状态是否可以继续执行
|
||||
if SwiftyStoreKit.canMakePayments {
|
||||
//可以继续购买项目
|
||||
MP_AnalyticsManager.shared.VIP_continue_clickAction(productId)
|
||||
self.pruchaseProductVIP(with: productId)
|
||||
}else {
|
||||
//不可以购买项目
|
||||
MP_HUD.error("Sorry. Subscription purchase failed", delay: 1.0, completion: nil)
|
||||
MP_AnalyticsManager.shared.VIP_buy_failureAction(productId, error: "The current project status is not supported")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///用户重新检索交易
|
||||
func restorePurchases() {
|
||||
MP_HUD.loading()
|
||||
//更新信息
|
||||
SKPaymentQueue.default().restoreCompletedTransactions()
|
||||
}
|
||||
///系统自动检索
|
||||
func systemRestorePurchases() {
|
||||
let receiptURL = Bundle.main.appStoreReceiptURL
|
||||
guard let receipt = try? Data(contentsOf: receiptURL!) else {
|
||||
// 收据不存在
|
||||
print("没有收据,广告默认开")
|
||||
//不能调用AppStore,默认使用广告
|
||||
MP_ADSimpleManager.shared.setOpenAdStatus(true)
|
||||
showHUD = true
|
||||
self.transactions.removeAll()
|
||||
SwiftyStoreKit.restorePurchases(atomically: false) { [weak self] results in
|
||||
guard let self = self else { return }
|
||||
if !results.restoredPurchases.isEmpty, let productId = results.restoredPurchases.first?.productId {
|
||||
//restore成功
|
||||
self.transactions.append(contentsOf: results.restoredPurchases.map({$0.transaction}))
|
||||
self.purchaseVerificationProductVIP(with: productId)
|
||||
return
|
||||
}
|
||||
//收据,重置交易记录
|
||||
SKPaymentQueue.default().restoreCompletedTransactions()
|
||||
}
|
||||
///启动时检索是否购买了广告
|
||||
func startLunchStatus() {
|
||||
//判断是否存在用户收据
|
||||
let receiptURL = Bundle.main.appStoreReceiptURL
|
||||
guard let receipt = try? Data(contentsOf: receiptURL!) else {
|
||||
// 收据不存在
|
||||
print("没有收据,广告默认开")
|
||||
//不能调用AppStore,默认使用广告
|
||||
MP_ADSimpleManager.shared.setOpenAdStatus(true)
|
||||
if !results.restoreFailedPurchases.isEmpty {
|
||||
MP_HUD.error("Restore purchase failed", delay: 1.0, completion: nil)
|
||||
return
|
||||
}
|
||||
//有收据
|
||||
print("有收据,开始检索本地信息")
|
||||
reloadOpenStatus()
|
||||
}
|
||||
///更新广告状态
|
||||
func reloadOpenStatus() {
|
||||
//更新广告开关状态
|
||||
if isProductPurchased(productId: productIdentifiers[0]) || isProductPurchased(productId: productIdentifiers[1]) || isProductPurchased(productId: productIdentifiers[2]){
|
||||
//设置广告开关为关
|
||||
MP_ADSimpleManager.shared.setOpenAdStatus(false)
|
||||
}else {
|
||||
MP_ADSimpleManager.shared.setOpenAdStatus(true)
|
||||
MP_HUD.error("No purchase can be restored", delay: 1.0, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
// ///启动时检索是否购买了广告
|
||||
// func startLunchStatus() {
|
||||
// //判断是否存在用户收据
|
||||
// let receiptURL = Bundle.main.appStoreReceiptURL
|
||||
// guard let receipt = try? Data(contentsOf: receiptURL!) else {
|
||||
// // 收据不存在
|
||||
// print("没有收据,广告默认开")
|
||||
// //不能调用AppStore,默认使用广告
|
||||
// MP_ADSimpleManager.shared.setOpenAdStatus(true)
|
||||
// return
|
||||
// }
|
||||
// //有收据
|
||||
// print("有收据,开始检索本地信息")
|
||||
// reloadOpenStatus()
|
||||
// }
|
||||
//
|
||||
|
||||
// ///更新广告状态
|
||||
// func reloadOpenStatus() {
|
||||
// //更新广告开关状态
|
||||
// if isProductPurchased(productId: productIdentifiers[0]) || isProductPurchased(productId: productIdentifiers[1]) || isProductPurchased(productId: productIdentifiers[2]){
|
||||
// //设置广告开关为关
|
||||
// MP_ADSimpleManager.shared.setOpenAdStatus(false)
|
||||
// }else {
|
||||
// MP_ADSimpleManager.shared.setOpenAdStatus(true)
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
//MARK: - SKProductsRequestDelegate
|
||||
extension MP_IAPManager: SKProductsRequestDelegate, SKPaymentTransactionObserver {
|
||||
///产品请求回调执行
|
||||
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
|
||||
//获得可用产品
|
||||
availableProducts = response.products
|
||||
//判空
|
||||
guard availableProducts.isEmpty == false else {
|
||||
//无可用产品
|
||||
print("无可用产品")
|
||||
return
|
||||
}
|
||||
//产品可用,检索产品内容
|
||||
for (index, item) in availableProducts.enumerated() {
|
||||
print("第\(index)号产品--\(item)")
|
||||
}
|
||||
}
|
||||
///产品请求失败
|
||||
func request(_ request: SKRequest, didFailWithError error: any Error) {
|
||||
print("Failed to fetch products: \(error.localizedDescription)")
|
||||
}
|
||||
///当交易的状况发生变化后
|
||||
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
|
||||
//遍历交易项
|
||||
transactions.forEach { item in
|
||||
//检索每笔交易的情况
|
||||
switch item.transactionState {
|
||||
case .purchasing://交易中
|
||||
break
|
||||
case .purchased://用户已经付款了
|
||||
complete(transaction: item)
|
||||
case .failed://交易失败了
|
||||
fail(transaction: item)
|
||||
case .restored://交易重置
|
||||
restore(transaction: item)
|
||||
case .deferred://等待外部操作
|
||||
break
|
||||
@unknown default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
///重置交易状况
|
||||
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
|
||||
print("已重启交易")
|
||||
MP_HUD.hideNow()
|
||||
//确定用户是否购买过订单
|
||||
if queue.transactions.isEmpty {
|
||||
//用户ID没有购买过任何与当前产品有关的交易
|
||||
print("没有购买产品/交易已过期")
|
||||
productIdentifiers.forEach { item in
|
||||
cleanPurchase(productId: item)
|
||||
}
|
||||
MP_ADSimpleManager.shared.setOpenAdStatus(true)
|
||||
}
|
||||
}
|
||||
func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: any Error) {
|
||||
print("重启交易失败")
|
||||
MP_HUD.error("The current transaction failed".localizableString(), delay: 1.0, completion: nil)
|
||||
//清理所有的VIP信息
|
||||
productIdentifiers.forEach { item in
|
||||
cleanPurchase(productId: item)
|
||||
}
|
||||
MP_ADSimpleManager.shared.setOpenAdStatus(true)
|
||||
}
|
||||
//存入交易信息值
|
||||
private func storePurchase(productId: String) {
|
||||
if isProductPurchased(productId: productId) == false {
|
||||
var purchasedProducts = UserDefaults.standard.array(forKey: "PurchasedProducts") as? [String] ?? []
|
||||
purchasedProducts.append(productId)
|
||||
UserDefaults.standard.set(purchasedProducts, forKey: "PurchasedProducts")
|
||||
}
|
||||
}
|
||||
//检索用户是否购买该产品VIP产品
|
||||
private func isProductPurchased(productId: String) -> Bool {
|
||||
let purchasedProducts = UserDefaults.standard.array(forKey: "PurchasedProducts") as? [String] ?? []
|
||||
return purchasedProducts.contains(productId)
|
||||
}
|
||||
//清理对应的广告ID
|
||||
private func cleanPurchase(productId: String) {
|
||||
if isProductPurchased(productId: productId) {
|
||||
var purchasedProducts = UserDefaults.standard.array(forKey: "PurchasedProducts") as? [String] ?? []
|
||||
purchasedProducts.removeAll(where: {$0 == productId})
|
||||
UserDefaults.standard.set(purchasedProducts, forKey: "PurchasedProducts")
|
||||
}
|
||||
}
|
||||
///交易完成
|
||||
private func complete(transaction: SKPaymentTransaction) {
|
||||
MP_HUD.success("Successfully purchased", delay: 1.0, completion: nil)
|
||||
print("Transaction completed successfully.")
|
||||
MP_AnalyticsManager.shared.VIP_buy_successAction(transaction.payment.productIdentifier)
|
||||
validateReceipt { [weak self] status in
|
||||
//MARK: - 交易执行
|
||||
extension MP_IAPManager {
|
||||
///执行交易
|
||||
private func pruchaseProductVIP(with productId:String) {
|
||||
//优先移除交易信息组
|
||||
self.transactions.removeAll()
|
||||
SwiftyStoreKit.purchaseProduct(productId, quantity: 1, atomically: false) { [weak self] (result) in
|
||||
guard let self = self else { return }
|
||||
if status {
|
||||
// 存储购买信息
|
||||
self.storePurchase(productId: transaction.payment.productIdentifier)
|
||||
SKPaymentQueue.default().finishTransaction(transaction)
|
||||
//更新广告开关状态
|
||||
reloadOpenStatus()
|
||||
switch result {
|
||||
case .success(let product)://订阅成功
|
||||
print("VIP Purchase Successfully.")
|
||||
//更新订阅信息
|
||||
self.transactions.append(product.transaction)
|
||||
//检索交易信息
|
||||
self.purchaseVerificationProductVIP(with: product.productId)
|
||||
case .error(let error)://订阅失败
|
||||
MP_HUD.error("Sorry. Subscription purchase failed", delay: 1.0, completion: nil)
|
||||
MP_AnalyticsManager.shared.VIP_buy_failureAction(productId, error: "The current project status is not supported")
|
||||
print("VIP: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
//重启交易
|
||||
private func restore(transaction: SKPaymentTransaction) {
|
||||
print("Transaction restored.")
|
||||
validateReceipt { [weak self] status in
|
||||
///交易验证
|
||||
private func purchaseVerificationProductVIP(with productId:String) {
|
||||
//生成一个验证服务器
|
||||
let receiptValidator = AppleReceiptValidator(service: checkIsSandbox() ? .sandbox:.production, sharedSecret: "d29627e4f78b4b50a0ce5166acd8aa9f")
|
||||
SwiftyStoreKit.verifyReceipt(using: receiptValidator, forceRefresh: true) { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
if status {
|
||||
// 存储购买信息
|
||||
self.storePurchase(productId: transaction.payment.productIdentifier)
|
||||
SKPaymentQueue.default().finishTransaction(transaction)
|
||||
//更新广告开关状态
|
||||
reloadOpenStatus()
|
||||
switch result {
|
||||
case .success(let receipt)://验证成功
|
||||
print("VIP Receipt: \(receipt)")
|
||||
//检索是否为终身项目
|
||||
if productId == "lifetimevip" {
|
||||
//终身项目
|
||||
let purchaseResult = SwiftyStoreKit.verifyPurchase(productId: productId, inReceipt: receipt)
|
||||
switch purchaseResult {
|
||||
case .purchased(let item):
|
||||
print("VIP LifeTime verifyPurchase successed current item: \(item)")
|
||||
//到期时间一百年后
|
||||
let lifeDate = Date().addingTimeInterval(60 * 60 * 24 * 365 * 100)
|
||||
//本地记录VIP到期时间
|
||||
UserDefaults.standard.set(lifeDate, forKey: "PurchaseVIPDate")
|
||||
UserDefaults.standard.synchronize()
|
||||
//结算交易信息
|
||||
self.showSuccessfullyHUD()
|
||||
self.finishedAllTransactionsVIP()
|
||||
case .notPurchased://订阅失败
|
||||
self.finishedAllTransactionsVIP()
|
||||
self.showFailedHUD()
|
||||
}
|
||||
}
|
||||
}
|
||||
///交易失败
|
||||
private func fail(transaction: SKPaymentTransaction) {
|
||||
//检索错误
|
||||
if let error = transaction.error as NSError? {
|
||||
MP_AnalyticsManager.shared.VIP_buy_failureAction(transaction.payment.productIdentifier, error: error.localizedDescription)
|
||||
if error.code != SKError.paymentCancelled.rawValue {
|
||||
MP_HUD.error("The current transaction failed".localizableString(), delay: 1.0, completion: nil)
|
||||
}else {
|
||||
MP_HUD.onlytext("The current transaction has been canceled".localizableString(), delay: 1.0, completion: nil)
|
||||
//非终身项目,要计算项目时间
|
||||
let purchaseResult = SwiftyStoreKit.verifySubscription(ofType: .autoRenewable, productId: productId, inReceipt: receipt)
|
||||
switch purchaseResult {
|
||||
case .purchased(let expiryDate, let items):
|
||||
print("VIP \(productId) valid until \(expiryDate)")
|
||||
UserDefaults.standard.set(expiryDate, forKey: "PurchaseVIPDate")
|
||||
UserDefaults.standard.synchronize()
|
||||
//结算交易信息
|
||||
self.showSuccessfullyHUD()
|
||||
self.finishedAllTransactionsVIP()
|
||||
case .expired(let expiryDate, let items)://过期了
|
||||
self.finishedAllTransactionsVIP()
|
||||
self.showFailedHUD()
|
||||
case .notPurchased://订阅失败
|
||||
self.finishedAllTransactionsVIP()
|
||||
self.showFailedHUD()
|
||||
}
|
||||
}
|
||||
case .error(let error)://验证失败
|
||||
self.finishedAllTransactionsVIP()
|
||||
self.showFailedHUD()
|
||||
}
|
||||
}
|
||||
}
|
||||
//获取收据信息
|
||||
func fetchReceipt() -> String? {
|
||||
guard let receiptURL = Bundle.main.appStoreReceiptURL else { return nil }
|
||||
guard let receiptData = try? Data(contentsOf: receiptURL) else { return nil }
|
||||
return receiptData.base64EncodedString(options: [])
|
||||
//展示交易成功
|
||||
private func showSuccessfullyHUD() {
|
||||
if showHUD == true {
|
||||
MP_HUD.success("Successfully purchased", delay: 1.5, completion: nil)
|
||||
}
|
||||
//验证收据信息
|
||||
func validateReceipt(completion: @escaping (Bool) -> Void) {
|
||||
guard MP_NetWorkManager.shared.netWorkStatu == .reachable else {
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
//获取收据信息
|
||||
guard let receiptString = fetchReceipt() else {
|
||||
completion(false)
|
||||
return
|
||||
//展示交易失败
|
||||
private func showFailedHUD() {
|
||||
if showHUD == true {
|
||||
MP_HUD.error("Failed purchased", delay: 1.5, completion: nil)
|
||||
}
|
||||
//生成请求参数
|
||||
let requestDictionary = ["receipt-data": receiptString,
|
||||
"password": "d29627e4f78b4b50a0ce5166acd8aa9f" ]
|
||||
guard JSONSerialization.isValidJSONObject(requestDictionary) else {
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
|
||||
//结算交易信息组
|
||||
private func finishedAllTransactionsVIP() {
|
||||
self.transactions.forEach { item in
|
||||
SwiftyStoreKit.finishTransaction(item)
|
||||
}
|
||||
self.transactions.removeAll()
|
||||
}
|
||||
|
||||
private func checkIsSandbox() -> Bool {
|
||||
var isSandbox = false
|
||||
#if DEBUG
|
||||
let validationURLString = "https://sandbox.itunes.apple.com/verifyReceipt"
|
||||
#else
|
||||
let validationURLString = "https://buy.itunes.apple.com/verifyReceipt"
|
||||
isSandbox = true
|
||||
#endif
|
||||
guard let validationURL = URL(string: validationURLString) else {
|
||||
completion(false)
|
||||
return
|
||||
return isSandbox
|
||||
}
|
||||
//创建请求
|
||||
var request = URLRequest(url: validationURL)
|
||||
request.httpMethod = "POST"
|
||||
request.cachePolicy = .reloadIgnoringCacheData
|
||||
request.httpBody = requestData
|
||||
//设置会话
|
||||
let session = URLSession.shared
|
||||
//执行任务
|
||||
let task = session.dataTask(with: request) { data, response, error in
|
||||
guard error == nil, let data = data else {
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
|
||||
if let status = jsonResponse["status"] as? Int {
|
||||
if status == 0 {
|
||||
completion(true)
|
||||
} else if status == 21007 {
|
||||
self.validateReceiptInSandbox(receiptString: receiptString, completion: completion)
|
||||
} else {
|
||||
print("Receipt validation failed with status: \(status)")
|
||||
completion(false)
|
||||
}
|
||||
} else {
|
||||
completion(false)
|
||||
}
|
||||
} else {
|
||||
completion(false)
|
||||
}
|
||||
} catch {
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
task.resume()
|
||||
} catch {
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
func validateReceiptInSandbox(receiptString: String, completion: @escaping (Bool) -> Void) {
|
||||
let requestDictionary = ["receipt-data": receiptString,
|
||||
"password": "d29627e4f78b4b50a0ce5166acd8aa9f"]
|
||||
guard JSONSerialization.isValidJSONObject(requestDictionary) else {
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
|
||||
let validationURLString = "https://sandbox.itunes.apple.com/verifyReceipt"
|
||||
guard let validationURL = URL(string: validationURLString) else {
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
|
||||
var request = URLRequest(url: validationURL)
|
||||
request.httpMethod = "POST"
|
||||
request.cachePolicy = .reloadIgnoringCacheData
|
||||
request.httpBody = requestData
|
||||
|
||||
let session = URLSession.shared
|
||||
let task = session.dataTask(with: request) { data, response, error in
|
||||
guard error == nil, let data = data else {
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
do {
|
||||
if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
|
||||
if let status = jsonResponse["status"] as? Int, status == 0 {
|
||||
completion(true)
|
||||
} else {
|
||||
completion(false)
|
||||
}
|
||||
} else {
|
||||
completion(false)
|
||||
}
|
||||
} catch {
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
task.resume()
|
||||
} catch {
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
//MARK: - SKProductsRequestDelegate
|
||||
//extension MP_IAPManager: SKProductsRequestDelegate, SKPaymentTransactionObserver {
|
||||
// ///产品请求回调执行
|
||||
// func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
|
||||
// //获得可用产品
|
||||
// availableProducts = response.products
|
||||
// //判空
|
||||
// guard availableProducts.isEmpty == false else {
|
||||
// //无可用产品
|
||||
// print("无可用产品")
|
||||
// return
|
||||
// }
|
||||
// //产品可用,检索产品内容
|
||||
// for (index, item) in availableProducts.enumerated() {
|
||||
// print("第\(index)号产品--\(item)")
|
||||
// }
|
||||
// }
|
||||
// ///产品请求失败
|
||||
// func request(_ request: SKRequest, didFailWithError error: any Error) {
|
||||
// print("Failed to fetch products: \(error.localizedDescription)")
|
||||
// }
|
||||
// ///当交易的状况发生变化后
|
||||
// func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
|
||||
// //遍历交易项
|
||||
// transactions.forEach { item in
|
||||
// //检索每笔交易的情况
|
||||
// switch item.transactionState {
|
||||
// case .purchasing://交易中
|
||||
// break
|
||||
// case .purchased://用户已经付款了
|
||||
// complete(transaction: item)
|
||||
// case .failed://交易失败了
|
||||
// fail(transaction: item)
|
||||
// case .restored://交易重置
|
||||
// restore(transaction: item)
|
||||
// case .deferred://等待外部操作
|
||||
// break
|
||||
// @unknown default:
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// ///重置交易状况
|
||||
// func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
|
||||
// print("已重启交易")
|
||||
// MP_HUD.hideNow()
|
||||
// //确定用户是否购买过订单
|
||||
// if queue.transactions.isEmpty {
|
||||
// //用户ID没有购买过任何与当前产品有关的交易
|
||||
// print("没有购买产品/交易已过期")
|
||||
// productIdentifiers.forEach { item in
|
||||
// cleanPurchase(productId: item)
|
||||
// }
|
||||
// MP_ADSimpleManager.shared.setOpenAdStatus(true)
|
||||
// }
|
||||
// }
|
||||
// func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: any Error) {
|
||||
// print("重启交易失败")
|
||||
// MP_HUD.error("The current transaction failed".localizableString(), delay: 1.0, completion: nil)
|
||||
// //清理所有的VIP信息
|
||||
// productIdentifiers.forEach { item in
|
||||
// cleanPurchase(productId: item)
|
||||
// }
|
||||
// MP_ADSimpleManager.shared.setOpenAdStatus(true)
|
||||
// }
|
||||
// //存入交易信息值
|
||||
// private func storePurchase(productId: String) {
|
||||
// if isProductPurchased(productId: productId) == false {
|
||||
// var purchasedProducts = UserDefaults.standard.array(forKey: "PurchasedProducts") as? [String] ?? []
|
||||
// purchasedProducts.append(productId)
|
||||
// UserDefaults.standard.set(purchasedProducts, forKey: "PurchasedProducts")
|
||||
// }
|
||||
// }
|
||||
// //检索用户是否购买该产品VIP产品
|
||||
// private func isProductPurchased(productId: String) -> Bool {
|
||||
// let purchasedProducts = UserDefaults.standard.array(forKey: "PurchasedProducts") as? [String] ?? []
|
||||
// return purchasedProducts.contains(productId)
|
||||
// }
|
||||
// //清理对应的广告ID
|
||||
// private func cleanPurchase(productId: String) {
|
||||
// if isProductPurchased(productId: productId) {
|
||||
// var purchasedProducts = UserDefaults.standard.array(forKey: "PurchasedProducts") as? [String] ?? []
|
||||
// purchasedProducts.removeAll(where: {$0 == productId})
|
||||
// UserDefaults.standard.set(purchasedProducts, forKey: "PurchasedProducts")
|
||||
// }
|
||||
// }
|
||||
// ///交易完成
|
||||
// private func complete(transaction: SKPaymentTransaction) {
|
||||
// MP_HUD.success("Successfully purchased", delay: 1.0, completion: nil)
|
||||
// print("Transaction completed successfully.")
|
||||
// MP_AnalyticsManager.shared.VIP_buy_successAction(transaction.payment.productIdentifier)
|
||||
// validateReceipt { [weak self] status in
|
||||
// guard let self = self else {return}
|
||||
// if status {
|
||||
// // 存储购买信息
|
||||
// self.storePurchase(productId: transaction.payment.productIdentifier)
|
||||
// SKPaymentQueue.default().finishTransaction(transaction)
|
||||
// //更新广告开关状态
|
||||
// reloadOpenStatus()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// //重启交易
|
||||
// private func restore(transaction: SKPaymentTransaction) {
|
||||
// print("Transaction restored.")
|
||||
// validateReceipt { [weak self] status in
|
||||
// guard let self = self else {return}
|
||||
// if status {
|
||||
// // 存储购买信息
|
||||
// self.storePurchase(productId: transaction.payment.productIdentifier)
|
||||
// SKPaymentQueue.default().finishTransaction(transaction)
|
||||
// //更新广告开关状态
|
||||
// reloadOpenStatus()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// ///交易失败
|
||||
// private func fail(transaction: SKPaymentTransaction) {
|
||||
// //检索错误
|
||||
// if let error = transaction.error as NSError? {
|
||||
// MP_AnalyticsManager.shared.VIP_buy_failureAction(transaction.payment.productIdentifier, error: error.localizedDescription)
|
||||
// if error.code != SKError.paymentCancelled.rawValue {
|
||||
// MP_HUD.error("The current transaction failed".localizableString(), delay: 1.0, completion: nil)
|
||||
// } else {
|
||||
// MP_HUD.onlytext("The current transaction has been canceled".localizableString(), delay: 1.0, completion: nil)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// //获取收据信息
|
||||
// func fetchReceipt() -> String? {
|
||||
// guard let receiptURL = Bundle.main.appStoreReceiptURL else { return nil }
|
||||
// guard let receiptData = try? Data(contentsOf: receiptURL) else { return nil }
|
||||
// return receiptData.base64EncodedString(options: [])
|
||||
// }
|
||||
// //验证收据信息
|
||||
// func validateReceipt(completion: @escaping (Bool) -> Void) {
|
||||
// guard MP_NetWorkManager.shared.netWorkStatu == .reachable else {
|
||||
// completion(false)
|
||||
// return
|
||||
// }
|
||||
// //获取收据信息
|
||||
// guard let receiptString = fetchReceipt() else {
|
||||
// completion(false)
|
||||
// return
|
||||
// }
|
||||
// //生成请求参数
|
||||
// let requestDictionary = ["receipt-data": receiptString,
|
||||
// "password": "d29627e4f78b4b50a0ce5166acd8aa9f" ]
|
||||
// guard JSONSerialization.isValidJSONObject(requestDictionary) else {
|
||||
// completion(false)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// do {
|
||||
// let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
|
||||
// #if DEBUG
|
||||
// let validationURLString = "https://sandbox.itunes.apple.com/verifyReceipt"
|
||||
// #else
|
||||
// let validationURLString = "https://buy.itunes.apple.com/verifyReceipt"
|
||||
// #endif
|
||||
// guard let validationURL = URL(string: validationURLString) else {
|
||||
// completion(false)
|
||||
// return
|
||||
// }
|
||||
// //创建请求
|
||||
// var request = URLRequest(url: validationURL)
|
||||
// request.httpMethod = "POST"
|
||||
// request.cachePolicy = .reloadIgnoringCacheData
|
||||
// request.httpBody = requestData
|
||||
// //设置会话
|
||||
// let session = URLSession.shared
|
||||
// //执行任务
|
||||
// let task = session.dataTask(with: request) { data, response, error in
|
||||
// guard error == nil, let data = data else {
|
||||
// completion(false)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// do {
|
||||
// if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
|
||||
// if let status = jsonResponse["status"] as? Int {
|
||||
// if status == 0 {
|
||||
// completion(true)
|
||||
// } else if status == 21007 {
|
||||
// self.validateReceiptInSandbox(receiptString: receiptString, completion: completion)
|
||||
// } else {
|
||||
// print("Receipt validation failed with status: \(status)")
|
||||
// completion(false)
|
||||
// }
|
||||
// } else {
|
||||
// completion(false)
|
||||
// }
|
||||
// } else {
|
||||
// completion(false)
|
||||
// }
|
||||
// } catch {
|
||||
// completion(false)
|
||||
// }
|
||||
// }
|
||||
// task.resume()
|
||||
// } catch {
|
||||
// completion(false)
|
||||
// }
|
||||
// }
|
||||
// func validateReceiptInSandbox(receiptString: String, completion: @escaping (Bool) -> Void) {
|
||||
// let requestDictionary = ["receipt-data": receiptString,
|
||||
// "password": "d29627e4f78b4b50a0ce5166acd8aa9f"]
|
||||
// guard JSONSerialization.isValidJSONObject(requestDictionary) else {
|
||||
// completion(false)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// do {
|
||||
// let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
|
||||
// let validationURLString = "https://sandbox.itunes.apple.com/verifyReceipt"
|
||||
// guard let validationURL = URL(string: validationURLString) else {
|
||||
// completion(false)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// var request = URLRequest(url: validationURL)
|
||||
// request.httpMethod = "POST"
|
||||
// request.cachePolicy = .reloadIgnoringCacheData
|
||||
// request.httpBody = requestData
|
||||
//
|
||||
// let session = URLSession.shared
|
||||
// let task = session.dataTask(with: request) { data, response, error in
|
||||
// guard error == nil, let data = data else {
|
||||
// completion(false)
|
||||
// return
|
||||
// }
|
||||
// do {
|
||||
// if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
|
||||
// if let status = jsonResponse["status"] as? Int, status == 0 {
|
||||
// completion(true)
|
||||
// } else {
|
||||
// completion(false)
|
||||
// }
|
||||
// } else {
|
||||
// completion(false)
|
||||
// }
|
||||
// } catch {
|
||||
// completion(false)
|
||||
// }
|
||||
// }
|
||||
// task.resume()
|
||||
// } catch {
|
||||
// completion(false)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
@ -10,6 +10,7 @@ import Network
|
||||
import Alamofire
|
||||
import AVFoundation
|
||||
import Kanna
|
||||
|
||||
///预览闭包(传递一个预览模块数据组和完成状态)
|
||||
typealias BrowseRequestStateBlock = (_ browse:[MPPositive_BrowseModuleListViewModel]) -> Void
|
||||
///预览数据失败闭包
|
||||
@ -164,13 +165,15 @@ class MP_NetWorkManager: NSObject {
|
||||
"clientVersion":"1.\(currTimeDate).01.00",
|
||||
"hl":Language_first_local,
|
||||
"gl":locaton ?? "US",
|
||||
"platform":"DESKTOP",
|
||||
"visitorData":text]
|
||||
}else {
|
||||
//没有访客数据
|
||||
return ["clientName":"WEB_REMIX",
|
||||
"clientVersion":"1.\(currTimeDate).01.00",
|
||||
"hl":Language_first_local,
|
||||
"gl":locaton ?? "US"]
|
||||
"gl":locaton ?? "US",
|
||||
"platform":"DESKTOP"]
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,14 +185,13 @@ class MP_NetWorkManager: NSObject {
|
||||
return [
|
||||
"context":[
|
||||
"client":[
|
||||
"clientName": "ANDROID",
|
||||
"clientVersion": "19.05.36",
|
||||
"clientName": "ANDROID_VR",
|
||||
"clientVersion": "1.56.21",
|
||||
"hl": "en",
|
||||
"gl": "US"
|
||||
]
|
||||
],
|
||||
"params": "CgIQBg",
|
||||
"key":netKeyCode,
|
||||
"key":netKeyCode
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -214,10 +216,16 @@ class MP_NetWorkManager: NSObject {
|
||||
|
||||
//固定时间点(当前日期的前两天)
|
||||
private var currTimeDate:String{
|
||||
return (Date().timeZone() - 1.days).toString(.custom("YYYYMMdd"))
|
||||
return (Date().timeZone() - 4.days).toString(.custom("YYYYMMdd"))
|
||||
}
|
||||
///地址
|
||||
private var locaton:String? = "US"
|
||||
private var locaton:String?{
|
||||
if let IP = UserDefaults.standard.object(forKey: "IP_Info") as? String {
|
||||
return IP
|
||||
}else {
|
||||
return "US"
|
||||
}
|
||||
}
|
||||
//预览下一阶段参数(网络请求获取)
|
||||
var continuationAndItct:ContinuationAndItct?
|
||||
//MARK: - GCD队列任务
|
||||
@ -337,7 +345,7 @@ class MP_NetWorkManager: NSObject {
|
||||
extension MP_NetWorkManager {
|
||||
//MARK: - 请求iP信息
|
||||
///请求IP信息
|
||||
func requestIPInfo(_ completion:@escaping((Bool) -> Void)) {
|
||||
func requestIPInfo() {
|
||||
//拼接出browse路径
|
||||
let path = iPInfo
|
||||
//设置url
|
||||
@ -345,11 +353,9 @@ extension MP_NetWorkManager {
|
||||
print("Url is Incorrect")
|
||||
return
|
||||
}
|
||||
requestPostIPInfo(url) { open in
|
||||
completion(open)
|
||||
requestPostIPInfo(url)
|
||||
}
|
||||
}
|
||||
private func requestPostIPInfo(_ url:URL, completion:@escaping((Bool) -> Void)) {
|
||||
private func requestPostIPInfo(_ url:URL) {
|
||||
IPSession.request(url, method: .get, encoding: JSONEncoding.default).responseDecodable(of: JsonIPInfo.self) { [weak self] (response) in
|
||||
guard let self = self else {return}
|
||||
switch response.result {
|
||||
@ -357,20 +363,13 @@ extension MP_NetWorkManager {
|
||||
guard let code = value.country else {
|
||||
return
|
||||
}
|
||||
let ip = value.ip
|
||||
let ip = value.country ?? "US"
|
||||
if ISOs.contains(ip) {
|
||||
UserDefaults.standard.set(ip, forKey: "IP_Info")
|
||||
if ISOs.contains(code) {
|
||||
locaton = code
|
||||
}else {
|
||||
locaton = "US"
|
||||
}
|
||||
completion(true)
|
||||
case .failure(let error):
|
||||
// 请求失败,处理错误
|
||||
handleError(url, error: error)
|
||||
//请求失败默认为US
|
||||
locaton = "US"
|
||||
completion(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -445,9 +444,6 @@ extension MP_NetWorkManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
///执行艺术家排行请求
|
||||
func requestArtistsRank() {
|
||||
//拼接出browse路径
|
||||
@ -467,7 +463,6 @@ extension MP_NetWorkManager {
|
||||
parameters["browseId"] = "FEmusic_charts"
|
||||
let formData = ["selectedValues":[code]]
|
||||
parameters["formData"] = formData
|
||||
|
||||
requestPostArtistsRank(url, parameters: parameters)
|
||||
}
|
||||
///请求艺术家排行
|
||||
@ -917,25 +912,31 @@ extension MP_NetWorkManager {
|
||||
// relatedRequests[videoId]?.cancel()
|
||||
relatedRequests.removeValue(forKey: videoId)
|
||||
}
|
||||
// func requestPlayer(_ videoId: String, playlistId: String, completion:@escaping ((([String],[Float],[String]), [String]?) -> Void)){
|
||||
// //拼接出player路径
|
||||
// let path = header+point+player
|
||||
// //设置url
|
||||
// guard let url = URL(string: path) else {
|
||||
// print("Url is Incorrect")
|
||||
// return
|
||||
// }
|
||||
// //设置参数,videoId与params参数是必定携带内容
|
||||
func requestPlayer(_ videoId: String, playlistId: String, completion:@escaping ((([String],[Int],[String])?, [String]?) -> Void), failure: ((Bool) -> Void)? = nil){
|
||||
guard netWorkStatu != .notReachable else {
|
||||
completion(nil,nil)
|
||||
return
|
||||
}
|
||||
//拼接出player路径
|
||||
let path = header+point+player
|
||||
//设置url
|
||||
guard let url = URL(string: path) else {
|
||||
print("Url is Incorrect")
|
||||
completion(nil,nil)
|
||||
return
|
||||
}
|
||||
//设置参数,videoId与params参数是必定携带内容
|
||||
// let parameters:[String:Any] = [
|
||||
// "videoId":videoId,
|
||||
// "prettyPrint":"false",
|
||||
// "context":[
|
||||
// "client":[
|
||||
// "clientName": "WEB_REMIX",
|
||||
//// //"visitorData":visitorData,
|
||||
//// "originalUrl":"https://music.youtube.com/watch?v=\(videoId)",
|
||||
// "visitorData":visitorData,
|
||||
// "originalUrl":"https://music.youtube.com/watch?v=\(videoId)",
|
||||
// //当前访问版本(日期值)
|
||||
// "clientVersion": clientVersion,
|
||||
//// "clientVersion": clientVersion,
|
||||
// "clientVersion":"1.\(currTimeDate).01.00",
|
||||
// "platform":"MOBILE",
|
||||
// //语言
|
||||
// "hl":Language_first_local,
|
||||
@ -945,32 +946,37 @@ extension MP_NetWorkManager {
|
||||
// ],
|
||||
// "playbackContext": [
|
||||
// "contentPlaybackContext": [
|
||||
// "signatureTimestamp": MP_WebWork.shared.signatureTimestamp ?? 0
|
||||
// "signatureTimestamp": MP_WebDecryptionWork.shared.signatureTimestamp ?? 0
|
||||
// ]
|
||||
// ]
|
||||
// ]
|
||||
// //guard netWorkStatu != .notReachable else {return}
|
||||
// var parameters:[String:Any] = requestParams
|
||||
// parameters["videoId"] = videoId
|
||||
// parameters["playbackContext"] = [
|
||||
// "contentPlaybackContext": [
|
||||
// "signatureTimestamp": MP_WebDecryptionWork.shared.signatureTimestamp ?? 0]
|
||||
// ]
|
||||
// requestPostPlayer(url, parameters: parameters){ resourceUlrs, coverUrls in
|
||||
// completion(resourceUlrs, coverUrls)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
//请求单曲/视频
|
||||
// private func requestPostPlayer(_ url:URL, parameters:Parameters, completion:@escaping((([String],[Float],[String]), [String]?) -> Void)) {
|
||||
// //发送post请求
|
||||
// MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonPlayer.self) { [weak self] (response) in
|
||||
// guard let self = self else {return}
|
||||
//
|
||||
// switch response.result {
|
||||
// case .success(let value):
|
||||
// parsingPlayer(value) { resourceUlrs, coverUrls in
|
||||
// completion(resourceUlrs, coverUrls)
|
||||
// }
|
||||
// case .failure(let error):
|
||||
// // 请求失败,处理错误
|
||||
// print("Request failed: \(error)")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
private func requestPostPlayer(_ url:URL, parameters:Parameters, completion:@escaping((([String],[Int],[String])?, [String]?) -> Void)) {
|
||||
//发送post请求
|
||||
MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonPlayer.self) { [weak self] (response) in
|
||||
guard let self = self else {return}
|
||||
|
||||
switch response.result {
|
||||
case .success(let value):
|
||||
parsingPlayer(value) { resourceUlrs, coverUrls in
|
||||
completion(resourceUlrs, coverUrls)
|
||||
}
|
||||
case .failure(let error):
|
||||
// 请求失败,处理错误
|
||||
print("Request failed: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
//MARK: - 请求歌词
|
||||
/// 请求歌词
|
||||
/// - Parameter lyricId: 歌词id
|
||||
@ -1812,18 +1818,18 @@ extension MP_NetWorkManager {
|
||||
/// - Parameters:
|
||||
/// - player: player库
|
||||
/// - completion: 传递两个字符串数组,第一个资源路径组,第二个是封面路径组
|
||||
// private func parsingPlayer(_ player:JsonPlayer, completion:@escaping((([String],[Float],[String]), [String]?) -> Void)){
|
||||
// var infos:[String]?
|
||||
// //解析player,获取资源库和信息库
|
||||
// if let videoDetails = player.videoDetails {
|
||||
// infos = parsingPlayerVideoDetails(videoDetails)
|
||||
// }
|
||||
// if let streamingData = player.streamingData {
|
||||
// parsingPlayerStreamingData(streamingData){ videos,floats,approxDurationMs in
|
||||
// completion((videos,floats,approxDurationMs),infos)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
private func parsingPlayer(_ player:JsonPlayer, completion:@escaping((([String],[Int],[String])?, [String]?) -> Void)){
|
||||
var infos:[String]?
|
||||
//解析player,获取资源库和信息库
|
||||
if let videoDetails = player.videoDetails {
|
||||
infos = parsingPlayerVideoDetails(videoDetails)
|
||||
}
|
||||
if let streamingData = player.streamingData {
|
||||
parsingPlayerStreamingData(streamingData){ videos,floats,approxDurationMs in
|
||||
completion((videos,floats,approxDurationMs),infos)
|
||||
}
|
||||
}
|
||||
}
|
||||
private func parsingAndroidPlayer(_ player:JsonAndroidPlayer,completion:@escaping((([String],[Int],[String]), [String]?) -> Void), failure:((Bool) -> Void)? = nil, resetion:(() -> Void)? = nil){
|
||||
//检索相应内容状态
|
||||
guard let statu = player.playabilityStatus?.status, statu == "OK" || statu == "LOGIN_REQUIRED" else {
|
||||
@ -1843,6 +1849,8 @@ extension MP_NetWorkManager {
|
||||
parsingAndroidPlayerStreamingData(streamingData){ videos,itags,mimeType in
|
||||
completion((videos,itags,mimeType),infos)
|
||||
}
|
||||
//
|
||||
|
||||
UserDefaults.standard.set(0, forKey: "Number_Failures")
|
||||
UserDefaults.standard.synchronize()
|
||||
}else {
|
||||
@ -1896,35 +1904,34 @@ extension MP_NetWorkManager {
|
||||
}
|
||||
completion(videos,itags,mimeType)
|
||||
}
|
||||
// private func parsingPlayerStreamingData(_ streamingData:JsonPlayer.StreamingData, completion:@escaping(([String],[Float],[String]) -> Void)) {
|
||||
// var group:DispatchGroup? = DispatchGroup()
|
||||
// var videos:[String] = []
|
||||
// var floats:[Float] = []
|
||||
// var approxDurationMs:[String] = []
|
||||
// let allFormats = (streamingData.formats ?? []) + (streamingData.adaptiveFormats ?? [])
|
||||
// for format in allFormats {
|
||||
// if let signatureCipher = format.signatureCipher {
|
||||
// // 进入DispatchGroup,表示开始一个异步任务
|
||||
// group?.enter()
|
||||
// //获得资源签名,开始解密签名内容
|
||||
// parsingPlayerSignatureCipher(signatureCipher) { result in
|
||||
// //这是条视频资源
|
||||
// videos.append(result)
|
||||
// floats.append(format.bitrate ?? 0)
|
||||
// approxDurationMs.append(format.approxDurationMs ?? "")
|
||||
// // 离开DispatchGroup,表示异步任务完成
|
||||
// group?.leave()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// group?.notify(queue: .main) {
|
||||
// completion(videos, floats, approxDurationMs)
|
||||
// group = nil
|
||||
// }
|
||||
// }
|
||||
private func parsingPlayerStreamingData(_ streamingData:JsonPlayer.StreamingData, completion:@escaping(([String],[Int],[String]) -> Void)) {
|
||||
var group:DispatchGroup? = DispatchGroup()
|
||||
var videos:[String] = []
|
||||
var floats:[Int] = []
|
||||
var approxDurationMs:[String] = []
|
||||
let allFormats = (streamingData.formats ?? []) + (streamingData.adaptiveFormats ?? [])
|
||||
for format in allFormats {
|
||||
if let signatureCipher = format.signatureCipher {
|
||||
// 进入DispatchGroup,表示开始一个异步任务
|
||||
group?.enter()
|
||||
//获得资源签名,开始解密签名内容
|
||||
parsingPlayerSignatureCipher(signatureCipher) { result in
|
||||
//这是条视频资源
|
||||
videos.append(result)
|
||||
floats.append(format.bitrate ?? 0)
|
||||
approxDurationMs.append(format.approxDurationMs ?? "")
|
||||
// 离开DispatchGroup,表示异步任务完成
|
||||
group?.leave()
|
||||
}
|
||||
}
|
||||
}
|
||||
group?.notify(queue: .main) {
|
||||
completion(videos, floats, approxDurationMs)
|
||||
group = nil
|
||||
}
|
||||
}
|
||||
///解析加密签名_SignatureCipher
|
||||
private func parsingPlayerSignatureCipher(_ signatureCipher:String, completion:@escaping((String) -> Void)) {
|
||||
// print("Resources-SignatureCipher:\(signatureCipher)")
|
||||
//该加密资源有两段式加密,先进行百分比加密解码
|
||||
let originalURLString = seperatorOff(String(signatureCipher))
|
||||
//第一段加密资源为权限资源
|
||||
@ -1943,12 +1950,12 @@ extension MP_NetWorkManager {
|
||||
let urlSubstring = originalURLString[urlStartIndex...] // 从 &url= 之后开始提取
|
||||
let signString = String(originalURLString[sRange.upperBound..<spSigRange.lowerBound])
|
||||
|
||||
// //加密的权限请求解码
|
||||
// MP_WebWork.shared.excuteJavaScript(signString) { result in
|
||||
// //与权限拼接
|
||||
// let abString = urlSubstring + "&sig=" + result
|
||||
// completion(abString)
|
||||
// }
|
||||
//加密的权限请求解码
|
||||
MP_WebDecryptionWork.shared.excuteJavaScript(signString) { result in
|
||||
//与权限拼接
|
||||
let abString = urlSubstring + "&sig=" + result
|
||||
completion(abString)
|
||||
}
|
||||
}
|
||||
/// 解析播放器_VideoDetails
|
||||
/// - Parameter videoDetails: 信息库
|
||||
@ -1967,20 +1974,20 @@ extension MP_NetWorkManager {
|
||||
})
|
||||
return urls
|
||||
}
|
||||
// private func parsingPlayerVideoDetails(_ videoDetails:JsonPlayer.VideoDetails) -> [String]? {
|
||||
// var urls:[String]?
|
||||
// videoDetails.thumbnail?.thumbnails?.forEach({ item in
|
||||
// if item.url != nil {
|
||||
// if urls != nil {
|
||||
// urls!.append(item.url!)
|
||||
// }else {
|
||||
// urls = []
|
||||
// urls!.append(item.url!)
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// return urls
|
||||
// }
|
||||
private func parsingPlayerVideoDetails(_ videoDetails:JsonPlayer.VideoDetails) -> [String]? {
|
||||
var urls:[String]?
|
||||
videoDetails.thumbnail?.thumbnails?.forEach({ item in
|
||||
if item.url != nil {
|
||||
if urls != nil {
|
||||
urls!.append(item.url!)
|
||||
}else {
|
||||
urls = []
|
||||
urls!.append(item.url!)
|
||||
}
|
||||
}
|
||||
})
|
||||
return urls
|
||||
}
|
||||
///解析歌词_Lyrics
|
||||
private func parsingLyrics(_ lyrics:JsonLyrics) -> String? {
|
||||
if let first = lyrics.contents?.sectionListRenderer?.contents?.first {
|
||||
|
||||
@ -47,7 +47,7 @@ struct JsonPlayer: Codable {
|
||||
///格式标签
|
||||
let itag: Int?
|
||||
///比特率
|
||||
let bitrate:Float?
|
||||
let bitrate:Int?
|
||||
///格式编码
|
||||
let mimeType:String?
|
||||
///格式名
|
||||
@ -67,7 +67,7 @@ struct JsonPlayer: Codable {
|
||||
init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
itag = try values.decodeIfPresent(Int.self, forKey: .itag)
|
||||
bitrate = try values.decodeIfPresent(Float.self, forKey: .bitrate)
|
||||
bitrate = try values.decodeIfPresent(Int.self, forKey: .bitrate)
|
||||
mimeType = try values.decodeIfPresent(String.self, forKey: .mimeType)
|
||||
qualityLabel = try values.decodeIfPresent(String.self, forKey: .qualityLabel)
|
||||
approxDurationMs = try values.decodeIfPresent(String.self, forKey: .approxDurationMs)
|
||||
|
||||
@ -109,7 +109,7 @@ class MPPositive_HomeViewController: MPPositive_BaseViewController, UIViewContro
|
||||
}
|
||||
}
|
||||
}
|
||||
MP_IAPManager.shared.systemRestorePurchases()
|
||||
|
||||
//设置API密钥
|
||||
MP_NetWorkManager.shared.requestYBMAPIKeyCode()
|
||||
setTitle("")
|
||||
@ -169,7 +169,7 @@ class MPPositive_HomeViewController: MPPositive_BaseViewController, UIViewContro
|
||||
MP_PlayerManager.shared.setPlayType(.normal)
|
||||
MP_PlayerManager.shared.setLastStatus(bool: true)
|
||||
MP_PlayerManager.shared.loadPlayer = lodaViewModel
|
||||
|
||||
MP_LuxServerManager.shared.upDateOpenActiveEventTask()
|
||||
}
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user