Stripe API wrapper for Node.js providing comprehensive payment processing, subscription management, and financial services integration.
—
Stripe Terminal enables in-person payment processing through certified card readers. This system provides comprehensive support for physical point-of-sale transactions, including chip cards, contactless payments, and mobile wallets across various reader types.
Manage reader configurations and settings:
interface TerminalConfiguration {
id: string;
object: 'terminal.configuration';
name?: string;
bbpos_wisepos_e?: {
splashscreen?: string;
};
offline?: {
enabled: boolean;
};
reboot_window?: {
end_hour: number;
start_hour: number;
};
stripe_s700?: {
splashscreen?: string;
};
tipping?: {
aud?: CurrencyTippingConfig;
cad?: CurrencyTippingConfig;
chf?: CurrencyTippingConfig;
czk?: CurrencyTippingConfig;
dkk?: CurrencyTippingConfig;
eur?: CurrencyTippingConfig;
gbp?: CurrencyTippingConfig;
hkd?: CurrencyTippingConfig;
myr?: CurrencyTippingConfig;
nok?: CurrencyTippingConfig;
nzd?: CurrencyTippingConfig;
pln?: CurrencyTippingConfig;
sek?: CurrencyTippingConfig;
sgd?: CurrencyTippingConfig;
usd?: CurrencyTippingConfig;
};
verifone_p400?: {
splashscreen?: string;
};
}
interface CurrencyTippingConfig {
fixed_amounts?: number[];
percentages?: number[];
smart_tip_threshold?: number;
}
// Create basic configuration
const configuration = await stripe.terminal.configurations.create({
name: 'Store Front Configuration',
tipping: {
usd: {
fixed_amounts: [100, 150, 200], // $1, $1.50, $2
percentages: [15, 18, 20],
smart_tip_threshold: 1000 // $10 threshold for smart tips
}
},
offline: {
enabled: true
},
reboot_window: {
start_hour: 2, // 2 AM
end_hour: 4 // 4 AM
}
});
// Create configuration with custom splash screen
const customConfig = await stripe.terminal.configurations.create({
name: 'Restaurant Configuration',
bbpos_wisepos_e: {
splashscreen: 'file_restaurant_logo'
},
stripe_s700: {
splashscreen: 'file_restaurant_logo'
},
tipping: {
usd: {
fixed_amounts: [200, 300, 500], // $2, $3, $5
percentages: [18, 20, 22],
smart_tip_threshold: 2000 // $20 threshold
}
}
});
// Retrieve configuration
const retrieved = await stripe.terminal.configurations.retrieve('tconf_123');
// Update configuration
const updated = await stripe.terminal.configurations.update('tconf_123', {
name: 'Updated Store Configuration',
tipping: {
usd: {
fixed_amounts: [150, 250, 350],
percentages: [15, 20, 25]
}
}
});
// List configurations
const configurations = await stripe.terminal.configurations.list({
limit: 10
});
// Delete configuration
const deleted = await stripe.terminal.configurations.del('tconf_123');Manage physical locations where readers operate:
interface TerminalLocation {
id: string;
object: 'terminal.location';
display_name?: string;
address: {
city?: string;
country: string;
line1?: string;
line2?: string;
postal_code?: string;
state?: string;
};
configuration_overrides?: string;
metadata?: { [key: string]: string };
}
// Create location
const location = await stripe.terminal.locations.create({
display_name: 'Main Store Location',
address: {
line1: '123 Main Street',
line2: 'Suite 100',
city: 'San Francisco',
state: 'CA',
postal_code: '94105',
country: 'US'
},
metadata: {
store_id: 'store_001',
manager: 'John Doe'
}
});
// Create location with configuration override
const restaurantLocation = await stripe.terminal.locations.create({
display_name: 'Downtown Restaurant',
address: {
line1: '456 Restaurant Row',
city: 'New York',
state: 'NY',
postal_code: '10001',
country: 'US'
},
configuration_overrides: 'tconf_restaurant_config'
});
// Retrieve location
const retrieved = await stripe.terminal.locations.retrieve('tml_123');
// Update location
const updated = await stripe.terminal.locations.update('tml_123', {
display_name: 'Updated Store Name',
metadata: {
store_id: 'store_001',
manager: 'Jane Smith',
last_updated: new Date().toISOString()
}
});
// List locations
const locations = await stripe.terminal.locations.list({
limit: 20
});
// Delete location
const deleted = await stripe.terminal.locations.del('tml_123');Manage and control card readers:
interface TerminalReader {
id: string;
object: 'terminal.reader';
device_type: 'bbpos_wisepad3' | 'bbpos_wisepos_e' | 'simulated_wisepos_e' | 'stripe_m2' | 'stripe_s700' | 'verifone_p400' | 'mobile_phone_reader';
label?: string;
location?: string;
serial_number: string;
status: 'online' | 'offline';
device_sw_version?: string;
ip_address?: string;
base_url?: string;
registration_code?: string;
action?: {
type: 'process_payment_intent' | 'process_setup_intent' | 'set_reader_display';
status: 'in_progress' | 'succeeded' | 'failed';
failure_code?: string;
failure_message?: string;
process_payment_intent?: {
payment_intent: string;
process_config?: {
skip_tipping?: boolean;
tipping?: TippingConfiguration;
};
};
process_setup_intent?: {
setup_intent: string;
process_config?: {
customer_cancellation?: boolean;
};
};
};
}
// Register new reader
const reader = await stripe.terminal.readers.create({
registration_code: 'simulated-wpe',
label: 'Front Desk Reader',
location: 'tml_main_store'
});
// Register reader with specific device type
const physicalReader = await stripe.terminal.readers.create({
registration_code: 'stripe-reader-12345',
label: 'Checkout Station 1',
location: 'tml_store_001',
metadata: {
station_number: '1',
cashier_terminal: 'pos_001'
}
});
// Retrieve reader
const retrieved = await stripe.terminal.readers.retrieve('tmr_123');
// Update reader
const updated = await stripe.terminal.readers.update('tmr_123', {
label: 'Updated Reader Label',
metadata: {
last_maintenance: new Date().toISOString(),
firmware_version: '1.2.3'
}
});
// List readers
const readers = await stripe.terminal.readers.list({
device_type: 'verifone_p400',
location: 'tml_123',
limit: 20
});
// List readers by status
const onlineReaders = await stripe.terminal.readers.list({
status: 'online'
});
// Delete reader
const deleted = await stripe.terminal.readers.del('tmr_123');Handle in-person payments through terminal readers:
// Process payment with reader
const payment = await stripe.terminal.readers.processPaymentIntent(
'tmr_reader_123',
{
payment_intent: 'pi_payment_123',
process_config: {
skip_tipping: false,
tipping: {
amount_eligible: 2000, // $20 eligible for tipping
}
}
}
);
// Process payment with custom tipping
const tippedPayment = await stripe.terminal.readers.processPaymentIntent(
'tmr_reader_123',
{
payment_intent: 'pi_payment_456',
process_config: {
tipping: {
amount_eligible: 5000, // $50 eligible for tipping
fixed_amounts: [200, 300, 500], // $2, $3, $5
percentages: [15, 18, 20]
}
}
}
);
// Process payment without tipping
const noTipPayment = await stripe.terminal.readers.processPaymentIntent(
'tmr_reader_123',
{
payment_intent: 'pi_payment_789',
process_config: {
skip_tipping: true
}
}
);Handle card setup for future payments:
// Process setup intent for card on file
const setup = await stripe.terminal.readers.processSetupIntent(
'tmr_reader_123',
{
setup_intent: 'seti_setup_123',
process_config: {
customer_cancellation: true // Allow customer to cancel
}
}
);
// Process setup intent without customer cancellation
const restrictedSetup = await stripe.terminal.readers.processSetupIntent(
'tmr_reader_123',
{
setup_intent: 'seti_setup_456',
process_config: {
customer_cancellation: false
}
}
);Control what appears on the reader screen:
interface ReaderDisplayCart {
line_items: Array<{
description: string;
amount: number;
quantity: number;
}>;
tax?: number;
total: number;
currency: string;
}
// Display shopping cart
const cartDisplay = await stripe.terminal.readers.setReaderDisplay(
'tmr_reader_123',
{
type: 'cart',
cart: {
line_items: [
{
description: 'Coffee',
amount: 350, // $3.50
quantity: 2
},
{
description: 'Pastry',
amount: 250, // $2.50
quantity: 1
}
],
tax: 95, // $0.95
total: 1045, // $10.45
currency: 'usd'
}
}
);
// Clear display
const clearDisplay = await stripe.terminal.readers.setReaderDisplay(
'tmr_reader_123',
{
type: 'clear'
}
);Cancel ongoing reader operations:
// Cancel current action on reader
const canceled = await stripe.terminal.readers.cancelAction('tmr_reader_123');Create connection tokens for reader connectivity:
interface TerminalConnectionToken {
object: 'terminal.connection_token';
secret: string;
}
// Create connection token
const connectionToken = await stripe.terminal.connectionTokens.create({
location: 'tml_store_location'
});
// Create connection token without location restriction
const globalToken = await stripe.terminal.connectionTokens.create();Simulate terminal scenarios in test mode:
// Present payment method to reader
const presented = await stripe.testHelpers.terminal.presentPaymentMethod(
'tmr_simulated_reader'
);
// Simulate different card types
const visaPresented = await stripe.testHelpers.terminal.presentPaymentMethod(
'tmr_simulated_reader',
{
type: 'card_present',
card_present: {
number: '4242424242424242'
}
}
);
const contactlessPresented = await stripe.testHelpers.terminal.presentPaymentMethod(
'tmr_simulated_reader',
{
type: 'card_present',
card_present: {
number: '4000000000000002', // Declined card for testing
brand: 'visa'
}
}
);// 1. Create payment intent
const paymentIntent = await stripe.paymentIntents.create({
amount: 2500, // $25.00
currency: 'usd',
payment_method_types: ['card_present'],
capture_method: 'manual' // For tip adjustment
});
// 2. Process payment on terminal
const terminalPayment = await stripe.terminal.readers.processPaymentIntent(
'tmr_reader_123',
{
payment_intent: paymentIntent.id,
process_config: {
tipping: {
amount_eligible: 2500,
percentages: [15, 18, 20, 25]
}
}
}
);
// 3. Wait for completion (via webhook or polling)
// Once completed, capture with tip if applicable
if (terminalPayment.status === 'succeeded') {
const finalPayment = await stripe.paymentIntents.capture(paymentIntent.id, {
amount_to_capture: terminalPayment.amount // Includes tip
});
}class TerminalPOS {
private readerId: string;
constructor(readerId: string) {
this.readerId = readerId;
}
async processOrder(orderItems: OrderItem[], customerId?: string) {
// Calculate total
const subtotal = orderItems.reduce((sum, item) =>
sum + (item.price * item.quantity), 0
);
const tax = Math.round(subtotal * 0.0875); // 8.75% tax
const total = subtotal + tax;
// Display cart on reader
await stripe.terminal.readers.setReaderDisplay(this.readerId, {
type: 'cart',
cart: {
line_items: orderItems.map(item => ({
description: item.name,
amount: item.price,
quantity: item.quantity
})),
tax,
total,
currency: 'usd'
}
});
// Create payment intent
const paymentIntent = await stripe.paymentIntents.create({
amount: total,
currency: 'usd',
customer: customerId,
payment_method_types: ['card_present'],
metadata: {
order_items: JSON.stringify(orderItems),
subtotal: subtotal.toString(),
tax: tax.toString()
}
});
// Process payment
const result = await stripe.terminal.readers.processPaymentIntent(
this.readerId,
{
payment_intent: paymentIntent.id,
process_config: {
tipping: {
amount_eligible: subtotal,
percentages: [15, 18, 20]
}
}
}
);
return result;
}
async handleRefund(originalPaymentIntentId: string, amount?: number) {
const refund = await stripe.refunds.create({
payment_intent: originalPaymentIntentId,
amount: amount, // Partial refund if specified
reason: 'requested_by_customer'
});
return refund;
}
}class TerminalManager {
async deployReadersToLocation(locationId: string, readerCount: number) {
const readers = [];
for (let i = 1; i <= readerCount; i++) {
const reader = await stripe.terminal.readers.create({
registration_code: `simulated-reader-${i}`,
label: `Station ${i}`,
location: locationId,
metadata: {
station_number: i.toString(),
deployment_date: new Date().toISOString()
}
});
readers.push(reader);
}
return readers;
}
async getLocationStatus(locationId: string) {
const readers = await stripe.terminal.readers.list({
location: locationId
});
const onlineCount = readers.data.filter(r => r.status === 'online').length;
const totalCount = readers.data.length;
return {
location: locationId,
total_readers: totalCount,
online_readers: onlineCount,
offline_readers: totalCount - onlineCount,
readers: readers.data
};
}
async updateAllReadersConfiguration(locationId: string, configId: string) {
const location = await stripe.terminal.locations.update(locationId, {
configuration_overrides: configId
});
return location;
}
}// Webhook handler for terminal events
app.post('/terminal-webhook', async (req, res) => {
const event = req.body;
switch (event.type) {
case 'terminal.reader.action_succeeded':
const successfulAction = event.data.object;
await handleSuccessfulPayment(successfulAction);
break;
case 'terminal.reader.action_failed':
const failedAction = event.data.object;
await handleFailedPayment(failedAction);
break;
default:
console.log(`Unhandled terminal event: ${event.type}`);
}
res.status(200).send('OK');
});
async function handleSuccessfulPayment(reader: TerminalReader) {
if (reader.action?.type === 'process_payment_intent') {
const paymentIntentId = reader.action.process_payment_intent?.payment_intent;
// Update order status, send receipt, etc.
await updateOrderStatus(paymentIntentId, 'paid');
await sendCustomerReceipt(paymentIntentId);
}
}Stripe Terminal provides a comprehensive in-person payment processing solution with support for various reader types, flexible tipping configurations, and real-time transaction monitoring capabilities.
Install with Tessl CLI
npx tessl i tessl/npm-stripe