Access the Stripe virtual terminal by going to Dashboard → Payments → + Create payment. Enter the customer's card number, expiration, CVC, and charge amount to process a manual payment. This works for phone orders and in-person payments without a card reader. No additional setup or hardware is required — it is built into every Stripe account.
What Is a Stripe Virtual Terminal?
A virtual terminal lets you type in a customer's card details directly to process a payment — no physical card reader needed. Stripe includes this feature in every account through the Dashboard's 'Create payment' form. It is commonly used for phone orders, mail orders (MOTO), and occasional in-person transactions where a customer reads their card number to you. Unlike card-present transactions, manually keyed payments carry higher fraud risk, so Stripe charges a slightly different fee structure and these transactions are not eligible for the liability shift from 3D Secure.
Prerequisites
- A Stripe account with live mode activated (identity verification complete)
- The customer's card number, expiration date, and CVC
- Admin or transfer-level access to the Stripe Dashboard
Step-by-step guide
Open the virtual terminal
Open the virtual terminal
Log in to the Stripe Dashboard. Click 'Payments' in the left sidebar, then click the '+ Create payment' button in the top right corner. This opens the manual payment form.
Expected result: A form appears with fields for amount, currency, card number, expiration, CVC, and optional customer details.
Enter payment details
Enter payment details
Enter the charge amount in dollars (Stripe converts to cents automatically in this form). Type the customer's card number, expiration date (MM/YY), and CVC. Optionally add the customer's email for a receipt and a description for your records.
Expected result: All payment fields are filled in and ready to submit.
Process the payment
Process the payment
Review the details, then click 'Create payment'. Stripe processes the charge in real time. If the card is declined, you see an error message immediately and can ask the customer for a different card.
Expected result: The payment is processed and appears in your Payments list. If an email was provided, a receipt is sent to the customer.
Process a manual payment via the API
Process a manual payment via the API
For automating manual card entry (e.g., a phone order system), create a PaymentMethod and PaymentIntent in sequence.
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);23// Create a PaymentMethod from raw card data4// NOTE: This requires PCI DSS compliance. For most businesses,5// use Stripe.js or Elements on the frontend instead.6const paymentIntent = await stripe.paymentIntents.create({7 amount: 7500, // $75.008 currency: 'usd',9 payment_method_data: {10 type: 'card',11 card: {12 token: 'tok_visa', // In production, collect via Stripe.js13 },14 },15 confirm: true,16 description: 'Phone order #1234',17 receipt_email: 'customer@example.com',18 automatic_payment_methods: {19 enabled: true,20 allow_redirects: 'never',21 },22});2324console.log('Payment:', paymentIntent.id, paymentIntent.status);Expected result: The payment is processed and a receipt email is sent to the customer.
Complete working example
1const express = require('express');2const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);34const app = express();5app.use(express.json());67// Process a manual/phone-order payment8// In production, card details should be tokenized via Stripe.js9// to avoid PCI compliance requirements on your server10app.post('/api/manual-charge', async (req, res) => {11 try {12 const { amount, token, email, description } = req.body;1314 if (!amount || !token) {15 return res.status(400).json({ error: 'Amount and card token are required' });16 }1718 const paymentIntent = await stripe.paymentIntents.create({19 amount, // in cents20 currency: 'usd',21 payment_method_data: {22 type: 'card',23 card: { token },24 },25 confirm: true,26 description: description || 'Manual charge',27 receipt_email: email || undefined,28 automatic_payment_methods: {29 enabled: true,30 allow_redirects: 'never',31 },32 });3334 res.json({35 id: paymentIntent.id,36 status: paymentIntent.status,37 amount: paymentIntent.amount / 100,38 receipt_email: paymentIntent.receipt_email,39 });40 } catch (err) {41 if (err.type === 'StripeCardError') {42 return res.status(402).json({43 error: err.message,44 decline_code: err.decline_code,45 });46 }47 res.status(500).json({ error: err.message });48 }49});5051// List recent manual payments52app.get('/api/manual-charges', async (req, res) => {53 try {54 const payments = await stripe.paymentIntents.list({55 limit: 20,56 });5758 res.json(payments.data.map((pi) => ({59 id: pi.id,60 amount: pi.amount / 100,61 status: pi.status,62 description: pi.description,63 created: new Date(pi.created * 1000).toISOString(),64 })));65 } catch (err) {66 res.status(500).json({ error: err.message });67 }68});6970const PORT = process.env.PORT || 3000;71app.listen(PORT, () => console.log(`Server on port ${PORT}`));Common mistakes when getting a Stripe virtual terminal
Why it's a problem: Using the virtual terminal as the primary checkout method for an online business
How to avoid: The virtual terminal is for occasional manual charges (phone orders, etc.). For online sales, use Stripe Checkout, Payment Links, or Elements for better security and conversion.
Why it's a problem: Handling raw card numbers on your server without PCI compliance
How to avoid: If you build a custom virtual terminal, use Stripe.js to tokenize card data on the frontend. Only the token (tok_ or pm_) should reach your server. This keeps you out of PCI DSS scope.
Why it's a problem: Not adding a description to manual charges
How to avoid: Always add a description (e.g., 'Phone order #1234') so you can identify the charge later in your Dashboard and for dispute evidence.
Why it's a problem: Forgetting to include the customer's email for receipt
How to avoid: Always enter the customer's email so Stripe sends an automatic receipt. This creates a paper trail useful for potential disputes.
Best practices
- Always add a description and customer email to manual charges for record-keeping and receipt delivery
- Use the Dashboard virtual terminal for occasional charges — build a custom terminal with Stripe.js for regular phone-order workflows
- Never store or log raw card numbers — use tokenization via Stripe.js or Stripe Terminal
- Test in test mode with card 4242424242424242 before processing live manual charges
- Be aware that manually entered card payments have higher fraud risk — verify customer identity when possible
- Consider Stripe Terminal (hardware card readers) if you regularly accept in-person payments
- Enable receipt emails in Settings → Emails to automatically send confirmations for all charges
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Write a Node.js Express endpoint that processes manual Stripe payments similar to a virtual terminal. Accept a card token (from Stripe.js), amount, customer email, and description. Create a PaymentIntent with confirm: true and handle card decline errors gracefully.
Build a simple virtual terminal for my phone-order business. Create a web form that tokenizes card details with Stripe.js on the frontend, sends the token to my server, and creates a confirmed PaymentIntent with receipt email and order description.
Frequently asked questions
Does Stripe charge extra for virtual terminal (manually keyed) transactions?
Stripe's standard online rate is 2.9% + $0.30. Manually keyed (card-not-present) transactions use the same rate. However, manually entered cards have higher fraud risk and are not protected by 3D Secure's liability shift.
Is the Stripe virtual terminal PCI compliant?
Yes. The Dashboard virtual terminal is fully PCI compliant because card data is entered directly into Stripe's interface, not through your systems. If you build a custom terminal, you must tokenize card data via Stripe.js.
Can I save the customer's card for future charges?
When using the Dashboard virtual terminal, you can create a customer record and save the card for future use. Check 'Save card for future use' during manual payment creation.
What is the difference between a virtual terminal and Stripe Terminal?
The virtual terminal is for manually typing card numbers (phone/mail orders). Stripe Terminal is a hardware card reader system for in-person chip and tap payments, which qualifies for lower card-present rates.
Can multiple team members use the virtual terminal?
Yes. Add team members in Dashboard → Settings → Team. Assign them the 'Transfer Analyst' or 'Administrator' role to let them create manual payments.
What if I need a custom phone-order system integrated with Stripe?
For businesses handling regular phone orders that need a branded interface, order management, and CRM integration, the RapidDev team can build a custom virtual terminal solution on top of Stripe's API.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation