Introduction
Payment methods beyond cards: what developers should know covers far more than just accepting Visa and Mastercard. As payment platforms mature, accepting wallets, bank debits, and region-specific methods isn't a nice-to-have anymore—it's a conversion lever. The best teams understand that the right payment method mix multiplies successful transactions and reduces friction at checkout.
By day 36 of your payment engineering journey, you've likely built card flows and managed disputes. Now it's time to expand into the ecosystem that surrounds them. This article walks through wallets that drive higher conversion, direct bank methods that cut fees, and the local payment landscape that unlocks international growth.
Apple Pay and Google Pay: Wallets increase conversion and reduce friction
Wallet integrations are the fastest way to improve checkout completion rates. Apple Pay and Google Pay tokenize card credentials on device, handling authentication and encryption before your API ever sees sensitive data. What makes them powerful isn't just security—it's speed. A user taps once, biometric auth happens, and the transaction completes in seconds.
In production, we see conversion uplift of 15–25% when wallet options sit alongside traditional card forms. Mobile traffic sees the largest gains because the payment sheet is native and seamless. Desktop users benefit from autofill convenience on Safari and Chrome.
From an implementation angle, Stripe's Payment Element or Stripe.js makes adding these wallets trivial. Here's the core pattern:
import { loadStripe } from '@stripe/stripe-js';
import { Stripe, StripeElements } from '@stripe/stripe-js';
async function setupWalletPayment() {
const stripe = await loadStripe(process.env.REACT_APP_STRIPE_KEY);
const elements = stripe.elements({
clientSecret: clientSecret,
appearance: { theme: 'stripe' },
});
const paymentElement = elements.create('payment', {
wallets: {
applePay: 'auto',
googlePay: 'auto',
},
});
paymentElement.mount('#payment-element');
return { stripe, elements, paymentElement };
}
The wallets configuration tells Stripe to render wallet buttons when the device and browser support them. On iOS Safari, users see Apple Pay. On Android or Chrome, Google Pay appears. On unsupported browsers, the card form falls back automatically.
One critical detail: wallets only work if your domain is registered in the Stripe Dashboard. Test environments need proper setup, and production must be HTTPS with valid DNS. We recommend validating this early in development to avoid "wallet button won't show" surprises in staging.
SEPA and ACH: Lower fees through direct bank transfers
Card networks charge 1.5–3% in interchange and scheme fees. Direct bank methods like SEPA (Single Euro Payments Area) and ACH (Automated Clearing House) cut that to 0.1–0.5% or even fixed amounts. For high-volume or low-margin businesses, this difference is enormous.
SEPA Direct Debit works across 36 European countries. A customer provides their IBAN once, and you can charge their account repeatedly with minimal friction. ACH covers the US and Canada with similarly low fees. Both settle slower than cards (3–5 days typically), so they suit subscription billing and planned transactions better than instant purchases.
The payment flow differs from cards. Instead of tokenizing and immediately charging, you collect the bank account details, create a mandate (permission to charge), and then debit on your schedule. Stripe handles mandate lifecycle for you:
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
async function createSepaPaymentIntent(customerId: string, amountCents: number) {
const paymentIntent = await stripe.paymentIntents.create({
amount: amountCents,
currency: 'eur',
customer: customerId,
payment_method_types: ['sepa_debit'],
mandate_data: {
customer_acceptance: {
type: 'online',
accepted_at: Math.floor(Date.now() / 1000),
},
},
});
return paymentIntent;
}
async function chargeSepaAccount(customerId: string, amountCents: number) {
const paymentIntent = await stripe.paymentIntents.create({
amount: amountCents,
currency: 'eur',
customer: customerId,
payment_method_types: ['sepa_debit'],
off_session: true, // Charging an existing mandate
});
await stripe.paymentIntents.confirm(paymentIntent.id);
return paymentIntent;
}
The first call sets up a mandate the customer agrees to. Once that's confirmed, subsequent calls use off_session: true to charge the mandate without re-authentication. Settlement takes 3–5 business days, so plan your reconciliation accordingly.
ACH works similarly but with US bank routing and account numbers instead of IBANs. Both methods require higher trust from customers—they're giving you recurring access to their account. Clear communication and easy opt-out are essential to maintain that trust and reduce disputes.
Local payment methods: Essential for international growth
A US merchant thinking globally quickly learns that card dominance is a myth outside English-speaking markets. In Germany, SOFORT bank transfers are preferred. In the Netherlands, iDEAL dominates. Japan loves convenience store payments. China runs on Alipay and WeChat Pay.
Ignoring local methods means leaving conversions on the table. A customer in Munich will abandon your checkout if Apple Pay and cards are the only options; they expect to pay via their local bank app. The same happens in Tokyo if you don't support Alipay or Konbini (convenience store) payments.
Stripe's Payment Element automatically localizes the method list based on customer location and currency. If you know the billing country, you can filter even tighter:
async function setupLocalizedPayment(
billingCountry: string,
currency: string,
clientSecret: string
) {
const stripe = await loadStripe(process.env.REACT_APP_STRIPE_KEY);
// Map countries to supported local methods
const localMethodsByCountry: Record = {
'DE': ['ideal', 'sofort', 'giropay', 'eps'],
'NL': ['ideal', 'giropay', 'sofort'],
'FR': ['giropay', 'sofort', 'bancontact'],
'JP': ['konbini', 'alipay', 'wechat_pay'],
'CN': ['alipay', 'wechat_pay'],
'BR': ['boleto'],
'MX': ['oxxo'],
};
const preferredMethods = localMethodsByCountry[billingCountry] || [];
const elements = stripe.elements({
clientSecret,
appearance: { theme: 'stripe', locale: 'auto' },
});
const paymentElement = elements.create('payment', {
defaultValues: {
billingDetails: {
country: billingCountry,
},
},
fields: {
// Stripe will surface preferred methods first
},
});
paymentElement.mount('#payment-element');
return { stripe, elements };
}
When you submit a payment intent with a specific currency and customer address, Stripe's backend filters the available methods intelligently. A German customer with EUR won't see OXXO (Mexico only), but will see Sofort, iDEAL, and Giropay.
Each local method has unique settlement rules and timing. Some clear same-day, others take 3–7 days. Build flexibility into your reconciliation and fulfillment logic. If you ship immediately on payment received, local bank methods introduce risk—consider holding fulfillment until settlement confirms.
Dynamic payment method selection: Show customers what fits
The best checkout experience doesn't show all methods to all customers. Instead, it adapts based on location, device, currency, and order size. A $5,000 purchase might warrant wire transfer or bank debit; a $15 impulse buy needs the fastest option available.
Dynamic method selection reduces cognitive load and improves completion rates. Fewer options actually convert better when those options are relevant.
Build this by fetching available methods server-side based on context, then passing that list to the client:
interface PaymentContext {
amount: number;
currency: string;
customerCountry: string;
deviceType: 'mobile' | 'desktop';
orderType: 'subscription' | 'one_time';
}
async function getAvailablePaymentMethods(
context: PaymentContext
): Promise {
const methods: string[] = [];
// All contexts support these core methods
methods.push('card');
// Wallets for mobile
if (context.deviceType === 'mobile') {
if (context.customerCountry === 'US') methods.push('apple_pay', 'google_pay');
if (['DE', 'NL', 'FR'].includes(context.customerCountry)) {
methods.push('apple_pay', 'google_pay');
}
}
// Bank debits for subscriptions
if (context.orderType === 'subscription') {
if (context.currency === 'EUR') methods.push('sepa_debit');
if (context.currency === 'USD') methods.push('us_bank_account');
}
// Local methods by country
if (context.customerCountry === 'JP') {
methods.push('konbini', 'alipay');
}
if (context.customerCountry === 'BR') {
methods.push('boleto');
}
// Large orders can support slower, cheaper methods
if (context.amount > 50000) {
methods.push('wire_transfer'); // if your processor supports it
}
return methods;
}
async function createPaymentIntentWithMethods(context: PaymentContext) {
const availableMethods = await getAvailablePaymentMethods(context);
const paymentIntent = await stripe.paymentIntents.create({
amount: context.amount,
currency: context.currency,
payment_method_types: availableMethods,
});
return paymentIntent;
}
This approach ensures your frontend never displays an unavailable method. Stripe rejects the payment intent creation if you include unsupported methods for a given country or currency combination, so validate server-side before rendering.
Common mistakes to avoid
- Not testing wallet buttons on real devices. Emulators often show wallets even when they're not properly configured. Always test Apple Pay on a physical iPhone and Google Pay on Android or Chrome with a saved card.
- Treating all methods as instant. Bank debits and local transfers settle asynchronously. If you immediately fulfill on payment intent creation, you risk chargebacks. Wait for webhook confirmation that the payment actually cleared.
- Forgetting HTTPS and DNS validation for wallets. Apple Pay and Google Pay require your domain to be verified. Missing this burns hours of debugging.
- Ignoring mandate expiry and renewal cycles for SEPA/ACH. Mandates expire after a period of inactivity. Implement renewal logic before customers hit that surprise decline.
- Not localizing currency and language. A German customer paying in USD with English copy will distrust your checkout. Always match currency to location where possible.
- Overcomplicating the method selection UX. Too many options paralyze users. Show 3–5 methods, not 15. Let Stripe's Payment Element handle the complexity.
Key takeaways
- Wallets (Apple Pay, Google Pay) drive 15–25% conversion uplift on mobile; enable them via Payment Element with minimal code.
- SEPA and ACH reduce fees from 1.5–3% to 0.1–0.5% but settle in 3–5 days; use them for subscriptions and planned charges, not impulse purchases.
- Local payment methods unlock international markets; customers won't convert if their preferred method isn't available, so map methods to customer geography.
- Build dynamic method selection server-side based on amount, currency, location, and device type; fewer relevant options convert better than every possible method.
- Always validate wallet domain setup and mandate lifecycles in staging before production; these edge cases burn time if missed.
- Plan reconciliation for asynchronous settlement; waiting for webhook confirmation before fulfillment eliminates most chargeback risk.






Comments
Loading comments…