Skip to main content

Saved Payment Methods

Learn how to save and manage payment methods securely for recurring billing, one-click checkout experiences, and subscription services. This guide covers card storage, management, and charging saved payment methods.

Overviewโ€‹

Saved payment methods enable you to charge customers without collecting payment details for each transaction. This is essential for:

  • Recurring subscription billing
  • One-click checkout experiences
  • Auto-renewal services
  • Membership platforms
  • Usage-based billing

Key Benefitsโ€‹

  • Improved Conversion - Reduce friction with one-click payments
  • Better Retention - Seamless subscription renewals
  • Reduced PCI Scope - Tokens instead of raw card data
  • Multiple Payment Options - Support multiple saved cards
  • Enhanced Security - 3D Secure on first use, faster subsequent charges

Security & Complianceโ€‹

Omise handles all card storage securely:

  • Cards are tokenized and encrypted
  • PCI DSS Level 1 compliant infrastructure
  • No raw card data touches your servers
  • Secure vault for card storage
  • Compliance with data protection regulations

Saving Payment Methodsโ€‹

Save Card with Customer Creationโ€‹

const omise = require('omise')({
secretKey: 'skey_test_123'
});

// Create customer with card
const customer = await omise.customers.create({
email: 'john@example.com',
description: 'John Doe',
card: 'tokn_test_123456' // Token from client-side
});

console.log('Customer ID:', customer.id);
console.log('Default Card:', customer.default_card);
import omise

omise.api_secret = 'skey_test_123'

# Create customer with card
customer = omise.Customer.create(
email='john@example.com',
description='John Doe',
card='tokn_test_123456'
)

print(f'Customer ID: {customer.id}')
print(f'Default Card: {customer.default_card}')
<?php
$omise = new Omise(['secretKey' => 'skey_test_123']);

$customer = $omise['customers']->create([
'email' => 'john@example.com',
'description' => 'John Doe',
'card' => 'tokn_test_123456'
]);

echo "Customer ID: " . $customer['id'] . "\n";
echo "Default Card: " . $customer['default_card'];
curl https://api.omise.co/customers \
-u skey_test_123: \
-d "email=john@example.com" \
-d "description=John Doe" \
-d "card=tokn_test_123456"

Add Card to Existing Customerโ€‹

// Add additional card
await omise.customers.update('cust_test_123456', {
card: 'tokn_test_new_card'
});

// Retrieve updated customer
const customer = await omise.customers.retrieve('cust_test_123456');
console.log(`Total cards: ${customer.cards.total}`);
console.log('Cards:', customer.cards.data.map(c =>
`${c.brand} โ€ขโ€ขโ€ขโ€ข ${c.last_digits}`
));
# Add additional card
customer = omise.Customer.retrieve('cust_test_123456')
customer.update(card='tokn_test_new_card')

# Retrieve updated customer
customer.reload()
print(f'Total cards: {customer.cards.total}')

for card in customer.cards.data:
print(f'{card.brand} โ€ขโ€ขโ€ขโ€ข {card.last_digits}')
curl https://api.omise.co/customers/cust_test_123456 \
-u skey_test_123: \
-d "card=tokn_test_new_card"

Client-Side Card Saving Flowโ€‹

// Frontend: Create token
async function saveCard() {
const cardData = {
name: document.getElementById('name').value,
number: document.getElementById('number').value,
expiration_month: document.getElementById('month').value,
expiration_year: document.getElementById('year').value,
security_code: document.getElementById('cvv').value
};

// Create token
const token = await Omise.createToken('card', cardData);

// Send token to backend
const response = await fetch('/api/save-card', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: token.id,
customerId: 'cust_test_123456'
})
});

return response.json();
}

// Backend: Save card to customer
app.post('/api/save-card', async (req, res) => {
try {
const { token, customerId } = req.body;

const customer = await omise.customers.update(customerId, {
card: token
});

res.json({
success: true,
cardId: customer.cards.data[0].id
});
} catch (error) {
res.status(400).json({
success: false,
error: error.message
});
}
});

Managing Payment Methodsโ€‹

List Saved Cardsโ€‹

const customer = await omise.customers.retrieve('cust_test_123456');

customer.cards.data.forEach(card => {
console.log(`Card ID: ${card.id}`);
console.log(`Brand: ${card.brand}`);
console.log(`Last 4: ${card.last_digits}`);
console.log(`Expires: ${card.expiration_month}/${card.expiration_year}`);
console.log(`Default: ${card.id === customer.default_card}`);
console.log('---');
});
customer = omise.Customer.retrieve('cust_test_123456')

for card in customer.cards.data:
print(f'Card ID: {card.id}')
print(f'Brand: {card.brand}')
print(f'Last 4: {card.last_digits}')
print(f'Expires: {card.expiration_month}/{card.expiration_year}')
print(f'Default: {card.id == customer.default_card}')
print('---')

Set Default Cardโ€‹

// Change default card
await omise.customers.update('cust_test_123456', {
default_card: 'card_test_789012'
});

// Verify change
const customer = await omise.customers.retrieve('cust_test_123456');
console.log('New default:', customer.default_card);
# Change default card
customer = omise.Customer.retrieve('cust_test_123456')
customer.update(default_card='card_test_789012')

# Verify change
customer.reload()
print(f'New default: {customer.default_card}')
curl https://api.omise.co/customers/cust_test_123456 \
-u skey_test_123: \
-d "default_card=card_test_789012"

Remove Cardโ€‹

// Delete specific card
await omise.customers.destroyCard('cust_test_123456', 'card_test_789012');

// Verify removal
const customer = await omise.customers.retrieve('cust_test_123456');
console.log(`Remaining cards: ${customer.cards.total}`);
# Delete specific card
customer = omise.Customer.retrieve('cust_test_123456')
customer.destroy_card('card_test_789012')

# Verify removal
customer.reload()
print(f'Remaining cards: {customer.cards.total}')
curl https://api.omise.co/customers/cust_test_123456/cards/card_test_789012 \
-u skey_test_123: \
-X DELETE

Retrieve Specific Cardโ€‹

// Get card details
const customer = await omise.customers.retrieve('cust_test_123456');
const card = customer.cards.data.find(c => c.id === 'card_test_789012');

console.log({
id: card.id,
brand: card.brand,
lastDigits: card.last_digits,
expiry: `${card.expiration_month}/${card.expiration_year}`,
name: card.name,
fingerprint: card.fingerprint
});
curl https://api.omise.co/customers/cust_test_123456/cards/card_test_789012 \
-u skey_test_123:

Charging Saved Payment Methodsโ€‹

Charge Default Cardโ€‹

// Charge customer's default card
const charge = await omise.charges.create({
customer: 'cust_test_123456',
amount: 100000, // 1,000.00 THB
currency: 'THB',
description: 'Monthly subscription - January 2024'
});

console.log('Charge status:', charge.status);
console.log('Card used:', charge.card.last_digits);
# Charge customer's default card
charge = omise.Charge.create(
customer='cust_test_123456',
amount=100000,
currency='THB',
description='Monthly subscription - January 2024'
)

print(f'Charge status: {charge.status}')
print(f'Card used: {charge.card.last_digits}')

Charge Specific Cardโ€‹

// Charge a specific saved card
const charge = await omise.charges.create({
customer: 'cust_test_123456',
card: 'card_test_789012',
amount: 100000,
currency: 'THB',
description: 'Purchase with backup card'
});
# Charge a specific saved card
charge = omise.Charge.create(
customer='cust_test_123456',
card='card_test_789012',
amount=100000,
currency='THB',
description='Purchase with backup card'
)

CVV-less Chargingโ€‹

// Saved cards don't require CVV for subsequent charges
const charge = await omise.charges.create({
customer: 'cust_test_123456',
amount: 50000,
currency: 'THB',
description: 'Auto-renewal payment'
});

// First charge may require 3D Secure
if (charge.authorize_uri) {
console.log('3DS required:', charge.authorize_uri);
// Redirect user for authentication
}

// Subsequent charges typically don't require 3DS
console.log('Charge successful:', charge.status === 'successful');

Charging with Fallbackโ€‹

