Add Revolut fiat payment provider#211
Add Revolut fiat payment provider#211claudinethelobster wants to merge 1 commit intoEdgeApp:masterfrom
Conversation
|
The fixup commit should have been squashed. If the fixup was apart of review feedback then it should be prefixed with |
- Created src/partners/revolut.ts following moonpay.ts pattern - Created src/routes/v1/revolut.ts REST endpoint - Updated src/indexApi.ts to register revolut router - Updated src/demo/partners.ts with revolut entry (fiat, #191C33) - Updated src/queryEngine.ts to register revolut plugin
bb46b01 to
515be6a
Compare
|
Done — the fixup was squashed into the original commit (now |
samholmes
left a comment
There was a problem hiding this comment.
See inline comments for specific issues.
| let cursor: string | undefined | ||
|
|
||
| while (true) { | ||
| const from = new Date(startTime).toISOString() | ||
| const to = new Date(endTime).toISOString() | ||
|
|
||
| let url = `https://api.revolut.com/partner/v1/transactions?from=${from}&to=${to}&limit=${QUERY_LIMIT}` | ||
| if (cursor != null) url += `&cursor=${cursor}` | ||
|
|
||
| datelog(`Querying Revolut from:${from} to:${to}`) | ||
|
|
||
| const response = await retryFetch(url, { | ||
| headers: { | ||
| Authorization: `Bearer ${apiKey}` | ||
| } | ||
| }) | ||
| if (!response.ok) { | ||
| const text = await response.text() | ||
| throw new Error(text) | ||
| } | ||
|
|
||
| const jsonObj = await response.json() | ||
| const result = asRevolutResult(jsonObj) | ||
| cursor = result.next_cursor | ||
|
|
||
| for (const rawTx of result.transactions) { | ||
| if (asPreRevolutTx(rawTx).state === 'completed') { | ||
| const standardTx = processRevolutTx(rawTx) | ||
| standardTxs.push(standardTx) | ||
| if (standardTx.isoDate > latestIsoDate) { | ||
| latestIsoDate = standardTx.isoDate | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (result.transactions.length > 0) { | ||
| datelog(`Revolut txs ${result.transactions.length}`) | ||
| } | ||
|
|
||
| if (cursor == null) { | ||
| break | ||
| } | ||
| } |
There was a problem hiding this comment.
Issue: The inner pagination loop only exits when cursor == null. If the API returns a repeated, empty-string, or non-advancing cursor, this loop runs indefinitely until the outer promiseTimeout kills it—delaying progress and producing noisy retries.
Recommendation: Add a max-pages guard and track the previous cursor to break on non-advancing values:
const MAX_PAGES = 1000
let cursor: string | undefined
let pages = 0
while (true) {
// ... existing fetch logic ...
const prevCursor = cursor
cursor = result.next_cursor
// ... existing tx processing ...
pages++
if (cursor == null || cursor === prevCursor || pages >= MAX_PAGES) {
break
}
}| export function processRevolutTx(rawTx: unknown): StandardTx { | ||
| const tx = asRevolutTx(rawTx) | ||
| const { isoDate, timestamp } = smartIsoDateFromTimestamp( | ||
| tx.created_at.getTime() | ||
| ) | ||
|
|
||
| const direction = tx.type | ||
| const depositTxid = direction === 'sell' ? tx.tx_hash : undefined | ||
| const payoutTxid = direction === 'buy' ? tx.tx_hash : undefined | ||
|
|
||
| const standardTx: StandardTx = { | ||
| status: 'complete', | ||
| orderId: tx.id, | ||
| countryCode: tx.country_code ?? null, | ||
| depositTxid, | ||
| depositAddress: undefined, | ||
| depositCurrency: | ||
| direction === 'buy' | ||
| ? tx.fiat_currency.toUpperCase() | ||
| : tx.crypto_currency.toUpperCase(), | ||
| depositAmount: direction === 'buy' ? tx.fiat_amount : tx.crypto_amount, | ||
| direction, | ||
| exchangeType: 'fiat', | ||
| paymentType: getRevolutPaymentType(tx), | ||
| payoutTxid, | ||
| payoutAddress: tx.wallet_address, | ||
| payoutCurrency: | ||
| direction === 'buy' | ||
| ? tx.crypto_currency.toUpperCase() | ||
| : tx.fiat_currency.toUpperCase(), | ||
| payoutAmount: direction === 'buy' ? tx.crypto_amount : tx.fiat_amount, | ||
| timestamp, | ||
| isoDate, | ||
| usdValue: -1, | ||
| rawTx | ||
| } | ||
| return standardTx | ||
| } |
There was a problem hiding this comment.
Suggestion: This mapping logic is high-impact for reporting accuracy and currently untested. Consider adding focused unit tests covering buy/sell direction, deposit/payout currency assignment, txid placement, and payment-method conversion.
Recommendation: Add a test file such as test/partners/revolut.test.ts:
import { expect } from 'chai'
import { processRevolutTx, getRevolutPaymentType } from '../../src/partners/revolut'
describe('processRevolutTx', () => {
const baseTx = {
id: 'order-1',
type: 'buy',
state: 'completed',
created_at: '2024-06-01T12:00:00.000Z',
fiat_amount: 100,
fiat_currency: 'usd',
crypto_amount: 0.005,
crypto_currency: 'btc',
wallet_address: 'bc1abc',
tx_hash: '0xabc',
country_code: 'US',
payment_method: 'card'
}
it('should map a buy tx with fiat deposit and crypto payout', () => {
const result = processRevolutTx(baseTx)
expect(result.direction).to.equal('buy')
expect(result.depositCurrency).to.equal('USD')
expect(result.payoutCurrency).to.equal('BTC')
expect(result.payoutTxid).to.equal('0xabc')
expect(result.depositTxid).to.be.undefined
})
it('should map a sell tx with crypto deposit and fiat payout', () => {
const result = processRevolutTx({ ...baseTx, type: 'sell' })
expect(result.direction).to.equal('sell')
expect(result.depositCurrency).to.equal('BTC')
expect(result.payoutCurrency).to.equal('USD')
expect(result.depositTxid).to.equal('0xabc')
expect(result.payoutTxid).to.be.undefined
})
})
Summary
Implements Revolut fiat payment provider following the moonpay.ts pattern.
Changes
src/partners/revolut.ts- Partner plugin for Revolut API integrationsrc/routes/v1/revolut.ts- REST endpoint for Revolut transactionssrc/indexApi.ts- Registered revolut routersrc/demo/partners.ts- Added revolut entry (type: fiat, color: #191C33)src/queryEngine.ts- Registered revolut pluginImplementation Details
Testing