Files
personal-site/pages/api/v1/rates.js
T

357 lines
14 KiB
JavaScript
Raw Normal View History

2025-12-09 07:27:49 +01:00
import { log } from "console";
2024-01-17 12:32:46 +01:00
const COMMON_HEADERS = {
'accept': '*/*',
'accept-language': 'en-GB,en-US;q=0.9,en;q=0.8',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36'
};
const createTapTapHeaders = () => {
const headers = new Headers();
Object.entries(COMMON_HEADERS).forEach(([key, value]) => headers.append(key, value));
headers.append('authority', 'api.taptapsend.com');
headers.append('appian-version', 'web/2022-05-03.0');
headers.append('origin', 'https://www.taptapsend.com');
headers.append('referer', 'https://www.taptapsend.com/');
headers.append('sec-ch-ua', '"Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"');
headers.append('sec-fetch-site', 'same-site');
headers.append('x-device-id', 'web');
headers.append('x-device-model', 'web');
headers.append('Cookie', 'SESSION=NTI4ZGZmOGEtZGZkNS00ZGUwLWE2MmYtMDA4OWNjZGZlZjkx');
return headers;
};
const createFlutterwaveHeaders = () => {
const headers = new Headers();
Object.entries(COMMON_HEADERS).forEach(([key, value]) => headers.append(key, value));
headers.append('authority', 'sendgateway.myflutterwave.com');
headers.append('accept', 'application/json, text/plain, */*');
headers.append('origin', 'https://send.flutterwave.com');
headers.append('referer', 'https://send.flutterwave.com/');
headers.append('sec-ch-ua', '"Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"');
headers.append('sec-fetch-site', 'cross-site');
headers.append('user-agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36');
return headers;
};
const createNalaHeaders = () => {
const headers = new Headers();
Object.entries(COMMON_HEADERS).forEach(([key, value]) => headers.append(key, value));
headers.append('accept-language', 'en-GB,en;q=0.9,nl-NL;q=0.8,nl;q=0.7,en-US;q=0.6');
headers.append('origin', 'https://www.nala.com');
headers.append('priority', 'u=1, i');
headers.append('referer', 'https://www.nala.com/');
headers.append('sec-ch-ua', '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"');
headers.append('sec-fetch-site', 'cross-site');
headers.append('user-agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36');
return headers;
};
const createAfriexHeaders = () => {
const headers = new Headers();
Object.entries(COMMON_HEADERS).forEach(([key, value]) => headers.append(key, value));
headers.append('accept-language', 'en-GB,en;q=0.9,nl-NL;q=0.8,nl;q=0.7,en-US;q=0.6');
headers.append('if-none-match', 'W/"acc-nnqCARN36oEAk9YJsCdpfbMyZVQ"');
headers.append('origin', 'https://www.afriex.com');
headers.append('priority', 'u=1, i');
headers.append('referer', 'https://www.afriex.com/');
headers.append('sec-ch-ua', '"Not;A=Brand";v="99", "Google Chrome";v="139", "Chromium";v="139"');
headers.append('sec-fetch-site', 'cross-site');
headers.append('user-agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36');
return headers;
};
const createDefaultErrorResponse = (provider, href) => ({
rate: 0,
provider,
bestRate: false,
href
});
2023-12-14 11:48:37 +01:00
export default async (req, res) => {
2024-05-28 02:13:04 +02:00
let rates = [];
2023-12-14 11:48:37 +01:00
const tapTapCall = fetch(
2024-05-28 02:13:04 +02:00
"https://api.taptapsend.com/api/fxRates",
{
method: "GET",
headers: createTapTapHeaders(),
redirect: "follow",
}
2024-05-28 02:04:32 +02:00
)
2024-05-28 02:13:04 +02:00
.then((response) => response.json())
.then((result) => {
2024-12-21 19:04:35 +01:00
const nlRates = result.availableCountries.find((availableCountry) => {
return availableCountry.isoCountryCode === "NL"
});
let fxRate = 0;
2024-12-21 19:04:35 +01:00
if (nlRates) {
for (let corridor of nlRates.corridors) {
if (corridor.isoCountryCode === "NG") {
fxRate = corridor.fxRate;
break;
}
2024-12-21 19:04:35 +01:00
}
}
2024-05-28 02:13:04 +02:00
return {
2024-12-21 19:04:35 +01:00
rate: fxRate,
2024-05-28 02:13:04 +02:00
provider: "TapTap",
bestRate: false,
href: "https://www.taptapsend.com/",
2025-12-09 07:27:49 +01:00
logo: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSFcB9ECgZxEULIhxf-PZWEn23UPZATQZblQQ&s"
2024-05-28 02:13:04 +02:00
};
})
2024-10-13 13:45:44 +02:00
.catch((error) => {
console.error("Error calling TapTap:", error);
return createDefaultErrorResponse("TapTap", "https://www.taptapsend.com/");
2024-10-13 13:45:44 +02:00
});
2024-05-28 02:13:04 +02:00
const sendCall = fetch(
2024-05-28 02:13:04 +02:00
"https://sendgateway.myflutterwave.com/api/v1/config/getcurrencyrate?fromCurrency=EUR&toCurrency=NGN",
{
method: "GET",
headers: createFlutterwaveHeaders(),
redirect: "follow",
}
2024-05-28 02:13:04 +02:00
)
.then((response) => response.json())
.then((response) => {
return {
rate: parseFloat(response.data[0].baseRate).toFixed(2),
provider: "Send",
bestRate: false,
href: "https://send.flutterwave.com/",
2025-12-09 07:27:49 +01:00
logo: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRyhQXYEg8iHCMajGdJn0B0FxwsuzAfkSeBgQ&s"
2024-05-28 02:13:04 +02:00
};
})
.catch((error) => {
console.error("Error calling Send:", error);
return createDefaultErrorResponse("Send", "https://send.flutterwave.com/");
});
2024-05-28 02:13:04 +02:00
2025-12-09 07:27:49 +01:00
// Ace Transfer temporarily disabled due to Cloudflare protection
// Will return a default response to avoid blocking the API
const aceCall = Promise.resolve(
createDefaultErrorResponse("Ace Transfer", "https://acemoneytransfer.com/referral-link/3056004")
);
2024-05-28 02:13:04 +02:00
const remitlyCall = new Promise((resolve, reject) => {
2024-10-13 13:45:44 +02:00
fetch("https://api.remitly.io/v5/pricing/estimates?amount=1%20EUR&anchor=SEND&conduit=NLD%3AEUR-NGA%3ANGN&purpose=OTHER", {
headers: {
accept: "application/json",
2024-07-18 06:58:26 +02:00
},
referrerPolicy: "no-referrer",
body: null,
method: "GET",
mode: "cors",
credentials: "omit"
2024-07-18 06:58:26 +02:00
}).then(result => resolve(result.json())).catch(error => reject(error));
2024-05-28 02:13:04 +02:00
})
.then((result) => {
return {
2024-10-04 09:59:27 +02:00
rate: parseFloat(result[0].exchange_rate_info.base_rate).toFixed(2),
2024-05-28 02:13:04 +02:00
provider: "Remitly",
bestRate: false,
href: "https://remit.ly/94fqcne9",
2025-12-09 07:27:49 +01:00
logo: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRNZm0irEt8u6LXo-OFXZ9bcTchNoSeaDDnsA&s"
2024-05-28 02:13:04 +02:00
};
})
.catch((error) => {
console.error("Error calling Remitly:", error);
return createDefaultErrorResponse("Remitly", "https://remit.ly/94fqcne9");
2024-05-28 02:13:04 +02:00
});
2024-07-17 19:27:51 +02:00
const nalaCall = new Promise((resolve, reject) => {
2024-07-18 06:58:26 +02:00
fetch("https://partners-api.prod.nala-api.com/v1/fx/rates", {
method: "GET",
headers: createNalaHeaders(),
2024-07-18 06:58:26 +02:00
redirect: "follow"
})
.then((response) => resolve(response.json()))
2024-07-17 19:27:51 +02:00
.catch((error) => reject(error));
2024-05-28 02:13:04 +02:00
})
.then((result) => {
2024-11-10 05:32:27 +01:00
const nalaRates = result.data.find(
(data) => {
return data.source_currency === "EUR" && data.destination_currency === "NGN"
2024-11-10 05:32:27 +01:00
}
);
2024-05-28 02:13:04 +02:00
return {
2024-11-10 05:32:27 +01:00
rate: parseFloat(nalaRates.rate).toFixed(2),
2024-07-17 19:27:51 +02:00
provider: "Nala",
2024-05-28 02:13:04 +02:00
bestRate: false,
2024-07-17 19:27:51 +02:00
href: "https://join.iwantnala.com/JESSE-715106",
2025-12-09 07:27:49 +01:00
logo: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRpbV0MWgCocA3dFuWoxgCmQsxGfqRuttbD3w&s"
2024-05-28 02:13:04 +02:00
};
})
.catch((error) => {
console.error("Error calling Nala:", error);
return createDefaultErrorResponse("Nala", "https://join.iwantnala.com/JESSE-715106");
2024-05-28 02:13:04 +02:00
});
2025-02-17 20:31:59 +01:00
const lemfiCall = new Promise((resolve, reject) => {
fetch("https://lemfi.com/api/lemonade/v2/exchange", {
headers: {
accept: "application/json",
2025-02-17 20:31:59 +01:00
"accept-language": "en-GB,en;q=0.9,nl-NL;q=0.8,nl;q=0.7,en-US;q=0.6",
"content-type": "application/json",
priority: "u=1, i",
"sec-ch-ua": '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
2025-02-17 20:31:59 +01:00
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"macOS"',
2025-02-17 20:31:59 +01:00
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin"
},
referrer: "https://lemfi.com/",
referrerPolicy: "origin",
body: '{"from":"EUR","to":"NGN"}',
method: "POST",
mode: "cors",
credentials: "include"
2025-02-17 20:31:59 +01:00
}).then((response) => resolve(response.json()))
.catch((error) => reject(error));
}).then((result) => {
return {
2025-12-01 22:42:59 +01:00
rate: 0,
2025-02-18 11:37:54 +01:00
provider: "Lemfi(referal code - OBIN6518)",
2025-02-17 20:31:59 +01:00
bestRate: false,
2025-02-18 11:37:54 +01:00
href: "https://lemfi.com",
2025-12-09 07:27:49 +01:00
logo: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSMYTBiPB_joupdSSoBL1fA0wySDPcMpY92XQ&s"
2025-02-17 20:31:59 +01:00
};
})
.catch((error) => {
console.error("Error calling Lemfi:", error);
return createDefaultErrorResponse("Lemfi(referal code - OBIN6518)", "https://lemfi.com");
});
const afriexCall = new Promise((resolve, reject) => {
fetch("https://prod.afx-server.com/v2/public/rates?base=EUR", {
method: "GET",
headers: createAfriexHeaders(),
redirect: "follow"
})
.then((response) => resolve(response.json()))
.catch((error) => reject(error));
})
.then((result) => {
// Afriex now rejects unauthenticated requests with {"message":"Device id missing"}
const ngnRate = result?.rates?.EUR?.NGN;
if (!ngnRate) {
throw new Error(`Unexpected response: ${result?.message || JSON.stringify(result).slice(0, 200)}`);
}
2025-02-17 20:31:59 +01:00
return {
rate: parseFloat(ngnRate).toFixed(2),
provider: "Afriex",
2025-02-17 20:31:59 +01:00
bestRate: false,
href: "https://www.afriex.com/",
2025-12-09 07:27:49 +01:00
logo: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTvFac7oIDG6QsJlYsBIRnWhFfy0OWjN5Mspg&s"
2025-02-17 20:31:59 +01:00
};
})
.catch((error) => {
console.error("Error calling Afriex:", error.message);
return createDefaultErrorResponse("Afriex", "https://www.afriex.com/");
2025-02-17 20:31:59 +01:00
});
const wiseCall = new Promise((resolve, reject) => {
2025-03-26 12:15:06 +01:00
fetch("https://wise.com/gateway/v4/comparisons?payInMethod=BANK_TRANSFER&sendAmount=10&sourceCurrency=EUR&targetCurrency=NGN", {
headers: {
accept: "*/*",
2025-02-17 20:31:59 +01:00
"accept-language": "en-GB,en;q=0.9,nl-NL;q=0.8,nl;q=0.7,en-US;q=0.6",
"content-type": "application/json",
priority: "u=1, i",
"sec-ch-ua": '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
2025-02-17 20:31:59 +01:00
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"macOS"',
2025-02-17 20:31:59 +01:00
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site"
},
referrer: "https://wise.com/",
referrerPolicy: "strict-origin-when-cross-origin",
body: null,
method: "GET",
mode: "cors",
credentials: "include"
2025-03-26 12:15:06 +01:00
}).then((response) => {
return resolve(response.json());
2025-03-26 12:15:06 +01:00
}).catch((error) => reject(error));
2025-02-17 20:31:59 +01:00
}).then((result) => {
2025-03-26 12:15:06 +01:00
const wiseProvider = result.providers.find(provider => provider.alias === "wise");
if (wiseProvider && wiseProvider.quotes.length > 0) {
const wiseQuote = wiseProvider.quotes[0];
2025-02-17 20:31:59 +01:00
return {
2025-03-26 12:15:06 +01:00
rate: parseFloat(wiseQuote.rate).toFixed(2),
2025-02-17 20:31:59 +01:00
provider: "Wise",
bestRate: false,
href: "https://wise.com/invite/dic/obinnae93",
2025-12-09 07:27:49 +01:00
logo: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3L26sFmjg2xyW4CJy_5peQt-v0JHgsYhzvA&s"
2025-02-17 20:31:59 +01:00
};
2025-03-26 12:15:06 +01:00
} else {
throw new Error("Wise provider or quotes not found");
}
}).catch((error) => {
console.error("Error calling Wise:", error);
return createDefaultErrorResponse("Wise", "https://wise.com/invite/dic/obinnae93");
2025-03-26 12:15:06 +01:00
});
2025-02-17 20:31:59 +01:00
2024-05-28 02:13:04 +02:00
await Promise.all([
aceCall,
tapTapCall,
remitlyCall,
2024-07-17 19:27:51 +02:00
nalaCall,
2025-02-17 20:31:59 +01:00
lemfiCall,
afriexCall,
2025-12-09 07:27:49 +01:00
wiseCall,
sendCall
2024-05-28 02:13:04 +02:00
])
.then(
([
aceResponse,
tapTapResponse,
remitlyResponse,
nalaResponse,
lemfiResponse,
afriexResponse,
2025-12-09 07:27:49 +01:00
wiseResponse,
sendResponse
2024-05-28 02:13:04 +02:00
]) => {
rates = [
aceResponse,
tapTapResponse,
remitlyResponse,
nalaResponse,
lemfiResponse,
afriexResponse,
2025-12-09 07:27:49 +01:00
wiseResponse,
sendResponse
2024-05-28 02:13:04 +02:00
];
}
)
.catch((error) => {
console.error("Error in Promise.all:", error);
2024-05-28 02:13:04 +02:00
});
const maxRateIndex = rates.reduce(
(maxIndex, currentObject, currentIndex, array) => {
const currentRate = parseFloat(currentObject.rate);
const maxRate = parseFloat(array[maxIndex].rate);
return currentRate > maxRate ? currentIndex : maxIndex;
},
0
);
rates.forEach((object, index) => {
object.bestRate = index === maxRateIndex;
});
2024-01-13 14:52:01 +01:00
rates = rates.filter(rate => rate.rate > 0);
2024-08-11 08:11:50 +02:00
2024-05-28 02:13:04 +02:00
return res.status(200).json(rates);
2023-12-14 11:48:37 +01:00
};