async function chargeWithFallback(customerId, amount, currency) {
const customer = await omise.customers.retrieve(customerId);
const cards = customer.cards.data;

// Try primary card first
try {
return await omise.charges.create({
customer: customerId,
card: customer.default_card,
amount,
currency,
description: 'Payment attempt - primary card'
});
} catch (primaryError) {
console.log('Primary card failed:', primaryError.message);

// Try backup cards
for (const card of cards) {
if (card.id === customer.default_card) continue;

try {
return await omise.charges.create({
customer: customerId,
card: card.id,
amount,
currency,
description: 'Payment attempt - backup card'
});
} catch (backupError) {
console.log(`Backup card ${card.last_digits} failed`);
}
}

throw new Error('All payment methods failed');
}
}

Card Lifecycle Managementโ€‹

Monitor Expiring Cardsโ€‹

async function getExpiringCards(daysAhead = 30) {
const customers = await omise.customers.list({ limit: 100 });
const expiringCards = [];

const today = new Date();
const futureDate = new Date();
futureDate.setDate(today.getDate() + daysAhead);

for (const customer of customers.data) {
for (const card of customer.cards.data) {
const cardExpiry = new Date(
card.expiration_year,
card.expiration_month - 1,
1
);

if (cardExpiry <= futureDate && cardExpiry >= today) {
expiringCards.push({
customer: customer.id,
email: customer.email,
card: card.id,
lastDigits: card.last_digits,
brand: card.brand,
expiryMonth: card.expiration_month,
expiryYear: card.expiration_year
});
}
}
}

return expiringCards;
}

// Send notifications
const expiringCards = await getExpiringCards(30);
expiringCards.forEach(card => {
console.log(
`Notify ${card.email}: ${card.brand} โ€ขโ€ขโ€ขโ€ข ${card.lastDigits} ` +
`expires ${card.expiryMonth}/${card.expiryYear}`
);
// Send email notification
});

Update Expired Cardsโ€‹

class CardUpdateService {
async requestCardUpdate(customerId, cardId) {
// Generate secure update link
const updateToken = this.generateUpdateToken(customerId, cardId);
const updateUrl = `https://yourapp.com/update-card?token=${updateToken}`;

// Send email to customer
await this.sendUpdateEmail(customerId, updateUrl);

return updateUrl;
}

async processCardUpdate(customerId, newCardToken) {
// Get old card details
const customer = await omise.customers.retrieve(customerId);
const oldCard = customer.cards.data[0];

// Add new card
await omise.customers.update(customerId, {
card: newCardToken
});

// Get updated customer with new card
const updatedCustomer = await omise.customers.retrieve(customerId);
const newCard = updatedCustomer.cards.data[0];

// Set as default
await omise.customers.update(customerId, {
default_card: newCard.id
});

// Remove old card
await omise.customers.destroyCard(customerId, oldCard.id);

return newCard;
}
}

Handle Failed Paymentsโ€‹

async function handleFailedPayment(charge, customerId) {
const failureCode = charge.failure_code;
const failureMessage = charge.failure_message;

console.log(`Payment failed: ${failureCode} - ${failureMessage}`);

// Handle specific failure types
switch (failureCode) {
case 'insufficient_funds':
// Retry in a few days
await this.scheduleRetry(customerId, charge.amount, 3);
await this.notifyCustomer(customerId, 'insufficient_funds');
break;

case 'expired_card':
case 'invalid_card':
// Request card update
await this.requestCardUpdate(customerId);
await this.notifyCustomer(customerId, 'card_update_required');
break;

case 'stolen_or_lost_card':
// Immediately require new card
await this.suspendSubscription(customerId);
await this.notifyCustomer(customerId, 'security_issue');
break;

default:
// Try backup card if available
const customer = await omise.customers.retrieve(customerId);
if (customer.cards.total > 1) {
await this.tryBackupCard(customerId, charge.amount);
}
}
}

Advanced Card Managementโ€‹

Multi-Card Supportโ€‹

