Implement收款详情与收款记录 pages with pay-code API, QR generation, amount setting, and record grouping so merchants can collect payments from the home quick action. Co-authored-by: Cursor <cursoragent@cursor.com>
176 lines
4.8 KiB
Swift
176 lines
4.8 KiB
Swift
//
|
||
// PaymentCollectionDetailsViewModel.swift
|
||
// suixinkan
|
||
//
|
||
|
||
import Foundation
|
||
import UIKit
|
||
|
||
/// 收款详情 ViewModel,管理收款码拉取、设金额、保存二维码与语音开关。
|
||
final class PaymentCollectionDetailsViewModel {
|
||
|
||
private(set) var loading = false
|
||
private(set) var qrImage: UIImage?
|
||
private(set) var scenicName = ""
|
||
private(set) var staffId = ""
|
||
private(set) var isVoiceBroadcastOpen = false
|
||
private(set) var showAmountDialog = false
|
||
private(set) var amount = ""
|
||
private(set) var remark = ""
|
||
|
||
let merchantInfo = PaymentMerchantInfo.defaultInfo
|
||
|
||
var onStateChange: (() -> Void)?
|
||
var onShowMessage: ((String) -> Void)?
|
||
|
||
private var staticPayURL = ""
|
||
private var dynamicPayURL = ""
|
||
private let appStore: AppStore
|
||
|
||
init(appStore: AppStore = .shared) {
|
||
self.appStore = appStore
|
||
}
|
||
|
||
var hasPayCode: Bool {
|
||
qrImage != nil && !staticPayURL.isEmpty
|
||
}
|
||
|
||
var displayScenicName: String {
|
||
scenicName.isEmpty ? "暂无景区" : scenicName
|
||
}
|
||
|
||
func refreshLocalState() {
|
||
scenicName = appStore.currentScenicName
|
||
staffId = appStore.userId
|
||
isVoiceBroadcastOpen = appStore.isOpenReceiveVoice
|
||
notifyStateChange()
|
||
}
|
||
|
||
func loadPayCode(api: PaymentAPI) async {
|
||
guard !loading else { return }
|
||
refreshLocalState()
|
||
loading = true
|
||
notifyStateChange()
|
||
defer {
|
||
loading = false
|
||
notifyStateChange()
|
||
}
|
||
|
||
let scenicId = appStore.currentScenicId
|
||
guard scenicId > 0 else {
|
||
clearPayCode()
|
||
return
|
||
}
|
||
|
||
do {
|
||
let response = try await api.payCode(scenicId: scenicId)
|
||
staticPayURL = response.staticPayURL
|
||
dynamicPayURL = response.dynamicPayURL
|
||
qrImage = QRCodeGenerator.generateStyledQRCode(content: staticPayURL)
|
||
} catch {
|
||
clearPayCode()
|
||
}
|
||
}
|
||
|
||
func refresh(api: PaymentAPI) async {
|
||
await loadPayCode(api: api)
|
||
}
|
||
|
||
func showSetAmountDialog() {
|
||
guard !staticPayURL.isEmpty else {
|
||
onShowMessage?("请先获取收款码")
|
||
return
|
||
}
|
||
amount = ""
|
||
remark = ""
|
||
showAmountDialog = true
|
||
notifyStateChange()
|
||
}
|
||
|
||
func hideAmountDialog() {
|
||
showAmountDialog = false
|
||
amount = ""
|
||
remark = ""
|
||
notifyStateChange()
|
||
}
|
||
|
||
func updateAmount(_ value: String) {
|
||
guard value.isEmpty || value.matches(PaymentAmountValidator.inputPattern) else { return }
|
||
amount = value
|
||
notifyStateChange()
|
||
}
|
||
|
||
func updateRemark(_ value: String) {
|
||
guard value.count <= 200 else { return }
|
||
remark = value
|
||
notifyStateChange()
|
||
}
|
||
|
||
func confirmAmount() {
|
||
if let message = PaymentAmountValidator.validationMessage(for: amount) {
|
||
onShowMessage?(message)
|
||
return
|
||
}
|
||
guard !dynamicPayURL.isEmpty else {
|
||
onShowMessage?("请先获取收款码")
|
||
return
|
||
}
|
||
|
||
let dynamicURL = dynamicPayURL + "&amount=\(amount)&remark=\(remark)"
|
||
qrImage = QRCodeGenerator.generateStyledQRCode(content: dynamicURL)
|
||
showAmountDialog = false
|
||
onShowMessage?("金额设置成功,二维码已更新")
|
||
notifyStateChange()
|
||
}
|
||
|
||
func saveQRCode() async {
|
||
guard qrImage != nil else {
|
||
onShowMessage?("请先获取收款码")
|
||
return
|
||
}
|
||
let success = await PhotoLibrarySaver.saveImage(qrImage!)
|
||
onShowMessage?(success ? "保存成功" : "保存失败")
|
||
}
|
||
|
||
func toggleReceiveVoice() {
|
||
isVoiceBroadcastOpen.toggle()
|
||
appStore.isOpenReceiveVoice = isVoiceBroadcastOpen
|
||
notifyStateChange()
|
||
}
|
||
|
||
private func clearPayCode() {
|
||
staticPayURL = ""
|
||
dynamicPayURL = ""
|
||
qrImage = nil
|
||
}
|
||
|
||
private func notifyStateChange() {
|
||
onStateChange?()
|
||
}
|
||
}
|
||
|
||
/// 收款金额校验,对齐 Android `PaymentCollectionDetailsViewModel.confirmAmount`。
|
||
enum PaymentAmountValidator {
|
||
static let inputPattern = #"^\d*(\.\d{0,2})?$"#
|
||
private static let confirmPattern = #"^\d+(\.\d{1,2})?$"#
|
||
|
||
static func validationMessage(for amount: String) -> String? {
|
||
if amount.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||
return "请输入收款金额"
|
||
}
|
||
guard let value = Double(amount), value > 0 else {
|
||
return "请输入有效的收款金额"
|
||
}
|
||
if !amount.matches(confirmPattern) {
|
||
return "金额最多支持两位小数"
|
||
}
|
||
return nil
|
||
}
|
||
}
|
||
|
||
private extension String {
|
||
func matches(_ pattern: String) -> Bool {
|
||
range(of: pattern, options: .regularExpression) != nil
|
||
}
|
||
}
|