Skip to content

Commit 9bf4195

Browse files
authored
Merge pull request #742 from dashpay/feature/piggycards
feat: Merge piggycards to master
2 parents 12cde0b + 963a687 commit 9bf4195

40 files changed

Lines changed: 3437 additions & 635 deletions

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,9 @@ TODO.md
4747
*/Coinbase-Info.plist
4848
*/ZenLedger-Info.plist
4949

50+
# SQLite temporary files
51+
*.db-shm
52+
*.db-wal
53+
5054
# Transifex CLI binary
5155
tx

CLAUDE.md

Lines changed: 1223 additions & 5 deletions
Large diffs are not rendered by default.

DashWallet.xcodeproj/project.pbxproj

Lines changed: 265 additions & 238 deletions
Large diffs are not rendered by default.

DashWallet.xcodeproj/xcshareddata/xcschemes/dashwallet.xcscheme

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
</Testables>
7474
</TestAction>
7575
<LaunchAction
76-
buildConfiguration = "Debug"
76+
buildConfiguration = "Testflight"
7777
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
7878
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
7979
language = "en"
@@ -120,7 +120,7 @@
120120
buildConfiguration = "Debug">
121121
</AnalyzeAction>
122122
<ArchiveAction
123-
buildConfiguration = "Release"
123+
buildConfiguration = "Testflight"
124124
revealArchiveInOrganizer = "YES">
125125
</ArchiveAction>
126126
</Scheme>

DashWallet/Sources/Infrastructure/Database/DatabaseConnection.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,12 @@ extension DatabaseConnection {
7070
}
7171

7272
static func migrations() -> [Migration] {
73-
[SeedDB(), AddGiftCardsTable(), AddIconBitmapsTable()]
73+
return [
74+
SeedDB(),
75+
AddGiftCardsTable(),
76+
AddIconBitmapsTable(),
77+
AddProviderToGiftCardsTable()
78+
]
7479
}
7580

