InAppPurchase 2.8.0

InAppPurchase 2.8.0

Jin Sasaki 维护。



InAppPurchase

Build Status Carthage compatible Version Platform codecov

一个简单、轻量级且安全的内部购买框架

特性

  • 简单且轻量👍
  • 支持推广内部购买💰
  • 无需考虑StoreKit😎
  • 覆盖率高且安全

安装

Carthage

github "jinSasaki/InAppPurchase"

CocoaPods

pod "InAppPurchase"

使用

设置观察者

注意:该方法应在启动时调用。

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

如果您想检测意外的交易,请将 addTransactionObserver()fallbackHandler 一起传递。
例如,您的应用请求了一笔支付,但在该过程中崩溃了。这笔交易未完成,将在下一次启动时接收。
当任何处理程序未被通过 purchase(productIdentifier: handler:) 方法等设置为 InAppPurchase 时,将调用此 fallbackHandler

自iOS 11起,推广内购是可用的。《InAppPurchase》支持它!

使用 shouldAddStorePaymentHandler 添加观察者。
另请参阅 SKPaymentTransactionObserver#paymentQueue(_:shouldAddStorePayment:for:)推广内购指南

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

⚠️不要使用 Product#priceLocale

只有当通过 AppStorePromoting 进行购买时,SKProduct#priceLocale 才未初始化。这会导致一个 BAD_ACCESS 崩溃。这是一个 StoreKit 错误。InAppPurchace 解决了在收到付款时发生的崩溃,但它在访问 Product#priceLocale 时尚未解决。因此,我建议在 AppStore Promoting 支付过程中不要使用 Product#priceLocale

如有需要,停止支付观察。

let iap = InAppPurchase.default
iap.removeTransactionObserver()

获取产品信息

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

恢复已完成的交易

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

购买

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

交易处理

如果您想处理交易完成的时机,请在初始化时将 shouldCompleteImmediately 设置为 false

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

多个 InAppPurchase 实例

如果您想使用多个 InAppPurchase,请创建每个实例。
但是,由于重复处理,请注意回退处理。

这是重复处理示例

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

为了避免这种情况,我建议您为每个实例指定产品 ID。

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

此外,如果您没有指定 productIds 或将其设置为 productIds: nil,则 iap 实例允许所有产品 ID。

用于依赖注入

应用中的购买逻辑应该是安全且可测试的。

例如,您按照以下方式实现了执行 In-App-Purchase 的类。

// PurchaseService.swift

import Foundation
import InAppPurchase

final class PurchaseService {
    static let shared = PurchaseService()

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

由于在购买过程中使用了 InAppPurchase.default,因此难以测试这个类。

可以将此 PurchaseService 重构为注入依赖关系。
使用 InAppPurchaseProvidable 协议。

// 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
        }
    }
}

然后您可以使用 InAppPurchaseStubs.framework轻松地测试 PurchaseService

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

如果您需要更多信息以进行测试,请参阅 InAppPurchaseStubsTests

需求

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

许可

MIT