Files
peko-ios/YuMi/Tools/PIIAPTool/PIIAPPayment.swift
2023-09-18 16:24:28 +08:00

258 lines
8.1 KiB
Swift
Raw 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.

//
// PIIAPPayment.swift
// YuMi
//
// Created by duoban on 2023/9/14.
//
import UIKit
import StoreKit
@available(iOS 15.0, *)
typealias Transaction = StoreKit.Transaction
@available(iOS 15.0, *)
typealias RenewalInfo = StoreKit.Product.SubscriptionInfo.RenewalInfo
@available(iOS 15.0, *)
typealias RenewalState = StoreKit.Product.SubscriptionInfo.RenewalState
enum PIStoreError: Error {
//
case failedVerification
case noProduct
}
@objc public enum StoreState: Int64 { //
case start //
case pay //
case verifiedServer //
case userCancelled //
case pending //
case unowned
case noProduct //
case failedVerification //
}
@available(iOS 15.0, *)
public class PIIAPPayment: NSObject {
public typealias KStateBlock = (_ state :StoreState,_ param:Dictionary<String,Any>?) ->()
@objc public var stateBlock: KStateBlock! //
var updateListenerTask: Task<Void, Error>? = nil //
var transactionMap :[String:Transaction] // Idmap
var name: String = "iosStore" //
@objc public static let shared = {
let instance = PIIAPPayment()
return instance
}()
private override init() { // private
transactionMap = [:] //
super.init()
Task {
updateListenerTask = listenForTransactions()
}
}
// 退
@objc public func refunRequest(view: UIView,transactionId:UInt64) async{
do {
if let scene = await view.window?.windowScene{
try await Transaction.beginRefundRequest(for:transactionId , in: scene)
}
}catch{
print("iap error")
}
}
/*All transactions
Latest transactions
Current entitlements
*/
func allTransaction() async -> [UInt64] {
var all = [UInt64]()
for await result in Transaction.all {
do {
let tran = try checkVerified(result)
let transactionId = try result.payloadValue.id
all.append(transactionId)
} catch let error {
print("error:----\(error)")
}
}
return all
//Transaction.latest(for: "pid")
}
//
@objc public func requestBuyProduct(productId:String, uuid: String) async throws {
if(stateBlock != nil ){
stateBlock(StoreState.start,nil)
}
do {
let list:[String] = [productId]
let storeProducts = try await Product.products(for: Set.init(list))
if storeProducts.count > 0 {
try await purchase(storeProducts[0],uuid)
}else {
print("iap: no found product")
if(stateBlock != nil ){
stateBlock(StoreState.noProduct,nil)
}
throw PIStoreError.noProduct //
}
} catch {
print("Failed product request from the App Store server: \(error)")
if(stateBlock != nil ){
stateBlock(StoreState.noProduct,nil)
}
throw PIStoreError.noProduct //
}
}
//
private func purchase(_ product: Product, _ uuid: String) async throws -> Transaction? {
if(stateBlock != nil ){
stateBlock(StoreState.pay,nil)
}
guard let curUUID = UUID.init(uuidString: uuid) else{
if(stateBlock != nil ){
stateBlock(StoreState.failedVerification,nil)
}
return nil
}
let getUUID = Product.PurchaseOption.appAccountToken(curUUID)
let result = try await product.purchase(options: [getUUID])
switch result {
case .success(let verification): //
let transaction = try await verifiedAndFinish(verification)
return transaction
case .userCancelled: //
if(stateBlock != nil ){
stateBlock(StoreState.userCancelled,nil)
}
return nil
case .pending: //
if(stateBlock != nil ){
stateBlock(StoreState.pending,nil)
}
return nil
default:
if(stateBlock != nil ){
stateBlock(StoreState.unowned,nil)
}
return nil
}
}
//
func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
//Check whether the JWS passes StoreKit verification.
switch result {
case .unverified:
//StoreKit parses the JWS, but it fails verification.
if(stateBlock != nil ){
stateBlock(StoreState.failedVerification,nil)
}
throw PIStoreError.failedVerification
case .verified(let safe):
//The result is verified. Return the unwrapped value.
print("iap: verified success")
return safe
}
}
// &
func verifiedAndFinish(_ verification:VerificationResult<Transaction>) async throws -> Transaction?{
//Check whether the transaction is verified. If it isn't,
//this function rethrows the verification error.
let transaction = try checkVerified(verification)
// ~~~
let transactionId = try verification.payloadValue.id
// map
let key = String(transactionId)
transactionMap[key] = transaction
await uploadServer(for: transactionId)
//
await transaction.finish()
print("iap: finish")
return transaction
}
//
@objc public func transactionFinish(transaction:String) async{
if(transactionMap[transaction] != nil){
await transactionMap[transaction]!.finish()
transactionMap.removeValue(forKey: transaction)
print("transactionFinish end")
}else {
print("transaction不存在参数不正确,Id=\(transaction)")
}
}
@MainActor
func uploadServer(for transactionId:UInt64) async {
let dic :Dictionary<String,Any> = ["transactionId":transactionId]
if(stateBlock != nil ){
stateBlock(StoreState.verifiedServer,dic)
}
}
//
func listenForTransactions() -> Task<Void, Error> {
return Task.detached {
//Iterate through any transactions that don't come from a direct call to `purchase()`.
// update unfinished?
for await result in Transaction.updates { //
do {
print("iap: updates")
print("result:\(result)")
let transaction = try await self.verifiedAndFinish(result)
} catch {
//StoreKit has a transaction that fails verification. Don't deliver content to the user.
print("Transaction failed verification")
}
}
}
}
//广
func Promotion() async -> [SKProduct]?{
let promotion = SKProductStorePromotionController()
let prodicts = try? await promotion.promotionOrder()
return prodicts
}
//
deinit {
updateListenerTask?.cancel()
}
}