|
1 | 1 | import Foundation |
2 | 2 |
|
3 | 3 | /// Request product type for filtering when fetching products |
4 | | -/// Maps to literal strings: "inapp", "subs", "all" |
| 4 | +/// Maps to literal strings: "in-app" (preferred), legacy "inapp" (deprecated, removal scheduled for 1.2.0), "subs", "all" |
5 | 5 | public enum OpenIapRequestProductType: String, Codable, Sendable { |
| 6 | + internal static let legacyInAppRawValue = "inapp" |
| 7 | + internal static let modernInAppRawValue = "in-app" |
| 8 | + |
| 9 | + @available(*, deprecated, message: "'inapp' is deprecated and will be removed in 1.2.0. Use .inApp instead.") |
6 | 10 | case inapp = "inapp" |
| 11 | + case inApp = "in-app" |
7 | 12 | case subs = "subs" |
8 | 13 | case all = "all" |
| 14 | + |
| 15 | + internal var normalizedRawValue: String { |
| 16 | + if rawValue == Self.legacyInAppRawValue { |
| 17 | + return Self.modernInAppRawValue |
| 18 | + } |
| 19 | + return rawValue |
| 20 | + } |
9 | 21 | } |
10 | 22 |
|
11 | 23 | /// Product request parameters following OpenIAP specification |
12 | 24 | public struct OpenIapProductRequest: Codable, Equatable, Sendable { |
13 | 25 | /// Product SKUs to fetch |
14 | 26 | public let skus: [String] |
15 | 27 |
|
16 | | - /// Product type filter: "inapp" (default), "subs", or "all" |
| 28 | + /// Product type filter: "in-app" (default), "subs", or "all". The legacy value "inapp" is still accepted but will be removed in 1.2.0. |
17 | 29 | public let type: String |
18 | | - |
19 | | - public init(skus: [String], type: String = "inapp") { |
| 30 | + |
| 31 | + private enum CodingKeys: String, CodingKey { |
| 32 | + case skus |
| 33 | + case type |
| 34 | + } |
| 35 | + |
| 36 | + /// Create request specifying raw type string. Passing `nil` or an empty string defaults to "in-app". |
| 37 | + public init(skus: [String], type: String? = nil) { |
20 | 38 | self.skus = skus |
21 | | - self.type = type |
| 39 | + self.type = OpenIapProductRequest.normalizeType(type) |
22 | 40 | } |
23 | 41 |
|
24 | 42 | /// Convenience initializer with RequestProductType enum |
25 | | - public init(skus: [String], type: OpenIapRequestProductType = .inapp) { |
| 43 | + public init(skus: [String], type: OpenIapRequestProductType = .inApp) { |
26 | 44 | self.skus = skus |
27 | | - self.type = type.rawValue |
| 45 | + self.type = type.normalizedRawValue |
| 46 | + } |
| 47 | + |
| 48 | + public init(from decoder: Decoder) throws { |
| 49 | + let container = try decoder.container(keyedBy: CodingKeys.self) |
| 50 | + let skus = try container.decode([String].self, forKey: .skus) |
| 51 | + let rawType = try container.decodeIfPresent(String.self, forKey: .type) |
| 52 | + self.init(skus: skus, type: rawType ?? OpenIapProductRequest.defaultTypeValue) |
| 53 | + } |
| 54 | + |
| 55 | + public func encode(to encoder: Encoder) throws { |
| 56 | + var container = encoder.container(keyedBy: CodingKeys.self) |
| 57 | + try container.encode(skus, forKey: .skus) |
| 58 | + try container.encode(type, forKey: .type) |
28 | 59 | } |
29 | 60 |
|
30 | 61 | /// Get the type as RequestProductType enum |
31 | 62 | public var requestType: OpenIapRequestProductType { |
32 | | - return OpenIapRequestProductType(rawValue: type) ?? .inapp |
| 63 | + if let parsedType = OpenIapRequestProductType(rawValue: type) { |
| 64 | + if parsedType.rawValue == OpenIapRequestProductType.legacyInAppRawValue { |
| 65 | + return .inApp |
| 66 | + } |
| 67 | + return parsedType |
| 68 | + } |
| 69 | + |
| 70 | + let normalized = OpenIapProductRequest.normalizeType(type) |
| 71 | + return OpenIapRequestProductType(rawValue: normalized) ?? .inApp |
| 72 | + } |
| 73 | + |
| 74 | + private static let defaultTypeValue = OpenIapRequestProductType.modernInAppRawValue |
| 75 | + |
| 76 | + private static func normalizeType(_ rawType: String?) -> String { |
| 77 | + guard let rawType else { |
| 78 | + return defaultTypeValue |
| 79 | + } |
| 80 | + |
| 81 | + let trimmed = rawType.trimmingCharacters(in: .whitespacesAndNewlines) |
| 82 | + guard !trimmed.isEmpty else { |
| 83 | + return defaultTypeValue |
| 84 | + } |
| 85 | + |
| 86 | + if let productType = OpenIapRequestProductType(rawValue: trimmed) { |
| 87 | + return productType.normalizedRawValue |
| 88 | + } |
| 89 | + |
| 90 | + let lowered = trimmed.lowercased() |
| 91 | + switch lowered { |
| 92 | + case OpenIapRequestProductType.legacyInAppRawValue, OpenIapRequestProductType.modernInAppRawValue: |
| 93 | + return OpenIapRequestProductType.modernInAppRawValue |
| 94 | + case OpenIapRequestProductType.subs.rawValue: |
| 95 | + return OpenIapRequestProductType.subs.rawValue |
| 96 | + case OpenIapRequestProductType.all.rawValue: |
| 97 | + return OpenIapRequestProductType.all.rawValue |
| 98 | + default: |
| 99 | + return defaultTypeValue |
| 100 | + } |
33 | 101 | } |
34 | 102 | } |
35 | 103 |
|
36 | 104 | // Backward compatibility aliases |
37 | 105 | public typealias RequestProductType = OpenIapRequestProductType |
38 | 106 | public typealias ProductRequest = OpenIapProductRequest |
39 | | - |
|
0 commit comments