GithubHelp home page GithubHelp logo

bizz84 / swiftystorekit Goto Github PK

View Code? Open in Web Editor NEW
6.5K 127.0 787.0 3.77 MB

Lightweight In App Purchases Swift framework for iOS 8.0+, tvOS 9.0+ and macOS 10.10+ ⛺

License: MIT License

Swift 95.37% Ruby 0.92% Objective-C 2.75% Shell 0.96%
swift-language ios in-app-purchase in-app-receipt swift-3 apple tvos macos iap

swiftystorekit's Introduction

License Platform Language Build Issues Slack

SwiftyStoreKit is a lightweight In App Purchases framework for iOS, tvOS, watchOS, macOS, and Mac Catalyst.

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

SwiftyStoreKit Alternatives

During WWDC21, Apple introduced StoreKit 2, a brand new Swift API for in-app purchases and auto-renewable subscriptions.

While it would be highly desirable to support StoreKit 2 in this project, little progress has been made over the last year and most issues remain unanswered.

Fortunately, there are some very good alternatives to SwiftyStoreKit, backed by real companies. By choosing their products, you'll make a safe choice and get much better support.

RevenueCat

RevenueCat is a great alternative to SwiftyStoreKit, offering great APIs, support, and much more at a very reasonable price.

If you've been using SwiftyStoreKit and want to migrate to RevenueCat, this guide covers everything you need:

Or if you're just getting started, consider skipping SwiftyStoreKit altogether and signing up for RevenueCat.

Glassfy

Glassfy makes it easy to build, handle, and optimize in-app subscriptions. If you switch to Glassfy from SwiftyStoreKit, you'll get a 20% discount by using this affiliate link.

Note from the author: if you sign up with the link above, I will receive an affiliate commission from Glassfy, at no cost to yourself. I only recommend products that I personally know and believe will help you.

Apphud

Apphud is more than just making a purchase and validating receipts. Apphud is all-in-one infrastructure for your app growth. Sign up for free and try it out.

Or you can learn how to migrate your app from SwiftyStoreKit to Apphud.

Adapty

With Adapty you can set up subscriptions in just an hour following these simple steps and instantly launch in-app purchases with the Paywall Builder. Adapty not only gives you the tools to embed purchases but also helps your customers grow. And the best part is that it’s free for apps <$10k.

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).

Maintainers Wanted

The author is no longer maintaining this project actively. If you'd like to become a maintainer, join the Slack workspace and enter the #maintainers channel. Going forward, SwiftyStoreKit should be made for the community, by the community.

More info here: The Future of SwiftyStoreKit: Maintainers Wanted.

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, CocoaPods, and Carthage integrations are the preferred and recommended approaches.

Regardless, make sure to import the project wherever you may use it:

import SwiftyStoreKit

Swift Package Manager

The Swift 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.
https://github.com/bizz84/SwiftyStoreKit.git

Carthage

To integrate SwiftyStoreKit into your Xcode project using Carthage, specify it in your Cartfile:

github "bizz84/SwiftyStoreKit"

NOTE: Please ensure that you have the latest Carthage installed.

CocoaPods

SwiftyStoreKit can be installed as a CocoaPod and builds as a Swift framework. To install, include this in your Podfile.

use_frameworks!

pod 'SwiftyStoreKit'

Contributing

Got issues / pull requests / want to contribute? Read here.

Documentation

Full documentation is available on the SwiftyStoreKit Wiki. As SwiftyStoreKit (and Apple's StoreKit) gains features, platforms, and implementation approaches, new information will be added to the Wiki. Essential documentation is available here in the README and should be enough to get you up and running.

App startup

Complete Transactions

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.

SwiftyStoreKit supports this by calling completeTransactions() when the app starts:

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

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.
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.
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)
        }
    }
}

Additional Purchase Documentation

These additional topics are available on the Wiki:

Restore Previous Purchases

According to Apple - Restoring Purchased Products:

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.
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.
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?

For more information about atomic vs. non-atomic restorations, view the Wiki page here.

Downloading content hosted with Apple

More information about downloading hosted content is available on the Wiki.

To start downloads (this can be done in purchaseProduct(), completeTransactions() or restorePurchases()):

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:

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

This helper can be used to retrieve the (encrypted) local receipt data:

let receiptData = SwiftyStoreKit.localReceiptData
let receiptString = receiptData.base64EncodedString(options: [])
// do your receipt validation here

However, the receipt file may be missing or outdated. Use this method to get the updated receipt:

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)")
    }
}

