Showing
5 changed files
with
164 additions
and
39 deletions
| ... | @@ -14,17 +14,8 @@ | ... | @@ -14,17 +14,8 @@ |
| 14 | "kind" : "remoteSourceControl", | 14 | "kind" : "remoteSourceControl", |
| 15 | "location" : "https://github.com/stephencelis/SQLite.swift", | 15 | "location" : "https://github.com/stephencelis/SQLite.swift", |
| 16 | "state" : { | 16 | "state" : { |
| 17 | - "revision" : "392dd6058624d9f6c5b4c769d165ddd8c7293394", | 17 | + "revision" : "0a9893ec030501a3956bee572d6b4fdd3ae158a1", |
| 18 | - "version" : "0.15.4" | 18 | + "version" : "0.12.2" |
| 19 | - } | ||
| 20 | - }, | ||
| 21 | - { | ||
| 22 | - "identity" : "swift-toolchain-sqlite", | ||
| 23 | - "kind" : "remoteSourceControl", | ||
| 24 | - "location" : "https://github.com/swiftlang/swift-toolchain-sqlite", | ||
| 25 | - "state" : { | ||
| 26 | - "revision" : "b626d3002773b1a1304166643e7f118f724b2132", | ||
| 27 | - "version" : "1.0.4" | ||
| 28 | } | 19 | } |
| 29 | }, | 20 | }, |
| 30 | { | 21 | { | ... | ... |
| ... | @@ -2552,6 +2552,82 @@ public final class WarplySDK { | ... | @@ -2552,6 +2552,82 @@ public final class WarplySDK { |
| 2552 | } | 2552 | } |
| 2553 | } | 2553 | } |
| 2554 | 2554 | ||
| 2555 | + // MARK: - Stores | ||
| 2556 | + | ||
| 2557 | + /// Get stores for a specific merchant | ||
| 2558 | + /// - Parameters: | ||
| 2559 | + /// - language: Language for the stores (optional, defaults to applicationLocale) | ||
| 2560 | + /// - merchantUuid: The UUID of the merchant whose stores to retrieve | ||
| 2561 | + /// - completion: Completion handler with stores array | ||
| 2562 | + /// - failureCallback: Failure callback with error code | ||
| 2563 | + public func getStores( | ||
| 2564 | + language: String? = nil, | ||
| 2565 | + merchantUuid: String? = nil, | ||
| 2566 | + completion: @escaping ([MerchantModel]?) -> Void, | ||
| 2567 | + failureCallback: @escaping (Int) -> Void | ||
| 2568 | + ) { | ||
| 2569 | + let finalLanguage = language ?? self.applicationLocale | ||
| 2570 | + | ||
| 2571 | + Task { | ||
| 2572 | + do { | ||
| 2573 | + let endpoint = Endpoint.getStores(language: finalLanguage, merchantUuid: merchantUuid ?? "") | ||
| 2574 | + let response = try await networkService.requestRaw(endpoint) | ||
| 2575 | + | ||
| 2576 | + await MainActor.run { | ||
| 2577 | + print("🔍 [WarplySDK] getStores raw response: \(response)") | ||
| 2578 | + | ||
| 2579 | + if response["MAPP_SHOPS-status"] as? Int == 1 { | ||
| 2580 | + let dynatraceEvent = LoyaltySDKDynatraceEventModel() | ||
| 2581 | + dynatraceEvent._eventName = "custom_success_get_stores_loyalty" | ||
| 2582 | + dynatraceEvent._parameters = nil | ||
| 2583 | + self.postFrameworkEvent("dynatrace", sender: dynatraceEvent) | ||
| 2584 | + | ||
| 2585 | + var stores: [MerchantModel] = [] | ||
| 2586 | + if let mappShops = response["MAPP_SHOPS"] as? [String: Any], | ||
| 2587 | + let result = mappShops["result"] as? [[String: Any]] { | ||
| 2588 | + for item in result { | ||
| 2589 | + stores.append(MerchantModel(dictionary: item)) | ||
| 2590 | + } | ||
| 2591 | + } | ||
| 2592 | + print("✅ [WarplySDK] getStores loaded \(stores.count) stores") | ||
| 2593 | + completion(stores) | ||
| 2594 | + } else { | ||
| 2595 | + let dynatraceEvent = LoyaltySDKDynatraceEventModel() | ||
| 2596 | + dynatraceEvent._eventName = "custom_error_get_stores_loyalty" | ||
| 2597 | + dynatraceEvent._parameters = nil | ||
| 2598 | + self.postFrameworkEvent("dynatrace", sender: dynatraceEvent) | ||
| 2599 | + failureCallback(-1) | ||
| 2600 | + } | ||
| 2601 | + } | ||
| 2602 | + } catch { | ||
| 2603 | + await MainActor.run { | ||
| 2604 | + print("❌ [WarplySDK] getStores error: \(error)") | ||
| 2605 | + self.handleError(error, context: "getStores", endpoint: "getStores", failureCallback: failureCallback) | ||
| 2606 | + } | ||
| 2607 | + } | ||
| 2608 | + } | ||
| 2609 | + } | ||
| 2610 | + | ||
| 2611 | + /// Get stores for a specific merchant (async/await variant) | ||
| 2612 | + /// - Parameters: | ||
| 2613 | + /// - language: Language for the stores (optional, defaults to applicationLocale) | ||
| 2614 | + /// - merchantUuid: The UUID of the merchant whose stores to retrieve | ||
| 2615 | + /// - Returns: Array of stores as MerchantModel | ||
| 2616 | + /// - Throws: WarplyError if the request fails | ||
| 2617 | + public func getStores(language: String? = nil, merchantUuid: String? = nil) async throws -> [MerchantModel] { | ||
| 2618 | + return try await withCheckedThrowingContinuation { continuation in | ||
| 2619 | + getStores(language: language, merchantUuid: merchantUuid, completion: { stores in | ||
| 2620 | + if let stores = stores { | ||
| 2621 | + continuation.resume(returning: stores) | ||
| 2622 | + } else { | ||
| 2623 | + continuation.resume(throwing: WarplyError.networkError) | ||
| 2624 | + } | ||
| 2625 | + }, failureCallback: { errorCode in | ||
| 2626 | + continuation.resume(throwing: WarplyError.unknownError(errorCode)) | ||
| 2627 | + }) | ||
| 2628 | + } | ||
| 2629 | + } | ||
| 2630 | + | ||
| 2555 | // MARK: - Articles | 2631 | // MARK: - Articles |
| 2556 | 2632 | ||
| 2557 | /// Get articles (carousel content) | 2633 | /// Get articles (carousel content) | ... | ... |
| ... | @@ -73,6 +73,7 @@ public enum Endpoint { | ... | @@ -73,6 +73,7 @@ public enum Endpoint { |
| 73 | case getMarketPassDetails | 73 | case getMarketPassDetails |
| 74 | case getMerchants(language: String, categories: [String], defaultShown: Bool, center: Double, tags: [String], uuid: String, distance: Int, parentUuids: [String]) | 74 | case getMerchants(language: String, categories: [String], defaultShown: Bool, center: Double, tags: [String], uuid: String, distance: Int, parentUuids: [String]) |
| 75 | case getMerchantCategories(language: String) | 75 | case getMerchantCategories(language: String) |
| 76 | + case getStores(language: String, merchantUuid: String) | ||
| 76 | 77 | ||
| 77 | // Articles | 78 | // Articles |
| 78 | case getArticles(language: String, categories: [String]?) | 79 | case getArticles(language: String, categories: [String]?) |
| ... | @@ -134,7 +135,7 @@ public enum Endpoint { | ... | @@ -134,7 +135,7 @@ public enum Endpoint { |
| 134 | return "/user/v5/{appUUID}/logout" | 135 | return "/user/v5/{appUUID}/logout" |
| 135 | 136 | ||
| 136 | // Standard Context endpoints - /api/mobile/v2/{appUUID}/context/ | 137 | // Standard Context endpoints - /api/mobile/v2/{appUUID}/context/ |
| 137 | - case .getCampaigns, .getAvailableCoupons, .getCouponSets, .getMerchantCategories, .getArticles: | 138 | + case .getCampaigns, .getAvailableCoupons, .getCouponSets, .getMerchantCategories, .getArticles, .getStores: |
| 138 | return "/api/mobile/v2/{appUUID}/context/" | 139 | return "/api/mobile/v2/{appUUID}/context/" |
| 139 | 140 | ||
| 140 | // Authenticated Context endpoints - /oauth/{appUUID}/context | 141 | // Authenticated Context endpoints - /oauth/{appUUID}/context |
| ... | @@ -167,7 +168,7 @@ public enum Endpoint { | ... | @@ -167,7 +168,7 @@ public enum Endpoint { |
| 167 | switch self { | 168 | switch self { |
| 168 | case .register, .changePassword, .resetPassword, .requestOtp, .verifyTicket, .refreshToken, .logout, .getCampaigns, .getCampaignsPersonalized, | 169 | case .register, .changePassword, .resetPassword, .requestOtp, .verifyTicket, .refreshToken, .logout, .getCampaigns, .getCampaignsPersonalized, |
| 169 | .getCoupons, .getCouponSets, .getAvailableCoupons, | 170 | .getCoupons, .getCouponSets, .getAvailableCoupons, |
| 170 | - .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .retrieveCoupon, .getMerchants, .getMerchantCategories, .getArticles, .sendEvent, .sendDeviceInfo, .getCosmoteUser, .deiLogin: | 171 | + .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .retrieveCoupon, .getMerchants, .getMerchantCategories, .getStores, .getArticles, .sendEvent, .sendDeviceInfo, .getCosmoteUser, .deiLogin: |
| 171 | return .POST | 172 | return .POST |
| 172 | case .getSingleCampaign, .getNetworkStatus: | 173 | case .getSingleCampaign, .getNetworkStatus: |
| 173 | return .GET | 174 | return .GET |
| ... | @@ -409,6 +410,17 @@ public enum Endpoint { | ... | @@ -409,6 +410,17 @@ public enum Endpoint { |
| 409 | "action": "retrieve_categories" | 410 | "action": "retrieve_categories" |
| 410 | ] | 411 | ] |
| 411 | ] | 412 | ] |
| 413 | + | ||
| 414 | + // Stores - retrieve stores for a specific merchant | ||
| 415 | + case .getStores(let language, let merchantUuid): | ||
| 416 | + var shopsParams: [String: Any] = [ | ||
| 417 | + "language": language, | ||
| 418 | + "action": "retrieve_stores" | ||
| 419 | + ] | ||
| 420 | + if !merchantUuid.isEmpty { | ||
| 421 | + shopsParams["merchant_uuid"] = merchantUuid | ||
| 422 | + } | ||
| 423 | + return ["shops": shopsParams] | ||
| 412 | 424 | ||
| 413 | // Articles - using content structure for DEI API | 425 | // Articles - using content structure for DEI API |
| 414 | case .getArticles(let language, let categories): | 426 | case .getArticles(let language, let categories): |
| ... | @@ -478,7 +490,7 @@ public enum Endpoint { | ... | @@ -478,7 +490,7 @@ public enum Endpoint { |
| 478 | return .userManagement | 490 | return .userManagement |
| 479 | 491 | ||
| 480 | // Standard Context - /api/mobile/v2/{appUUID}/context/ | 492 | // Standard Context - /api/mobile/v2/{appUUID}/context/ |
| 481 | - case .getCampaigns, .getAvailableCoupons, .getCouponSets, .getMerchantCategories, .getArticles: | 493 | + case .getCampaigns, .getAvailableCoupons, .getCouponSets, .getMerchantCategories, .getArticles, .getStores: |
| 482 | return .standardContext | 494 | return .standardContext |
| 483 | 495 | ||
| 484 | // Authenticated Context - /oauth/{appUUID}/context | 496 | // Authenticated Context - /oauth/{appUUID}/context |
| ... | @@ -520,7 +532,7 @@ public enum Endpoint { | ... | @@ -520,7 +532,7 @@ public enum Endpoint { |
| 520 | // Standard Authentication (loyalty headers only) | 532 | // Standard Authentication (loyalty headers only) |
| 521 | case .register, .resetPassword, .requestOtp, .getCampaigns, .getAvailableCoupons, .getCouponSets, .refreshToken, .logout, | 533 | case .register, .resetPassword, .requestOtp, .getCampaigns, .getAvailableCoupons, .getCouponSets, .refreshToken, .logout, |
| 522 | .verifyTicket, .getSingleCampaign, .sendEvent, .sendDeviceInfo, | 534 | .verifyTicket, .getSingleCampaign, .sendEvent, .sendDeviceInfo, |
| 523 | - .getMerchants, .getMerchantCategories, .getArticles, .getNetworkStatus, .deiLogin: | 535 | + .getMerchants, .getMerchantCategories, .getArticles, .getStores, .getNetworkStatus, .deiLogin: |
| 524 | return .standard | 536 | return .standard |
| 525 | 537 | ||
| 526 | // Bearer Token Authentication (loyalty headers + Authorization: Bearer) | 538 | // Bearer Token Authentication (loyalty headers + Authorization: Bearer) | ... | ... |
| ... | @@ -134,30 +134,56 @@ public class MerchantModel { | ... | @@ -134,30 +134,56 @@ public class MerchantModel { |
| 134 | 134 | ||
| 135 | public init(dictionary: [String: Any]) { | 135 | public init(dictionary: [String: Any]) { |
| 136 | // Parse existing fields | 136 | // Parse existing fields |
| 137 | - self.address = dictionary["address"] as? String? ?? "" | 137 | + self.address = dictionary["address"] as? String ?? "" |
| 138 | - self.id = dictionary["id"] as? String? ?? "" | 138 | + self.id = dictionary["id"] as? String ?? "" |
| 139 | - self.store_id = dictionary["store_id"] as? String? ?? "" | 139 | + // store_id may arrive as Int or String |
| 140 | - self.name = dictionary["name"] as? String? ?? "" | 140 | + if let storeIdInt = dictionary["store_id"] as? Int { |
| 141 | - self.logo = dictionary["logo"] as? String? ?? "" | 141 | + self.store_id = String(storeIdInt) |
| 142 | - self.website = dictionary["website"] as? String? ?? "" | 142 | + } else { |
| 143 | - self.email = dictionary["email"] as? String? ?? "" | 143 | + self.store_id = dictionary["store_id"] as? String ?? "" |
| 144 | - self.telephone = dictionary["telephone"] as? String? ?? "" | 144 | + } |
| 145 | - self.category = dictionary["category"] as? String? ?? "" | 145 | + self.name = dictionary["name"] as? String ?? "" |
| 146 | - self.description = dictionary["description"] as? String? ?? "" | 146 | + self.logo = dictionary["logo"] as? String ?? "" |
| 147 | - self.short_description = dictionary["short_description"] as? String? ?? "" | 147 | + self.website = dictionary["website"] as? String ?? "" |
| 148 | - self.region = dictionary["region"] as? String? ?? "" | 148 | + self.email = dictionary["email"] as? String ?? "" |
| 149 | - self.latitude = dictionary["latitude"] as? Double? ?? 0.0 | 149 | + self.telephone = dictionary["telephone"] as? String ?? "" |
| 150 | - self.longitude = dictionary["longitude"] as? Double? ?? 0.0 | 150 | + self.category = dictionary["category"] as? String ?? "" |
| 151 | - self.image = dictionary["image"] as? String? ?? "" | 151 | + self.description = dictionary["description"] as? String ?? "" |
| 152 | - self.active = dictionary["active"] as? Bool? ?? false | 152 | + self.short_description = dictionary["short_description"] as? String ?? "" |
| 153 | - self.city = dictionary["city"] as? String? ?? "" | 153 | + self.region = dictionary["region"] as? String ?? "" |
| 154 | - self.country = dictionary["country"] as? String? ?? "" | 154 | + // latitude may arrive as String or Double |
| 155 | - self.postal_code = dictionary["postal_code"] as? String? ?? "" | 155 | + if let latString = dictionary["latitude"] as? String { |
| 156 | - self.vat = dictionary["vat"] as? String? ?? "" | 156 | + self.latitude = Double(latString) ?? 0.0 |
| 157 | - self.uuid = dictionary["uuid"] as? String? ?? "" | 157 | + } else { |
| 158 | - self.category_uuid = dictionary["category_uuid"] as? String? ?? "" | 158 | + self.latitude = dictionary["latitude"] as? Double ?? 0.0 |
| 159 | - self.created = dictionary["created"] as? String? ?? "" | 159 | + } |
| 160 | - self.parent = dictionary["parent"] as? String? ?? "" | 160 | + // longitude may arrive as String or Double |
| 161 | + if let lonString = dictionary["longitude"] as? String { | ||
| 162 | + self.longitude = Double(lonString) ?? 0.0 | ||
| 163 | + } else { | ||
| 164 | + self.longitude = dictionary["longitude"] as? Double ?? 0.0 | ||
| 165 | + } | ||
| 166 | + self.image = dictionary["image"] as? String ?? "" | ||
| 167 | + // active may arrive as Int (1/0) or Bool | ||
| 168 | + if let activeInt = dictionary["active"] as? Int { | ||
| 169 | + self.active = activeInt == 1 | ||
| 170 | + } else { | ||
| 171 | + self.active = dictionary["active"] as? Bool ?? false | ||
| 172 | + } | ||
| 173 | + self.city = dictionary["city"] as? String ?? "" | ||
| 174 | + self.country = dictionary["country"] as? String ?? "" | ||
| 175 | + // postal_code may arrive as Int or String | ||
| 176 | + if let postalInt = dictionary["postal_code"] as? Int { | ||
| 177 | + self.postal_code = String(postalInt) | ||
| 178 | + } else { | ||
| 179 | + self.postal_code = dictionary["postal_code"] as? String ?? "" | ||
| 180 | + } | ||
| 181 | + self.vat = dictionary["vat"] as? String ?? "" | ||
| 182 | + self.uuid = dictionary["uuid"] as? String ?? "" | ||
| 183 | + self.category_uuid = dictionary["category_uuid"] as? String ?? "" | ||
| 184 | + self.created = dictionary["created"] as? String ?? "" | ||
| 185 | + // parent_uuid key used by stores endpoint; fallback to parent for merchants | ||
| 186 | + self.parent = dictionary["parent_uuid"] as? String ?? dictionary["parent"] as? String ?? "" | ||
| 161 | self.img_preview = dictionary["img_preview"] as? String? ?? "" | 187 | self.img_preview = dictionary["img_preview"] as? String? ?? "" |
| 162 | self.admin_name = dictionary["admin_name"] as? String? ?? "" | 188 | self.admin_name = dictionary["admin_name"] as? String? ?? "" |
| 163 | self.sorting = dictionary["sorting"] as? Int? ?? 0 | 189 | self.sorting = dictionary["sorting"] as? Int? ?? 0 | ... | ... |
| ... | @@ -295,6 +295,26 @@ import UIKit | ... | @@ -295,6 +295,26 @@ import UIKit |
| 295 | } | 295 | } |
| 296 | 296 | ||
| 297 | // MARK: - Merchants Loading | 297 | // MARK: - Merchants Loading |
| 298 | + // private func loadMerchants() { | ||
| 299 | + // WarplySDK.shared.getStores(merchantUuid: "86eba6980cf746cbbcca5c6446700121") { [weak self] merchants in | ||
| 300 | + // guard let self = self, let merchants = merchants else { | ||
| 301 | + // self?.createCouponSetsSection() | ||
| 302 | + // return | ||
| 303 | + // } | ||
| 304 | + | ||
| 305 | + // self.merchants = merchants | ||
| 306 | + // print("✅ [MyRewardsViewController] Loaded \(merchants.count) merchants") | ||
| 307 | + | ||
| 308 | + // // Load merchant categories after merchants success | ||
| 309 | + // self.loadMerchantCategories() | ||
| 310 | + | ||
| 311 | + // } failureCallback: { [weak self] errorCode in | ||
| 312 | + // print("Failed to load merchants: \(errorCode)") | ||
| 313 | + // self?.createCouponSetsSection() | ||
| 314 | + // } | ||
| 315 | + // } | ||
| 316 | + | ||
| 317 | + // TODO: DELETE loadMerchants - No matching needed | ||
| 298 | private func loadMerchants() { | 318 | private func loadMerchants() { |
| 299 | // Load merchants from WarplySDK (using enhanced getMerchants method) | 319 | // Load merchants from WarplySDK (using enhanced getMerchants method) |
| 300 | WarplySDK.shared.getMerchants { [weak self] merchants in | 320 | WarplySDK.shared.getMerchants { [weak self] merchants in | ... | ... |
-
Please register or login to post a comment