我走的是原生桥接方法
-
在 uni 原生工程项目下新增两个 swift 文件
- IAPBridge.swift
- IAPManager.swift

具体两个文件内容如下
IAPBridge.swift
import Foundation
@objc(IAPBridge)
class IAPBridge: NSObject {
private var lastResult: String = ""@objc func getLastResult() -> String { return lastResult } private func runPurchase(productId: String, uuid: String, callback: @escaping (Any?) -> Void) { print("[IAP][原生][桥接] 开始购买流程, productId=\(productId), uuid=\(uuid)") let unitSep = "\u{001e}" let paramBlob = productId + unitSep + uuid print("[IAP][原生][桥接] 设置参数并启动购买") IAPManager.shared.setPurchaseParamBlob(paramBlob) IAPManager.shared.startPurchaseFlow() DispatchQueue.global().async { print("[IAP][原生][桥接] 开始等待购买结果") let result = IAPManager.shared.waitPurchaseResultMs(120000) print("[IAP][原生][桥接] 等待结果返回=\(result)") self.lastResult = result DispatchQueue.main.async { print("[IAP][原生][桥接] 回调给 JS") // 强制桥接为 NSString,避免部分基座把 Swift.String 透传成 undefined callback(result as NSString) } } } /// JS 调用入口 @objc func purchase(_ params: NSDictionary, callback: @escaping (Any?) -> Void) { print("[IAP][原生][桥接] purchase 被调用, params=\(params)") guard let productId = params["productId"] as? String, !productId.isEmpty else { print("[IAP][原生][桥接] 缺少 productId") callback("error: missing productId") return } guard let uuid = params["uuid"] as? String, !uuid.isEmpty else { print("[IAP][原生][桥接] 缺少 uuid") callback("error: missing uuid") return } runPurchase(productId: productId, uuid: uuid, callback: callback) } /// JS 兜底入口:避免 NSDictionary 桥接或 selector 解析失败 @objc func purchaseWithProductId(_ productId: String, uuid: String, callback: @escaping (Any?) -> Void) { print("[IAP][原生][桥接] purchaseWithProductId 被调用, productId=\(productId), uuid=\(uuid)") guard !productId.isEmpty else { callback("error: missing productId") return } guard !uuid.isEmpty else { callback("error: missing uuid") return } runPurchase(productId: productId, uuid: uuid, callback: callback) }}
IAPManager.swift
import Foundation
import StoreKit
/// 不向 JS/UTS 桥传递 **block**,避免 `NSDictionary initWithObjects:... objects[0]=nil` 类崩溃。
/// 流程:`setPurchaseParamBlob` → `startPurchaseFlow` → `waitPurchaseResultMs`(在子线程阻塞等待结果)。
@objc public class IAPManager: NSObject {
@objc public static let shared = IAPManager()
private let lock = NSLock()
private var paramBlobWork = ""
private var sem: DispatchSemaphore?
private var outcome: String = "__lxh_iap_not_started__"
private let unitSep = "\u{001E}"
/// 仅 NSString,无 block
@objc public func setPurchaseParamBlob(_ blob: String) {
print("[IAP][原生][管理器] 设置购买参数 blob=\(blob)")
lock.lock()
paramBlobWork = blob
outcome = "__lxh_iap_not_started__"
sem = DispatchSemaphore(value: 0)
lock.unlock()
}
/// 无参数,无返回值;在内部 MainActor Task 中跑 StoreKit,结束时 signal
@objc public func startPurchaseFlow() {
print("[IAP][原生][管理器] startPurchaseFlow 被调用")
let semCopy: DispatchSemaphore?
let blobCopy: String
lock.lock()
semCopy = sem
blobCopy = paramBlobWork
lock.unlock()
guard let waitSem = semCopy else { return }
guard let sepRange = blobCopy.range(of: unitSep) else {
print("[IAP][原生][管理器] 参数格式无效")
lock.lock()
outcome = "error: invalid params"
lock.unlock()
waitSem.signal()
return
}
let productId = String(blobCopy[..<sepRange.lowerBound])
let uuid = String(blobCopy[sepRange.upperBound...])
print("[IAP][原生][管理器] 解析参数 productId=\(productId), uuid=\(uuid)")
guard !productId.isEmpty, !uuid.isEmpty else {
print("[IAP][原生][管理器] 缺少 productId 或 uuid")
lock.lock()
outcome = "error: missing productId or uuid"
lock.unlock()
waitSem.signal()
return
}
Task { @MainActor in
print("[IAP][原生][管理器] 开始执行 runPurchase")
let result = await self.runPurchase(productId: productId, uuid: uuid)
print("[IAP][原生][管理器] runPurchase 结束, result=\(result)")
self.lock.lock()
self.outcome = result
self.lock.unlock()
waitSem.signal()
}
}
/// 仅 Int 入参 + NSString 返回,无 block
@objc public func waitPurchaseResultMs(_ timeoutMs: Int) -> String {
print("[IAP][原生][管理器] 等待购买结果 timeoutMs=\(timeoutMs)")
let waitSem: DispatchSemaphore?
lock.lock()
waitSem = sem
lock.unlock()
guard let s = waitSem else {
return "error: not prepared"
}
let t = max(1, min(timeoutMs, 300_000))
let deadline = DispatchTime.now() + .milliseconds(t)
let ok = s.wait(timeout: deadline) == .success
print("[IAP][原生][管理器] 等待结束 ok=\(ok)")
lock.lock()
let text = outcome
outcome = "__lxh_iap_not_started__"
sem = nil
lock.unlock()
if !ok {
return "error: timeout"
}
return text
}
@MainActor
private func runPurchase(productId: String, uuid: String) async -> String {
print("[IAP][原生][管理器] runPurchase 入参 productId=\(productId), uuid=\(uuid)")
do {
await clearUnfinishedTransactions()
let products = try await Product.products(for: [productId])
print("[IAP][原生][管理器] 商品查询数量=\(products.count)")
guard let product = products.first else {
return "no product"
}
guard let accountToken = UUID(uuidString: uuid) else {
print("[IAP][原生][管理器] uuid 格式无效=\(uuid)")
return "error: invalid uuid"
}
print("[IAP][原生][管理器] 发起购买 appAccountToken uuid=\(accountToken.uuidString)")
let result = try await product.purchase(options: [.appAccountToken(accountToken)])
switch result {
case .success(let verification):
print("[IAP][原生][管理器] 购买结果=成功")
let transaction = try checkVerified(verification)
let token = transaction.appAccountToken?.uuidString ?? ""
let msg = """
success:
transactionId=\(transaction.id)
appAccountToken=\(token)
"""
await transaction.finish()
return msg
case .userCancelled:
print("[IAP][原生][管理器] 购买结果=用户取消")
return "cancel"
case .pending:
print("[IAP][原生][管理器] 购买结果=待处理")
return "pending"
default:
print("[IAP][原生][管理器] 购买结果=未知")
return "unknown"
}
} catch {
print("[IAP][原生][管理器] runPurchase 异常=\(error.localizedDescription)")
return "error: \(error.localizedDescription)"
}
}
@MainActor
private func clearUnfinishedTransactions() async {
var total = 0
var finished = 0
var skipped = 0
print("[IAP][原生][管理器] 开始清理未完成交易")
for await pending in Transaction.unfinished {
total += 1
do {
let tx = try checkVerified(pending)
// 仅作为支付前兜底清理:统一 finish,避免历史交易阻塞
await tx.finish()
finished += 1
print("[IAP][原生][管理器] 已结束未完成交易 id=\(tx.id), productId=\(tx.productID)")
} catch {
skipped += 1
print("[IAP][原生][管理器] 跳过未完成交易(校验失败): \(error.localizedDescription)")
}
}
print("[IAP][原生][管理器] 清理未完成交易结束 total=\(total), finished=\(finished), skipped=\(skipped)")
}
private func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
switch result {
case .verified(let safe):
return safe
case .unverified:
throw NSError(domain: "iap", code: -1)
}
}
}
使用方法 关键代码
function iapBridgePurchase(productId: string, uuid: string): Promise<string> {
return new Promise((resolve, reject) => {
try {
console.log("[IAP][JS] 开始调用 iapBridgePurchase", { productId, uuid });
// @ts-ignore APP-PLUS 原生
const p = typeof plus !== "undefined" ? plus : undefined;
if (!p?.ios?.newObject) {
console.error("[IAP][JS] plus.ios 不可用");
reject(new Error("plus.ios 不可用"));
return;
}
let bridge: any;
try {
bridge = p.ios.newObject("IAPBridge");
console.log("[IAP][JS] 桥接类 = IAPBridge");
} catch (_) {
bridge = p.ios.newObject("LxhIapBridge");
console.log("[IAP][JS] 桥接回退类 = LxhIapBridge");
}
let done = false;
const finish = (...cbArgs: unknown[]) => {
if (done) return;
done = true;
const rawRet =
cbArgs.length > 0 && cbArgs[0] !== undefined
? cbArgs[0]
: cbArgs.length > 1
? cbArgs[1]
: undefined;
let rawText = "";
if (typeof rawRet === "string") {
rawText = rawRet;
} else if (rawRet == null) {
rawText = "";
} else {
try {
rawText = JSON.stringify(rawRet);
} catch (_) {
rawText = String(rawRet);
}
}
if (!rawText) {
try {
const syncRet = p.ios.invoke(bridge, "getLastResult");
if (typeof syncRet === "string" && syncRet) {
rawText = syncRet;
console.log("[IAP][JS] 回调为空,已通过 getLastResult 获取 =", rawText);
}
} catch (e2) {
console.error("[IAP][JS] getLastResult 读取失败", e2);
}
}
console.log("[IAP][JS] 支付回调参数 =", cbArgs);
console.log("[IAP][JS] 支付回调原始值 =", rawRet);
try {
p.ios.deleteObject(bridge);
} catch (_) {
/* ignore */
}
resolve(rawText);
};
setTimeout(() => {
if (!done) {
console.error("[IAP][JS] 桥接回调超时(8秒)");
}
}, 8000);
console.log("[IAP][JS] 调用 purchaseWithProductId:uuid:callback:");
try {
p.ios.invoke(bridge, "purchaseWithProductId:uuid:callback:", productId, uuid, finish);
} catch (e1) {
console.error("[IAP][JS] 调用 purchaseWithProductId 失败,回退 purchase:callback:", e1);
const params = p.ios.newObject("NSMutableDictionary");
p.ios.invoke(params, "setObject:forKey:", productId, "productId");
p.ios.invoke(params, "setObject:forKey:", uuid, "uuid");
p.ios.invoke(bridge, "purchase:callback:", params, (rawRet: unknown) => {
try {
p.ios.deleteObject(params);
} catch (_) {
/* ignore */
}
finish(rawRet);
});
}
} catch (e) {
console.error("[IAP][JS] iapBridgePurchase 异常", e);
reject(e);
}
});
}
具体可以问问 AI