Use this method to (optionally) refresh the receipt and perform validation in one step.

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)")
    }
}

Additional details about receipt verification are available on the wiki.

Verifying purchases and subscriptions

Once you have retrieved the receipt using the verifyReceipt method, you can verify your purchases and subscriptions by product identifier.

Verify Purchase

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)")
    }
}

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:

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.

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)")
    }
}

Further documentation on verifying subscriptions is available on the wiki.

Subscription Groups

From Apple Docs - Offering 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. Learn more on the wiki.

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).

Change Log

See the Releases Page.

Sample Code

The project includes demo apps for iOS and macOS 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

I have also written about building SwiftyStoreKit on Medium:

Troubleshooting

Video Tutorials

Jared Davidson: In App Purchases! (Swift 3 in Xcode : Swifty Store Kit)

@rebeloper: Ultimate In-app Purchases Guide

Written Tutorials

Credits

Many thanks to phimage for adding macOS support and receipt verification.

Apps using SwiftyStoreKit

It would be great to showcase apps using SwiftyStoreKit here. Pull requests welcome :)

A full list of apps is published on AppSight.

swiftystorekit's People

Contributors

0ber avatar albinekcom avatar bizz84 avatar confusedvorlon avatar daveluong avatar delebedev avatar felixlam avatar jamestapping avatar janremes avatar jeehut avatar jk2k avatar jonathandowning avatar lifez avatar manylattice avatar marcprux avatar mariohahn avatar mattbarker016 avatar maxhaertwig avatar mgnt avatar motocodeltd avatar msamoylov avatar muukii avatar neoplastic avatar nicolasmahe avatar phimage avatar romanpodymov avatar sam-spencer avatar spencerwhyte avatar tbaranes avatar warpling avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

swiftystorekit's Issues

Resumption of non-renewable subscription by changing date

After the expiry of the subscription user can change the system date to the date prior expiration. I suggest using request_date_ms from receipt response in InAppReceipt.verifySubscription

maybe this function helps:

private class func getRequestDate(inReceipt receipt: ReceiptInfo) -> NSDate {
        guard let receiptInfo = receipt["receipt"] as? ReceiptInfo else {
            return NSDate()
        }
        let requestDateValue = receiptInfo["request_date_ms"]
        guard let doubleValue = requestDateValue?.doubleValue else { return NSDate() }
        let requestDateDouble = (doubleValue / 1000)
        return NSDate(timeIntervalSince1970: requestDateDouble)
    }

auto renewable subscription status on app launch

Is there a way to request the status of subscription on app launch without asking for the user credentials?

If not, is there at least a way to get the notification, when the subscription is being renewed?

Thanks

Question on purchase response

Hello,
I'm using 0.2.7 version
I'm experiencing the following problem: I got the Success response before I got the Store popups to do the purchase. Sometimes I got the Failed (Cannot connect to the App Store) and after that the Store popups to do the purchase appear.
Is there a way to control the flow of Store popups? or at least prevent them to appear in those situations?

Critical issue when app have multi products.

First, thanks for your framework.

