// // PushPayload.swift // suixinkan // // Created by Codex on 2026/6/26. // import Foundation /// APNs token 工具,把系统返回的 Data 转成后端可接收的小写十六进制字符串。 enum APNsDeviceToken { static func hexString(from data: Data) -> String { data.map { String(format: "%02x", $0) }.joined() } } /// 推送 payload 解析器,兼容顶层字段和第三方通道常见 extras/data 嵌套结构。 struct PushPayload: Sendable { enum Route: Sendable, Equatable { case payment case order case verificationOrder case task case queue case messageCenter } private let values: [String: String] nonisolated init(userInfo: [AnyHashable: Any]) { var result: [String: String] = [:] for (key, value) in userInfo { guard let key = key as? String else { continue } result[key] = Self.stringValue(value) if key == "extras" || key == "extra" || key == "data" || key == "JMessageExtra" { Self.mergeJSON(value, into: &result) } } values = result } nonisolated var route: Route { let typeText = values["type"] ?? "" let routeText = values["route"] ?? "" let uriText = values["uri"] ?? "" let actionText = values["action"] ?? "" let merged = [typeText, routeText, uriText, actionText].joined(separator: " ").lowercased() if typeText == "1" || typeText == "6" || merged.contains("payment") || merged.contains("pay") || merged.contains("收款") { return .payment } if merged.contains("queue") || merged.contains("排队") || merged.contains("叫号") || uriText == "/scenic-queue" { return .queue } if merged.contains("verification") || merged.contains("writeoff") || merged.contains("核销") { return .verificationOrder } if merged.contains("order") || merged.contains("订单") { return .order } if merged.contains("task") || merged.contains("任务") { return .task } return .messageCenter } /// 推送 extras 中的 type 字段,供收款页区分扫码/付款事件。 nonisolated var paymentEventType: Int? { guard let typeText = values["type"], !typeText.isEmpty else { return nil } return Int(typeText) } /// 推送 extras 中的 data 字段,付款成功时通常为 JSON 字符串。 nonisolated var paymentEventData: String? { let data = values["data"]?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" return data.isEmpty ? nil : data } /// 从付款成功 data JSON 中解析到账金额,供全局语音播报使用。 nonisolated var paymentSuccessAmount: String? { guard let dataJSON = paymentEventData, let data = dataJSON.data(using: .utf8), let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return nil } if let amount = json["order_amount"] as? String, !amount.isEmpty { return amount } if let amount = json["order_amount"] as? NSNumber { return amount.stringValue } return nil } nonisolated private static func mergeJSON(_ value: Any, into result: inout [String: String]) { if let dict = value as? [String: Any] { for (key, value) in dict { result[key] = stringValue(value) mergeJSON(value, into: &result) } return } guard let text = value as? String, let data = text.data(using: .utf8), let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return } for (key, value) in object { result[key] = stringValue(value) mergeJSON(value, into: &result) } } nonisolated private static func stringValue(_ value: Any?) -> String { switch value { case let string as String: return string case let number as NSNumber: return number.stringValue case .some(let value): return "\(value)" case nil: return "" } } }