@pulgueta/bold
v1.0.1
Published
Node.js Bold SDK
Maintainers
Readme
Bold Node.js SDK
Implement Bold in your Node.js applications.
Installation
bun add @pulgueta/bold zodQuick Start
import { Bold } from "@pulgueta/bold";
export const bold = new Bold({
identityKey: process.env.BOLD_IDENTITY_KEY,
secretKey: process.env.BOLD_SECRET_KEY,
environment: "sandbox", // or "production"
});
// Create a payment
const [error, payment] = await bold.payments.create({
amount: {
currency: "COP",
total_amount: 50000,
taxes: [],
tip_amount: 0,
},
payment_method: "POS",
terminal_model: "P400",
terminal_serial: "12345678",
reference: "ORDER-123",
user_email: "[email protected]",
});
if (error) {
console.error("Payment failed:", error);
} else {
console.log("Payment created:", payment.payload.integration_id);
}Configuration
SDK Options
interface BoldOptions {
// Required: Your Bold API identity key
identityKey: string;
// Optional: Secret key for webhook verification
secretKey?: string;
// Optional: OAuth client credentials
clientId?: string;
clientSecret?: string;
// Optional: Environment (default: "sandbox")
environment?: "sandbox" | "production";
// Optional: Request timeout in milliseconds (default: 30000)
timeoutMs?: number;
// Optional: Number of retry attempts (default: 0)
retries?: number;
// Optional: Delay between retries in milliseconds (default: 1000)
retryDelayMs?: number;
}Example Configuration
const bold = new Bold({
identityKey: process.env.BOLD_IDENTITY_KEY!,
secretKey: process.env.BOLD_SECRET_KEY,
clientId: process.env.BOLD_CLIENT_ID,
clientSecret: process.env.BOLD_CLIENT_SECRET,
environment: "production",
timeoutMs: 15000,
retries: 3,
retryDelayMs: 2000,
});API Reference
OAuth
Get an OAuth access token using client credentials.
const [error, token] = await bold.oauth.getToken();
if (error) {
console.error("Failed to get token:", error);
} else {
console.log("Access token:", token.access_token);
console.log("Expires in:", token.expires_in);
}Payments
Create Payment
Create a payment through the API Integrations (App Checkout).
import type { AppCheckoutRequest } from "@pulgueta/bold";
const paymentRequest: AppCheckoutRequest = {
amount: {
currency: "COP",
total_amount: 100000,
taxes: [
{
type: "VAT",
base: 84034,
value: 15966,
},
],
tip_amount: 5000,
},
payment_method: "NEQUI",
terminal_model: "SMARTPHONE",
terminal_serial: "123456789",
reference: "ORDER-001",
user_email: "[email protected]",
description: "Product purchase",
payer: {
email: "[email protected]",
phone_number: "+573001234567",
document: {
document_type: "CEDULA",
document_number: "1234567890",
},
},
};
const [error, payment] = await bold.payments.create(paymentRequest, {
// Optional request config
idempotencyKey: "unique-request-id",
timeoutMs: 15000,
});Get Payment Methods
Get available payment methods for the merchant.
const [error, methods] = await bold.payments.getMethods();
if (error) {
console.error("Failed to get payment methods:", error);
} else {
console.log("Available methods:", methods.payload.payment_methods);
}Get Payment Status
Get the payment voucher / transaction status for a sale.
const [error, status] = await bold.payments.getStatus("sale-id-123");
if (error) {
console.error("Failed to get payment status:", error);
} else {
console.log("Payment status:", status.payment_status);
console.log("Transaction ID:", status.transaction_id);
}Terminals
List Terminals
Get available payment terminals (dataphones) for the merchant.
const [error, terminals] = await bold.terminals.list();
if (error) {
console.error("Failed to get terminals:", error);
} else {
for (const terminal of terminals.payload.available_terminals) {
console.log(`${terminal.name}: ${terminal.terminal_serial}`);
}
}Webhooks
Verify Webhook Signature
Verify the authenticity of a webhook payload using HMAC-SHA256.
import express from "express";
const app = express();
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-bold-signature"] as string;
// Verify the webhook signature
const result = bold.webhooks.verify(req.body, signature);
if (!result.valid) {
console.error("Invalid webhook signature:", result.error);
return res.status(400).json({ error: result.error });
}
// Parse the webhook payload
const notification = bold.webhooks.parse<WebhookNotification>(
req.body.toString()
);
if (!notification) {
return res.status(400).json({ error: "Invalid payload" });
}
// Process the webhook
console.log("Webhook received:", notification.type);
console.log("Payment ID:", notification.data.payment_id);
res.status(200).json({ received: true });
});Generate Webhook Signature (for testing)
Generate a webhook signature for testing purposes.
const payload = JSON.stringify({
id: "evt_123",
type: "SALE_APPROVED",
data: {
payment_id: "pay_123",
merchant_id: "merchant_123",
amount: { total: 50000, currency: "COP" },
},
});
const signature = bold.webhooks.generateSignature(payload);
// Use in tests
const response = await fetch("http://localhost:3000/webhook", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-bold-signature": signature,
},
body: payload,
});Get Webhook Notifications
Get webhook notifications for a payment (fallback service).
// By payment ID
const [error, notifications] = await bold.webhooks.getNotifications("pay_123");
// By external reference
const [error2, notifications2] = await bold.webhooks.getNotifications(
"ORDER-123",
{ isExternalReference: true }
);
if (error) {
console.error("Failed to get notifications:", error);
} else {
for (const notification of notifications.notifications) {
console.log(`${notification.type} at ${notification.time}`);
}
}Error Handling
The SDK uses a Result Tuple pattern for explicit error handling. All async methods return [error, null] on failure or [null, result] on success.
Result Tuple Pattern
import type { BoldError } from "@pulgueta/bold";
const [error, payment] = await bold.payments.create(paymentRequest);
if (error) {
// Handle error based on type
switch (error.kind) {
case "network":
console.error("Network error:", error.message);
break;
case "http":
console.error(`HTTP ${error.status}: ${error.statusText}`);
break;
case "invalid_response":
console.error("Invalid response:", error.issues);
break;
case "api_error":
console.error("API error:", error.errors);
break;
case "config":
console.error("Configuration error:", error.message);
break;
case "timeout":
console.error(`Request timeout after ${error.timeoutMs}ms`);
break;
case "aborted":
console.error("Request aborted:", error.message);
break;
}
return;
}
// Success - payment is guaranteed to be non-null
console.log("Payment ID:", payment.payload.integration_id);Error Types
type BoldError = { kind: Kind } & (
| NetworkError // Fetch failed (network issues, timeout, abort)
| HttpError // Non-2xx HTTP status
| InvalidResponseError // Response failed Zod validation
| ApiError // Bold API returned errors
| ConfigError // SDK misconfiguration
| TimeoutError // Request timeout
| AbortedError
); // Request abortedTypeScript Support
The SDK is fully typed with TypeScript and exports all schemas and types.
Using Types
import type {
AppCheckoutRequest,
AppCheckoutResponse,
PaymentStatus,
WebhookNotification,
WebhookEventType,
BoldError,
} from "@pulgueta/bold";
const payment: AppCheckoutRequest = {
amount: {
currency: "COP",
total_amount: 50000,
taxes: [],
tip_amount: 0,
},
payment_method: "POS",
terminal_model: "P400",
terminal_serial: "12345678",
reference: "ORDER-123",
user_email: "[email protected]",
};Using Schemas (Runtime Validation)
import { AppCheckoutRequestSchema } from "@pulgueta/bold";
const result = AppCheckoutRequestSchema.safeParse(unknownData);
if (result.success) {
const payment = result.data;
// payment is now typed as AppCheckoutRequest
} else {
console.error("Validation errors:", result.error.issues);
}Advanced Features
Request Configuration
All methods accept optional request configuration:
interface RequestConfig {
// Custom timeout for this request
timeoutMs?: number;
// AbortSignal for request cancellation
signal?: AbortSignal;
// Number of retry attempts
retries?: number;
// Delay between retries in milliseconds
retryDelayMs?: number;
// Idempotency key for safe retries (payments only)
idempotencyKey?: string;
}Request Cancellation
const controller = new AbortController();
// Cancel request after 5 seconds
setTimeout(() => controller.abort(), 5000);
const [error, payment] = await bold.payments.create(paymentRequest, {
signal: controller.signal,
});
if (error?.kind === "aborted") {
console.log("Request was cancelled");
}Idempotent Requests
import { randomUUID } from "crypto";
const idempotencyKey = randomUUID();
// First attempt
const [error1, payment1] = await bold.payments.create(paymentRequest, {
idempotencyKey,
});
// Retry with same key - will not create duplicate payment
const [error2, payment2] = await bold.payments.create(paymentRequest, {
idempotencyKey,
});Webhook Event Types
type WebhookEventType =
| "SALE_APPROVED"
| "SALE_REJECTED"
| "VOID_APPROVED"
| "VOID_REJECTED";Payment Methods
type PaymentMethod = "DAVIPLATA" | "NEQUI" | "PAY_BY_LINK" | "POS";Payment Status
type PaymentStatus =
| "NO_TRANSACTION_FOUND"
| "PROCESSING"
| "PENDING"
| "APPROVED"
| "REJECTED"
| "FAILED"
| "VOIDED";Document Types
type DocumentType =
| "CEDULA"
| "NIT"
| "CEDULA_EXTRANJERIA"
| "PEP"
| "PASAPORTE"
| "NUIP"
| "REGISTRO_CIVIL"
| "DOCUMENTO_EXTRANJERIA"
| "TARJETA_IDENTIDAD"
| "PPT";Development
Building
pnpm install
pnpm buildLinting and Formatting
pnpm lint
pnpm formatContributing
Contributions are welcome! Please open an issue or submit a pull request.
License
MIT © Andrés Rodríguez