class MultiCardManager {
async addCard(customerId, cardToken, metadata = {}) {
const customer = await omise.customers.retrieve(customerId);

// Check if card already exists (by fingerprint)
const existingCard = await this.findDuplicateCard(
customer,
cardToken
);

if (existingCard) {
return { duplicate: true, card: existingCard };
}

// Add new card
await omise.customers.update(customerId, {
card: cardToken
});

const updatedCustomer = await omise.customers.retrieve(customerId);
const newCard = updatedCustomer.cards.data[0];

// Store card metadata in your database
await this.saveCardMetadata(customerId, newCard.id, metadata);

return { duplicate: false, card: newCard };
}

async findDuplicateCard(customer, cardToken) {
// Get card fingerprint from token
const token = await omise.tokens.retrieve(cardToken);
const fingerprint = token.card.fingerprint;

// Check existing cards
return customer.cards.data.find(
card => card.fingerprint === fingerprint
);
}

async organizeCards(customerId) {
const customer = await omise.customers.retrieve(customerId);
const cards = customer.cards.data;

// Separate by validity
const valid = [];
const expiring = [];
const expired = [];

const today = new Date();
const in30Days = new Date();
in30Days.setDate(today.getDate() + 30);

cards.forEach(card => {
const expiry = new Date(
card.expiration_year,
card.expiration_month - 1,
1
);

if (expiry < today) {
expired.push(card);
} else if (expiry < in30Days) {
expiring.push(card);
} else {
valid.push(card);
}
});

return { valid, expiring, expired };
}
}

Card Validation Rulesโ€‹

class CardValidator {
async validateCard(cardToken) {
const token = await omise.tokens.retrieve(cardToken);
const card = token.card;

// Check expiry
const expiry = new Date(
card.expiration_year,
card.expiration_month - 1,
1
);

if (expiry <= new Date()) {
return { valid: false, reason: 'Card has expired' };
}

// Check security code
if (!card.security_code_check) {
return { valid: false, reason: 'Security code check failed' };
}

// Check brand
const allowedBrands = ['Visa', 'MasterCard', 'JCB'];
if (!allowedBrands.includes(card.brand)) {
return { valid: false, reason: 'Card brand not supported' };
}

return { valid: true };
}

async testCard(customerId, cardToken) {
// Create small test charge
try {
const charge = await omise.charges.create({
customer: customerId,
card: cardToken,
amount: 100, // 1.00 THB
currency: 'THB',
description: 'Card verification charge'
});

// Refund immediately
if (charge.status === 'successful') {
await omise.refunds.create(charge.id, {
amount: charge.amount
});
}

return { valid: true };
} catch (error) {
return { valid: false, reason: error.message };
}
}
}

Smart Retry Logicโ€‹

class PaymentRetryService {
async retryFailedPayment(customerId, amount, currency, attempt = 1) {
const maxAttempts = 3;
const backoffDays = [1, 3, 7]; // Retry after 1, 3, and 7 days

if (attempt > maxAttempts) {
await this.handleMaxRetriesReached(customerId);
return null;
}

try {
const charge = await omise.charges.create({
customer: customerId,
amount,
currency,
description: `Retry attempt ${attempt} of ${maxAttempts}`
});

if (charge.status === 'successful') {
await this.notifyRetrySuccess(customerId, attempt);
return charge;
}

} catch (error) {
console.log(`Attempt ${attempt} failed:`, error.message);

// Schedule next retry
if (attempt < maxAttempts) {
const nextRetryDays = backoffDays[attempt];
await this.scheduleRetry(
customerId,
amount,
currency,
nextRetryDays,
attempt + 1
);
}
}

return null;
}

async scheduleRetry(customerId, amount, currency, days, attempt) {
const retryDate = new Date();
retryDate.setDate(retryDate.getDate() + days);

// Store retry info in your database
await this.saveRetrySchedule({
customerId,
amount,
currency,
retryDate,
attempt
});

// Notify customer
await this.notifyRetryScheduled(customerId, retryDate, attempt);
}
}

Common Use Casesโ€‹

Subscription Billingโ€‹

class SubscriptionBilling {
async processMonthlyBilling() {
const subscriptions = await this.getActiveSubscriptions();

for (const sub of subscriptions) {
try {
const charge = await omise.charges.create({
customer: sub.customerId,
amount: sub.amount,
currency: 'THB',
description: `${sub.planName} - ${this.getCurrentBillingPeriod()}`,
metadata: {
subscription_id: sub.id,
billing_period: this.getCurrentBillingPeriod()
}
});

if (charge.status === 'successful') {
await this.recordSuccessfulPayment(sub.id, charge.id);
await this.notifyCustomer(sub.customerId, 'payment_success');
}

} catch (error) {
await this.handleFailedPayment(sub, error);
}
}
}

getCurrentBillingPeriod() {
const now = new Date();
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
}
}

