JXIAPManager/Sources/JXIAPManager.swift
2025-12-18 16:16:20 +08:00

217 lines
7.0 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import UIKit
import StoreKit
public protocol JXIAPManagerDelegate: AnyObject {
///
func jx_iapPaySuccess(productId: String, receipt: String, transactionIdentifier: String)
///
func jx_iapPayFailed(productId: String, code: JXIAPManager.ErrorCode, msg: String?)
///
func iapPayRestore(productIds: [String], transactionIds: [String])
}
public extension JXIAPManagerDelegate {
func jx_iapPaySuccess(productId: String, receipt: String, transactionIdentifier: String) {}
func jx_iapPayFailed(productId: String, code: JXIAPManager.ErrorCode, msg: String?) {}
func iapPayRestore(productIds: [String], transactionIds: [String]) {}
}
public class JXIAPManager: NSObject {
public enum ErrorCode {
///
case unknown
///
case cancelled
///
case noProduct
}
public static let manager: JXIAPManager = JXIAPManager()
public weak var delegate: JXIAPManagerDelegate?
private var payment: SKPayment?
private var product: SKProduct?
private var productId: String?
private var discount: SKPaymentDiscount?
private var orderId: String?
private var applicationUsername: String?
private var receiptCompletion: ((URL?) -> Void)?
override init() {
super.init()
SKPaymentQueue.default().add(self)
}
public func start(productId: String, orderId: String, applicationUsername: String?, discount: SKPaymentDiscount? = nil) {
self.product = nil
self.productId = productId
self.orderId = orderId
self.discount = discount
self.applicationUsername = applicationUsername
let set = Set([productId])
let productsRequest = SKProductsRequest(productIdentifiers: set)
productsRequest.delegate = self
productsRequest.start()
}
///
private func buyProduct() {
guard let product = self.product else { return }
//
let payment = SKMutablePayment(product: product)
payment.applicationUsername = self.applicationUsername
if let discount = self.discount {
payment.paymentDiscount = discount
self.discount = nil
}
self.payment = payment
//
SKPaymentQueue.default().add(payment)
}
}
//MARK: -------------- SKProductsRequestDelegate --------------
extension JXIAPManager: SKProductsRequestDelegate {
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
guard let product = response.products.first else {
DispatchQueue.main.async {
if let productId = self.productId {
self.productId = nil
self.delegate?.jx_iapPayFailed(productId: productId, code: .noProduct, msg: nil)
}
}
return
}
self.product = product
self.buyProduct()
}
}
//MARK: -------------- SKPaymentTransactionObserver --------------
extension JXIAPManager: SKPaymentTransactionObserver {
///
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .purchased:
DispatchQueue.main.async {
self.completeTransaction(transaction: transaction)
}
SKPaymentQueue.default().finishTransaction(transaction)
case .failed:
DispatchQueue.main.async {
self.failedTransaction(transaction: transaction)
}
SKPaymentQueue.default().finishTransaction(transaction)
// case .restored:
// self.restoreTransaction(transaction: transaction)
case .purchasing:
break
default:
SKPaymentQueue.default().finishTransaction(transaction)
break
}
}
}
// func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool {
// return true
// }
///
public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
}
}
extension JXIAPManager {
private func completeTransaction(transaction: SKPaymentTransaction) {
guard let encodeStr = getAppStoreReceipt() else { return }
guard let transactionIdentifier = transaction.transactionIdentifier else { return }
guard let productId = self.productId, productId == transaction.payment.productIdentifier else { return }
self.productId = nil
self.delegate?.jx_iapPaySuccess(productId: productId, receipt: encodeStr, transactionIdentifier: transactionIdentifier)
}
private func failedTransaction(transaction: SKPaymentTransaction) {
let error = transaction.error as? SKError
guard let productId = self.productId else { return }
self.productId = nil
switch error?.code {
case SKError.paymentCancelled:
self.delegate?.jx_iapPayFailed(productId: productId, code: .cancelled, msg: error?.localizedDescription)
default:
self.delegate?.jx_iapPayFailed(productId: productId, code: .unknown, msg: error?.localizedDescription)
}
}
}
extension JXIAPManager: SKRequestDelegate {
public func getAppStoreReceipt() -> String? {
guard let receiptURL = Bundle.main.appStoreReceiptURL else { return nil }
let receiptData = NSData(contentsOf: receiptURL)
return receiptData?.base64EncodedString(options: .endLineWithLineFeed)
}
///
public func fetchReceipt(completion: @escaping (URL?) -> Void) {
let receiptURL = Bundle.main.appStoreReceiptURL
if let url = receiptURL, FileManager.default.fileExists(atPath: url.path) {
completion(url)
return
}
let request = SKReceiptRefreshRequest()
request.delegate = self
request.start()
// delegate
self.receiptCompletion = completion
}
public func requestDidFinish(_ request: SKRequest) {
let receiptURL = Bundle.main.appStoreReceiptURL
if let url = receiptURL, FileManager.default.fileExists(atPath: url.path) {
receiptCompletion?(url)
} else {
receiptCompletion?(nil)
}
}
public func request(_ request: SKRequest, didFailWithError error: Error) {
print("Receipt request failed: \(error)")
receiptCompletion?(nil)
}
}