ProductPromotion
Logo

Swift

made by https://0x3d.site

GitHub - jinSasaki/InAppPurchase: A Simple and Lightweight framework for In App Purchase
A Simple and Lightweight framework for In App Purchase - jinSasaki/InAppPurchase
Visit Site

GitHub - jinSasaki/InAppPurchase: A Simple and Lightweight framework for In App Purchase

GitHub - jinSasaki/InAppPurchase: A Simple and Lightweight framework for In App Purchase

InAppPurchase

Build Status Carthage compatible Version Platform codecov

A Simple, Lightweight and Safe framework for In App Purchase

Feature

  • Simple and Light :+1:
  • Support Promoting In-App Purchases :moneybag:
  • No need to consider StoreKit! :sunglasses:
  • High coverage and safe :white_check_mark:

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

:warning: 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

More Resources
to explore the angular.

mail [email protected] to add your project or resources here ๐Ÿ”ฅ.

Related Articles
to learn about angular.

FAQ's
to learn more about Angular JS.

mail [email protected] to add more queries here ๐Ÿ”.

More Sites
to check out once you're finished browsing here.

0x3d
https://www.0x3d.site/
0x3d is designed for aggregating information.
NodeJS
https://nodejs.0x3d.site/
NodeJS Online Directory
Cross Platform
https://cross-platform.0x3d.site/
Cross Platform Online Directory
Open Source
https://open-source.0x3d.site/
Open Source Online Directory
Analytics
https://analytics.0x3d.site/
Analytics Online Directory
JavaScript
https://javascript.0x3d.site/
JavaScript Online Directory
GoLang
https://golang.0x3d.site/
GoLang Online Directory
Python
https://python.0x3d.site/
Python Online Directory
Swift
https://swift.0x3d.site/
Swift Online Directory
Rust
https://rust.0x3d.site/
Rust Online Directory
Scala
https://scala.0x3d.site/
Scala Online Directory
Ruby
https://ruby.0x3d.site/
Ruby Online Directory
Clojure
https://clojure.0x3d.site/
Clojure Online Directory
Elixir
https://elixir.0x3d.site/
Elixir Online Directory
Elm
https://elm.0x3d.site/
Elm Online Directory
Lua
https://lua.0x3d.site/
Lua Online Directory
C Programming
https://c-programming.0x3d.site/
C Programming Online Directory
C++ Programming
https://cpp-programming.0x3d.site/
C++ Programming Online Directory
R Programming
https://r-programming.0x3d.site/
R Programming Online Directory
Perl
https://perl.0x3d.site/
Perl Online Directory
Java
https://java.0x3d.site/
Java Online Directory
Kotlin
https://kotlin.0x3d.site/
Kotlin Online Directory
PHP
https://php.0x3d.site/
PHP Online Directory
React JS
https://react.0x3d.site/
React JS Online Directory
Angular
https://angular.0x3d.site/
Angular JS Online Directory