Usage-Based Billingโ€‹

class UsageBasedBilling {
async chargeForUsage(customerId, usageData) {
// Calculate amount based on usage
const amount = this.calculateAmount(usageData);

if (amount === 0) {
console.log('No usage to charge');
return null;
}

const charge = await omise.charges.create({
customer: customerId,
amount,
currency: 'THB',
description: `Usage billing: ${usageData.units} units`,
metadata: {
usage_period: usageData.period,
units_used: usageData.units,
rate: usageData.rate
}
});

return charge;
}

calculateAmount(usageData) {
const { units, rate, includedUnits } = usageData;
const billableUnits = Math.max(0, units - includedUnits);
return billableUnits * rate;
}
}

Wallet Managementโ€‹

class WalletService {
async topUpWallet(customerId, amount) {
// Charge saved card
const charge = await omise.charges.create({
customer: customerId,
amount,
currency: 'THB',
description: 'Wallet top-up'
});

if (charge.status === 'successful') {
// Add to wallet balance in your database
await this.addToWalletBalance(customerId, amount);
}

return charge;
}

async autoTopUp(customerId) {
const balance = await this.getWalletBalance(customerId);
const threshold = 10000; // 100.00 THB
const topUpAmount = 50000; // 500.00 THB

if (balance < threshold) {
return await this.topUpWallet(customerId, topUpAmount);
}

return null;
}
}

Best Practicesโ€‹

Securityโ€‹

// 1. Never store raw card data
// โœ… Good - Use tokens
const customer = await omise.customers.create({
email: 'user@example.com',
card: tokenFromClient
});

// โŒ Bad - Don't store card numbers
// NEVER DO THIS
const user = {
cardNumber: '4242424242424242',
cvv: '123'
};

// 2. Validate before saving
async function saveCardSecurely(customerId, cardToken) {
const validation = await validateCard(cardToken);

if (!validation.valid) {
throw new Error(`Invalid card: ${validation.reason}`);
}

return await omise.customers.update(customerId, {
card: cardToken
});
}

// 3. Monitor for suspicious activity
async function detectFraud(customerId, charge) {
const recentCharges = await this.getRecentCharges(customerId);

// Multiple failed attempts
const failedAttempts = recentCharges.filter(
c => c.status === 'failed'
).length;

if (failedAttempts >= 3) {
await this.flagForReview(customerId);
throw new Error('Multiple failed payment attempts detected');
}
}

User Experienceโ€‹

// 1. Show clear card information
function displaySavedCard(card) {
return {
display: `${card.brand} โ€ขโ€ขโ€ขโ€ข ${card.last_digits}`,
expiry: `${card.expiration_month}/${card.expiration_year}`,
isDefault: card.id === customer.default_card,
isExpiring: isCardExpiring(card),
icon: getCardBrandIcon(card.brand)
};
}

// 2. Proactive expiry notifications
async function sendExpiryReminders() {
const expiringIn30Days = await getExpiringCards(30);
const expiringIn7Days = await getExpiringCards(7);

// First reminder - 30 days
for (const card of expiringIn30Days) {
await sendEmail(card.email, 'card-expiring-30-days', card);
}

// Final reminder - 7 days
for (const card of expiringIn7Days) {
await sendEmail(card.email, 'card-expiring-7-days', card);
}
}

// 3. Graceful failure handling
async function chargeWithGracefulFailure(customerId, amount) {
try {
return await omise.charges.create({
customer: customerId,
amount,
currency: 'THB'
});
} catch (error) {
// User-friendly error messages
const userMessage = this.getUserFriendlyMessage(error);

// Log for debugging
console.error('Charge failed:', error);

// Notify customer
await this.notifyCustomer(customerId, userMessage);

throw new Error(userMessage);
}
}

Performanceโ€‹

// 1. Cache customer data
class CustomerCache {
constructor() {
this.cache = new Map();
this.ttl = 5 * 60 * 1000; // 5 minutes
}

async getCustomer(customerId) {
const cached = this.cache.get(customerId);

if (cached && Date.now() - cached.timestamp < this.ttl) {
return cached.data;
}

const customer = await omise.customers.retrieve(customerId);
this.cache.set(customerId, {
data: customer,
timestamp: Date.now()
});

return customer;
}
}

