Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Feature/payment queue controller #131

Merged
merged 33 commits into from
Jan 29, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ca5239b
Started implementing PaymentQueueController and associated tests
bizz84 Jan 17, 2017
64c7325
Add PaymentsController
bizz84 Jan 17, 2017
aef356d
Implemented purchases in PaymentsController and started implementing …
bizz84 Jan 18, 2017
68532af
Share SwiftyStoreKitTests target
bizz84 Jan 18, 2017
d8f444c
Progress on restore purchases controller
bizz84 Jan 18, 2017
517d7d6
Add purchase failed unit test
bizz84 Jan 19, 2017
2adfadf
Implemented RestorePurchasesController and tests
bizz84 Jan 19, 2017
c0749d2
Cleanup
bizz84 Jan 19, 2017
f2e371b
Add restoreCompletedTransactionsFailed unit test
bizz84 Jan 19, 2017
91481df
Add restoreCompletedTransactionsFinished and unit test
bizz84 Jan 19, 2017
b285bc1
Add CompleteTransactionsController definition
bizz84 Jan 19, 2017
4eca383
Wiring up CompleteTransactionsController
bizz84 Jan 19, 2017
9ec0289
Preparing to hook PaymentQueueController in SwiftyStoreKit
bizz84 Jan 19, 2017
63bab1d
Added CompleteTransactionsControllerTests
bizz84 Jan 20, 2017
3616668
Added PaymentQueueController integration tests. These are useful as r…
bizz84 Jan 20, 2017
4eef88e
Integrate new PaymentQueueController into SwiftyStoreKit implementation
bizz84 Jan 20, 2017
bc50b3a
Allow PaymentController to enqueue multiple payments with same produc…
bizz84 Jan 20, 2017
226ded5
Documented SKPaymentQueue behaviour and implementation in SwiftyStoreKit
bizz84 Jan 21, 2017
61a1cb8
Remove old InAppCompleteTransactionsObserver and InAppPurchaseRequest
bizz84 Jan 21, 2017
65d135f
Add applicationUsername support to restore purchases
bizz84 Jan 21, 2017
1170440
Update RestorePurchasesController to accumulate all relevant updated …
bizz84 Jan 21, 2017
e9d8f2d
Additional PaymentQueueController tests
bizz84 Jan 21, 2017
439dc61
Moved logic to retrieve products info to ProductsInfoController
bizz84 Jan 21, 2017
03d563a
Clearer separation between SwiftyStoreKit class and instance methods …
bizz84 Jan 21, 2017
edc3b2a
Perform InAppProductQueryRequest on calling thread
bizz84 Jan 21, 2017
059873b
Update documentation
bizz84 Jan 21, 2017
41d9110
Remove verifyReceipt code in app delegate
bizz84 Jan 21, 2017
74c700e
Add unit tests target to build script
bizz84 Jan 21, 2017
10d7b7a
Merge develop into feature/payment-queue-controller
bizz84 Jan 21, 2017
294293a
Make all new payment classes internal rather than public as they are …
bizz84 Jan 21, 2017
267dbd6
Documented new payment flows in README
bizz84 Jan 21, 2017
23db944
Merge develop into feature/payment-queue-controller
bizz84 Jan 21, 2017
8e07fc5
Refactor ProductsInfoController to use more functional syle
bizz84 Jan 29, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 22 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,33 +308,12 @@ The project includes demo apps [for iOS](https://github.com/bizz84/SwiftyStoreKi
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.

#### Features

- Super easy to use block based API
- Support for consumable, non-consumable in-app purchases
- Support for free, auto renewable and non renewing subscriptions
- Receipt verification
- iOS, tvOS and macOS compatible
- enum-based error handling

## Known issues

#### Requests lifecycle

While SwiftyStoreKit tries handle concurrent purchase or restore purchases requests, it is not guaranteed that this will always work flawlessly.
This is in part because using a closure-based API does not map perfectly well with the lifecycle of payments in `SKPaymentQueue`.

In real applications the following could happen:

1. User starts a purchase
2. User kills the app
3. OS continues processing this, resulting in a failed or successful purchase
4. App is restarted (payment queue is not updated yet)
5. User starts another purchase (the old transaction may interfere with the new purchase)

To prevent situations like this from happening, a `completeTransactions()` method has been added in version 0.2.8. This should be called when the app starts as it can take care of clearing the payment queue and notifying the app of the transactions that have finished.

#### Multiple accounts

The user can background the hosting application and change the Apple ID used with the App Store, then foreground the app. This has been observed to cause problems with SwiftyStoreKit - other IAP implementations may suffer from this as well.

## Essential Reading
* [Apple - WWDC16, Session 702: Using Store Kit for In-app Purchases with Swift 3](https://developer.apple.com/videos/play/wwdc2016/702/)
Expand All @@ -346,45 +325,41 @@ The user can background the hosting application and change the Apple ID used wit
* [objc.io - Receipt Validation](https://www.objc.io/issues/17-security/receipt-validation/)


## Implementation Details
## Payment flows - implementation Details
In order to make a purchase, two operations are needed:

- Obtain the ```SKProduct``` corresponding to the productId that identifies the app purchase, via ```SKProductRequest```.
- Perform a `SKProductRequest` to obtain the `SKProduct` corresponding to the product identifier.

- Submit the payment for that product via ```SKPaymentQueue```.
- 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```.

### Requesting products information
### Payment queue

SwiftyStoreKit wraps the delegate-based ```SKProductRequest``` API with a block based class named ```InAppProductQueryRequest```, which returns a `RetrieveResults` value with information about the obtained products:
The following list outlines how requests are processed by SwiftyStoreKit.

```swift
public struct RetrieveResults {
public let retrievedProducts: Set<SKProduct>
public let invalidProductIDs: Set<String>
public let error: NSError?
}
```
This value is then surfaced back to the caller of the `retrieveProductsInfo()` method the completion closure so that the client can update accordingly.
* `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 translations only ever belong to queued payment request.
* `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.

### Purchasing a product / Restoring purchases
`InAppProductPurchaseRequest` is a wrapper class for `SKPaymentQueue` that can be used to purchase a product or restore purchases.
The order in which transaction updates are processed is:

The class conforms to the `SKPaymentTransactionObserver` protocol in order to receive transactions notifications from the payment queue. The following outcomes are defined for a purchase/restore action:
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`)

```swift
enum TransactionResult {
case purchased(productId: String)
case restored(productId: String)
case failed(error: NSError)
}
```
Depending on the operation, the completion closure for `InAppProductPurchaseRequest` is then mapped to either a `PurchaseResult` or a `RestoreResults` value and returned to the caller.
Any transactions where state == `.purchasing` are ignored.

## Contributing

[Read here](CONTRIBUTING.md)
[Read here](CONTRIBUTING.md).

## Credits
Many thanks to [phimage](https://github.com/phimage) for adding macOS support and receipt verification.
Expand Down
17 changes: 0 additions & 17 deletions SwiftyStoreKit-iOS-Demo/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {

verifyReceipt()

completeIAPTransactions()

return true
}

func verifyReceipt() {

let appleValidator = AppleReceiptValidator(service: .production)
SwiftyStoreKit.verifyReceipt(using: appleValidator, password: "your-shared-secret") { result in
switch result {
case .success(let receipt):
print("\(receipt)")
case .error(let error):
if case .noReceiptData = error {
SwiftyStoreKit.refreshReceipt { result in }
}
}
}
}

func completeIAPTransactions() {

SwiftyStoreKit.completeTransactions(atomically: true) { products in
Expand Down
Loading