Skip to content

Commit d055dc6

Browse files
aecsocketIMB11
andauthored
Payout flows in backend - fix Tremendous forex cards (#5001)
* wip: payouts flow api * working * Finish up flow migration * vibe-coded frontend changes * fix typos and vue * fix: types --------- Co-authored-by: Calum H. (IMB11) <[email protected]>
1 parent 50a87ba commit d055dc6

17 files changed

+1224
-873
lines changed

apps/frontend/src/components/ui/dashboard/WithdrawFeeBreakdown.vue

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@
1212
<div class="flex items-center justify-between">
1313
<span class="text-primary">{{ formatMessage(messages.feeBreakdownGiftCardValue) }}</span>
1414
<span class="font-semibold text-contrast"
15-
>{{ formatMoney(amount || 0) }} ({{ formattedLocalCurrency }})</span
15+
>{{ formatMoney(amountInUsd) }} ({{ formattedLocalCurrencyAmount }})</span
1616
>
1717
</div>
1818
</template>
1919
<template v-else>
2020
<div class="flex items-center justify-between">
2121
<span class="text-primary">{{ formatMessage(messages.feeBreakdownAmount) }}</span>
22-
<span class="font-semibold text-contrast">{{ formatMoney(amount || 0) }}</span>
22+
<span class="font-semibold text-contrast">{{ formatMoney(amountInUsd) }}</span>
2323
</div>
2424
</template>
2525

@@ -29,7 +29,7 @@
2929
<template v-if="feeLoading">
3030
<LoaderCircleIcon class="size-5 animate-spin !text-secondary" />
3131
</template>
32-
<template v-else>-{{ formatMoney(fee || 0) }}</template>
32+
<template v-else>-{{ formatMoney(feeInUsd) }}</template>
3333
</span>
3434
</div>
3535

@@ -79,9 +79,23 @@ const props = withDefaults(
7979
8080
const { formatMessage } = useVIntl()
8181
82+
const amountInUsd = computed(() => {
83+
if (props.isGiftCard && shouldShowExchangeRate.value) {
84+
return (props.amount || 0) / (props.exchangeRate || 1)
85+
}
86+
return props.amount || 0
87+
})
88+
89+
const feeInUsd = computed(() => {
90+
if (props.isGiftCard && shouldShowExchangeRate.value) {
91+
return (props.fee || 0) / (props.exchangeRate || 1)
92+
}
93+
return props.fee || 0
94+
})
95+
8296
const netAmount = computed(() => {
83-
const amount = props.amount || 0
84-
const fee = props.fee || 0
97+
const amount = amountInUsd.value
98+
const fee = feeInUsd.value
8599
return Math.max(0, amount - fee)
86100
})
87101
@@ -96,6 +110,11 @@ const netAmountInLocalCurrency = computed(() => {
96110
return netAmount.value * (props.exchangeRate || 0)
97111
})
98112
113+
const localCurrencyAmount = computed(() => {
114+
if (!shouldShowExchangeRate.value) return null
115+
return props.amount || 0
116+
})
117+
99118
const formattedLocalCurrency = computed(() => {
100119
if (!shouldShowExchangeRate.value || !netAmountInLocalCurrency.value || !props.localCurrency)
101120
return ''
@@ -112,6 +131,21 @@ const formattedLocalCurrency = computed(() => {
112131
}
113132
})
114133
134+
const formattedLocalCurrencyAmount = computed(() => {
135+
if (!shouldShowExchangeRate.value || !localCurrencyAmount.value || !props.localCurrency) return ''
136+
137+
try {
138+
return new Intl.NumberFormat('en-US', {
139+
style: 'currency',
140+
currency: props.localCurrency,
141+
minimumFractionDigits: 2,
142+
maximumFractionDigits: 2,
143+
}).format(localCurrencyAmount.value)
144+
} catch {
145+
return `${props.localCurrency} ${localCurrencyAmount.value.toFixed(2)}`
146+
}
147+
})
148+
115149
const messages = defineMessages({
116150
feeBreakdownAmount: {
117151
id: 'dashboard.creator-withdraw-modal.fee-breakdown-amount',

apps/frontend/src/components/ui/dashboard/withdraw-stages/TremendousDetailsStage.vue

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,14 @@
9090
</Combobox>
9191
</div>
9292
<span v-if="selectedMethodDetails" class="text-secondary">
93-
{{ formatMoney(fixedDenominationMin ?? effectiveMinAmount)
93+
{{
94+
formatMoney(
95+
selectedMethodCurrencyCode &&
96+
selectedMethodCurrencyCode !== 'USD' &&
97+
selectedMethodExchangeRate
98+
? (fixedDenominationMin ?? effectiveMinAmount) / selectedMethodExchangeRate
99+
: (fixedDenominationMin ?? effectiveMinAmount),
100+
)
94101
}}<template v-if="selectedMethodCurrencyCode && selectedMethodCurrencyCode !== 'USD'">
95102
({{
96103
formatAmountForDisplay(
@@ -103,9 +110,15 @@
103110
min,
104111
{{
105112
formatMoney(
106-
fixedDenominationMax ??
107-
selectedMethodDetails.interval?.standard?.max ??
108-
effectiveMaxAmount,
113+
selectedMethodCurrencyCode &&
114+
selectedMethodCurrencyCode !== 'USD' &&
115+
selectedMethodExchangeRate
116+
? (fixedDenominationMax ??
117+
selectedMethodDetails.interval?.standard?.max ??
118+
effectiveMaxAmount) / selectedMethodExchangeRate
119+
: (fixedDenominationMax ??
120+
selectedMethodDetails.interval?.standard?.max ??
121+
effectiveMaxAmount),
109122
)
110123
}}<template v-if="selectedMethodCurrencyCode && selectedMethodCurrencyCode !== 'USD'">
111124
({{
@@ -124,7 +137,15 @@
124137
v-if="selectedMethodDetails && effectiveMinAmount > roundedMaxAmount"
125138
class="text-sm text-red"
126139
>
127-
You need at least {{ formatMoney(effectiveMinAmount)
140+
You need at least
141+
{{
142+
formatMoney(
143+
selectedMethodCurrencyCode &&
144+
selectedMethodCurrencyCode !== 'USD' &&
145+
selectedMethodExchangeRate
146+
? effectiveMinAmount / selectedMethodExchangeRate
147+
: effectiveMinAmount,
148+
)
128149
}}<template v-if="selectedMethodCurrencyCode && selectedMethodCurrencyCode !== 'USD'">
129150
({{
130151
formatAmountForDisplay(
@@ -186,7 +207,7 @@
186207
formatMessage(messages.balanceWorthHint, {
187208
usdBalance: formatMoney(roundedMaxAmount),
188209
localBalance: formatAmountForDisplay(
189-
roundedMaxAmount,
210+
roundedMaxAmount * selectedMethodExchangeRate,
190211
selectedMethodCurrencyCode,
191212
selectedMethodExchangeRate,
192213
),
@@ -252,7 +273,7 @@
252273
formatMessage(messages.balanceWorthHint, {
253274
usdBalance: formatMoney(roundedMaxAmount),
254275
localBalance: formatAmountForDisplay(
255-
roundedMaxAmount,
276+
roundedMaxAmount * selectedMethodExchangeRate,
256277
selectedMethodCurrencyCode,
257278
selectedMethodExchangeRate,
258279
),
@@ -573,14 +594,13 @@ const giftCardExchangeRate = computed(() => {
573594
})
574595
575596
function formatAmountForDisplay(
576-
usdAmount: number,
597+
localAmount: number,
577598
currencyCode: string | null | undefined,
578599
rate: number | null | undefined,
579600
): string {
580601
if (!currencyCode || currencyCode === 'USD' || !rate) {
581-
return formatMoney(usdAmount)
602+
return formatMoney(localAmount)
582603
}
583-
const localAmount = usdAmount * rate
584604
try {
585605
return new Intl.NumberFormat('en-US', {
586606
style: 'currency',

apps/frontend/src/providers/creator-withdraw.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,6 @@ export interface PayoutMethod {
4040
category?: string
4141
image_url: string | null
4242
image_logo_url: string | null
43-
fee: {
44-
percentage: number
45-
min: number
46-
max: number | null
47-
}
4843
interval: {
4944
standard: {
5045
min: number
@@ -130,6 +125,7 @@ export interface TaxData {
130125
export interface CalculationData {
131126
amount: number
132127
fee: number | null
128+
netUsd: number | null
133129
exchangeRate: number | null
134130
}
135131

@@ -400,6 +396,7 @@ export function createWithdrawContext(
400396
calculation: {
401397
amount: 0,
402398
fee: null,
399+
netUsd: null,
403400
exchangeRate: null,
404401
},
405402
providerData: {
@@ -841,14 +838,20 @@ export function createWithdrawContext(
841838
apiVersion: 3,
842839
method: 'POST',
843840
body: payload,
844-
})) as { fee: number | string | null; exchange_rate: number | string | null }
841+
})) as {
842+
net_usd: number | string | null
843+
fee: number | string | null
844+
exchange_rate: number | string | null
845+
}
845846

846847
const parsedFee = response.fee ? Number.parseFloat(String(response.fee)) : 0
848+
const parsedNetUsd = response.net_usd ? Number.parseFloat(String(response.net_usd)) : null
847849
const parsedExchangeRate = response.exchange_rate
848850
? Number.parseFloat(String(response.exchange_rate))
849851
: null
850852

851853
withdrawData.value.calculation.fee = parsedFee
854+
withdrawData.value.calculation.netUsd = parsedNetUsd
852855
withdrawData.value.calculation.exchangeRate = parsedExchangeRate
853856

854857
return {
@@ -872,7 +875,9 @@ export function createWithdrawContext(
872875
created: new Date(),
873876
amount: withdrawData.value.calculation.amount,
874877
fee: withdrawData.value.calculation.fee || 0,
875-
netAmount: withdrawData.value.calculation.amount - (withdrawData.value.calculation.fee || 0),
878+
netAmount:
879+
withdrawData.value.calculation.netUsd ??
880+
withdrawData.value.calculation.amount - (withdrawData.value.calculation.fee || 0),
876881
methodType: getMethodDisplayName(withdrawData.value.selection.method),
877882
recipientDisplay: getRecipientDisplay(withdrawData.value),
878883
}

apps/labrinth/.sqlx/query-1adbd24d815107e13bc1440c7a8f4eeff66ab4165a9f4980032e114db4dc1286.json

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/labrinth/.sqlx/query-76cf88116014e3151db2f85b0bd30dba70ae62f9f922ed711dbb85fcf4ec5f7c.json

Lines changed: 0 additions & 15 deletions
This file was deleted.

apps/labrinth/.sqlx/query-b92b5bb7d179c4fcdbc45600ccfd2402f52fea71e27b08e7926fcc2a9e62c0f3.json

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/labrinth/.sqlx/query-cd5ccd618fb3cc41646a6de86f9afedb074492b4ec7f2457c14113f5fd13aa02.json

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/labrinth/.sqlx/query-cec4240c7c848988b3dfd13e3f8e5c93783c7641b019fdb698a1ec0be1393606.json

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/labrinth/src/models/v3/payouts.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ impl Payout {
4949
}
5050
}
5151

52+
#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
53+
pub struct Withdrawal {
54+
pub amount: Decimal,
55+
#[serde(flatten)]
56+
pub method: PayoutMethodRequest,
57+
pub method_id: String,
58+
}
59+
5260
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
5361
#[serde(tag = "method", rename_all = "lowercase")]
5462
#[expect(
@@ -238,14 +246,13 @@ pub struct PayoutMethod {
238246
pub image_url: Option<String>,
239247
pub image_logo_url: Option<String>,
240248
pub interval: PayoutInterval,
241-
pub fee: PayoutMethodFee,
242249
pub currency_code: Option<String>,
243250
/// USD to the given `currency_code`.
244251
#[serde(with = "rust_decimal::serde::float_option")]
245252
pub exchange_rate: Option<Decimal>,
246253
}
247254

248-
#[derive(Serialize, Deserialize, Clone)]
255+
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
249256
pub struct PayoutMethodFee {
250257
#[serde(with = "rust_decimal::serde::float")]
251258
pub percentage: Decimal,

0 commit comments

Comments
 (0)