Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion FlipcashCore/Sources/FlipcashCore/Models/TipFloor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ extension TipFloor {

/// The floor for the tip that *opens* a DM with a recipient: the fee they
/// charge, restated in `currency`, falling back to the regional preset when
/// they charge nothing. Nil when neither is known.
/// they charge nothing or their fee is below it. Nil when neither is known.
///
/// The fallback also covers a fee that can't be converted — stating a floor
/// in a currency the entry isn't using would be worse than stating the
Expand All @@ -67,11 +67,24 @@ extension TipFloor {
// Rounded up: a half-up floor can land under the fee, pass `isMet`,
// and be denied by the server.
if let fee = recipientFee?.converted(to: currency, rates: rates, roundingUp: true), fee.isPositive {
// Every tip carries the regional minimum, so a fee under it would
// pass the fee check and be denied for the minimum.
if let presets, !clears(fee, presets, rates: rates) {
return .preset(presets)
}
return .recipientFee(fee)
}
return systemMinimum(presets: presets)
}

/// Whether `fee` meets the preset row's minimum, compared the way
/// `TipPresets.meetsMinimum` compares an entry. A fee that can't be
/// restated in the row's currency keeps its own floor.
private static func clears(_ fee: FiatAmount, _ presets: UserFlags.TipPresets, rates: [CurrencyCode: Rate]) -> Bool {
guard let restated = fee.converted(to: presets.currency, rates: rates) else { return true }
return restated.value >= presets.minimum
}

/// The regional minimum every tip carries, regardless of recipient.
public static func systemMinimum(presets: UserFlags.TipPresets?) -> TipFloor? {
presets.map { .preset($0) }
Expand Down
37 changes: 37 additions & 0 deletions FlipcashCore/Tests/FlipcashCoreTests/TipFloorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,43 @@ struct TipFloorTests {
#expect(floor == .preset(presets))
}

@Test("A fee below the regional minimum is raised to the minimum")
func feeBelowPresetUsesPreset() {
let floor = TipFloor.toOpenDM(
recipientFee: FiatAmount(value: Decimal(string: "0.50")!, currency: .usd),
presets: presets,
in: .usd,
rates: rates
)

#expect(floor == .preset(presets))
}

@Test("A fee below the USD fallback minimum is raised to it")
func feeBelowFallbackPresetUsesPreset() {
// CAD 1 is USD 0.50, under the USD row's minimum of 1.
let floor = TipFloor.toOpenDM(
recipientFee: FiatAmount(value: 1, currency: .cad),
presets: presets,
in: .cad,
rates: rates
)

#expect(floor == .preset(presets))
}

@Test("A fee at the regional minimum stays the fee")
func feeAtPresetStaysFee() {
let floor = TipFloor.toOpenDM(
recipientFee: FiatAmount(value: 2, currency: .cad),
presets: presets,
in: .cad,
rates: rates
)

#expect(floor == .recipientFee(FiatAmount(value: 2, currency: .cad)))
}

@Test("No fee and no presets leaves no floor")
func nilWithoutEither() {
#expect(TipFloor.toOpenDM(recipientFee: nil, presets: nil, in: .usd, rates: rates) == nil)
Expand Down
Loading