Toggle navigation
Toggle navigation
This project
Loading...
Sign in
open-source
/
warply_sdk_framework
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Graphs
Network
Create a new issue
Commits
Issue Boards
Authored by
Manos Chorianopoulos
2025-07-28 13:58:57 +0300
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
52b089765e47d77b979290b23179754eda9b1edc
52b08976
1 parent
ddfbac86
added getMerchantCategories request
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
332 additions
and
9 deletions
NETWORK_TESTING_AUTHORIZATION.md
SwiftWarplyFramework/SwiftWarplyFramework/Core/WarplySDK.swift
SwiftWarplyFramework/SwiftWarplyFramework/Network/Endpoints.swift
SwiftWarplyFramework/SwiftWarplyFramework/Network/NetworkService.swift
SwiftWarplyFramework/SwiftWarplyFramework/models/MerchantCategoryModel.swift
SwiftWarplyFramework/SwiftWarplyFramework/screens/MyRewardsViewController/MyRewardsViewController.swift
NETWORK_TESTING_AUTHORIZATION.md
View file @
52b0897
This diff is collapsed. Click to expand it.
SwiftWarplyFramework/SwiftWarplyFramework/Core/WarplySDK.swift
View file @
52b0897
...
...
@@ -2194,8 +2194,88 @@ public final class WarplySDK {
}
}
// MARK: - Profile
// MARK: - Merchant Categories
/// Get merchant categories
/// - Parameters:
/// - language: Language for the categories (optional, defaults to applicationLocale)
/// - completion: Completion handler with merchant categories array
/// - failureCallback: Failure callback with error code
public
func
getMerchantCategories
(
language
:
String
?
=
nil
,
completion
:
@escaping
([
MerchantCategoryModel
]?)
->
Void
,
failureCallback
:
@escaping
(
Int
)
->
Void
)
{
let
finalLanguage
=
language
??
self
.
applicationLocale
Task
{
do
{
let
endpoint
=
Endpoint
.
getMerchantCategories
(
language
:
finalLanguage
)
let
response
=
try
await
networkService
.
requestRaw
(
endpoint
)
await
MainActor
.
run
{
if
response
[
"status"
]
as?
Int
==
1
{
// Success analytics
let
dynatraceEvent
=
LoyaltySDKDynatraceEventModel
()
dynatraceEvent
.
_eventName
=
"custom_success_get_merchant_categories_loyalty"
dynatraceEvent
.
_parameters
=
nil
self
.
postFrameworkEvent
(
"dynatrace"
,
sender
:
dynatraceEvent
)
var
categories
:
[
MerchantCategoryModel
]
=
[]
// Parse from context.MAPP_SHOPS.result structure
if
let
mappShops
=
response
[
"MAPP_SHOPS"
]
as?
[
String
:
Any
],
let
result
=
mappShops
[
"result"
]
as?
[[
String
:
Any
]]
{
for
categoryDict
in
result
{
let
category
=
MerchantCategoryModel
(
dictionary
:
categoryDict
)
categories
.
append
(
category
)
}
print
(
"✅ [WarplySDK] Retrieved
\(
categories
.
count
)
merchant categories"
)
completion
(
categories
)
}
else
{
print
(
"⚠️ [WarplySDK] No merchant categories found in response"
)
completion
([])
}
}
else
{
// Error analytics
let
dynatraceEvent
=
LoyaltySDKDynatraceEventModel
()
dynatraceEvent
.
_eventName
=
"custom_error_get_merchant_categories_loyalty"
dynatraceEvent
.
_parameters
=
nil
self
.
postFrameworkEvent
(
"dynatrace"
,
sender
:
dynatraceEvent
)
failureCallback
(
-
1
)
}
}
}
catch
{
await
MainActor
.
run
{
self
.
handleError
(
error
,
context
:
"getMerchantCategories"
,
endpoint
:
"getMerchantCategories"
,
failureCallback
:
failureCallback
)
}
}
}
}
/// Get merchant categories (async/await variant)
/// - Parameter language: Language for the categories (optional, defaults to applicationLocale)
/// - Returns: Array of merchant categories
/// - Throws: WarplyError if the request fails
public
func
getMerchantCategories
(
language
:
String
?
=
nil
)
async
throws
->
[
MerchantCategoryModel
]
{
return
try
await
withCheckedThrowingContinuation
{
continuation
in
getMerchantCategories
(
language
:
language
,
completion
:
{
categories
in
if
let
categories
=
categories
{
continuation
.
resume
(
returning
:
categories
)
}
else
{
continuation
.
resume
(
throwing
:
WarplyError
.
networkError
)
}
},
failureCallback
:
{
errorCode
in
continuation
.
resume
(
throwing
:
WarplyError
.
unknownError
(
errorCode
))
})
}
}
// MARK: - Profile
/// Get user profile details
/// - Parameters:
/// - completion: Completion handler with profile model
...
...
SwiftWarplyFramework/SwiftWarplyFramework/Network/Endpoints.swift
View file @
52b0897
...
...
@@ -71,6 +71,7 @@ public enum Endpoint {
// Market & Merchants
case
getMarketPassDetails
case
getMerchants
(
language
:
String
,
categories
:
[
String
],
defaultShown
:
Bool
,
center
:
Double
,
tags
:
[
String
],
uuid
:
String
,
distance
:
Int
,
parentUuids
:
[
String
])
case
getMerchantCategories
(
language
:
String
)
// Card Management
case
addCard
(
cardNumber
:
String
,
cardIssuer
:
String
,
cardHolder
:
String
,
expirationMonth
:
String
,
expirationYear
:
String
)
...
...
@@ -126,7 +127,7 @@ public enum Endpoint {
return
"/user/v5/{appUUID}/logout"
// Standard Context endpoints - /api/mobile/v2/{appUUID}/context/
case
.
getCampaigns
,
.
getAvailableCoupons
,
.
getCouponSets
:
case
.
getCampaigns
,
.
getAvailableCoupons
,
.
getCouponSets
,
.
getMerchantCategories
:
return
"/api/mobile/v2/{appUUID}/context/"
// Authenticated Context endpoints - /oauth/{appUUID}/context
...
...
@@ -159,7 +160,7 @@ public enum Endpoint {
switch
self
{
case
.
register
,
.
changePassword
,
.
resetPassword
,
.
requestOtp
,
.
verifyTicket
,
.
refreshToken
,
.
logout
,
.
getCampaigns
,
.
getCampaignsPersonalized
,
.
getCoupons
,
.
getCouponSets
,
.
getAvailableCoupons
,
.
getMarketPassDetails
,
.
getProfile
,
.
addCard
,
.
getCards
,
.
deleteCard
,
.
getTransactionHistory
,
.
getPointsHistory
,
.
validateCoupon
,
.
redeemCoupon
,
.
getMerchants
,
.
sendEvent
,
.
sendDeviceInfo
,
.
getCosmoteUser
:
.
getMarketPassDetails
,
.
getProfile
,
.
addCard
,
.
getCards
,
.
deleteCard
,
.
getTransactionHistory
,
.
getPointsHistory
,
.
validateCoupon
,
.
redeemCoupon
,
.
getMerchants
,
.
getMerchantCategories
,
.
sendEvent
,
.
sendDeviceInfo
,
.
getCosmoteUser
:
return
.
POST
case
.
getSingleCampaign
,
.
getNetworkStatus
:
return
.
GET
...
...
@@ -379,6 +380,15 @@ public enum Endpoint {
]
]
// Merchant Categories - using shops structure for DEI API
case
.
getMerchantCategories
(
let
language
):
return
[
"shops"
:
[
"language"
:
language
,
"action"
:
"retrieve_categories"
]
]
// Analytics endpoints - events structure
case
.
sendEvent
(
let
eventName
,
let
priority
):
return
[
...
...
@@ -434,7 +444,7 @@ public enum Endpoint {
return
.
userManagement
// Standard Context - /api/mobile/v2/{appUUID}/context/
case
.
getCampaigns
,
.
getAvailableCoupons
,
.
getCouponSets
:
case
.
getCampaigns
,
.
getAvailableCoupons
,
.
getCouponSets
,
.
getMerchantCategories
:
return
.
standardContext
// Authenticated Context - /oauth/{appUUID}/context
...
...
@@ -476,7 +486,7 @@ public enum Endpoint {
// Standard Authentication (loyalty headers only)
case
.
register
,
.
resetPassword
,
.
requestOtp
,
.
getCampaigns
,
.
getAvailableCoupons
,
.
getCouponSets
,
.
refreshToken
,
.
logout
,
.
verifyTicket
,
.
getSingleCampaign
,
.
sendEvent
,
.
sendDeviceInfo
,
.
getMerchants
,
.
getNetworkStatus
:
.
getMerchants
,
.
get
MerchantCategories
,
.
get
NetworkStatus
:
return
.
standard
// Bearer Token Authentication (loyalty headers + Authorization: Bearer)
...
...
SwiftWarplyFramework/SwiftWarplyFramework/Network/NetworkService.swift
View file @
52b0897
...
...
@@ -962,6 +962,22 @@ extension NetworkService {
return
response
}
// MARK: - Merchant Categories Methods
/// Get merchant categories
/// - Parameter language: Language for the categories
/// - Returns: Response dictionary containing merchant categories
/// - Throws: NetworkError if request fails
public
func
getMerchantCategories
(
language
:
String
)
async
throws
->
[
String
:
Any
]
{
print
(
"🔄 [NetworkService] Getting merchant categories for language:
\(
language
)
"
)
let
endpoint
=
Endpoint
.
getMerchantCategories
(
language
:
language
)
let
response
=
try
await
requestRaw
(
endpoint
)
print
(
"✅ [NetworkService] Get merchant categories request completed"
)
return
response
}
// MARK: - Coupon Operations Methods
/// Validate a coupon for the user
...
...
SwiftWarplyFramework/SwiftWarplyFramework/models/MerchantCategoryModel.swift
0 → 100644
View file @
52b0897
//
// MerchantCategoryModel.swift
// SwiftWarplyFramework
//
// Created by Warply on 28/07/2025.
// Copyright © 2025 Warply. All rights reserved.
//
import
Foundation
// MARK: - Merchant Category Model
public
class
MerchantCategoryModel
:
NSObject
{
private
var
uuid
:
String
?
private
var
admin_name
:
String
?
private
var
image
:
String
?
private
var
parent
:
String
?
private
var
fields
:
String
?
private
var
children
:
[
Any
]?
private
var
count
:
Int
?
private
var
name
:
String
?
public
init
()
{
self
.
uuid
=
""
self
.
admin_name
=
""
self
.
image
=
""
self
.
parent
=
""
self
.
fields
=
""
self
.
children
=
[]
self
.
count
=
0
self
.
name
=
""
}
public
init
(
dictionary
:
[
String
:
Any
])
{
self
.
uuid
=
dictionary
[
"uuid"
]
as?
String
??
""
self
.
admin_name
=
dictionary
[
"admin_name"
]
as?
String
??
""
self
.
image
=
dictionary
[
"image"
]
as?
String
??
""
self
.
parent
=
dictionary
[
"parent"
]
as?
String
self
.
fields
=
dictionary
[
"fields"
]
as?
String
??
""
self
.
children
=
dictionary
[
"children"
]
as?
[
Any
]
??
[]
self
.
count
=
dictionary
[
"count"
]
as?
Int
??
0
self
.
name
=
dictionary
[
"name"
]
as?
String
}
// MARK: - Public Accessors
public
var
_uuid
:
String
{
get
{
return
self
.
uuid
??
""
}
set
(
newValue
)
{
self
.
uuid
=
newValue
}
}
public
var
_admin_name
:
String
{
get
{
return
self
.
admin_name
??
""
}
set
(
newValue
)
{
self
.
admin_name
=
newValue
}
}
public
var
_image
:
String
{
get
{
return
self
.
image
??
""
}
set
(
newValue
)
{
self
.
image
=
newValue
}
}
public
var
_parent
:
String
?
{
get
{
return
self
.
parent
}
set
(
newValue
)
{
self
.
parent
=
newValue
}
}
public
var
_fields
:
String
{
get
{
return
self
.
fields
??
""
}
set
(
newValue
)
{
self
.
fields
=
newValue
}
}
public
var
_children
:
[
Any
]
{
get
{
return
self
.
children
??
[]
}
set
(
newValue
)
{
self
.
children
=
newValue
}
}
public
var
_count
:
Int
{
get
{
return
self
.
count
??
0
}
set
(
newValue
)
{
self
.
count
=
newValue
}
}
public
var
_name
:
String
?
{
get
{
return
self
.
name
}
set
(
newValue
)
{
self
.
name
=
newValue
}
}
// MARK: - Computed Properties
/// Display name for the category - uses name if available, otherwise falls back to admin_name
public
var
displayName
:
String
{
return
self
.
name
??
self
.
admin_name
??
""
}
/// Clean image URL with whitespace trimmed
public
var
cleanImageUrl
:
String
{
return
self
.
image
?
.
trimmingCharacters
(
in
:
.
whitespacesAndNewlines
)
??
""
}
/// Check if this category has a parent category
public
var
hasParent
:
Bool
{
return
self
.
parent
!=
nil
&&
!
(
self
.
parent
?
.
isEmpty
??
true
)
}
/// Check if this category has child categories
public
var
hasChildren
:
Bool
{
return
!
self
.
children
.
isEmpty
}
}
// MARK: - Codable Support
extension
MerchantCategoryModel
:
Codable
{
private
enum
CodingKeys
:
String
,
CodingKey
{
case
uuid
case
admin_name
case
image
case
parent
case
fields
case
children
case
count
case
name
}
public
func
encode
(
to
encoder
:
Encoder
)
throws
{
var
container
=
encoder
.
container
(
keyedBy
:
CodingKeys
.
self
)
try
container
.
encode
(
uuid
,
forKey
:
.
uuid
)
try
container
.
encode
(
admin_name
,
forKey
:
.
admin_name
)
try
container
.
encode
(
image
,
forKey
:
.
image
)
try
container
.
encodeIfPresent
(
parent
,
forKey
:
.
parent
)
try
container
.
encode
(
fields
,
forKey
:
.
fields
)
try
container
.
encode
(
count
,
forKey
:
.
count
)
try
container
.
encodeIfPresent
(
name
,
forKey
:
.
name
)
// Note: children is [Any] so we skip encoding it for now
}
public
required
init
(
from
decoder
:
Decoder
)
throws
{
let
container
=
try
decoder
.
container
(
keyedBy
:
CodingKeys
.
self
)
self
.
uuid
=
try
container
.
decodeIfPresent
(
String
.
self
,
forKey
:
.
uuid
)
self
.
admin_name
=
try
container
.
decodeIfPresent
(
String
.
self
,
forKey
:
.
admin_name
)
self
.
image
=
try
container
.
decodeIfPresent
(
String
.
self
,
forKey
:
.
image
)
self
.
parent
=
try
container
.
decodeIfPresent
(
String
.
self
,
forKey
:
.
parent
)
self
.
fields
=
try
container
.
decodeIfPresent
(
String
.
self
,
forKey
:
.
fields
)
self
.
count
=
try
container
.
decodeIfPresent
(
Int
.
self
,
forKey
:
.
count
)
self
.
name
=
try
container
.
decodeIfPresent
(
String
.
self
,
forKey
:
.
name
)
self
.
children
=
[]
// Default empty array for children
}
}
// MARK: - Debug Description
extension
MerchantCategoryModel
{
public
override
var
description
:
String
{
return
"""
MerchantCategoryModel {
uuid:
\(
_uuid
)
displayName:
\(
displayName
)
admin_name:
\(
_admin_name
)
image:
\(
cleanImageUrl
)
parent:
\(
_parent
??
"nil"
)
count:
\(
_count
)
hasChildren:
\(
hasChildren
)
}
"""
}
}
SwiftWarplyFramework/SwiftWarplyFramework/screens/MyRewardsViewController/MyRewardsViewController.swift
View file @
52b0897
...
...
@@ -46,6 +46,9 @@ import UIKit
// Merchants data
var
merchants
:
[
MerchantModel
]
=
[]
// Merchant categories data
var
merchantCategories
:
[
MerchantCategoryModel
]
=
[]
// Profile data
var
profileModel
:
ProfileModel
?
var
profileSection
:
SectionModel
?
...
...
@@ -176,9 +179,8 @@ import UIKit
self
.
merchants
=
merchants
print
(
"✅ [MyRewardsViewController] Loaded
\(
merchants
.
count
)
merchants"
)
// For now, create the coupon sets section without filtering
// Later this will be enhanced to filter by merchant categories
self
.
createCouponSetsSection
()
// Load merchant categories after merchants success
self
.
loadMerchantCategories
()
}
failureCallback
:
{
[
weak
self
]
errorCode
in
print
(
"Failed to load merchants:
\(
errorCode
)
"
)
...
...
@@ -187,8 +189,58 @@ import UIKit
}
}
// MARK: - Merchant Categories Loading
private
func
loadMerchantCategories
()
{
// Load merchant categories from WarplySDK
WarplySDK
.
shared
.
getMerchantCategories
{
[
weak
self
]
categories
in
guard
let
self
=
self
,
let
categories
=
categories
else
{
// If categories fail to load, still create coupon sets section without filtering
self
?
.
createCouponSetsSection
()
return
}
self
.
merchantCategories
=
categories
print
(
"✅ [MyRewardsViewController] Loaded
\(
categories
.
count
)
merchant categories"
)
// TODO: Implement category-based filtering for coupon sets sections
// For now, create the standard coupon sets section
self
.
createCouponSetsSection
()
}
failureCallback
:
{
[
weak
self
]
errorCode
in
print
(
"Failed to load merchant categories:
\(
errorCode
)
"
)
// If categories fail, still show coupon sets without filtering
self
?
.
createCouponSetsSection
()
}
}
private
func
createCouponSetsSection
()
{
// Create coupon sets section with real data
// TODO: IMPLEMENT CATEGORY-BASED FILTERING
//
// Current logic: Creates one section with all coupon sets
//
// Future enhancement: Filter coupon sets into different sections based on categories
// Logic:
// 1. For each couponset, get its merchant_uuid
// 2. Find the merchant with that merchant_uuid in self.merchants
// 3. Get the merchant's category_uuid
// 4. Find the category with that category_uuid in self.merchantCategories
// 5. Group coupon sets by category
// 6. Create separate sections for each category
//
// Example structure after filtering:
// - Section: "Εκπαίδευση" (Education) - coupon sets from education merchants
// - Section: "Ψυχαγωγία" (Entertainment) - coupon sets from entertainment merchants
// - etc.
//
// Implementation steps:
// 1. Create a dictionary to group coupon sets by category: [String: [CouponSetItemModel]]
// 2. Iterate through self.couponSets
// 3. For each coupon set, find its merchant and category
// 4. Add coupon set to the appropriate category group
// 5. Create a SectionModel for each category group
// 6. Sort sections by category name or priority
// Current implementation (temporary):
if
!
self
.
couponSets
.
isEmpty
{
let
couponSetsSection
=
SectionModel
(
sectionType
:
.
myRewardsHorizontalCouponsets
,
...
...
Please
register
or
login
to post a comment