// 2. Batch operations
async function batchChargeCustomers(charges) {
const results = await Promise.allSettled(
charges.map(({ customerId, amount }) =>
omise.charges.create({
customer: customerId,
amount,
currency: 'THB'
})
)
);

const successful = results.filter(r => r.status === 'fulfilled');
const failed = results.filter(r => r.status === 'rejected');

return { successful, failed };
}

Troubleshootingโ€‹

Card Already Existsโ€‹

// Check for duplicate cards before adding
async function addCardSafely(customerId, cardToken) {
const token = await omise.tokens.retrieve(cardToken);
const customer = await omise.customers.retrieve(customerId);

// Check if card with same fingerprint exists
const duplicate = customer.cards.data.find(
card => card.fingerprint === token.card.fingerprint
);

if (duplicate) {
return {
added: false,
message: 'This card is already saved',
existingCard: duplicate
};
}

await omise.customers.update(customerId, { card: cardToken });
return { added: true };
}

Charge Fails with Saved Cardโ€‹

async function diagnoseChargeFailure(customerId, error) {
console.log('Diagnosing charge failure...');

// Check customer exists
let customer;
try {
customer = await omise.customers.retrieve(customerId);
} catch (e) {
return 'Customer not found';
}

// Check has payment method
if (!customer.default_card) {
return 'No default payment method';
}

// Check card validity
const card = customer.cards.data.find(
c => c.id === customer.default_card
);

const expiry = new Date(
card.expiration_year,
card.expiration_month - 1,
1
);

if (expiry < new Date()) {
return 'Card has expired';
}

// Check error code
if (error.code === 'insufficient_funds') {
return 'Insufficient funds on card';
}

return 'Unknown error - contact support';
}

Cannot Remove Cardโ€‹

async function removeCardSafely(customerId, cardId) {
const customer = await omise.customers.retrieve(customerId);

// Prevent removing only card
if (customer.cards.total === 1) {
throw new Error('Cannot remove the only payment method');
}

// If removing default, set new default first
if (customer.default_card === cardId) {
const otherCard = customer.cards.data.find(c => c.id !== cardId);

await omise.customers.update(customerId, {
default_card: otherCard.id
});
}

// Now safe to remove
await omise.customers.destroyCard(customerId, cardId);
}

FAQโ€‹

General Questionsโ€‹

Q: How long are saved cards valid?

A: Saved cards remain valid until they expire or are manually removed. Monitor expiration dates and request updates proactively.

Q: Can customers save cards from different countries?

A: Yes, customers can save cards from any country, subject to your account's supported countries and currencies.

Q: Do saved cards require CVV for charges?

A: No, once a card is saved (with CVV verified initially), subsequent charges don't require CVV.

Q: Can I migrate saved cards between systems?

A: No, for security reasons, card tokens cannot be exported. Customers must re-enter payment information when migrating platforms.

Q: What happens if a saved card expires?

A: Charges will fail. Proactively monitor expiration dates and request updated card information from customers.

Q: How many cards can a customer save?

A: There's no strict limit, but for best UX, we recommend allowing 3-5 active cards per customer.

Security Questionsโ€‹

Q: Are saved cards PCI compliant?

A: Yes, Omise handles all card storage in PCI DSS Level 1 compliant infrastructure. You don't store any card data.

Q: Can saved cards be used fraudulently?

A: Omise implements fraud detection and 3D Secure when appropriate. Monitor for suspicious patterns in your application.

Q: Should I implement additional verification for saved card charges?

A: For high-value transactions or suspicious activity, consider requiring additional authentication (SMS OTP, email confirmation, etc.).

Q: How do I handle compromised cards?

A: Remove the compromised card immediately and notify the customer to add a new payment method.

Technical Questionsโ€‹

Q: Can I charge a saved card in a different currency?

A: Yes, you can charge saved cards in any supported currency, subject to your account settings.

Q: What's the difference between card and token parameters?

A: Use card (token ID) when saving a new card. Use card (card ID) when charging a specific saved card.

Q: Can I update saved card details?

A: No, you cannot update card details. Remove the old card and add a new one with updated information.

Q: How do I handle 3D Secure with saved cards?

A: First charge may require 3D Secure. Subsequent charges typically don't, but banks may request it for high-risk transactions.

Next Stepsโ€‹