addtowallet
Version:
Create and manage Google Wallet and Apple Wallet passes with an easy-to-use API client for Node.js and browsers.
260 lines (259 loc) • 9.52 kB
JavaScript
const DEFAULT_BASE_URL = 'https://app.addtowallet.co';
function buildQuery(params) {
if (!params)
return '';
const usp = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value === undefined || value === null)
return;
usp.append(key, String(value));
});
const qs = usp.toString();
return qs ? `?${qs}` : '';
}
/**
* AddToWallet API client for creating and managing wallet passes
*
* @example
* ```ts
* const client = new AddToWalletClient({
* apiKey: process.env.ADDTOWALLET_API_KEY!
* });
*
* const { cardId, passUrl } = await client.createPass({
* logoUrl: 'https://s3.amazonaws.com/i.addtowallet.co/assets/realestatelogo.png',
* cardTitle: 'Your Business Name',
* header: 'Amy Jane',
* textModulesData: [
* { id: 'r1start', header: 'Phone', body: '+1 8888888888' },
* { id: 'r1end', header: 'Email', body: 'amy@gmail.com' }
* ],
* linksModuleData: [
* { id: 'r1', description: 'Call Us', uri: 'tel:+1 8287489293' },
* { id: 'r2', description: 'Email Us', uri: 'mailto:support@addtowallet.co' },
* { id: 'r3', description: 'Visit our website', uri: 'https://app.addtowallet.co' },
* { id: 'r4', description: 'Visit our office', uri: 'geo:https://maps.google.com/?q=123+Main+Street,+Anytown,+CA' }
* ],
* barcodeType: 'QR_CODE',
* barcodeValue: '',
* barcodeAltText: '',
* hexBackgroundColor: '#141f31',
* appleFontColor: '#FFFFFF',
* heroImage: 'https://s3.amazonaws.com/i.addtowallet.co/assets/realestatehero.png'
* });
*
* console.log(cardId, passUrl);
* ```
*/
export class AddToWalletClient {
/**
* Creates a new AddToWallet client instance
*
* @param options - Configuration options for the client
* @param options.apiKey - Secret API key from AddToWallet dashboard (server-side only)
* @param options.getAuthToken - Function that returns a short-lived auth token (for browser use)
* @param options.baseUrl - Base URL for the AddToWallet API (defaults to https://app.addtowallet.co)
* @param options.fetchFn - Custom fetch implementation (for Node < 18 or custom behavior)
* @param options.timeoutMs - Request timeout in milliseconds
*/
constructor(options) {
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, '');
this.apiKey = options.apiKey;
this.getAuthToken = options.getAuthToken;
this.fetchFn = options.fetchFn ?? fetch;
this.timeoutMs = options.timeoutMs;
this.defaultTracking = options.defaultTracking;
}
async resolveToken() {
if (this.getAuthToken) {
const token = await this.getAuthToken();
if (!token)
throw new Error('getAuthToken returned empty token');
return token;
}
if (this.apiKey)
return this.apiKey;
throw new Error('No authentication configured. Please provide an API key or getAuthToken function.\n\n' +
'To get your API key:\n' +
'1. Sign up at https://app.addtowallet.co\n' +
'2. Navigate to the API Docs section\n' +
'3. Copy your API key from the sidebar\n\n' +
'For more information, visit: https://app.addtowallet.co/api-docs');
}
/**
* Merges default tracking parameters with request-specific tracking parameters
* Request-specific parameters override default ones
*/
mergeTrackingParams(requestTracking) {
if (!this.defaultTracking && !requestTracking)
return undefined;
const merged = {
...this.defaultTracking,
...requestTracking,
};
// Remove undefined values
return Object.fromEntries(Object.entries(merged).filter(([, value]) => value !== undefined));
}
async request(options) {
const controller = new AbortController();
const timeout = this.timeoutMs
? setTimeout(() => controller.abort(), this.timeoutMs)
: undefined;
const url = `${this.baseUrl}${options.path}${buildQuery(options.query)}`;
const token = await this.resolveToken();
const res = await this.fetchFn(url, {
method: options.method,
headers: {
'Content-Type': 'application/json',
'apikey': token,
},
body: options.body ? JSON.stringify(options.body) : undefined,
signal: options.signal ?? controller.signal,
}).catch((err) => {
if (timeout)
clearTimeout(timeout);
throw err;
});
if (timeout)
clearTimeout(timeout);
if (!res.ok) {
const text = await res.text().catch(() => '');
const err = new Error(`AddToWallet API error ${res.status}: ${res.statusText} ${text}`.trim());
throw err;
}
const contentType = res.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return (await res.json());
}
// Fallback to text payloads (e.g., signed JWT)
return { url: await res.text() };
}
/**
* Creates a new wallet pass
*
* @param payload - The pass creation request
* @returns Promise resolving to the created pass information, including `cardId` and `passUrl`
*
* @example
* ```ts
* const { cardId, passUrl } = await client.createPass({
* logoUrl: 'https://s3.amazonaws.com/i.addtowallet.co/assets/realestatelogo.png',
* cardTitle: 'Your Business Name',
* header: 'Amy Jane',
* textModulesData: [
* { id: 'r1start', header: 'Phone', body: '+1 8888888888' },
* { id: 'r1end', header: 'Email', body: 'amy@gmail.com' }
* ],
* linksModuleData: [
* { id: 'r1', description: 'Call Us', uri: 'tel:+1 8287489293' },
* { id: 'r2', description: 'Email Us', uri: 'mailto:support@addtowallet.co' },
* { id: 'r3', description: 'Visit our website', uri: 'https://app.addtowallet.co' },
* { id: 'r4', description: 'Visit our office', uri: 'geo:https://maps.google.com/?q=123+Main+Street,+Anytown,+CA' }
* ],
* barcodeType: 'QR_CODE',
* barcodeValue: '',
* barcodeAltText: '',
* hexBackgroundColor: '#141f31',
* appleFontColor: '#FFFFFF',
* heroImage: 'https://s3.amazonaws.com/i.addtowallet.co/assets/realestatehero.png',
* });
*
* console.log(cardId, passUrl);
* ```
*/
async createPass(payload) {
// Merge default tracking with request-specific tracking
const mergedTracking = this.mergeTrackingParams(payload.tracking);
// Create the request payload with merged tracking
const requestPayload = {
...payload,
tracking: mergedTracking,
};
const res = await this.request({
method: 'POST',
path: '/api/card/create',
body: requestPayload,
});
if (res && res.cardId && !res.passUrl) {
const passUrl = `${this.baseUrl}/card/${encodeURIComponent(res.cardId)}`;
return { ...res, passUrl, tracking: mergedTracking };
}
return { ...res, tracking: mergedTracking };
}
/**
* Updates an existing wallet pass
*
* @param cardId - The ID of the pass to update
* @param payload - The pass update request
* @returns Promise resolving to the updated pass information
*
* @example
* ```ts
* await client.updatePass('66b6005bb7bddce8f05a3392', {
* cardTitle: 'Updated Card Title',
* header: 'Updated Header',
* hexBackgroundColor: '#33FF57'
* });
* ```
*/
async updatePass(cardId, payload) {
return this.request({
method: 'PUT',
path: `/api/card/edit/${encodeURIComponent(cardId)}`,
body: payload,
});
}
/**
* Deletes a wallet pass by ID
*
* @param cardId - The ID of the pass to delete
* @returns Promise resolving to deletion confirmation
*
* @example
* ```ts
* const { msg } = await client.deletePass('66b6005bb7bddce8f05a3392');
* console.log('Pass deleted:', msg);
* ```
*/
async deletePass(cardId) {
return this.request({
method: 'DELETE',
path: `/api/card/delete/${encodeURIComponent(cardId)}`,
});
}
/**
* Gets current credit information
*
* @returns Promise resolving to credit information
*
* @example
* ```ts
* const { premiumCredits, freeCredits } = await client.getCredits();
* console.log(`Premium credits: ${premiumCredits}, Free credits: ${freeCredits}`);
* ```
*/
async getCredits() {
return this.request({
method: 'GET',
path: '/api/getCredits',
});
}
/**
* Gets a specific pass by ID
*
* @param cardId - The ID of the pass to retrieve
* @returns Promise resolving to pass details
*
* @example
* ```ts
* const pass = await client.getPass('66b6005bb7bddce8f05a3392');
* console.log('Pass title:', pass.cardTitle);
* ```
*/
async getPass(cardId) {
return this.request({
method: 'GET',
path: `/api/card/${encodeURIComponent(cardId)}`,
});
}
}