Manos Chorianopoulos

add getCouponSetsNew request

...@@ -2457,6 +2457,94 @@ public final class WarplySDK { ...@@ -2457,6 +2457,94 @@ public final class WarplySDK {
2457 } 2457 }
2458 } 2458 }
2459 2459
2460 + /// Get coupon sets via new endpoint with optional region/offer_category filters
2461 + /// - Parameters:
2462 + /// - region: Optional region filter (omitted from payload if nil)
2463 + /// - offerCategory: Optional offer category filter (omitted from payload if nil)
2464 + /// - completion: Returns the raw response dictionary on success
2465 + /// - failureCallback: Returns error code on failure
2466 + public func getCouponSetsNew(
2467 + language: String? = nil,
2468 + region: String? = nil,
2469 + offerCategory: String? = nil,
2470 + completion: @escaping ([CouponSetItemModel]?) -> Void,
2471 + failureCallback: @escaping (Int) -> Void
2472 + ) {
2473 + let finalLanguage = language ?? self.applicationLocale
2474 +
2475 + Task {
2476 + do {
2477 + let endpoint = Endpoint.getCouponSetsNew(
2478 + language: finalLanguage,
2479 + active: true,
2480 + visible: true,
2481 + region: region,
2482 + offerCategory: offerCategory
2483 + )
2484 + let response = try await networkService.requestRaw(endpoint)
2485 +
2486 + var couponSetsArray: [CouponSetItemModel] = []
2487 +
2488 + await MainActor.run {
2489 + print("📥 getCouponSetsNew response: \(response)")
2490 +
2491 + let dynatraceEvent = LoyaltySDKDynatraceEventModel()
2492 + dynatraceEvent._eventName = "custom_success_couponsetsnew_loyalty"
2493 + dynatraceEvent._parameters = nil
2494 + self.postFrameworkEvent("dynatrace", sender: dynatraceEvent)
2495 +
2496 + if let resultData = response["result"] as? [[String: Any]] {
2497 + for couponsetDict in resultData {
2498 + let tempCouponset = CouponSetItemModel(dictionary: couponsetDict)
2499 + couponSetsArray.append(tempCouponset)
2500 + }
2501 + }
2502 +
2503 + self.setCouponSetList(couponSetsArray)
2504 + completion(couponSetsArray)
2505 + }
2506 + } catch {
2507 + await MainActor.run {
2508 + let dynatraceEvent = LoyaltySDKDynatraceEventModel()
2509 + dynatraceEvent._eventName = "custom_error_couponsetsnew_loyalty"
2510 + dynatraceEvent._parameters = nil
2511 + self.postFrameworkEvent("dynatrace", sender: dynatraceEvent)
2512 +
2513 + if let networkError = error as? NetworkError {
2514 + failureCallback(networkError.code)
2515 + } else {
2516 + failureCallback(-1)
2517 + }
2518 + }
2519 + }
2520 + }
2521 + }
2522 +
2523 + /// Get coupon sets new (async/await variant)
2524 + /// - Parameters:
2525 + /// - language: Language code for localized content (optional, defaults to applicationLocale)
2526 + /// - region: Optional region filter (omitted from payload if nil)
2527 + /// - offerCategory: Optional offer category filter (omitted from payload if nil)
2528 + /// - Returns: Array of coupon set models
2529 + /// - Throws: WarplyError if the request fails
2530 + public func getCouponSetsNew(
2531 + language: String? = nil,
2532 + region: String? = nil,
2533 + offerCategory: String? = nil
2534 + ) async throws -> [CouponSetItemModel] {
2535 + return try await withCheckedThrowingContinuation { continuation in
2536 + getCouponSetsNew(language: language, region: region, offerCategory: offerCategory, completion: { couponSets in
2537 + if let couponSets = couponSets {
2538 + continuation.resume(returning: couponSets)
2539 + } else {
2540 + continuation.resume(throwing: WarplyError.networkError)
2541 + }
2542 + }, failureCallback: { errorCode in
2543 + continuation.resume(throwing: WarplyError.unknownError(errorCode))
2544 + })
2545 + }
2546 + }
2547 +
2460 /// Get available coupons (async/await variant) 2548 /// Get available coupons (async/await variant)
2461 /// - Returns: Dictionary of coupon availability data 2549 /// - Returns: Dictionary of coupon availability data
2462 /// - Throws: WarplyError if the request fails 2550 /// - Throws: WarplyError if the request fails
......
...@@ -67,6 +67,7 @@ public enum Endpoint { ...@@ -67,6 +67,7 @@ public enum Endpoint {
67 // Coupons 67 // Coupons
68 case getCoupons(language: String, couponsetType: String) 68 case getCoupons(language: String, couponsetType: String)
69 case getCouponSets(language: String, active: Bool, visible: Bool, uuids: [String]?) 69 case getCouponSets(language: String, active: Bool, visible: Bool, uuids: [String]?)
70 + case getCouponSetsNew(language: String, active: Bool, visible: Bool, region: String?, offerCategory: String?)
70 case getAvailableCoupons 71 case getAvailableCoupons
71 72
72 // Market & Merchants 73 // Market & Merchants
...@@ -142,7 +143,7 @@ public enum Endpoint { ...@@ -142,7 +143,7 @@ public enum Endpoint {
142 return "/api/mobile/v2/{appUUID}/context/" 143 return "/api/mobile/v2/{appUUID}/context/"
143 144
144 // Authenticated Context endpoints - /oauth/{appUUID}/context 145 // Authenticated Context endpoints - /oauth/{appUUID}/context
145 - case .getCampaignsPersonalized, .getCoupons, .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .retrieveCoupon, .getCarouselContent: 146 + case .getCampaignsPersonalized, .getCoupons, .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .retrieveCoupon, .getCarouselContent, .getCouponSetsNew:
146 return "/oauth/{appUUID}/context" 147 return "/oauth/{appUUID}/context"
147 148
148 // Session endpoints - /api/session/{sessionUuid} 149 // Session endpoints - /api/session/{sessionUuid}
...@@ -170,7 +171,7 @@ public enum Endpoint { ...@@ -170,7 +171,7 @@ public enum Endpoint {
170 public var method: HTTPMethod { 171 public var method: HTTPMethod {
171 switch self { 172 switch self {
172 case .register, .changePassword, .resetPassword, .requestOtp, .verifyTicket, .refreshToken, .logout, .getCampaigns, .getCampaignsPersonalized, 173 case .register, .changePassword, .resetPassword, .requestOtp, .verifyTicket, .refreshToken, .logout, .getCampaigns, .getCampaignsPersonalized,
173 - .getCoupons, .getCouponSets, .getAvailableCoupons, 174 + .getCoupons, .getCouponSets, .getCouponSetsNew, .getAvailableCoupons,
174 .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .retrieveCoupon, .getMerchants, .getMerchantCategories, .getStores, .getArticles, .sendEvent, .sendDeviceInfo, .getCosmoteUser, .deiLogin, .getCarouselContent: 175 .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .retrieveCoupon, .getMerchants, .getMerchantCategories, .getStores, .getArticles, .sendEvent, .sendDeviceInfo, .getCosmoteUser, .deiLogin, .getCarouselContent:
175 return .POST 176 return .POST
176 case .getSingleCampaign, .getNetworkStatus: 177 case .getSingleCampaign, .getNetworkStatus:
...@@ -296,6 +297,23 @@ public enum Endpoint { ...@@ -296,6 +297,23 @@ public enum Endpoint {
296 "coupon": couponParams 297 "coupon": couponParams
297 ] 298 ]
298 299
300 + case .getCouponSetsNew(let language, let active, let visible, let region, let offerCategory):
301 + var couponParams: [String: Any] = [
302 + "action": "get_offers",
303 + "active": active,
304 + "visible": visible,
305 + "language": language
306 + ]
307 + if let region = region {
308 + couponParams["region"] = region
309 + }
310 + if let offerCategory = offerCategory {
311 + couponParams["offer_category"] = offerCategory
312 + }
313 + return [
314 + "coupon": couponParams
315 + ]
316 +
299 case .getAvailableCoupons: 317 case .getAvailableCoupons:
300 return [ 318 return [
301 "coupon": [ 319 "coupon": [
...@@ -505,7 +523,7 @@ public enum Endpoint { ...@@ -505,7 +523,7 @@ public enum Endpoint {
505 return .standardContext 523 return .standardContext
506 524
507 // Authenticated Context - /oauth/{appUUID}/context 525 // Authenticated Context - /oauth/{appUUID}/context
508 - case .getCampaignsPersonalized, .getCoupons, .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .retrieveCoupon, .getCarouselContent: 526 + case .getCampaignsPersonalized, .getCoupons, .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .retrieveCoupon, .getCarouselContent, .getCouponSetsNew:
509 return .authenticatedContext 527 return .authenticatedContext
510 528
511 // Authentication - /oauth/{appUUID}/login, /oauth/{appUUID}/token 529 // Authentication - /oauth/{appUUID}/login, /oauth/{appUUID}/token
...@@ -547,7 +565,7 @@ public enum Endpoint { ...@@ -547,7 +565,7 @@ public enum Endpoint {
547 return .standard 565 return .standard
548 566
549 // Bearer Token Authentication (loyalty headers + Authorization: Bearer) 567 // Bearer Token Authentication (loyalty headers + Authorization: Bearer)
550 - case .changePassword, .getCampaignsPersonalized, .getCoupons, .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .retrieveCoupon, .getCarouselContent: 568 + case .changePassword, .getCampaignsPersonalized, .getCoupons, .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .retrieveCoupon, .getCarouselContent, .getCouponSetsNew:
551 return .bearerToken 569 return .bearerToken
552 570
553 // Basic Authentication (loyalty headers + Authorization: Basic) 571 // Basic Authentication (loyalty headers + Authorization: Basic)
......
...@@ -77,6 +77,19 @@ public class CouponSetItemModel { ...@@ -77,6 +77,19 @@ public class CouponSetItemModel {
77 private var points_cause: String? 77 private var points_cause: String?
78 private var third_party_service: String? 78 private var third_party_service: String?
79 private var category: String? 79 private var category: String?
80 + private var offer_category: String?
81 +
82 + // getCouponSetsNew fields
83 + private var score: String?
84 + private var locked: Bool?
85 + private var app_img_preview: String?
86 + private var app_imgs: [String]?
87 + private var merchant_admin_name: String?
88 + private var merchant_img_preview: String?
89 + private var merchant_website: String?
90 + private var regions: [String]?
91 + private var show_as_banner: [String: Any]?
92 + private var tagging: [String: Any]?
80 93
81 // Bound merchant data for performance 94 // Bound merchant data for performance
82 private var merchant: MerchantModel? 95 private var merchant: MerchantModel?
...@@ -159,6 +172,37 @@ public class CouponSetItemModel { ...@@ -159,6 +172,37 @@ public class CouponSetItemModel {
159 self.points_cause = dictionary["points_cause"] as? String? ?? "" 172 self.points_cause = dictionary["points_cause"] as? String? ?? ""
160 self.third_party_service = dictionary["third_party_service"] as? String? ?? "" 173 self.third_party_service = dictionary["third_party_service"] as? String? ?? ""
161 self.category = dictionary["category"] as? String? ?? "" 174 self.category = dictionary["category"] as? String? ?? ""
175 + self.offer_category = dictionary["offer_category"] as? String? ?? ""
176 +
177 + // getCouponSetsNew fields
178 + self.score = dictionary["score"] as? String
179 + if let lockedInt = dictionary["locked"] as? Int {
180 + self.locked = lockedInt == 1
181 + } else {
182 + self.locked = dictionary["locked"] as? Bool
183 + }
184 + self.app_img_preview = dictionary["app_img_preview"] as? String
185 + self.merchant_admin_name = dictionary["merchant_admin_name"] as? String
186 + self.merchant_img_preview = dictionary["merchant_img_preview"] as? String
187 + self.merchant_website = dictionary["merchant_website"] as? String
188 + self.show_as_banner = dictionary["show_as_banner"] as? [String: Any]
189 + self.tagging = dictionary["tagging"] as? [String: Any]
190 +
191 + // app_imgs — JSON-encoded string array, same pattern as img
192 + if let appImgsString = dictionary["app_imgs"] as? String,
193 + let appImgsData = appImgsString.data(using: .utf8),
194 + let appImgsArray = try? JSONSerialization.jsonObject(with: appImgsData, options: []) as? [String] {
195 + self.app_imgs = appImgsArray
196 + } else {
197 + self.app_imgs = []
198 + }
199 +
200 + // regions — array with potential NSNull entries, filter them out
201 + if let regionsArray = dictionary["regions"] as? [Any] {
202 + self.regions = regionsArray.compactMap { $0 as? String }
203 + } else {
204 + self.regions = []
205 + }
162 206
163 // UPDATED EXPIRATION HANDLING - store raw date string 207 // UPDATED EXPIRATION HANDLING - store raw date string
164 if let expirationObject = dictionary["expiration"] as? [String: Any], 208 if let expirationObject = dictionary["expiration"] as? [String: Any],
...@@ -266,6 +310,19 @@ public class CouponSetItemModel { ...@@ -266,6 +310,19 @@ public class CouponSetItemModel {
266 public var _points_cause: String { get { return self.points_cause ?? "" } } 310 public var _points_cause: String { get { return self.points_cause ?? "" } }
267 public var _third_party_service: String { get { return self.third_party_service ?? "" } } 311 public var _third_party_service: String { get { return self.third_party_service ?? "" } }
268 public var _category: String { get { return self.category ?? "" } } 312 public var _category: String { get { return self.category ?? "" } }
313 + public var _offer_category: String { get { return self.offer_category ?? "" } }
314 +
315 + // getCouponSetsNew field accessors
316 + public var _score: String? { get { return self.score } set { self.score = newValue } }
317 + public var _locked: Bool { get { return self.locked ?? false } }
318 + public var _app_img_preview: String { get { return self.app_img_preview ?? "" } }
319 + public var _app_imgs: [String]? { get { return self.app_imgs } }
320 + public var _merchant_admin_name: String { get { return self.merchant_admin_name ?? "" } }
321 + public var _merchant_img_preview: String { get { return self.merchant_img_preview ?? "" } }
322 + public var _merchant_website: String { get { return self.merchant_website ?? "" } }
323 + public var _regions: [String]? { get { return self.regions } }
324 + public var _show_as_banner: [String: Any]? { get { return self.show_as_banner } }
325 + public var _tagging: [String: Any]? { get { return self.tagging } }
269 326
270 // Bound merchant data accessor 327 // Bound merchant data accessor
271 public var _merchant: MerchantModel? { 328 public var _merchant: MerchantModel? {
......
...@@ -270,20 +270,49 @@ import UIKit ...@@ -270,20 +270,49 @@ import UIKit
270 } 270 }
271 271
272 // MARK: - Coupon Sets Loading 272 // MARK: - Coupon Sets Loading
273 + // private func loadCouponSets() {
274 + // // Load coupon sets from WarplySDK
275 + // WarplySDK.shared.getCouponSets { [weak self] couponSets in
276 + // guard let self = self, let couponSets = couponSets else { return }
277 +
278 + // self.couponSets = couponSets
279 +
280 + // // Load merchants after getting coupon sets
281 + // self.loadMerchants()
282 +
283 + // } failureCallback: { [weak self] errorCode in
284 + // print("Failed to load coupon sets: \(errorCode)")
285 + // // No sections added on failure - table will be empty
286 + // }
287 + // }
288 +
273 private func loadCouponSets() { 289 private func loadCouponSets() {
274 // Load coupon sets from WarplySDK 290 // Load coupon sets from WarplySDK
275 - WarplySDK.shared.getCouponSets { [weak self] couponSets in 291 + // WarplySDK.shared.getCouponSets { [weak self] couponSets in
292 + // guard let self = self, let couponSets = couponSets else { return }
293 +
294 + // self.couponSets = couponSets
295 +
296 + // // Load merchants after getting coupon sets
297 + // self.loadMerchants()
298 +
299 + // } failureCallback: { [weak self] errorCode in
300 + // print("Failed to load coupon sets: \(errorCode)")
301 + // // No sections added on failure - table will be empty
302 + // }
303 +
304 + WarplySDK.shared.getCouponSetsNew(
305 + completion: { [weak self] couponSets in
276 guard let self = self, let couponSets = couponSets else { return } 306 guard let self = self, let couponSets = couponSets else { return }
277 307
278 self.couponSets = couponSets 308 self.couponSets = couponSets
309 + self?.createCouponSetsSection()
279 310
280 - // Load merchants after getting coupon sets 311 + },
281 - self.loadMerchants() 312 + failureCallback: { errorCode in
282 - 313 + print("=== getCouponSetsNew Error: \(errorCode)")
283 - } failureCallback: { [weak self] errorCode in
284 - print("Failed to load coupon sets: \(errorCode)")
285 - // No sections added on failure - table will be empty
286 } 314 }
315 + )
287 } 316 }
288 317
289 // MARK: - Coupons Loading 318 // MARK: - Coupons Loading
...@@ -385,6 +414,8 @@ import UIKit ...@@ -385,6 +414,8 @@ import UIKit
385 } 414 }
386 415
387 private func createCouponSetsSection() { 416 private func createCouponSetsSection() {
417 + // TODO: no merchant match any more
418 +
388 print("🔍 [MyRewardsViewController] Starting coupon filtering:") 419 print("🔍 [MyRewardsViewController] Starting coupon filtering:")
389 print(" - Coupon Sets: \(couponSets.count)") 420 print(" - Coupon Sets: \(couponSets.count)")
390 print(" - Merchants: \(merchants.count)") 421 print(" - Merchants: \(merchants.count)")
......