import Foundation import UIKit import DypaySDK public class TbDouyinPayNative { private static var pending: ((String) -> Void)? private static var generation = UUID() public static func initialize(_ appId: String, _ scheme: String, _ link: String) { if link.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { // Use the SDK's Scheme-only API instead of registering an empty // Universal Link with the higher-priority Universal Link API. DypayAPI.register(withAppID: appId, callbackScheme: scheme) } else { DypayAPI.register(withAppID: appId, universalLink: link, callbackScheme: scheme) } } public static func available() -> Bool { DypayAPI.canOpenDypay() } public static func resetPending() { DispatchQueue.main.async { generation = UUID() pending = nil } } public static func pay(_ payload: String, _ done: @escaping (String) -> Void) { DispatchQueue.main.async { guard pending == nil else { done("{\"resultCode\":\"103\",\"errorMsg\":\"已有支付正在处理,请先查单\"}") return } guard DypayAPI.canOpenDypay() else { done("{\"resultCode\":\"100\",\"errorMsg\":\"请安装或升级抖音客户端\"}") return } guard let bytes = payload.data(using: .utf8), let info = try? JSONSerialization.jsonObject(with: bytes) as? [String: String], let controller = foregroundController() else { done("{\"resultCode\":\"2\",\"errorMsg\":\"支付参数或当前页面不可用\"}") return } generation = UUID() let ticket = generation pending = done DypayAPI.openDypay(withInfo: info, from: controller) { result in finish(result as? [AnyHashable: Any], ticket) } // Only authenticated server reconciliation can release a missing callback's lock. // URL callbacks are hints, never payment success; the host always queries its saved order. } } public static func processURL(_ url: URL) -> Bool { let ticket = generation return DypayAPI.processDypayResult(with: url) { result in finish(result as? [AnyHashable: Any], ticket) } } public static func processActivity(_ activity: NSUserActivity) -> Bool { let ticket = generation return DypayAPI.processDypayResult(with: activity) { result in finish(result as? [AnyHashable: Any], ticket) } } private static func finish(_ result: [AnyHashable: Any]?, _ ticket: UUID) { DispatchQueue.main.async { guard ticket == generation, let callback = pending else { return } pending = nil let code = result?["resultCode"].map { String(describing: $0) } ?? "3" let message = result?["errorMsg"] as? String ?? "" let normalized = ["resultCode": code, "errorMsg": message] guard let data = try? JSONSerialization.data(withJSONObject: normalized), let json = String(data: data, encoding: .utf8) else { callback("{\"resultCode\":\"3\",\"errorMsg\":\"请查询订单状态\"}") return } callback(json) } } private static func foregroundController() -> UIViewController? { var windows: [UIWindow] = [] if #available(iOS 13.0, *) { windows = UIApplication.shared.connectedScenes .compactMap { $0 as? UIWindowScene } .filter { $0.activationState == .foregroundActive } .flatMap { $0.windows } } else { windows = UIApplication.shared.windows } var controller = windows.first(where: { $0.isKeyWindow })?.rootViewController while let current = controller { if let presented = current.presentedViewController { controller = presented } else if let nav = current as? UINavigationController { controller = nav.visibleViewController } else if let tabs = current as? UITabBarController { controller = tabs.selectedViewController } else { break } } return controller } }