统一收款详情布局、设置金额弹窗和收款记录样式,接入 type 6/1 推送驱动与轮询 fallback,使 iOS 收款反馈与 Android 一致。 Co-authored-by: Cursor <cursoragent@cursor.com>
196 lines
6.8 KiB
Swift
196 lines
6.8 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 }
|
||
}
|
||
|
||
/// 付款成功展示数据,对齐 Android PaymentSuccessData。
|
||
struct PaymentSuccessDisplayData: Equatable {
|
||
let orderAmount: String
|
||
let orderNumber: String
|
||
let payTime: String
|
||
let remark: String
|
||
}
|
||
|
||
/// 收款模块固定商户信息,对齐 Android MerchantInfo。
|
||
enum PaymentMerchantInfo {
|
||
static let payeeCompany = "扬州元智享网络科技有限公司"
|
||
}
|
||
|
||
/// 收款状态实体,表示当前动态收款轮询结果。
|
||
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
|
||
}
|
||
}
|