Manos Chorianopoulos

getStores request

...@@ -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
......