新增支付与钱包模块,并接入首页路由
引入真实收款与钱包页面替换首页占位入口,通过 RootView 接入 API,并支持银行卡 OSS 上传及二维码保存到相册。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
182
suixinkan/Features/Payment/Models/PaymentModels.swift
Normal file
182
suixinkan/Features/Payment/Models/PaymentModels.swift
Normal file
@ -0,0 +1,182 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user