Collecting Card Information
Use Omise.js to securely collect card details directly from your website while keeping sensitive data off your servers.
Overviewโ
Omise.js is a client-side JavaScript library that enables secure card tokenization directly from your website. This approach ensures that sensitive payment data never touches your server, significantly reducing your PCI compliance burden.
How Tokenization Worksโ
Key Benefits:
- Card data goes directly to Omise's PCI-certified servers
- Your server only receives a single-use token
- Reduced PCI compliance requirements
- Enhanced security for your customers
Implementation Stepsโ
Step 1: Include Omise.js Libraryโ
Add the Omise.js script before the closing </body> tag:
<script src="https://cdn.omise.co/omise.js"></script>
Omise.js requires HTTPS on your checkout page. Use TLS 1.2 or higher for optimal security.
Step 2: Create Payment Formโ
Build your HTML form with card input fields:
<form id="payment-form">
<div class="form-group">
<label for="card-name">Cardholder Name</label>
<input type="text" id="card-name" placeholder="John Doe" required />
</div>
<div class="form-group">
<label for="card-number">Card Number</label>
<input type="text" id="card-number" placeholder="4242 4242 4242 4242" required />
</div>
<div class="form-row">
<div class="form-group">
<label for="expiry-month">Expiry Month</label>
<input type="text" id="expiry-month" placeholder="12" maxlength="2" required />
</div>
<div class="form-group">
<label for="expiry-year">Expiry Year</label>
<input type="text" id="expiry-year" placeholder="2027" maxlength="4" required />
</div>
<div class="form-group">
<label for="cvv">CVV</label>
<input type="text" id="cvv" placeholder="123" maxlength="4" required />
</div>
</div>
<!-- Optional billing address (improves authorization rates) -->
<div class="form-group">
<label for="postal-code">Postal Code</label>
<input type="text" id="postal-code" placeholder="10110" />
</div>
<button type="submit" id="pay-button">Pay Now</button>
<div id="error-message" style="color: red;"></div>
</form>
Step 3: Set Your Public Keyโ
Initialize Omise.js with your public key:
Omise.setPublicKey("pkey_test_YOUR_PUBLIC_KEY");
Use test keys (pkey_test_...) during development and live keys (pkey_...) in production. Never use live keys in test environments!
Step 4: Handle Form Submissionโ
Create token on form submission:
document.getElementById('payment-form').addEventListener('submit', function(e) {
e.preventDefault();
// Disable submit button to prevent double submission
const payButton = document.getElementById('pay-button');
payButton.disabled = true;
payButton.textContent = 'Processing...';
// Create card token
Omise.createToken("card", {
name: document.getElementById('card-name').value,
number: document.getElementById('card-number').value,
expiration_month: parseInt(document.getElementById('expiry-month').value),
expiration_year: parseInt(document.getElementById('expiry-year').value),
security_code: document.getElementById('cvv').value,
postal_code: document.getElementById('postal-code').value
}, function(statusCode, response) {
if (statusCode === 200) {
// Success! Token created
handleToken(response.id);
} else {
// Error occurred
handleError(response.message);
payButton.disabled = false;
payButton.textContent = 'Pay Now';
}
});
});
function handleToken(token) {
// Send token to your server
fetch('/checkout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// Include CSRF token for security (framework-dependent)
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.content
},
body: JSON.stringify({
token: token,
amount: 10025, // Amount in smallest unit (e.g., cents)
currency: 'thb'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Redirect to success page
window.location.href = '/payment-success';
} else {
handleError(data.error);
}
})
.catch(error => {
handleError('Network error occurred');
});
}
function handleError(message) {
document.getElementById('error-message').textContent = message;
const payButton = document.getElementById('pay-button');
payButton.disabled = false;
payButton.textContent = 'Pay Now';
}
Step 5: Process Payment on Serverโ
Receive the token on your server and create a charge:
- Node.js
- PHP
- Python
const express = require('express');
const omise = require('omise')({
secretKey: 'skey_test_YOUR_SECRET_KEY'
});
app.post('/checkout', async (req, res) => {
try {
const { token, amount, currency } = req.body;
const charge = await omise.charges.create({
amount: amount,
currency: currency,
card: token
});
if (charge.status === 'successful') {
res.json({ success: true, chargeId: charge.id });
} else {
res.json({ success: false, error: charge.failure_message });
}
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
<?php
require_once 'vendor/autoload.php';
define('OMISE_SECRET_KEY', 'skey_test_YOUR_SECRET_KEY');
$token = $_POST['token'];
$amount = $_POST['amount'];
$currency = $_POST['currency'];
try {
$charge = OmiseCharge::create(array(
'amount' => $amount,
'currency' => $currency,
'card' => $token
));
if ($charge['status'] == 'successful') {
echo json_encode(['success' => true, 'chargeId' => $charge['id']]);
} else {
echo json_encode(['success' => false, 'error' => $charge['failure_message']]);
}
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
?>
from flask import Flask, request, jsonify
import omise
omise.api_secret = 'skey_test_YOUR_SECRET_KEY'
@app.route('/checkout', methods=['POST'])
def checkout():
try:
data = request.get_json()
token = data['token']
amount = data['amount']
currency = data['currency']
charge = omise.Charge.create(
amount=amount,
currency=currency,
card=token
)
if charge.status == 'successful':
return jsonify({'success': True, 'chargeId': charge.id})
else:
return jsonify({'success': False, 'error': charge.failure_message})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
Security Best Practicesโ
1. Never Store Card Data on Your Serverโ
NEVER log, store, or transmit raw card data through your server. Always use tokens.
// โ BAD: Sending card data to your server
fetch('/checkout', {
body: JSON.stringify({
cardNumber: '4242424242424242', // DON'T DO THIS!
cvv: '123' // DON'T DO THIS!
})
});
// โ
GOOD: Sending only the token
fetch('/checkout', {
body: JSON.stringify({
token: 'tokn_test_...' // This is safe!
})
});
2. Use HTTPS Everywhereโ
- Minimum TLS 1.2 required
- Apply to entire site, not just checkout
- Use Let's Encrypt for free certificates
3. Implement CSP Headersโ
<meta http-equiv="Content-Security-Policy"
content="script-src 'self' https://cdn.omise.co;">
4. Disable Analytics on Checkoutโ
Prevent accidental card data capture:
// Disable Google Analytics on checkout page
window['ga-disable-UA-XXXXXX-Y'] = true;
5. Implement CSRF Protectionโ
Always include CSRF tokens in payment requests to prevent cross-site request forgery attacks:
// Client-side: Include CSRF token in requests
fetch('/checkout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({ token: tokenId })
});
<!-- Add CSRF meta tag in your page head -->
<meta name="csrf-token" content="<?php echo $_SESSION['csrf_token']; ?>">
Server-side validation examples:
- Node.js (Express)
- PHP
- Python (Flask)
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
app.post('/checkout', csrfProtection, async (req, res) => {
// CSRF validation automatic with middleware
const { token, amount } = req.body;
// Process payment...
});
<?php
session_start();
// Verify CSRF token
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
http_response_code(403);
die('CSRF token mismatch');
}
// Process payment...
?>
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)
@app.route('/checkout', methods=['POST'])
@csrf.exempt # Use this only if validating manually
def checkout():
# CSRF validation automatic with Flask-WTF
token = request.json['token']
# Process payment...
6. Clear Form After Submissionโ
function clearForm() {
document.getElementById('payment-form').reset();
// Or remove values programmatically
document.getElementById('card-number').value = '';
document.getElementById('cvv').value = '';
}
Improving Authorization Ratesโ
Collect Billing Addressโ
Omise.createToken("card", {
// Required fields
name: "John Doe",
number: "4242424242424242",
expiration_month: 12,
expiration_year: 2027,
security_code: "123",
// Optional but recommended for better authorization
city: "Bangkok",
postal_code: "10110",
country: "TH",
street1: "1448 Praditmanutham Road",
phone_number: "+66876543210"
}, callback);
Benefits of Complete Billing Information:
- Improves authorization rates by 5-15%
- Enables Address Verification Service (AVS)
- Reduces fraud risk
- Required for some international cards
Common Issues & Troubleshootingโ
Issue: "Public key is required"โ
Cause: Forgot to set public key
Solution:
Omise.setPublicKey("pkey_test_YOUR_KEY");
Issue: "Card number is invalid"โ
Causes:
- Incorrect card number format
- Test card used in live mode
- Unsupported card brand
Solution:
- Validate card format before submission
- Use Luhn algorithm for client-side validation
- Check supported card brands
Issue: Token creation returns 400 errorโ
Common Causes:
- Missing required fields (name, number, expiration, CVV)
- Invalid expiration date
- Wrong data types (string vs integer)
Solution:
// Ensure correct data types
Omise.createToken("card", {
name: cardName, // string
number: cardNumber.replace(/\s/g, ''), // string, remove spaces
expiration_month: parseInt(expiryMonth), // integer!
expiration_year: parseInt(expiryYear), // integer!
security_code: cvv // string
}, callback);
Issue: Mixed Content Errorโ
Cause: Loading Omise.js over HTTP
Solution: Ensure entire page uses HTTPS
Advanced Featuresโ
Client-Side Card Validationโ
function validateCard() {
const cardNumber = document.getElementById('card-number').value;
// Basic Luhn algorithm check
function luhnCheck(num) {
let arr = (num + '')
.split('')
.reverse()
.map(x => parseInt(x));
let lastDigit = arr.splice(0, 1)[0];
let sum = arr.reduce(
(acc, val, i) => (i % 2 !== 0 ? acc + val : acc + ((val * 2) % 9) || 9),
0
);
sum += lastDigit;
return sum % 10 === 0;
}
if (!luhnCheck(cardNumber.replace(/\s/g, ''))) {
document.getElementById('error-message').textContent =
'Invalid card number';
return false;
}
return true;
}
Format Card Number Inputโ
document.getElementById('card-number').addEventListener('input', function(e) {
let value = e.target.value.replace(/\s/g, '');
let formattedValue = value.match(/.{1,4}/g)?.join(' ') || value;
e.target.value = formattedValue;
});
Detect Card Brandโ
function detectCardBrand(number) {
const patterns = {
visa: /^4/,
mastercard: /^5[1-5]/,
amex: /^3[47]/,
jcb: /^35/
};
for (let brand in patterns) {
if (patterns[brand].test(number)) {
return brand;
}
}
return 'unknown';
}
FAQโ
Can I customize the appearance of the payment form?
Yes! You have complete control over HTML and CSS. Omise.js only handles tokenization - you design the form. Style it to match your brand perfectly.
Are tokens reusable?
No, tokens are single-use only. Each payment requires a new token. To save cards for future use, create a Customer and attach the card.
What happens if tokenization fails?
Omise.js returns a status code and error message. Common errors:
- Invalid card number
- Expired card
- Incorrect CVV
- Network connectivity issues
Always handle errors gracefully and show user-friendly messages.
Do I need PCI compliance?
Using Omise.js significantly reduces PCI requirements. Since card data never touches your server, you typically only need SAQ A compliance (simplest form). Consult with your security team for specifics.
Can I collect cards without a form?
Yes, Omise.js offers a pre-built payment form that handles UI and validation:
Omise.setPublicKey("pkey_test_...");
OmiseCard.open({
amount: 10025,
currency: "THB",
onCreateTokenSuccess: (token) => { /* handle token */ }
});
How do I test card collection?
Use Omise test cards:
- Success: 4242 4242 4242 4242
- Decline: 4000 0000 0000 0002
Any future expiration date and any CVV work in test mode.
Related Resourcesโ
- Omise.js Documentation - Complete API reference
- Credit Card Payments - Implementation guide
- Security Best Practices - Protect your integration
- Testing Guide - Test cards and scenarios
- 3D Secure - Enhanced security
Next Stepsโ
- Set up your HTML form
- Implement JavaScript tokenization
- Create server-side charge endpoint
- Test with test cards
- Add 3D Secure for high-value transactions
- Go live