Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.test.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
BOLD_API_KEY=<BOLD_API_KEY>
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ jobs:
- name: Check Format
run: yarn check-format
- name: Run Tests
run: yarn test
run: |
echo BOLD_API_KEY=${{ secrets.BOLD_API_KEY }} > .env.test
yarn test
rm -Rf .env.test
- name: Coveralls
uses: coverallsapp/github-action@v2
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 0.1.4 (15.01.2025)

- Add iva and tip amount to payment link

## 0.1.3 (13.01.2025)

- Add typescript definitions
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[![Coverage Status](https://coveralls.io/repos/github/boterop/bold/badge.svg?branch=main)](https://coveralls.io/github/boterop/bold?branch=main)
[![Coverage Status](https://coveralls.io/repos/github/boterop/bold-api-sdk/badge.svg?branch=add-types)](https://coveralls.io/github/boterop/bold-api-sdk?branch=add-types)
![NPM Type Definitions](https://img.shields.io/npm/types/bold-api-sdk)

# bold-api-sdk
Expand All @@ -25,9 +25,11 @@ const response = await paymentLink.create('bold-identity-key', {
description: 'Payment for order order-id',
payerEmail: 'test@example.org',
amount: 300,
tipAmount: 0,
currency: 'USD',
callbackUrl: 'https://example.org/return',
expirationMinutes: 30,
iva: 19,
});

console.log(response);
Expand Down
9 changes: 7 additions & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
process.env.NODE_ENV = 'test';

// Load environment variables
try {
process.loadEnvFile(process.cwd() + '/.env.test');
} catch (_error) {
logger.info('No se encontró el archivo de variables de entorno .env.test');
}

module.exports = {
testEnvironment: 'node',
moduleFileExtensions: ['js', 'json'],
setupFiles: ['./tests/test_helper.js'],
setupFilesAfterEnv: ['./tests/runtime.js'],
transform: {
'^.+\\.js?$': 'babel-jest',
},
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bold-api-sdk",
"version": "0.1.3",
"version": "0.1.4",
"description": "A Node.js library for interacting with the Bold API, providing seamless integration to manage payments, customers, and transactions efficiently.",
"main": "src/app.js",
"repository": "https://github.com/boterop/bold-api-sdk",
Expand All @@ -9,7 +9,7 @@
"types": "src/app.d.ts",
"scripts": {
"start": "node --env-file .env --watch src/app.js",
"test": "NODE_ENV=test node --experimental-vm-modules --max-old-space-size=8192 node_modules/jest/bin/jest.js --detectOpenHandles --runInBand",
"test": "NODE_ENV=test node --experimental-vm-modules --max-old-space-size=8192 node_modules/jest/bin/jest.js --runInBand",
"format": "prettier --write .",
"check-format": "prettier -c .",
"lint": "eslint ."
Expand Down
16 changes: 9 additions & 7 deletions src/app.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
declare module 'bold-api-sdk' {
export interface Order {
amountType: 'OPEN' | 'CLOSE';
description: string;
payerEmail: string;
amount: number;
callbackUrl: string;
expirationMinutes: number;
currency: string;
amountType?: 'OPEN' | 'CLOSE';
description?: string;
payerEmail?: string;
amount?: number;
tipAmount?: number;
callbackUrl?: string;
expirationMinutes?: number;
currency?: string;
iva?: number;
}

export interface LinkResponse {
Expand Down
2 changes: 1 addition & 1 deletion src/domain/payment_link.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const paymentLinkService = require('../services/payment_link');
const { paymentLinkService } = require('../services');

const paymentLink = {
create: async (apiKey, order) => paymentLinkService.create(apiKey, order),
Expand Down
2 changes: 1 addition & 1 deletion src/domain/payment_methods.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const paymentMethodsService = require('../services/payment_methods');
const { paymentMethodsService } = require('../services');

const paymentMethods = {
list: async apiKey => paymentMethodsService.list(apiKey),
Expand Down
1 change: 1 addition & 0 deletions src/services/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
exports.paymentLinkService = require('./payment_link');
exports.paymentMethodsService = require('./payment_methods');
36 changes: 28 additions & 8 deletions src/services/payment_link.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,49 @@ const { httpClient } = require('../shared');
exports.create = async (
apiKey,
{
amountType = 'OPEN',
amountType,
description,
payerEmail,
amount,
tipAmount,
callbackUrl,
expirationMinutes,
currency,
},
iva,
} = {},
) => {
amountType = amountType || 'OPEN';
description = description || '';
payerEmail = payerEmail || '';
amount = Math.abs(amount || 0);
tipAmount = Math.abs(tipAmount || 0);
callbackUrl = callbackUrl || null;
expirationMinutes = expirationMinutes || 30;
currency = currency || 'COP';
iva = iva || 0;

if (amountType !== 'CLOSE' && amountType !== 'OPEN') {
throw new Error('Invalid amount type, must be CLOSE or OPEN');
}

const ivaValue = iva / 100;

const currentNanoseconds = Date.now() * 1e6;
const minutesInNanoseconds = (expirationMinutes || 30) * 60 * 1e9;
const minutesInNanoseconds = expirationMinutes * 60 * 1e9;
const futureNanoseconds = currentNanoseconds + minutesInNanoseconds;

const hasTaxes = iva > 0;
const ivaTax =
iva > 0 ? { type: 'VAT', base: amount, value: amount * ivaValue } : null;

const amountOpt =
amountType === 'CLOSE'
? {
amount: {
total_amount: amount || 0,
currency: currency || 'COP',
total_amount: amount * (ivaValue + 1) + tipAmount,
currency,
tip_amount: tipAmount,
taxes: hasTaxes ? [ivaTax] : [],
},
}
: {};
Expand All @@ -38,10 +58,10 @@ exports.create = async (
body: JSON.stringify({
amount_type: amountType,
...amountOpt,
description: description || '',
description,
expiration_date: futureNanoseconds,
callback_url: callbackUrl || '',
payer_email: payerEmail || '',
callback_url: callbackUrl,
payer_email: payerEmail,
}),
},
});
Expand Down
25 changes: 25 additions & 0 deletions tests/app.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const fs = require('fs');
const path = require('path');
const boldApiSdk = require('../src/app');

const fileToExportKey = key => {
const newKey = key
.split('.')
.shift()
.split('_')
.map(s => s[0].toUpperCase() + s.slice(1))
.join('');

return newKey[0].toLowerCase() + newKey.slice(1);
};

describe('boldApiSdk', () => {
it('should export all domains', () => {
const domains = fs.readdirSync(path.join(__dirname, '../src/domain'));

for (const domain of domains) {
const key = fileToExportKey(domain);
expect(boldApiSdk[key]).toBeDefined();
}
});
});
93 changes: 66 additions & 27 deletions tests/domain/payment_link.test.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
const paymentLink = require('../../src/domain/payment_link');

describe('paymentLink', () => {
const PAYMENT_LINK = 'bold.co/payment';

describe('create', () => {
const amountType = 'CLOSE';
const description = 'Payment for order order-id';
const payerEmail = 'test@example.org';
const amount = 300;
const amount = 1000;
const callbackUrl = 'https://example.org/return';
const expirationMinutes = 30;
const currency = 'USD';
const currency = 'COP';
const apiKey = process.env.BOLD_API_KEY || 'bold-api-key';

it('should return a payment link', async () => {
process.env.BOLD_API_URL = 'https://example.org';
beforeEach(() => {
process.env.BOLD_API_URL = 'https://integrations.api.bold.co';
});

const response = await paymentLink.create('bold-api-key', {
it('should return a payment link', async () => {
const response = await paymentLink.create(apiKey, {
amountType,
description,
payerEmail,
Expand All @@ -22,38 +27,72 @@ describe('paymentLink', () => {
expirationMinutes,
currency,
});
const { url, options } = response;
const body = JSON.parse(options.body);

expect(url).toBe('https://example.org/online/link/v1');
expect(options.headers.Authorization).toBe('x-api-key bold-api-key');
expect(options.method).toBe('POST');
expect(body.amount_type).toBe(amountType);
expect(body.amount.total_amount).toBe(amount);
expect(body.amount.currency).toBe(currency);
expect(body.description).toBe(description);
expect(body.expiration_date).toBeGreaterThan(Date.now() * 1e6);
expect(body.callback_url).toBe(callbackUrl);
expect(body.payer_email).toBe(payerEmail);
});

it('should not send amount if type is not CLOSE', async () => {
process.env.BOLD_API_URL = 'https://example.org';
const { payload, errors } = response;

expect(errors).toHaveLength(0);

const response = await paymentLink.create('bold-api-key', {
amountType: 'OPEN',
expect(payload.url).toContain(PAYMENT_LINK);
});

it('should not send amount if type is OPEN', async () => {
const response = await paymentLink.create(apiKey, {
description,
payerEmail,
amount,
callbackUrl,
expirationMinutes,
currency,
});
const { url, options } = response;
const body = JSON.parse(options.body);

expect(url).toBe('https://example.org/online/link/v1');
expect(body.amount).toBeUndefined();
const { payload, errors } = response;

expect(errors).toHaveLength(0);

expect(payload.url).toContain(PAYMENT_LINK);
});

it('should set default values if not provided', async () => {
const response = await paymentLink.create(apiKey);

const { payload, errors } = response;

expect(errors).toHaveLength(0);

expect(payload.url).toContain(PAYMENT_LINK);
});

it('should create a payment link with iva', async () => {
const response = await paymentLink.create(apiKey, {
amountType: 'CLOSE',
amount: 1000,
tipAmount: 100,
iva: 19,
});

const { payload, errors } = response;

expect(errors).toHaveLength(0);

expect(payload.url).toContain(PAYMENT_LINK);
});

it('should send error if amount type is not CLOSE or OPEN', async () => {
try {
await paymentLink.create(apiKey, {
amountType: 'INVALID',
description,
payerEmail,
amount,
callbackUrl,
expirationMinutes,
currency,
});
} catch (error) {
expect(error.message).toBe(
'Invalid amount type, must be CLOSE or OPEN',
);
}
});
});
});
16 changes: 9 additions & 7 deletions tests/domain/payment_methds.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ const paymentMethods = require('../../src/domain/payment_methods');
describe('paymentMethods', () => {
describe('list', () => {
it('should return a list of payment methods', async () => {
process.env.BOLD_API_URL = 'https://example.org';
const response = await paymentMethods.list('bold-api-key');
const { url, options } = response;
process.env.BOLD_API_URL = 'https://integrations.api.bold.co';
const response = await paymentMethods.list();
const { payload, errors } = response;

expect(response).toBeDefined();
expect(response).toBeInstanceOf(Object);
expect(url).toBe('https://example.org/online/link/v1/payment_methods');
expect(options.headers.Authorization).toBe('x-api-key bold-api-key');
expect(errors).toBeDefined();
expect(errors).toHaveLength(0);

expect(payload).toBeDefined();
expect(payload.payment_methods).toBeDefined();
expect(payload.payment_methods.CREDIT_CARD).toBeDefined();
});
});
});
3 changes: 0 additions & 3 deletions tests/runtime.js

This file was deleted.

Loading
Loading