Skip to content

Commit

Permalink
Merge branch 'paul/paybis' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
paullinator committed Mar 30, 2024
2 parents b10c997 + c34177b commit c5f855e
Show file tree
Hide file tree
Showing 7 changed files with 312 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- added: Add Lifi reporting
- added: Kado reporting
- added: Paybis support
- changed: Paginate caching engine to prevent timeouts
- changed: Create caching engine 'initialized' document entry for each app:partner pair

Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"build.lib": "sucrase -q -t typescript,imports,jsx -d ./lib ./src",
"build.types": "tsc",
"build.dist": "parcel build",
"clean": "rimraf lib dist",
"clean": "rimraf lib dist .parcel-cache",
"fix": "npm run lint -- --fix",
"fio:promo": "node -r sucrase/register src/bin/fioPromo/fioPromo.ts",
"precommit": "lint-staged && npm run prepare",
Expand Down Expand Up @@ -59,6 +59,7 @@
"react-loader-spinner": "^3.1.14",
"react-router-dom": "^5.2.0",
"recharts": "^1.8.5",
"url-parse": "^1.5.10",
"web3": "^1.2.11"
},
"devDependencies": {
Expand All @@ -68,6 +69,7 @@
"@types/react": "^16.9.22",
"@types/react-dom": "^16.9.5",
"@types/react-router-dom": "^5.3.3",
"@types/url-parse": "^1.4.11",
"@typescript-eslint/eslint-plugin": "^5.36.2",
"@typescript-eslint/parser": "^5.36.2",
"assert": "^2.0.0",
Expand Down
4 changes: 4 additions & 0 deletions src/demo/partners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ export default {
type: 'fiat',
color: '#7214F5'
},
paybis: {
type: 'fiat',
color: '#FFB400'
},
paytrie: {
type: 'fiat',
color: '#99A5DE'
Expand Down
17 changes: 17 additions & 0 deletions src/initDbs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ const transactionIndexFields: string[][] = [
['timestamp']
]

const transactionIndexFieldsNoPartition: string[][] = [
['depositAddress'],
['payoutAddress']
]

interface Index {
index: { fields: string[] }
ddoc: string
Expand Down Expand Up @@ -46,6 +51,18 @@ transactionIndexFields.forEach(index => {
transactionIndexes.push(out2)
})

transactionIndexFieldsNoPartition.forEach(index => {
const indexLower = index.map(i => i.toLowerCase())
const out: Index = {
index: { fields: index },
ddoc: indexLower.join('-'),
name: indexLower.join('-'),
type: 'json',
partitioned: false
}
transactionIndexes.push(out)
})

const cacheIndexes: Index[] = [
{
index: { fields: ['timestamp'] },
Expand Down
279 changes: 279 additions & 0 deletions src/partners/paybis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
import {
asArray,
asDate,
asMaybe,
asNumber,
asObject,
asOptional,
asString,
asUnknown,
asValue
} from 'cleaners'
import URL from 'url-parse'

import {
asStandardPluginParams,
EDGE_APP_START_DATE,
PartnerPlugin,
PluginParams,
PluginResult,
StandardTx,
Status
} from '../types'
import { datelog, retryFetch, smartIsoDateFromTimestamp, snooze } from '../util'

const PLUGIN_START_DATE = '2023-09-01T00:00:00.000Z'
const asStatuses = asMaybe(
asValue('created', 'completed', 'cancelled', 'payment_error', 'rejected'),
'other'
)
type PartnerStatuses = ReturnType<typeof asStatuses>

// Basic structures
const asCurrencyCode = asString
const asAmount = asString
const asCurrency = asObject({
amount: asAmount,
currency: asCurrencyCode
})
// const asUserCountry = asObject({
// name: asString,
// code: asString
// })
// const asUser = asObject({
// id: asString,
// email: asString,
// country: asUserCountry
// })
// const asExchangeRate = asObject({
// currencyTo: asCurrency,
// currencyFrom: asCurrency
// })
// const asFee = asObject({
// amount: asOptional(asString), // Optional because some fees may be null
// currency: asOptional(asString)
// })

// More complex structures
// const asBlockchain = asObject({
// name: asString,
// network: asString
// })
// const asCurrencyDetail = asObject({
// id: asString,
// name: asString,
// currency: asObject({
// code: asCurrencyCode
// }),
// blockchain: asOptional(asBlockchain)
// })
const asFromToStructure = asObject({
// name: asString,
// asset: asOptional(asCurrencyDetail),
address: asOptional(asString)
// destinationTag: asOptional(asString)
})
const asAmounts = asObject({
spentOriginal: asCurrency,
spentFiat: asCurrency,
receivedOriginal: asCurrency,
receivedFiat: asCurrency
})
// const asFees = asObject({
// paybisFee: asFee,
// paymentFee: asFee,
// networkFee: asFee,
// partnerFee: asFee,
// partnerFeeFiat: asFee
// })
// const asRequest = asObject({
// id: asString,
// flow: asString,
// createdAt: asDate
// })
const asTransaction = asObject({
id: asString,
gateway: asValue('crypto_to_fiat', 'fiat_to_crypto'),
status: asString,
from: asFromToStructure,
to: asFromToStructure,
// exchangeRate: asExchangeRate,
hash: asOptional(asString),
// explorerLink: asOptional(asString),
createdAt: asDate,
// paidAt: asOptional(asDate),
// completedAt: asOptional(asDate),
amounts: asAmounts
// fees: asFees,
// user: asUser,
// request: asRequest
})
const asMeta = asObject({
// limit: asNumber,
// currentCursor: asOptional(asString),
nextCursor: asOptional(asString)
})

// Cleaner for the entire structure
const asTransactions = asObject({
data: asArray(asUnknown),
meta: asMeta
})

/** Max fetch retries before bailing */
const MAX_RETRIES = 5

/** How many txs to query per fetch call */
const QUERY_LIMIT_TXS = 50

/**
* How far to rollback from the last successful query
* date when starting a new query
*/
const QUERY_LOOKBACK = 1000 * 60 * 60 * 24 * 30 // 30 days

/** Time period to query per loop */
const QUERY_TIME_BLOCK_MS = QUERY_LOOKBACK

const URLS = {
prod: 'https://widget-api.paybis.com/v2/transactions',
sandbox: 'https://widget-api.sandbox.paybis.com/v2/transactions'
}

const statusMap: { [key in PartnerStatuses]: Status } = {
created: 'pending',
cancelled: 'refunded',
payment_error: 'refunded',
completed: 'complete',
rejected: 'refunded',
other: 'other'
}

export async function queryPaybis(
pluginParams: PluginParams
): Promise<PluginResult> {
const { settings, apiKeys } = asStandardPluginParams(pluginParams)
const { apiKey } = apiKeys
let { latestIsoDate } = settings

if (latestIsoDate === EDGE_APP_START_DATE) {
latestIsoDate = new Date(PLUGIN_START_DATE).toISOString()
}

let lastCheckedTimestamp = new Date(latestIsoDate).getTime() - QUERY_LOOKBACK
if (lastCheckedTimestamp < 0) lastCheckedTimestamp = 0

const ssFormatTxs: StandardTx[] = []
let retry = 0
let startTime = lastCheckedTimestamp

while (true) {
const endTime = startTime + QUERY_TIME_BLOCK_MS
const now = Date.now()

try {
let cursor: string | undefined

while (true) {
const urlObj = new URL(URLS.prod, true)

const queryParams: any = {
from: new Date(startTime).toISOString(),
to: new Date(endTime).toISOString(),
limit: QUERY_LIMIT_TXS
}
if (cursor != null) queryParams.cursor = cursor

urlObj.set('query', queryParams)
const url = urlObj.href

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 txs = asTransactions(jsonObj)
cursor = txs.meta.nextCursor
for (const rawTx of txs.data) {
const tx = asTransaction(rawTx)
const { amounts, createdAt, gateway, hash, id } = tx
const { spentOriginal, receivedOriginal } = amounts

const { isoDate, timestamp } = smartIsoDateFromTimestamp(
createdAt.getTime()
)

const depositAmount = Number(spentOriginal.amount)
const payoutAmount = Number(receivedOriginal.amount)
const depositTxid = gateway === 'crypto_to_fiat' ? hash : undefined
const payoutTxid = gateway === 'fiat_to_crypto' ? hash : undefined

const ssTx: StandardTx = {
status: statusMap[tx.status],
orderId: id,
depositTxid,
depositAddress: undefined,
depositCurrency: spentOriginal.currency,
depositAmount,
payoutTxid,
payoutAddress: tx.to.address ?? undefined,
payoutCurrency: receivedOriginal.currency,
payoutAmount,
timestamp,
isoDate,
usdValue: -1,
rawTx
}
ssFormatTxs.push(ssTx)
if (ssTx.isoDate > latestIsoDate) {
latestIsoDate = ssTx.isoDate
}
}
if (cursor == null) {
break
} else {
datelog(`Get nextCursor: ${cursor}`)
}
}

const endDate = new Date(endTime)
startTime = endTime
datelog(
`Paybis endDate:${endDate.toISOString()} latestIsoDate:${latestIsoDate}`
)
if (endTime > now) {
break
}
retry = 0
} catch (e) {
datelog(e)
// Retry a few times with time delay to prevent throttling
retry++
if (retry <= MAX_RETRIES) {
datelog(`Snoozing ${60 * retry}s`)
await snooze(60000 * retry)
} else {
// We can safely save our progress since we go from oldest to newest.
break
}
}
await snooze(1000)
}

const out = {
settings: { latestIsoDate },
transactions: ssFormatTxs
}
return out
}

export const paybis: PartnerPlugin = {
queryFunc: queryPaybis,
pluginName: 'Paybis',
pluginId: 'paybis'
}
2 changes: 2 additions & 0 deletions src/queryEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { letsexchange } from './partners/letsexchange'
import { libertyx } from './partners/libertyx'
import { lifi } from './partners/lifi'
import { moonpay } from './partners/moonpay'
import { paybis } from './partners/paybis'
import { paytrie } from './partners/paytrie'
import { safello } from './partners/safello'
import { sideshift } from './partners/sideshift'
Expand Down Expand Up @@ -57,6 +58,7 @@ const plugins = [
libertyx,
lifi,
moonpay,
paybis,
paytrie,
safello,
sideshift,
Expand Down
7 changes: 6 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,11 @@
resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz"
integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==

"@types/url-parse@^1.4.11":
version "1.4.11"
resolved "https://registry.yarnpkg.com/@types/url-parse/-/url-parse-1.4.11.tgz#01f8faa7b8bfd438e5f5efb8337a74513a15602b"
integrity sha512-FKvKIqRaykZtd4n47LbK/W/5fhQQ1X7cxxzG9A48h0BGN+S04NH7ervcCjM8tyR0lyGru83FAHSmw2ObgKoESg==

"@typescript-eslint/eslint-plugin@^5.36.2":
version "5.62.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz#aeef0328d172b9e37d9bab6dbc13b87ed88977db"
Expand Down Expand Up @@ -7275,7 +7280,7 @@ url-parse-lax@^3.0.0:
dependencies:
prepend-http "^2.0.0"

url-parse@^1.5.3:
url-parse@^1.5.10, url-parse@^1.5.3:
version "1.5.10"
resolved "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz"
integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
Expand Down

0 comments on commit c5f855e

Please sign in to comment.