Giter Club home page Giter Club logo

inapppurchase's Introduction

InAppPurchase

Build Status Carthage compatible Version Platform codecov

A Simple, Lightweight and Safe framework for In App Purchase

Feature

Installation

Carthage

github "jinSasaki/InAppPurchase"

CocoaPods

pod "InAppPurchase"

Usage

Setup Observer

NOTE: This method should be called at launch.

let iap = InAppPurchase.default
iap.addTransactionObserver(fallbackHandler: {
    // Handle the result of payment added by Store
    // See also `InAppPurchase#purchase`
})

If you want to detect the unexpected transactions, pass addTransactionObserver() with fallbackHandler.
For example, your app requested a payment, but it crashed in that process. That transaction is not finished, and then will receive at next launch.
This fallbackHandler is called when any handlers are not set to InAppPurchase via purchase(productIdentifier: handler:) method and so on.

Promoting In App Purchases is available from iOS 11. InAppPurchase supports it!

Add observer with shouldAddStorePaymentHandler.
See also SKPaymentTransactionObserver#paymentQueue(_:shouldAddStorePayment:for:)and Promoting In-App Purchases Guides

promoting

let iap = InAppPurchase.default
iap.set(shouldAddStorePaymentHandler: { (product) -> Bool in
    // Return whether starting payment
}, handler: { (result) in
    // Handle the result of payment added by Store
    // See also `InAppPurchase#purchase`
})

⚠️ Do not use Product#priceLocale

Only if purchase via AppStore Promoting, SKProduct#priceLocale has been not initialized. It occurs a BAD_ACCESS crash. This is a StoreKit bug. InAppPurchace resolved the crash that is occurred when received the payment, but it occurs when accessed Product#priceLocale yet. So, I recommend not to use Product#priceLocale in AppStore Promoting Payment process.

Stop payment observing if needed.

let iap = InAppPurchase.default
iap.removeTransactionObserver()

Fetch Product Information

let iap = InAppPurchase.default
iap.fetchProduct(productIdentifiers: ["PRODUCT_ID"], handler: { (result) in
    switch result {
    case .success(let products):
        // Use products
    case .failure(let error):
        // Handle `InAppPurchase.Error`
    }
})

Restore Completed Transaction

let iap = InAppPurchase.default
iap.restore(handler: { (result) in
    switch result {
    case .success(let productIds):
        // Restored with product ids
    case .failure(let error):
        // Handle `InAppPurchase.Error`
    }
})

Purchase

let iap = InAppPurchase.default
iap.purchase(productIdentifier: "PRODUCT_ID", handler: { (result) in
    // This handler is called if the payment purchased, restored, deferred or failed.

    switch result {
    case .success(let response):
        // Handle `PaymentResponse`
    case .failure(let error):
        // Handle `InAppPurchase.Error`
    }
})

Transaction handling

If you want to handle the timing to complete transaction, set shouldCompleteImmediately to false at initializing.

let iap = InAppPurchase(shouldCompleteImmediately: false)
iap.purchase(productIdentifier: "PRODUCT_ID", handler: { (result) in
    // This handler is called if the payment purchased, restored, deferred or failed.

    switch result {
    case .success(let response):
        // Handle `PaymentResponse`
        // MUST: InAppPurchase does not complete transaction, if purchased, restored. Your app must call `InAppPurchase.finish(transaction:)`.
        if response.state == .purchased || response.state == .restored {
            iap.finish(transaction: response.transaction)
        }
    case .failure(let error):
        // Handle `InAppPurchase.Error`
    }
})

Multiple instances of InAppPurchase

If you want to use multiple InAppPurchase, make each instance.
However, be careful the fallback handling because of duplicate handlings.

This is duplicate handling example:

let iap1 = InAppPurchase.default
let iap2 = InAppPurchase(shouldCompleteImmediately: false)
iap1.addTransactionObserver(fallbackHandler: {
    // NOT CALLED
    // This fallback handler is NOT called because the purchase handler is used.
})
iap2.addTransactionObserver(fallbackHandler: {
    // CALLED
    // This fallback handler is called because the purchase handler is not associated to iap2.
})
iap1.purchase(productIdentifier: "your.purchase.item1", handler: { (result) in
    // CALLED
})

To avoid this situation, I recommend to specify product IDs for each instance.

