investec-card-api
Version:
A simple package for interacting with Investec's programmable banking card API
325 lines (324 loc) • 10.6 kB
JavaScript
// src/investec-card-api.ts
import fetch from "node-fetch";
var createEndpoint = (host, path) => new URL(path, host).toString();
var InvestecCardApi = class {
/** The API host URL. */
host;
/** The OAuth client ID. */
clientId;
/** The OAuth client secret. */
clientSecret;
/** The Investec API key. */
apiKey;
/** The current OAuth token. */
token;
/** The token expiry date. */
expiresIn;
/**
* Constructs a new InvestecCardApi instance.
* @param clientId - OAuth client ID
* @param clientSecret - OAuth client secret
* @param apiKey - Investec API key
* @param host - API host URL (default: 'https://openapi.investec.com')
*/
constructor(clientId, clientSecret, apiKey, host = "https://openapi.investec.com") {
this.host = host;
this.apiKey = apiKey;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.expiresIn = /* @__PURE__ */ new Date();
}
/**
* Gets a valid OAuth token, refreshing if necessary.
* @returns The OAuth token string.
* @throws Error if token cannot be retrieved.
*/
async getToken() {
const now = /* @__PURE__ */ new Date();
if (this.token) {
if (now.getTime() < this.expiresIn.getTime()) {
return this.token;
}
}
const result = await this.getAccessToken();
now.setSeconds(now.getSeconds() + result.expires_in);
this.expiresIn = now;
return result.access_token;
}
/**
* Requests a new OAuth access token from Investec.
* @returns The AuthResponse object.
* @throws Error if authentication fails or the cards scope is missing.
*/
async getAccessToken() {
const endpoint = createEndpoint(this.host, `/identity/v2/oauth2/token`);
const response = await fetch(endpoint, {
method: "POST",
headers: {
Authorization: "Basic " + Buffer.from(this.clientId + ":" + this.clientSecret).toString("base64"),
"x-api-key": this.apiKey,
"content-type": "application/x-www-form-urlencoded"
},
body: "grant_type=client_credentials"
});
if (response.status !== 200) {
throw new Error(response.statusText);
}
const result = await response.json();
if (!result.scope.includes("cards")) {
throw new Error("You require the cards scope to use this tool");
}
this.token = result.access_token;
return result;
}
/**
* Uploads environment variables to a programmable card.
* @param cardKey - The card key
* @param env - The environment variables object
* @returns The EnvResponse object
* @throws Error if parameters are missing or the request fails.
*/
async uploadEnv(cardKey, env) {
if (!cardKey || !env) {
throw new Error("Missing required parameters");
}
const endpoint = createEndpoint(
this.host,
`/za/v1/cards/${encodeURIComponent(cardKey.toString())}/environmentvariables`
);
const token = this.token || await this.getToken();
return fetchPost(endpoint, token, env);
}
/**
* Uploads code to a programmable card.
* @param cardKey - The card key
* @param code - The code object
* @returns The CodeResponse object
* @throws Error if parameters are missing or the request fails.
*/
async uploadCode(cardKey, code) {
if (!cardKey || !code) {
throw new Error("Missing required parameters");
}
const endpoint = createEndpoint(
this.host,
`/za/v1/cards/${encodeURIComponent(cardKey.toString())}/code`
);
const token = this.token || await this.getToken();
return fetchPost(endpoint, token, code);
}
/**
* Publishes code to a programmable card.
* @param cardKey - The card key
* @param codeId - The code ID
* @param code - The code string
* @returns The CodeResponse object
* @throws Error if parameters are missing or the request fails.
*/
async uploadPublishedCode(cardKey, codeId, code) {
if (!cardKey || !codeId || !code) {
throw new Error("Missing required parameters");
}
const raw = { code, codeId };
const endpoint = createEndpoint(
this.host,
`/za/v1/cards/${encodeURIComponent(cardKey.toString())}/publish`
);
const token = this.token || await this.getToken();
return fetchPost(endpoint, token, raw);
}
/**
* Retrieves all programmable cards for the authenticated user.
* @returns The CardResponse object
* @throws Error if the request fails.
*/
async getCards() {
const endpoint = createEndpoint(this.host, `/za/v1/cards`);
const token = this.token || await this.getToken();
return fetchGet(endpoint, token);
}
/**
* Retrieves environment variables for a programmable card.
* @param cardKey - The card key
* @returns The EnvResponse object
* @throws Error if parameters are missing or the request fails.
*/
async getEnv(cardKey) {
if (!cardKey) {
throw new Error("Missing required parameters");
}
const endpoint = createEndpoint(
this.host,
`/za/v1/cards/${encodeURIComponent(cardKey.toString())}/environmentvariables`
);
const token = this.token || await this.getToken();
return fetchGet(endpoint, token);
}
/**
* Retrieves code for a programmable card.
* @param cardKey - The card key
* @returns The CodeResponse object
* @throws Error if parameters are missing or the request fails.
*/
async getCode(cardKey) {
if (!cardKey) {
throw new Error("Missing required parameters");
}
const endpoint = createEndpoint(
this.host,
`/za/v1/cards/${encodeURIComponent(cardKey.toString())}/code`
);
const token = this.token || await this.getToken();
return fetchGet(endpoint, token);
}
/**
* Retrieves published code for a programmable card.
* @param cardKey - The card key
* @returns The CodeResponse object
* @throws Error if parameters are missing or the request fails.
*/
async getPublishedCode(cardKey) {
if (!cardKey) {
throw new Error("Missing required parameters");
}
const endpoint = createEndpoint(
this.host,
`/za/v1/cards/${encodeURIComponent(cardKey.toString())}/publishedcode`
);
const token = this.token || await this.getToken();
return fetchGet(endpoint, token);
}
/**
* Enables or disables programmable features on a card.
* @param cardKey - The card key
* @param enabled - Whether to enable the feature
* @returns The CodeToggle object
* @throws Error if parameters are missing or the request fails.
*/
async toggleCode(cardKey, enabled) {
if (!cardKey || enabled === void 0) {
throw new Error("Missing required parameters");
}
const endpoint = createEndpoint(
this.host,
`/za/v1/cards/${encodeURIComponent(cardKey.toString())}/toggle-programmable-feature`
);
const token = this.token || await this.getToken();
return fetchPost(endpoint, token, {
Enabled: enabled
});
}
/**
* Retrieves code execution results for a programmable card.
* @param cardKey - The card key
* @returns The ExecutionResult object
* @throws Error if parameters are missing or the request fails.
*/
async getExecutions(cardKey) {
if (!cardKey) {
throw new Error("Missing required parameters");
}
const endpoint = createEndpoint(
this.host,
`/za/v1/cards/${encodeURIComponent(cardKey.toString())}/code/executions`
);
const token = this.token || await this.getToken();
return fetchGet(endpoint, token);
}
/**
* Executes code in a simulated transaction context.
* @param code - The code string
* @param transaction - The transaction object
* @param cardKey - The card key
* @returns The ExecuteResult object
* @throws Error if parameters are missing or the request fails.
*/
async executeCode(code, transaction, cardKey) {
if (!code || !cardKey) {
throw new Error("Missing required parameters");
}
const raw = {
simulationcode: code,
centsAmount: transaction.centsAmount,
currencyCode: transaction.currencyCode,
merchantCode: transaction.merchant.category.code,
merchantName: transaction.merchant.name,
merchantCity: transaction.merchant.city,
countryCode: transaction.merchant.country.code
};
const endpoint = createEndpoint(
this.host,
`/za/v1/cards/${encodeURIComponent(cardKey.toString())}/code/execute`
);
const token = this.token || await this.getToken();
return fetchPost(endpoint, token, raw);
}
/**
* Retrieves supported currencies for programmable cards.
* @returns The ReferenceResponse object
* @throws Error if the request fails.
*/
async getCurrencies() {
const endpoint = createEndpoint(this.host, `/za/v1/cards/currencies`);
const token = this.token || await this.getToken();
return fetchGet(endpoint, token);
}
/**
* Retrieves supported countries for programmable cards.
* @returns The ReferenceResponse object
* @throws Error if the request fails.
*/
async getCountries() {
const endpoint = createEndpoint(this.host, `/za/v1/cards/countries`);
const token = this.token || await this.getToken();
return fetchGet(endpoint, token);
}
/**
* Retrieves supported merchants for programmable cards.
* @returns The ReferenceResponse object
* @throws Error if the request fails.
*/
async getMerchants() {
const endpoint = createEndpoint(this.host, `/za/v1/cards/merchants`);
const token = this.token || await this.getToken();
return fetchGet(endpoint, token);
}
};
async function fetchGet(endpoint, token) {
const response = await fetch(endpoint, {
method: "GET",
signal: AbortSignal.timeout(3e4),
headers: {
Authorization: "Bearer " + token,
"content-type": "application/json"
}
});
if (response.status !== 200) {
if (response.status === 404) {
throw new Error("Card not found");
}
throw new Error(response.statusText);
}
return await response.json();
}
async function fetchPost(endpoint, token, body) {
const response = await fetch(endpoint, {
method: "POST",
signal: AbortSignal.timeout(3e4),
headers: {
Authorization: "Bearer " + token,
"content-type": "application/json"
},
body: JSON.stringify(body)
});
if (response.status !== 200) {
if (response.status === 404) {
throw new Error("Card not found");
}
throw new Error(response.statusText);
}
return await response.json();
}
export {
InvestecCardApi
};