Compare commits

...

2 Commits

Author SHA1 Message Date
jinyaqiu
08db3dc287 Merge branch 'V2.0.0' of https://git.fairclip.cn/FairClip/wake-ios into V2.0.0 2025-09-02 13:53:44 +08:00
jinyaqiu
f8b0bc8617 feat: 接口 2025-09-02 13:53:43 +08:00
4 changed files with 405 additions and 12 deletions

View File

@ -328,6 +328,7 @@
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -362,6 +363,7 @@
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",

101
wake/Models/OrderInfo.swift Normal file
View File

@ -0,0 +1,101 @@
import Foundation
///
struct OrderInfo: Codable, Identifiable {
let id: String
let userId: String
let totalAmount: Amount
let status: String
let items: [OrderItem]
let paymentInfo: PaymentInfo?
let createdAt: String
let updatedAt: String
let expiredAt: String
enum CodingKeys: String, CodingKey {
case id
case userId = "user_id"
case totalAmount = "total_amount"
case status
case items
case paymentInfo = "payment_info"
case createdAt = "created_at"
case updatedAt = "updated_at"
case expiredAt = "expired_at"
}
}
///
struct PaymentInfo: Codable {
let id: String
let paymentMethod: String
let paymentStatus: String
let paymentAmount: Amount
let transactionId: String?
let thirdPartyTransactionId: String?
let paidAt: String?
let createdAt: String
let updatedAt: String
enum CodingKeys: String, CodingKey {
case id
case paymentMethod = "payment_method"
case paymentStatus = "payment_status"
case paymentAmount = "payment_amount"
case transactionId = "transaction_id"
case thirdPartyTransactionId = "third_party_transaction_id"
case paidAt = "paid_at"
case createdAt = "created_at"
case updatedAt = "updated_at"
}
}
///
struct Amount: Codable {
let amount: String
let currency: String
}
///
struct OrderItem: Codable, Identifiable {
let id: String
let productId: Int
let productType: String
let productCode: String
let productName: String
let unitPrice: Amount
let discountAmount: Amount
let quantity: Int
let totalPrice: Amount
enum CodingKeys: String, CodingKey {
case id
case productId = "product_id"
case productType = "product_type"
case productCode = "product_code"
case productName = "product_name"
case unitPrice = "unit_price"
case discountAmount = "discount_amount"
case quantity
case totalPrice = "total_price"
}
}
///
enum OrderStatus: Int, Codable {
case pending = 0 //
case paid = 1 //
case completed = 2 //
case cancelled = 3 //
case refunded = 4 // 退
var description: String {
switch self {
case .pending: return "待支付"
case .paid: return "已支付"
case .completed: return "已完成"
case .cancelled: return "已取消"
case .refunded: return "已退款"
}
}
}

View File

@ -34,13 +34,12 @@ final class IAPManager: ObservableObject {
}
// Trigger App Store purchase sheet
func purchasePioneer() async {
guard !isPurchasing else { return }
func purchasePioneer() async throws -> String {
guard !isPurchasing else { throw NSError(domain: "IAPError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Purchase already in progress"]) }
guard let product = pioneerProduct else {
// Surface an actionable error so the UI can inform the user
self.errorMessage = "Subscription product unavailable. Please try again later."
return
throw NSError(domain: "IAPError", code: -2, userInfo: [NSLocalizedDescriptionKey: "Subscription product unavailable"])
}
isPurchasing = true
defer { isPurchasing = false }
@ -50,21 +49,26 @@ final class IAPManager: ObservableObject {
case .success(let verification):
switch verification {
case .unverified(_, let error):
self.errorMessage = "Purchase unverified: \(error.localizedDescription)"
case .verified(let transaction):
// Update entitlement for the purchased product
throw error
case .verified(let transaction):
print("🎉 订阅成功!", transaction)
print("🔄 交易验证通过 - ID: \(transaction.id), 原始ID: \(transaction.originalID), 产品ID: \(transaction.productID)")
updateEntitlement(from: transaction)
let transactionID = String(transaction.id)
print("📝 使用交易ID: \(transactionID)")
await transaction.finish()
return transactionID
}
case .userCancelled:
break
throw NSError(domain: "IAPError", code: -3, userInfo: [NSLocalizedDescriptionKey: "Purchase was cancelled"])
case .pending:
break
throw NSError(domain: "IAPError", code: -4, userInfo: [NSLocalizedDescriptionKey: "Purchase is pending approval"])
@unknown default:
break
throw NSError(domain: "IAPError", code: -5, userInfo: [NSLocalizedDescriptionKey: "Unknown purchase result"])
}
} catch {
self.errorMessage = "Purchase failed: \(error.localizedDescription)"
throw error
}
}

View File