let iap1 = InAppPurchase(shouldCompleteImmediately: true, productIds: ["your.purchase.item1", "your.purchase.item2"])
let iap2 = InAppPurchase(shouldCompleteImmediately: false, productIds: ["your.purchase.item3", "your.purchase.item4"])
iap1.addTransactionObserver(fallbackHandler: {
    // NOT CALLED
    // This fallback handler is NOT called because the purchase handler is used.
})
iap2.addTransactionObserver(fallbackHandler: {
    // NOT CALLED
    // This fallback handler is NOT called because "your.purchase.item1" is not specified for iap2.
})
iap1.purchase(productIdentifier: "your.purchase.item1", handler: { (result) in
    // CALLED
})

In addition, if you do not specify productIds or set productIds: nil, the iap instance allow all product ids.

For Dependency Injection

The purchase logic in the App should be safe and testable.

For example, you implemented a class to execute In-App-Purchase as follows.

// PurchaseService.swift

import Foundation
import InAppPurchase

final class PurchaseService {
    static let shared = PurchaseService()

    func purchase() {
        // Purchase with `InAppPurchase`
        InAppPurchase.default.purchase(productIdentifier: ...) {
            // Do something
        }
    }
}

It is hard to test this class because using the InAppPurchase.default in the purchase process.

This PurchaseService can be refactored to inject the dependency.
Use InAppPurchaseProvidable protocol.

// PurchaseService.swift

import Foundation
import InAppPurchase

final class PurchaseService {
    static let shared = PurchaseService()

    let iap: InAppPurchaseProvidable

    init(iap: InAppPurchaseProvidable = InAppPurchase.default) {
        self.iap = iap
    }

    func purchase() {
        // Purchase with `InAppPurchase`
        iap.purchase(productIdentifier: ...) {
            // Do something
        }
    }
}

And then you can test PurchaseService easily with InAppPurchaseStubs.framework.

// PurchaseServiceTests.swift

import XCTest
@testable import YourApp
import InAppPurchaseStubs

// Test
final class PurchaseServiceTests: XCTestCase {
    func testPurchase() {
        let expectation = self.expectation(description: "purchase handler was called.")
        let iap = StubInAppPurchase(purchaseHandler: { productIdentifier, handler in
            // Assert productIdentifier, handler, and so on.
        })
        let purchaseService = PurchaseService(iap: iap)
        purchaseService.purchase(productIdentifier: ...) {
            // Assert result
            expectation.fulfill()
        }

        wait(for: [expectation], timeout: 1)
    }
}

If you want more information for test, see also InAppPurchaseStubs and Tests.

Requirements

  • iOS 9.0+
  • Xcode 9+
  • Swift 4+

License

MIT

inapppurchase's People

Contributors

jinsasaki avatar yanyin1986 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

inapppurchase's Issues

cocoapods not working

I added this line to my podfile:

pod 'InAppPurchase'

then call "pod install" and I get this error:

[!] Unable to find a specification for InAppPurchase``

My other pod dependencies work correctly (ie. PromiseKit, SwiftyJSON, etc)

Purchase method's completion handler is not called

Describe the bug
Neither the success nor the failure are called. I've completed a successful purchase many times, but I can't do anything after the purchase has completed.

To Reproduce
My sample code.

    static func removeAds() {
        let iap = InAppPurchase.default
        iap.addTransactionObserver()
        iap.purchase(productIdentifier: "1") { result in
            switch result {
            case .success(let state):
                print("SUCCESS: \(state)")
                UserDefaults.standard.removeAds = true
            case .failure(let error):
                print(error)
            }
        }
        iap.removeTransactionObserver()
    }

Is something wrong my code?

Screenshots
IMG_3627
UNADJUSTEDNONRAW_thumb_55df

Desktop (please complete the following information):

  • iOS 12

Smartphone (please complete the following information):

  • iOS 7 Device

can we check given product is already purchased before?

Is your feature request related to a problem? Please describe.

can we check given product is already purchased before?
we need to show restore Button based on his previous transaction

Describe the solution you'd like

create new method that will return the previous purchased product array

Describe alternatives you've considered
we need to make purchased request again an apple will handle

Xcode: "found 1 file(s) which are unhandled"

Describe the bug
I am using InAppPurchase in my apps, via SPM. Somehow, on a successful build, a warning pops up saying that 1 file, which is this library's Info.plist. Functionality unaffected though.

To Reproduce
Steps to reproduce the behavior:

  1. Add library to project, via SPM select InAppPurchase
  2. Build

Screenshots
Screen Shot 2021-06-02 at 10 49 02 PM

Desktop (please complete the following information):

  • OS: macOS 11.2.3
  • Xcode: 12.5

Smartphone (please complete the following information):

  • Device: iPhone 11 Pro Max
  • OS: iOS 14.5.1

Restore Product Option

Describe the bug
Whenever I use the current restore Product option it succeeds even when a user has not purchased the in app purchase

