Compare commits
2 Commits
3d525992ed
...
08db3dc287
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08db3dc287 | ||
|
|
f8b0bc8617 |
@ -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
101
wake/Models/OrderInfo.swift
Normal 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 "已退款"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user