Skip to content

Commit

Permalink
Added SKPayment and SKProductsRequest wrappers
Browse files Browse the repository at this point in the history
  • Loading branch information
mpsnp committed Apr 15, 2017
1 parent 525750d commit 7d942b5
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 6 deletions.
16 changes: 12 additions & 4 deletions PMKStoreKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
/* Begin PBXBuildFile section */
637B4A6C1D5D5F9B00E1BC6C /* SKRequest+AnyPromise.h in Headers */ = {isa = PBXBuildFile; fileRef = 637B4A691D5D5F9B00E1BC6C /* SKRequest+AnyPromise.h */; settings = {ATTRIBUTES = (Public, ); }; };
637B4A6D1D5D5F9B00E1BC6C /* SKRequest+AnyPromise.m in Sources */ = {isa = PBXBuildFile; fileRef = 637B4A6A1D5D5F9B00E1BC6C /* SKRequest+AnyPromise.m */; };
637B4A6E1D5D5F9B00E1BC6C /* SKRequest+Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 637B4A6B1D5D5F9B00E1BC6C /* SKRequest+Promise.swift */; };
637B4A6E1D5D5F9B00E1BC6C /* SKProductsRequest+Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 637B4A6B1D5D5F9B00E1BC6C /* SKProductsRequest+Promise.swift */; };
637B4A711D5D5FA400E1BC6C /* TestStoreKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 637B4A6F1D5D5FA400E1BC6C /* TestStoreKit.m */; };
637B4A721D5D5FA400E1BC6C /* TestStoreKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 637B4A701D5D5FA400E1BC6C /* TestStoreKit.swift */; };
637B4A741D5D5FC600E1BC6C /* PMKStoreKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 637B4A731D5D5FC600E1BC6C /* PMKStoreKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
63C7FFF71D5C020D003BAE60 /* PMKStoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 63C7FFA71D5BEE09003BAE60 /* PMKStoreKit.framework */; };
D3F6DBF31EA245750013E242 /* SKPayment+Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3F6DBF21EA245750013E242 /* SKPayment+Promise.swift */; };
D3F6DBF51EA246340013E242 /* SKReceiptRefreshRequest+Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3F6DBF41EA246340013E242 /* SKReceiptRefreshRequest+Promise.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand All @@ -29,7 +31,7 @@
/* Begin PBXFileReference section */
637B4A691D5D5F9B00E1BC6C /* SKRequest+AnyPromise.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SKRequest+AnyPromise.h"; path = "Sources/SKRequest+AnyPromise.h"; sourceTree = SOURCE_ROOT; };
637B4A6A1D5D5F9B00E1BC6C /* SKRequest+AnyPromise.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "SKRequest+AnyPromise.m"; path = "Sources/SKRequest+AnyPromise.m"; sourceTree = SOURCE_ROOT; };
637B4A6B1D5D5F9B00E1BC6C /* SKRequest+Promise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "SKRequest+Promise.swift"; path = "Sources/SKRequest+Promise.swift"; sourceTree = SOURCE_ROOT; };
637B4A6B1D5D5F9B00E1BC6C /* SKProductsRequest+Promise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "SKProductsRequest+Promise.swift"; path = "Sources/SKProductsRequest+Promise.swift"; sourceTree = SOURCE_ROOT; };
637B4A6F1D5D5FA400E1BC6C /* TestStoreKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TestStoreKit.m; path = Tests/TestStoreKit.m; sourceTree = SOURCE_ROOT; };
637B4A701D5D5FA400E1BC6C /* TestStoreKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TestStoreKit.swift; path = Tests/TestStoreKit.swift; sourceTree = SOURCE_ROOT; };
637B4A731D5D5FC600E1BC6C /* PMKStoreKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PMKStoreKit.h; path = Sources/PMKStoreKit.h; sourceTree = SOURCE_ROOT; };
Expand All @@ -38,6 +40,8 @@
63C7FFF21D5C020D003BAE60 /* PMKSKTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PMKSKTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
63CCF8121D5C0C4E00503216 /* Cartfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Cartfile; sourceTree = "<group>"; };
63CCF8171D5C11B500503216 /* Carthage.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Carthage.xcconfig; sourceTree = "<group>"; };
D3F6DBF21EA245750013E242 /* SKPayment+Promise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "SKPayment+Promise.swift"; path = "Sources/SKPayment+Promise.swift"; sourceTree = SOURCE_ROOT; };
D3F6DBF41EA246340013E242 /* SKReceiptRefreshRequest+Promise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "SKReceiptRefreshRequest+Promise.swift"; path = "Sources/SKReceiptRefreshRequest+Promise.swift"; sourceTree = SOURCE_ROOT; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -86,7 +90,9 @@
637B4A731D5D5FC600E1BC6C /* PMKStoreKit.h */,
637B4A691D5D5F9B00E1BC6C /* SKRequest+AnyPromise.h */,
637B4A6A1D5D5F9B00E1BC6C /* SKRequest+AnyPromise.m */,
637B4A6B1D5D5F9B00E1BC6C /* SKRequest+Promise.swift */,
637B4A6B1D5D5F9B00E1BC6C /* SKProductsRequest+Promise.swift */,
D3F6DBF21EA245750013E242 /* SKPayment+Promise.swift */,
D3F6DBF41EA246340013E242 /* SKReceiptRefreshRequest+Promise.swift */,
);
name = Sources;
path = "PMK+UIKit";
Expand Down Expand Up @@ -215,8 +221,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D3F6DBF51EA246340013E242 /* SKReceiptRefreshRequest+Promise.swift in Sources */,
637B4A6D1D5D5F9B00E1BC6C /* SKRequest+AnyPromise.m in Sources */,
637B4A6E1D5D5F9B00E1BC6C /* SKRequest+Promise.swift in Sources */,
637B4A6E1D5D5F9B00E1BC6C /* SKProductsRequest+Promise.swift in Sources */,
D3F6DBF31EA245750013E242 /* SKPayment+Promise.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
45 changes: 45 additions & 0 deletions Sources/SKPayment+Promise.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import StoreKit
#if !COCOAPODS
import PromiseKit
#endif

extension SKPayment {
public func promise() -> Promise<SKPaymentTransaction> {
return PaymentObserver(payment: self).promise
}
}

fileprivate class PaymentObserver: NSObject, SKPaymentTransactionObserver {
let (promise, fulfill, reject) = Promise<SKPaymentTransaction>.pending()
let payment: SKPayment
var retainCycle: PaymentObserver?

init(payment: SKPayment) {
self.payment = payment
super.init()
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(payment)
retainCycle = self
}

func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
guard let transaction = transactions.first(where: { $0.payment == payment }) else {
return

This comment has been minimized.

Copy link
@simsaens

simsaens Jul 17, 2020

@mpsnp @mxcl I've encountered an issue here where attempting a subscription purchase under the new StoreKit Testing in Xcode 12 has an updated transaction (SKMutablePayment) that is not equal to the stored SKPayment (despite having all the same fields)

In the case of a single transaction:

transactions.first!.payment == self.payment returns true initially if the transactionState of the payment in the queue is .purchasing. However when the transactionState becomes .purchased the equality check in the closure fails and the promise never resolves because the SKPayment originally purchased is always skipped by the guard statement when it shows up in the updatedTransactions list

I've noticed that in the .purchasing state, the payment in the queue is an SKPayment. However in the .purchased state it is an SKMutablePayment. I'm not sure if the guard statement here can be made robust to this, and I'm also not sure if this behaviour is unique to the StoreKit Testing feature of Xcode 12

}
switch transaction.transactionState {
case .purchased:
queue.finishTransaction(transaction)
fulfill(transaction)
queue.remove(self)
retainCycle = nil
case .failed:
let error = transaction.error ?? NSError.cancelledError()
queue.finishTransaction(transaction)
reject(error)
queue.remove(self)
retainCycle = nil
default:
break
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import PromiseKit

import PromiseKit
*/
extension SKRequest {
extension SKProductsRequest {
/**
Sends the request to the Apple App Store.

Expand All @@ -29,7 +29,7 @@ extension SKRequest {
}


private class SKDelegate: NSObject, SKProductsRequestDelegate {
fileprivate class SKDelegate: NSObject, SKProductsRequestDelegate {
let (promise, fulfill, reject) = Promise<SKProductsResponse>.pending()
var retainCycle: SKDelegate?

Expand Down
35 changes: 35 additions & 0 deletions Sources/SKReceiptRefreshRequest+Promise.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import StoreKit
#if !COCOAPODS
import PromiseKit
#endif

extension SKReceiptRefreshRequest {
public func promise() -> Promise<SKReceiptRefreshRequest> {
return ReceiptRefreshObserver(request: self).promise
}
}

fileprivate class ReceiptRefreshObserver: NSObject, SKRequestDelegate {
let (promise, fulfill, reject) = Promise<SKReceiptRefreshRequest>.pending()
let request: SKReceiptRefreshRequest
var retainCycle: ReceiptRefreshObserver?

init(request: SKReceiptRefreshRequest) {
self.request = request
super.init()
request.delegate = self
request.start()
retainCycle = self
}


func requestDidFinish(_ request: SKRequest) {
fulfill(self.request)
retainCycle = nil
}

func request(_ request: SKRequest, didFailWithError error: Error) {
reject(error)
retainCycle = nil
}
}

0 comments on commit 7d942b5

Please sign in to comment.