To Reproduce
Steps to reproduce the behavior:

  1. Implement IAPS in App Store Connect
  2. Call In App Purchase.Restore
  3. Call succeeds even if user has not purchased the product

Expected behavior
A user should be prompted to sign in to their iCloud account and it should fail if they have not purchased the product before

'PeriodUnit' is only available on iOS 11.2 or newer

When i try to build a project with deployment target 9, i get a compile error:

Product.swift:66:28: 'PeriodUnit' is only available on iOS 11.2 or newer

Your readme says the requirement is iOS 9.0+.

I'm on macOS Mojave, Xcode 10.

XCode 13 'Product' is ambiguous for type lookup in this context

Describe the bug
I get an error about 'Product' is ambiguous for type lookup in this context

To Reproduce
Steps to reproduce the behavior:
Any mention of Product

Expected behavior
Not throw an error, maybe it needs to be renamed?

Screenshots
9076F1E9-F4FD-43B7-B540-691B3AD0BC4A_4_5005_c

Desktop (please complete the following information):

  • OS: iOS
  • Browser Mobile
  • Version XCode 13.0

Transaction never completes with success

I try to purchase something.

The final handler with the result is never called.

If I ask to buy something and cancel, the handler is called with result = error.

I am on iOS 15

Save set of purchased items

Have you considered keeping a set or list of purchased/restored product ids? Also saved to disk. Or is this out of the scope of this library?

Cocoapod can't install latest version 2.2.0

Describe the bug
Cocoapod can't install latest version 2.2.0

CocoaPods could not find compatible versions for pod "InAppPurchase":
  In Podfile:
    InAppPurchase (= 2.1.5)

or 2.2.0

To Reproduce
Steps to reproduce the behavior:

  1. pod 'InAppPurchase', '2.1.5'
  2. pod install

Expected behavior
Version 2.1.5 or 2.2.0 to be installed

Smartphone (please complete the following information):

  • Device: Any
  • OS: iOS 13
  • XCode: 11.1

Restore with product ids

If you do a restore purchase call, how do you know which product ids were restored? Do you have to do a fetchProduct first and then assume all those ids were restored if a restore call was success? Would it be possible to add a list of product ids in the restore sucess call?

a

Describe the bug
A clear and concise description of what the bug is.

To Reproduce
Steps to reproduce the behavior:

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

Expected behavior
A clear and concise description of what you expected to happen.

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: [e.g. iOS]
  • Browser [e.g. chrome, safari]
  • Version [e.g. 22]

Smartphone (please complete the following information):

  • Device: [e.g. iPhone6]
  • OS: [e.g. iOS8.1]
  • Browser [e.g. stock browser, safari]
  • Version [e.g. 22]

Additional context
Add any other context about the problem here.

fetchProduct Error

Here is my purchase button, first I attempt to see if my IAP is reachable. it's not!

@IBAction func purchase(_ sender: Any) {
        let iap = InAppPurchase.default
        iap.fetchProduct(productIdentifiers: ["AppUnlock"], handler: { (result) in
            switch result {
            case .success(let products):
                print("ok")
            // Use products
            case .failure(let error):
                print(error)
                // Handle `InAppPurchase.Error`

            }
        })
    }

Here is my IAP in iTunes Connect.

image

I receive the error:

invalid(["AppUnlock"])

....

What's going on here?

Request: Swift Package Manager support

Is your feature request related to a problem? Please describe.
I currently can't use PM to download your package. It would be great to get PM support to download this library via Swift PM. When I try

What I see when I try to download from your git repo directly from Xcode:
Screen Shot 2020-02-23 at 10 50 54 AM

What I would like to see eventually:
Screen Shot 2020-02-23 at 10 44 27 AM

Describe the solution you'd like
While I have not made one myself, I founded this article as a possible start point:
https://www.swiftbysundell.com/articles/managing-dependencies-using-the-swift-package-manager/

My general understanding is that a manifest needs to be made along with some other tweaks.

BTW Thank you so much for making such a useful library. I've used it a lot in the past.

How to disable sandbox env on TestFlight?

I uploaded a version to the TestFlight version, ready to submit to the Apple Store. But in-app purchases are in the Sandbox environment, how do I turn off the Sandbox mode?

Snipaste_2022-08-14_09-40-41

Cocoapod iOS version change

I used InAppPurchase in my podspec and this error shows

ERROR | [iOS] xcodebuild: InAppPurchase/Sources/Product.swift:66:28: error: 'PeriodUnit' is only available on iOS 11.2 or newer

so can you change the ' s.platform = :ios, '9.0'' to " s.platform = :ios, '11.2' " in InAppPurchase.podspec

thank you

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.