@ -7,6 +7,7 @@
import SwiftUI
import StoreKit
import Network
// MARK: -
enum SubscriptionPlan: String, CaseIterable {
@ -46,6 +47,7 @@ struct SubscribeView: View {
@State private var showErrorAlert = false
@State private var errorText = ""
@State private var memberProfile: MemberProfile?
@State private var showSuccessAlert = false
//
private let features = [
@ -122,6 +124,11 @@ struct SubscribeView: View {
} message: {
Text(errorText)
}
.alert("Purchase Success", isPresented: $showSuccessAlert) {
Button("OK", role: .cancel) { }
} message: {
Text("购买成功!")
}
}
// MARK: -
@ -280,7 +287,286 @@ struct SubscribeView: View {
// MARK: -
private func handleSubscribe() {
Task { await store.purchasePioneer() }
isLoading = true
Task {
do {
print("🔄 开始订阅流程...")
// 1.
print("🔄 正在创建订单...")
let orderInfo = try await createOrder()
// 2. id
print("🔄 正在创建支付...")
let paymentInfo = try await createPayment(orderId: orderInfo.id)
// 3. 使
print("🔄 开始苹果内购流程...")
do {
//
let transactionId = try await store.purchasePioneer()
print("✅ 苹果内购成功交易ID: \(transactionId)")
// 4.
print("🔄 正在通知服务器支付处理中...")
_ = try await notifyPaymentProcessing(
transactionId: paymentInfo.transactionId ?? paymentInfo.id,
// thirdPartyTransactionId: transactionId
)
print("🔄 正在通知服务器支付成功...")
_ = try await notifyPaymentSuccess(
transactionId: paymentInfo.transactionId ?? paymentInfo.id,
// thirdPartyTransactionId: transactionId
)
print("✅ 订阅流程完成")
// 5.
await MainActor.run {
self.isLoading = false
self.dismiss()
}
} catch let purchaseError as NSError {
print("❌ 苹果内购失败: \(purchaseError.localizedDescription)")
//
print("🔄 正在通知服务器支付失败...")
_ = try? await notifyPaymentFailure(
transactionId: paymentInfo.transactionId ?? paymentInfo.id,
reason: purchaseError.localizedDescription
)
// 便
throw purchaseError
}
} catch let error as NSError {
print("❌ 订阅失败: \(error.localizedDescription)")
//
var errorMessage = error.localizedDescription
if error.domain == "NetworkError" {
errorMessage = "网络连接失败,请检查您的网络设置"
} else if error.domain == "APIError" {
errorMessage = "请求失败,请稍后重试 (错误码: \(error.code))"
} else if error.domain == NSURLErrorDomain {
switch error.code {
case NSURLErrorNotConnectedToInternet, NSURLErrorNetworkConnectionLost:
errorMessage = "网络连接已断开,请检查您的网络设置"
case NSURLErrorTimedOut:
errorMessage = "请求超时,请稍后重试"
case NSURLErrorCannotConnectToHost, NSURLErrorCannotFindHost:
errorMessage = "无法连接到服务器,请稍后重试"
default:
errorMessage = "网络错误: \(error.localizedDescription)"
}
}
// 线UI
await MainActor.run {
self.isLoading = false
self.errorText = errorMessage
self.showErrorAlert = true
print("❌ 错误提示: \(errorMessage)")
}
}
}
}
//
private func createOrder() async throws -> OrderInfo {
return try await withCheckedThrowingContinuation { continuation in
let parameters: [String: Any] = [
"items": [
[
"product_item_id": 5,
"quantity": 1
]
]
]
print("🔄 开始创建订单请求,参数:\(parameters)")
//
let monitor = NWPathMonitor()
let queue = DispatchQueue(label: "NetworkMonitor")
monitor.pathUpdateHandler = { path in
if path.status == .satisfied {
//
NetworkService.shared.postWithToken(
path: "/order/create",
parameters: parameters
) { (result: Result<APIResponse<OrderInfo>, NetworkError>) in
switch result {
case .success(let response):
print("✅ 请求成功,状态码:\(response.code)")
print("📦 返回数据:\(String(describing: response.data))")
if response.code == 0 {
continuation.resume(returning: response.data)
} else {
let errorMessage = "创建订单失败,状态码:\(response.code)"
print("\(errorMessage)")
continuation.resume(throwing: NSError(
domain: "APIError",
code: response.code,
userInfo: [NSLocalizedDescriptionKey: errorMessage]
))
}
case .failure(let error):
print("❌ 请求异常:\(error.localizedDescription)")
print("🔍 错误详情:\(error)")
if let urlError = error as? URLError {
print("🌐 URL错误: \(urlError.code.rawValue) - \(urlError.localizedDescription)")
print("🔗 失败URL: \(urlError.failingURL?.absoluteString ?? "未知")")
}
continuation.resume(throwing: error)
}
}
} else {
//
let errorMessage = "网络连接不可用,请检查网络设置"
print("\(errorMessage)")
continuation.resume(throwing: NSError(domain: "NetworkError", code: 0, userInfo: [NSLocalizedDescriptionKey: errorMessage]))
}
}
monitor.start(queue: queue)
}
}
//
private func createPayment(orderId: String) async throws -> PaymentInfo {
return try await withCheckedThrowingContinuation { continuation in
let parameters: [String: Any] = [
"order_id": orderId,
"payment_method": "ApplePay"
]
print("🔄 开始创建支付请求,参数:\(parameters)")
NetworkService.shared.postWithToken(
path: "/order/pay",
parameters: parameters
) { (result: Result<APIResponse<PaymentInfo>, NetworkError>) in
switch result {
case .success(let response):
print("✅ 请求成功,状态码:\(response.code)")
print("📦 返回数据:\(String(describing: response.data))")
if response.code == 0 {
continuation.resume(returning: response.data)
} else {
let errorMessage = "创建支付失败,状态码:\(response.code)"
print("\(errorMessage)")
continuation.resume(throwing: NSError(
domain: "APIError",
code: response.code,
userInfo: [NSLocalizedDescriptionKey: errorMessage]
))
}
case .failure(let error):
print("❌ 请求异常:\(error.localizedDescription)")
print("🔍 错误详情:\(error)")
if let urlError = error as? URLError {
print("🌐 URL错误: \(urlError.code.rawValue) - \(urlError.localizedDescription)")
print("🔗 失败URL: \(urlError.failingURL?.absoluteString ?? "未知")")
}
continuation.resume(throwing: error)
}
}
}
}
// MARK: -
///
/// - Parameter transactionId: ID
/// - Parameter thirdPartyTransactionId: ID
private func notifyPaymentProcessing(transactionId: String, thirdPartyTransactionId: String? = nil) async throws -> Bool {
return try await withCheckedThrowingContinuation { continuation in
var parameters: [String: Any] = ["transaction_id": transactionId]
// ID
if let thirdPartyId = thirdPartyTransactionId {
parameters["third_party_transaction_id"] = thirdPartyId
}
print("🔄 通知服务器支付处理中,参数:\(parameters)")
NetworkService.shared.postWithToken(
path: "/order/pay-processing",
parameters: parameters
) { (result: Result<APIResponse<[String: String]>, NetworkError>) in
switch result {
case .success(let response):
print("✅ 支付处理通知发送成功,状态码:\(response.code)")
continuation.resume(returning: response.code == 0)
case .failure(let error):
print("❌ 支付处理通知发送失败:\(error.localizedDescription)")
continuation.resume(throwing: error)
}
}
}
}
///
/// - Parameter transactionId: ID
/// - Parameter thirdPartyTransactionId: ID
private func notifyPaymentSuccess(transactionId: String, thirdPartyTransactionId: String? = nil) async throws -> Bool {
return try await withCheckedThrowingContinuation { continuation in
var parameters: [String: Any] = ["transaction_id": transactionId]
// ID
if let thirdPartyId = thirdPartyTransactionId {
parameters["third_party_transaction_id"] = thirdPartyId
}
print("🔄 通知服务器支付成功,参数:\(parameters)")
NetworkService.shared.postWithToken(
path: "/order/pay-success",
parameters: parameters
) { (result: Result<APIResponse<[String: String]>, NetworkError>) in
switch result {
case .success(let response):
print("✅ 支付成功通知发送成功,状态码:\(response.code)")
continuation.resume(returning: response.code == 0)
case .failure(let error):
print("❌ 支付成功通知发送失败:\(error.localizedDescription)")
continuation.resume(throwing: error)
}
}
}
}
///
/// - Parameter transactionId: ID
/// - Parameter reason:
private func notifyPaymentFailure(transactionId: String, reason: String) async throws -> Bool {
return try await withCheckedThrowingContinuation { continuation in
let parameters: [String: Any] = [
"transaction_id": transactionId,
"reason": reason
]
print("🔄 通知服务器支付失败,参数:\(parameters)")
NetworkService.shared.postWithToken(
path: "/order/pay-failure",
parameters: parameters
) { (result: Result<APIResponse<[String: String]>, NetworkError>) in
switch result {
case .success(let response):
print("✅ 支付失败通知发送成功,状态码:\(response.code)")
continuation.resume(returning: response.code == 0)
case .failure(let error):
print("❌ 支付失败通知发送失败:\(error.localizedDescription)")
continuation.resume(throwing: error)
}
}
}
}
// MARK: - Helper Methods