// // PaymentModels.swift // suixinkan // // Created by Codex on 2026/6/22. // import Foundation /// 收款码响应实体,表示静态收款码 URL 和动态收款码基础 URL。 struct PayCodeResponse: Decodable, Equatable { let staticPayUrl: String let dynamicPayUrl: String /// 收款码 JSON 字段映射。 enum CodingKeys: String, CodingKey { case staticPayUrl = "static_pay_url" case dynamicPayUrl = "dynamic_pay_url" } /// 创建收款码响应实体,主要用于测试替身。 init(staticPayUrl: String = "", dynamicPayUrl: String = "") { self.staticPayUrl = staticPayUrl self.dynamicPayUrl = dynamicPayUrl } /// 自定义解码,兼容后端把 URL 字段返回为数字或空值。 init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) staticPayUrl = try container.decodeLossyString(forKey: .staticPayUrl) dynamicPayUrl = try container.decodeLossyString(forKey: .dynamicPayUrl) } } /// 收款记录响应实体,包含后端日汇总和原始记录列表。 struct PaymentCollectionRecordResponse: Decodable, Equatable { let analyse: [PaymentCollectionRecordAnalyseItem] let list: [PaymentCollectionRecordItem] /// 收款记录响应 JSON 字段映射。 enum CodingKeys: String, CodingKey { case analyse case list } /// 创建收款记录响应实体,主要用于测试替身。 init(analyse: [PaymentCollectionRecordAnalyseItem] = [], list: [PaymentCollectionRecordItem] = []) { self.analyse = analyse self.list = list } /// 自定义解码,兼容后端字段缺失。 init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) analyse = try container.decodeIfPresent([PaymentCollectionRecordAnalyseItem].self, forKey: .analyse) ?? [] list = try container.decodeIfPresent([PaymentCollectionRecordItem].self, forKey: .list) ?? [] } } /// 收款日汇总实体,表示某一天的收款笔数和金额。 struct PaymentCollectionRecordAnalyseItem: Decodable, Equatable, Identifiable { let date: String let orderCount: Int let orderAmountSum: String var id: String { date } /// 收款日汇总 JSON 字段映射。 enum CodingKeys: String, CodingKey { case date case orderCount = "order_count" case orderAmountSum = "order_amount_sum" } /// 创建收款日汇总实体,主要用于列表兜底和测试。 init(date: String, orderCount: Int, orderAmountSum: String) { self.date = date self.orderCount = orderCount self.orderAmountSum = orderAmountSum } /// 自定义解码,兼容金额和数量字段类型不稳定。 init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) date = try container.decodeLossyString(forKey: .date) orderCount = try container.decodeLossyInt(forKey: .orderCount) ?? 0 orderAmountSum = try container.decodeLossyString(forKey: .orderAmountSum) } } /// 收款记录实体,表示一次扫码收款订单。 struct PaymentCollectionRecordItem: Decodable, Equatable, Identifiable { let orderNumber: String let userPhone: String let orderAmount: String let createDate: String let createTime: String var id: String { orderNumber.isEmpty ? "\(createDate)-\(createTime)-\(orderAmount)" : orderNumber } /// 收款记录 JSON 字段映射。 enum CodingKeys: String, CodingKey { case orderNumber = "order_number" case userPhone = "user_phone" case orderAmount = "order_amount" case createDate = "create_date" case createTime = "create_time" } /// 创建收款记录实体,主要用于测试替身。 init(orderNumber: String, userPhone: String, orderAmount: String, createDate: String, createTime: String) { self.orderNumber = orderNumber self.userPhone = userPhone self.orderAmount = orderAmount self.createDate = createDate self.createTime = createTime } /// 自定义解码,兼容订单号、手机号和金额字段类型不稳定。 init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) orderNumber = try container.decodeLossyString(forKey: .orderNumber) userPhone = try container.decodeLossyString(forKey: .userPhone) orderAmount = try container.decodeLossyString(forKey: .orderAmount) createDate = try container.decodeLossyString(forKey: .createDate) createTime = try container.decodeLossyString(forKey: .createTime) } } /// 收款记录分组实体,表示一个日期下的汇总和明细。 struct PaymentCollectionRecordGroup: Equatable, Identifiable { let analyse: PaymentCollectionRecordAnalyseItem let items: [PaymentCollectionRecordItem] var id: String { analyse.id } } /// 收款状态实体,表示当前动态收款轮询结果。 enum PaymentCollectionStatus: Equatable { case idle case waiting case success(PaymentCollectionRecordItem) case failed(String) } private extension KeyedDecodingContainer { /// 将字符串、数字或空值宽松解码为字符串。 func decodeLossyString(forKey key: Key) throws -> String { if let value = try decodeIfPresent(String.self, forKey: key) { return value } if let value = try decodeIfPresent(Int.self, forKey: key) { return String(value) } if let value = try decodeIfPresent(Double.self, forKey: key) { return value.truncatingRemainder(dividingBy: 1) == 0 ? String(Int(value)) : String(value) } return "" } /// 将字符串或数字宽松解码为整数。 func decodeLossyInt(forKey key: Key) throws -> Int? { if let value = try decodeIfPresent(Int.self, forKey: key) { return value } if let value = try decodeIfPresent(String.self, forKey: key) { let text = value.trimmingCharacters(in: .whitespacesAndNewlines) if let intValue = Int(text) { return intValue } if let doubleValue = Double(text) { return Int(doubleValue) } } if let value = try decodeIfPresent(Double.self, forKey: key) { return Int(value) } return nil } }