引入真实收款与钱包页面替换首页占位入口,通过 RootView 接入 API,并支持银行卡 OSS 上传及二维码保存到相册。 Co-authored-by: Cursor <cursoragent@cursor.com>
183 lines
6.4 KiB
Swift
183 lines
6.4 KiB
Swift
//
|
||
// 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
|
||
}
|
||
}
|