PurchaseKit is an In-App Purchase Framework written in Swift.
- iOS 8.0+
- macOS 10.10+
- tvOS 9.0+
- Xcode 9 with Swift 4
pod 'PurchaseKit'You are welcome to fork and submit pull requests.
PurchaseKit is open-sourced software, licensed under the MIT license.
Apple recommends to register a transaction observer as soon as the app starts:
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.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
PurchaseKit.completeTransactions(atomically: true) { purchases in
for purchase in purchases {
if purchase.transaction.transactionState == .purchased || purchase.transaction.transactionState == .restored {
if purchase.needsFinishTransaction {
// Deliver content from server, then:
PurchaseKit.finishTransaction(purchase.transaction)
}
print("purchased: \(purchase)")
}
}
}
return true
}PurchaseKit.retrieveProductsInfo(["cn.meniny.PurchaseKit.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 {
return alertWithTitle("Could not retrieve product info", message: "Invalid product identifier: \(invalidProductId)")
}
else {
print("Error: \(result.error)")
}
}PurchaseKit.purchaseProduct("cn.meniny.PurchaseKit.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")
}
}
}PurchaseKit.purchaseProduct("cn.meniny.PurchaseKit.Purchase1", quantity: 1, atomically: false) { result in
switch result {
case .success(let product):
// fetch content from your server, then:
if product.needsFinishTransaction {
PurchaseKit.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")
}
}
}PurchaseKit.retrieveProductsInfo(["cn.meniny.PurchaseKit.Purchase1"]) { result in
if let product = result.retrievedProducts.first {
PurchaseKit.purchaseProduct(product, quantity: 1, atomically: true) { result in
// handle result (same as above)
}
}
}PurchaseKit.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")
}
}PurchaseKit.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 {
PurchaseKit.finishTransaction(purchase.transaction)
}
}
print("Restore Success: \(results.restoredPurchases)")
}
else {
print("Nothing to Restore")
}
}let receiptData = PurchaseKit.localReceiptData
let receiptString = receiptData.base64EncodedString(options: [])
// do your receipt validation herelet appleValidator = PKAppleReceiptValidator(service: .production)
PurchaseKit.verifyReceipt(using: appleValidator, password: "your-shared-secret", forceRefresh: false) { result in
switch result {
case .success(let receipt):
print("Verify receipt Success: \(receipt)")
case .error(let error):
print("Verify receipt Failed: \(error)")
}
}let appleValidator = PKAppleReceiptValidator(service: .production)
PurchaseKit.verifyReceipt(using: appleValidator, password: "your-shared-secret") { result in
switch result {
case .success(let receipt):
// Verify the purchase of Consumable or NonConsumable
let purchaseResult = PurchaseKit.verifyPurchase(
productId: "cn.meniny.PurchaseKit.Purchase1",
inReceipt: receipt)
switch purchaseResult {
case .purchased(let receiptItem):
print("Product is purchased: \(receiptItem)")
case .notPurchased:
print("The user has never purchased this product")
}
case .error(let error):
print("Receipt verification failed: \(error)")
}
}let appleValidator = PKAppleReceiptValidator(service: .production)
PurchaseKit.verifyReceipt(using: appleValidator, password: "your-shared-secret") { result in
switch result {
case .success(let receipt):
// Verify the purchase of a Subscription
let purchaseResult = PurchaseKit.verifySubscription(
type: .autoRenewable, // or .nonRenewing (see below)
productId: "cn.meniny.PurchaseKit.Subscription",
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("The user has never purchased this product")
}
case .error(let error):
print("Receipt verification failed: \(error)")
}
}let purchaseResult = PurchaseKit.verifySubscription(
type: .autoRenewable,
productId: "cn.meniny.PurchaseKit.Subscription",
inReceipt: receipt)// validDuration: time interval in seconds
let purchaseResult = PurchaseKit.verifySubscription(
type: .nonRenewing(validDuration: 3600 * 24 * 30),
productId: "cn.meniny.PurchaseKit.Subscription",
inReceipt: receipt)let productId = "your-product-id"
PurchaseKit.purchaseProduct(productId, atomically: true) { result in
if case .success(let purchase) = result {
// Deliver content from server, then:
if purchase.needsFinishTransaction {
PurchaseKit.finishTransaction(purchase.transaction)
}
let appleValidator = PKAppleReceiptValidator(service: .production)
PurchaseKit.verifyReceipt(using: appleValidator, password: "your-shared-secret") { result in
if case .success(let receipt) = result {
let purchaseResult = PurchaseKit.verifySubscription(
type: .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
}
}