But, after spend days to finish the IAP with your framework, I find a critical issue which prevent me to release this product. This really make me feel upset, change another framework (or implement using Apple's api), or provide help and wait you until this critical issue is fixed?

Steps to reproduce this issue:

  • The App has 2 products to purchase: Plus and Pro.
  • First purchase Plus and succeed.
  • Start to purchase Pro.
  • The problem is, in func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction])
    • sometimes it has 3 transactions: 1 for Pro with status of Purchasing. 2 for Plus with status of restored (Note: I didn't purchase Plus at this time, I purchase Pro.)
    • sometimes it has 1 transaction for Plus with status of restored (Note: I didn't purchase Plus at this time, I purchase Pro.) I attached the screen capture for this case.
      purchasefailed
    • sometimes it's correct, with normal status of Purchasing (as I even hasn't input password in App Store's pop up dialog.)
  • If has issue in pre-step, it returns, call the handler with SwiftyStoreKit.InternalErrorCode.RestoredPurchaseWhenPurchasing.
    purchasefailed2

This issue doesn't happen every time, but happen 4 times for the 5 test. I guess it's related with the multi-threads/queue. Similar with issue #3 . As AlexCatch said,

I get this error occasionally. It makes the library unusable in Production.

I would like to provide test account if you need. You can also directly contact me beside Github. Thanks.
Jason

refresh receipt and validate afterwards

So if I understand right, I can refresh the receipt if there is no receipt present, while verifying the receipt with: SwiftyStoreKit.refreshReceipt

But after doing this, would't it be right to verify the refreshed receipt again calling
SwiftyStoreKit.verifyReceipt ?

But in the completion handler I cannot access the product the receipts belongs to. I have nonconsumable products and autorenewing subscriptions, so I'm not able to choose the right verification method.

In your example you just refresh the receipt without doing anything with it, what's the purpose of this?

Thanks

No callback when the a subscription is already purchased

If user is already subscribed to a subscription and he tries to subscribe again to this subscription - the completion handler never gets executed:

        SwiftyStoreKit.purchaseProduct(subscriptionSKU) { result in
              // this never gets executed 
        }

In order to reproduce:

  1. subscribe to a renewable subscription
  2. after subscribing successfully - execute the purchaseProduct method again
    this window will eventually will show up:
    img_8116

and then nothing happens...

Change Restore to return all products ids at once

Could it be possible to change the Restore method to return all productIds at once?

Sometimes one needs some heavy computation after the restores, but like it is right now, it is not possible to know which callback is the last.

Purchase and product delivery does not seem to be atomic

Hi, I'm planning to use SwiftyStoreKit in my app, but I have concerns about the following lines:

switch transactionState {
case .Purchased:
if isPurchaseRequest {
transactionResults.append(.Purchased(productId: transactionProductIdentifier))
paymentQueue.finishTransaction(transaction)
}

finishTransaction is called before any product is delivered.
I think there is a risk that the user will be charged without the product being delivered, especially if the product is a consumable type.
Is this risk handled somewhere else in the library?

The API docs also says that finishTransaction should be called after product delivery.

Your application should call finishTransaction: only after it has successfully processed the transaction and unlocked the functionality purchased by the user.

Synchronously load receipt without verification.

Right now the only way to read receipt data is by calling verifyReceipt, and getting access to the ReceiptInfo in the VerifyReceiptResult.

The issue is that this always make a network call out to Apple, just to read receipt data in a format that I can pass into verifyPurchase. This makes it impossible provide an offline-enabled app with this framework alone. I'm happy to verify the receipt with apple using this technique immediately after purchase, but there is no reason to do it every time the app is launched.

Is it possible to provide an accessor to the current ReceiptInfo data?

iOS Demo app

Why is it giving me the alert: return alertWithTitle("Purchase failed", message: "Please check your Internet connection or try again later")

When I hit cancel instead of buying?

Restore issue with v0.2.7

Just create a new test account and then try to restore, will get this issue. In this case should return that no purchase was found. But I'm not sure it's caused by Apple's cache or not.

Downloadable content

Can I use this for IAPs that use downloadable content? (Apple hosted content) I know there isn't any particular functionality for handling downloading, but can I handle downloading myself and just use this library for the purchase part?

Validate Receipt - Security issue

There are two ways to validate a receipt properly. Locally or via App Store from a server.

https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Introduction.html

"Use a trusted server to communicate with the App Store. Using your own server lets you design your app to recognize and trust only your server, and lets you ensure that your server connects with the App Store server. It is not possible to build a trusted connection between a user’s device and the App Store directly because you don’t control either end of that connection."

In the way you are doing, you may have security problems.

Best practice to store the Shared Secret

I wondering where to store the Shared Secret as it is very sensitive data.
At the moment i have stored it as a string in the SwiftyStoreKit class but i know that this is not a good idea.
What if I regenerate the Shared Secret, i should update the whole app and would be a mess.

Usage questions

  1. after SwiftyStoreKit.restorePurchases(), how to check if auto renewable subscription is active?
    Do I need to call verify subscription? SwiftyStoreKit.verifySubscription
  2. let subscriptionState = SwiftyStoreKit.verifySubscription(productId: "myprouctID", inReceipt: AppState.receipt, validUntil: NSDate(), validDuration: nil)
    where do I get receipt? Can I use
    receiptInfo = InAppReceipt.base64EncodedString as receipt to verify the active subscription?

Product Price Example Should be Changed

In the example used to get product details the approach uses the following to get price:

let priceString = NSNumberFormatter.localizedStringFromNumber(product.price ?? 0, numberStyle: .CurrencyStyle)

The problem I see with this is that it's using the device's region setting to set currency. However, it is possible that the device's region settings are not aligned with the locale that Apple is sending with the product description. In that case the above approach will incorrectly present a currency symbol for the amount given. For example if region is set to France and Apple sends the amount in USD, the code above would put in a Euro symbol but with a USD amount.

Rather than the above I think we should look at the locale of the price Apple sends. This will ensure that the currency symbol always matches the amount. Something like this:

                let numberFormatter = NSNumberFormatter()
                numberFormatter.locale = product.priceLocale
                numberFormatter.numberStyle = .CurrencyStyle
                let priceString = numberFormatter.stringFromNumber(product.price ?? 0)!

JSONDecodeError(Optional(""))??

I keep getting this error when I try to verify the receipt. How can I fix this?
I'm running the iOS version on xcode 7.3.1

Debug Output:
CoreFoundation = 1145.150000
Purchase Success: com.testapp.inapp10
Verify receipt Failed: JSONDecodeError(Optional(""))

Question: completeTransactions asks for login with fresh install

Hi, I'd like to know if I have to call in some special way to "completeTransactions" as it's asking the user to login when the app is freshly installed. It says something similar to what would be translted to English like:
"It is necessary to login. Tap on continue and login in order to look for downloads"

Is there any mechanism for this?
Can this be avoided in order not to ask the login?
Is this because of auto-renewal subscriptions (and same questions above apply).

Thank you, keep going for Swift 3 soon and let this framework grow :D

Invalid state returned when restoring purchases

Sometimes (I dont know how the users do it - i was not able to simulate it) when user is trying to restore purchases, payment queue returns invalid state .Purchased and the app crashes in the following line:

screen shot 2016-01-11 at 21 33 27

any idea what is wrong?

Transaction Id of purchase?

I'm migrating my app to swift from obj-c and found this really nice library. We use google analytics to track purchases and currently pass in the transaction id. For the nice swift callbacks the only thing available is the product id and the transaction observer closure only seems to be called when there isn't an active purchase.

Would it make sense to just always call that closure? Or alternatively pass the transaction details back with the purchase success?

Auto-renewable verification ~ Subscription group

I have 3 products under auto-renewable verification. All of them unlock a feature, but they are of different durations. Would I have to verify all three with SwiftyStoreKit? If not, how would I verify a group of subscription or something like that?

App store constantly asking for password, even if I cancel the it.

I am implementing an in-app purchase with consumable products. The problem I am facing is dialogue box asking for password constantly start appearing, even if I cancel, it pop up again.

While debugging I found that finishTransaction is not called in InAppCompleteTransactionObserver.

Below method didn't call complete transaction on callbackCalled = true, which is causing dialogue box to appear again and again.

func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {

        if callbackCalled {
            return
        }
        if SwiftyStoreKit.hasInFlightPayments {
            return
        }

        var completedTransactions: [SwiftyStoreKit.CompletedTransaction] = []

        for transaction in transactions {

            #if os(iOS) || os(tvOS)
                let transactionState = transaction.transactionState
            #elseif os(OSX)
                let transactionState = PaymentTransactionState(rawValue: transaction.transactionState)!
            #endif

            if transactionState != .Purchasing {

                let completedTransaction = SwiftyStoreKit.CompletedTransaction(productId: transaction.payment.productIdentifier, transactionState: transactionState)

                completedTransactions.append(completedTransaction)

                print("Finishing transaction for payment \"\(transaction.payment.productIdentifier)\" with state: \(transactionState.stringValue)")

                paymentQueue.finishTransaction(transaction)
            }
        }
        callbackCalled = true

        callback(completedTransactions: completedTransactions)
    }

SKPaymentTransactionObserver updatedTransactions called multiple times

I briefly read some of the other threads and it seems like there might be an issue everyone is running into. The InAppProductPurchaseRequest class is being deinitialized after updatedTransactions is called for other transactions. This results in the completion block being called "early" for the purchase, and causes the completion to not be called when the user actually purchases something again.

Unit tests

Need to devise a plan and add unit tests for this library as the feature set and the number of contributors has been increasing.

Ideally we could discuss proposed approach for test coverage here and then proceed to implementation.

Purchase Issue for v0.2.7

  • If first purchase net.toolinbox.ihosts.plus, then try to purchase net.toolinbox.ihosts.pro. The Mac App Store part is correct (show the password dialog, confirm dialog, "Thank You" for purchase succeed dialog), but in paymentQueue, beside 2 transaction of Purchasing state, only get a transaction with id of net.toolinbox.ihosts.plus which was continued. And then no more transaction is called, thus the complete handler of purchase is never called.
    purchase_0
    • I manually cancel the purchasing and purchase again, it will return .Purchased even I haven't input password.
      purchase_2
  • In your demo of 'alertForPurchaseResult', doesn't deal with the case user cancel purchasing.
  • In SwiftyStoreKit.purchaseProduct, if the product identifier is empty string, the complete handler will not be called.

Xcode 8 & Swift 3.0 compatibility

hey guys,

I would like to know if theres a status or any workaround to use this pod with xcode 8.. I'm getting errors as soon it converts to swift 3.0..

Rename targets and schemes

Following the addition of OSX (now macOS) and tvOS compatibility, it seems to appropriate to rename the targets and schemes as follows:

  • SwiftyStoreKit -> SwiftyStoreKitIOS
  • SwiftyStoreKitOSX -> SwiftyStoreKitMacOS
  • SwiftyStoreKitTV -> SwiftyStoreKitTvOS

This should not be a code breaking change for clients as the module would be imported as import SwiftyStoreKit as before.

The folder structure could also be updated:

  • Introduce SwiftyStoreKitIOS folder to include SwiftyStoreKitIOS.h and Info.plist files.
  • Rename SwiftyStoreKitOSX folder to SwiftyStoreKitMacOS
  • Rename SwiftyStoreKitTV folder to SwiftyStoreKitTvOS
  • Rename SwiftyStoreDemo folder to SwiftyStoreKitDemoIOS
  • Rename SwiftyStoreOSXDemo folder to SwiftyStoreKitDemoMacOS

The above should bring more consistency to the naming conventions in SwiftyStoreKit. Feedback is appreciated about this proposal.

Purchase Failed: Failed "Cannot connect to iTunes Store" changed from .Test to .Production

I changed yesterday from .Test to .Production but today i changed back to .Test and In-App purchases does not work i get this:

Purchase Failed: Failed(Error Domain=SKErrorDomain Code=0 "Cannot connect to iTunes Store" UserInfo={NSLocalizedDescription=Cannot connect to iTunes Store})

Can anyone help me what should i do?

Update:

I was able to test with my regular apple ID until 6 days ago.

After that i created a sandbox user and everything seems to work again.

receipt validation sandbox / production

Would it be possible to implement the verification according to apple?

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.

Using SwiftyStoreKit to verify if user has purchased a previous version of app

Hi guys,

I've got a paid app that I want to make free with in-app purchases. I don't want my users that have already purchased my app to pay for the in app purchase. I had a quick look over your code and I feel I can use it to check the user's receipt to determine if they purchased the app before I changed it from paid to free. I just wondered what you guys thought about this implementation? I'm thinking retrieve the user's receipt then check their original purchase date and original purchased version then lock or unlock all features dependant on the result.

How can i know if a user extends Auto Renewal subscription from Settings or PC

When a user subscribe I store the expiration date on Firebase but also on NSUserDefaults but How can i know if a user extends Auto Renewal subscription from Settings or PC.

Check here: https://support.apple.com/en-us/HT202039

P.S Should the Auto Renewal subscriptions autorenew itself? After a product expire i verify the receipt and the date does not change, it should be extended for example +1month.

.
.
I'm also having issue sometimes when i click the purchase button it takes too long or sometimes never to popup the dialog to buy. I should close the app and then the popup comes up. I call completeIAPTransactions on didFinishLaunchingWithOptions

Get a list of all products available from products ids

In my current application, I have a list of IDs clear to sales from the API, to be sure which ones I can show to the user, I need to verify which products are available or not on Apple side.

I though about using retrieveProductInfo, but that will create one request per product. One easy solution would be to create retrieveProductsInfo which could take an array of product ids, and return the ids available in the completion block.

What do you think?

Bug after merging pull request #24?

Hi,

everytime after someone has purchased an item, the app asks for the App Store password when you send it to background and bring it back again.

Cheers, Jan

Missing backoffice validation support

Albeit a very appreciated attempt at porting storekit functionaliies to Swift, the solution misses the possibility of validating the purchase remotely in cases when a certain oepration on the backoffice is subjected to the correct processing of the purchase and to prevent fraud. This functionality is missing from all solutions I found using appStoreReceiptUrl while is present in the original MKStoreKit using the deprecated old construct. I know by myself the issue is a very tough one having attempt myself to implement it for a month three years ago, but I am baffled no one suceeded since then.

How to getInfo in an orderly fashion?

So I have 1, 3, and 6 months subscription option. I am using the getInfo func in the demo with the products appended into an array. As of now the order in which the products appear are random, how do I control this so that it will load 1, 3, 6 months accordingly?

Retrieve Issue for v0.2.7

If disconnect network connection and try to retrieve product list, will only get error of "No bag entry". It's better using better string if could figure it out.

BTW, did you consider to localise the error strings? There're some choices.

  • You can let volunteers help to translate.
  • Or let the developer to translate in their own projects.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.