7681
static func migrationsBundle() -> Bundle {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// Created by Andrei Ashikhmin
3+
// Copyright © 2025 Dash Core Group. All rights reserved.
4+
//
5+
// Licensed under the MIT License (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// https://opensource.org/licenses/MIT
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
//
17+
18+
import Foundation
19+
import SQLite
20+
import SQLiteMigrationManager
21+
22+
struct AddProviderToGiftCardsTable: Migration {
23+
var version: Int64 = 20251120150000
24+
25+
func migrateDatabase(_ db: Connection) throws {
26+
try db.run(GiftCard.table.addColumn(GiftCard.provider, defaultValue: nil))
27+
}
28+
}

DashWallet/Sources/Models/Explore Dash/Infrastructure/DAO Impl/MerchantDAO.swift

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,31 @@ class MerchantDAO: PointOfUseDAO {
3535
connection = dbConnection
3636
}
3737

38+
/// Extracts sourceId from a database row, handling String, Int64, Int types and nil/empty values
39+
/// - Parameters:
40+
/// - row: The database row (Statement.Element)
41+
/// - index: The column index for sourceId
42+
/// - Returns: The sourceId as a String, or nil if missing/empty/invalid
43+
private func extractSourceId(from row: Statement.Element, at index: Int) -> String? {
44+
guard row.count > index else { return nil }
45+
46+
if let sourceId = row[index] as? String, !sourceId.isEmpty {
47+
return sourceId
48+
} else if let sourceId = row[index] as? Int64 {
49+
return String(sourceId)
50+
} else if let sourceId = row[index] as? Int {
51+
return String(sourceId)
52+
} else if let sourceId = row[index] {
53+
let stringValue = String(describing: sourceId)
54+
if stringValue == "<null>" || stringValue == "nil" {
55+
return nil
56+
}
57+
return stringValue
58+
}
59+
60+
return nil
61+
}
62+
3863
func items(filters: PointOfUseDAOFilters, offset: Int?,
3964
completion: @escaping (Swift.Result<PaginationResult<Item>, Error>) -> Void) { }
4065

@@ -180,7 +205,7 @@ class MerchantDAO: PointOfUseDAO {
180205
// Only fetch CTX providers when PiggyCards is disabled
181206
#if PIGGYCARDS_ENABLED
182207
let providersQuery = """
183-
SELECT provider, savingsPercentage, denominationsType FROM gift_card_providers
208+
SELECT provider, savingsPercentage, denominationsType, sourceId FROM gift_card_providers
184209
WHERE merchantId = '\(merchant.merchantId)'
185210
"""
186211
#else
@@ -192,21 +217,23 @@ class MerchantDAO: PointOfUseDAO {
192217

193218
do {
194219
guard let db = wSelf.connection.db else {
195-
print("Error: Database connection is nil for merchant \(merchant.merchantId)")
196220
continue
197221
}
198-
199222
let rows = try db.prepare(providersQuery)
200223
var providers: [ExplorePointOfUse.Merchant.GiftCardProviderInfo] = []
224+
var rowCount = 0
201225

202226
for row in rows {
227+
rowCount += 1
203228
if let providerId = row[0] as? String,
204229
let savingsPercentage = row[1] as? Int64,
205230
let denominationsType = row[2] as? String {
231+
206232
providers.append(ExplorePointOfUse.Merchant.GiftCardProviderInfo(
207233
providerId: providerId,
208234
savingsPercentage: Int(savingsPercentage),
209-
denominationsType: denominationsType
235+
denominationsType: denominationsType,
236+
sourceId: wSelf.extractSourceId(from: row, at: 3)
210237
))
211238
}
212239
}
@@ -249,7 +276,7 @@ class MerchantDAO: PointOfUseDAO {
249276
allItems[index] = updatedItem
250277
}
251278
} catch {
252-
print("Error fetching gift card providers for merchant \(merchant.merchantId): \(error)")
279+
// Error fetching gift card providers
253280
}
254281
}
255282
}
@@ -410,7 +437,7 @@ class MerchantDAO: PointOfUseDAO {
410437
// Only fetch CTX providers when PiggyCards is disabled
411438
#if PIGGYCARDS_ENABLED
412439
let providersQuery = """
413-
SELECT provider, savingsPercentage, denominationsType FROM gift_card_providers
440+
SELECT provider, savingsPercentage, denominationsType, sourceId FROM gift_card_providers
414441
WHERE merchantId = '\(merchant.merchantId)'
415442
"""
416443
#else
@@ -422,25 +449,29 @@ class MerchantDAO: PointOfUseDAO {
422449

423450
do {
424451
guard let db = wSelf.connection.db else {
425-
print("Error: Database connection is nil for merchant \(merchant.merchantId)")
426452
continue
427453
}
428454

429455
let rows = try db.prepare(providersQuery)
430456
var providers: [ExplorePointOfUse.Merchant.GiftCardProviderInfo] = []
431-
457+
var rowCount = 0
458+
432459
for row in rows {
460+
rowCount += 1
433461
if let providerId = row[0] as? String,
434462
let savingsPercentage = row[1] as? Int64,
435463
let denominationsType = row[2] as? String {
464+
436465
providers.append(ExplorePointOfUse.Merchant.GiftCardProviderInfo(
437466
providerId: providerId,
438467
savingsPercentage: Int(savingsPercentage),
439-
denominationsType: denominationsType
468+
denominationsType: denominationsType,
469+
sourceId: wSelf.extractSourceId(from: row, at: 3)
440470
))
441471
}
442472
}
443-
473+
474+
444475
if !providers.isEmpty {
445476
// Create updated merchant with providers
446477
let updatedMerchant = ExplorePointOfUse.Merchant(
@@ -480,11 +511,10 @@ class MerchantDAO: PointOfUseDAO {
480511
}
481512
} catch {
482513
// If we can't fetch providers, just continue with empty providers
483-
print("Error fetching gift card providers for merchant \(merchant.merchantId): \(error)")
484514
}
485515
}
486516
}
487-
517+
488518
completion(.success(PaginationResult(items: items, offset: offset)))
489519
} catch {
490520
print(error)
@@ -583,59 +613,59 @@ extension MerchantDAO {
583613
let boundsRadius = min(latDiff, lonDiff) * 111000 / 2 // Convert degrees to meters, divide by 2
584614
let filterRadius = boundsRadius
585615

586-
print("🎯 MerchantDAO.allLocations: Applying circular distance filter with radius=\(filterRadius)m (\(filterRadius/1609.34) miles)")
587616

588617
let initialCount = items.count
589618
items = items.filter { item in
590619
guard let lat = item.latitude, let lon = item.longitude else { return false }
591620
let distance = userCLLocation.distance(from: CLLocation(latitude: lat, longitude: lon))
592621
let isWithinRadius = distance <= filterRadius
593622
if !isWithinRadius {
594-
print("🎯 MerchantDAO.allLocations: Filtering out '\(item.name)' at \(distance/1609.34) miles (outside \(filterRadius/1609.34) mile radius)")
595623
}
596624
return isWithinRadius
597625
}
598626

599-
print("🎯 MerchantDAO.allLocations: After circular distance filtering: \(items.count) locations remain (was \(initialCount))")
600627
}
601628

602629
// Fetch gift card provider information for gift card merchants
603630
for (index, item) in items.enumerated() {
604631
if let merchant = item.merchant, merchant.paymentMethod == .giftCard {
605-
// Only fetch CTX providers when PiggyCards is disabled
606-
#if PIGGYCARDS_ENABLED
632+
// Use parameterized query to avoid SQL injection
607633
let providersQuery = """
608-
SELECT provider, savingsPercentage, denominationsType FROM gift_card_providers
609-
WHERE merchantId = '\(merchant.merchantId)'
634+
SELECT provider, savingsPercentage, denominationsType, sourceId FROM gift_card_providers
635+
WHERE merchantId = ?
610636
"""
611-
#else
612-
let providersQuery = """
613-
SELECT provider, savingsPercentage, denominationsType FROM gift_card_providers
614-
WHERE merchantId = '\(merchant.merchantId)' AND provider = 'CTX'
615-
"""
616-
#endif
617637

618638
do {
619639
guard let db = wSelf.connection.db else {
620640
print("Error: Database connection is nil for merchant \(merchant.merchantId)")
621641
continue
622642
}
623643

624-
let rows = try db.prepare(providersQuery)
644+
let statement = try db.prepare(providersQuery, merchant.merchantId)
625645
var providers: [ExplorePointOfUse.Merchant.GiftCardProviderInfo] = []
646+
var rowCount = 0
647+
648+
for row in statement {
649+
rowCount += 1
650+
// Access columns by index
651+
let providerId = row[0] as? String
652+
let savingsPercentage = row[1] as? Int64
653+
let denominationsType = row[2] as? String
654+
655+
if let providerId = providerId,
656+
let savingsPercentage = savingsPercentage,
657+
let denominationsType = denominationsType {
626658

627-
for row in rows {
628-
if let providerId = row[0] as? String,
629-
let savingsPercentage = row[1] as? Int64,
630-
let denominationsType = row[2] as? String {
631659
providers.append(ExplorePointOfUse.Merchant.GiftCardProviderInfo(
632660
providerId: providerId,
633661
savingsPercentage: Int(savingsPercentage),
634-
denominationsType: denominationsType
662+
denominationsType: denominationsType,
663+
sourceId: wSelf.extractSourceId(from: row, at: 3)
635664
))
636665
}
637666
}
638667

668+
639669
if !providers.isEmpty {
640670
// Create updated merchant with providers
641671
let updatedMerchant = ExplorePointOfUse.Merchant(

0 commit comments

Comments
 (0)