Skip to main content

KakaoPay

Accept payments from KakaoPay in Singapore and Thailand, an integrated payment wallet supporting multiple Southeast Asian markets.

Overviewโ€‹

KakaoPay is a digital wallet payment service that operates in Singapore and Thailand, providing customers with convenient mobile payment options integrated with e-commerce platforms.

Key Features:

  • โœ… Multi-region - Available in Singapore and Thailand
  • โœ… Fast confirmation - Near real-time payment verification (typically within seconds)
  • โœ… Mobile-optimized - Seamless smartphone experience
  • โœ… Quick checkout - Fast payment authorization
  • โœ… E-commerce focus - Popular for online shopping

Supported Regionsโ€‹

RegionCurrencyMin AmountMax Amount
SingaporeSGD$1.00$20,000
ThailandTHBเธฟ20.00เธฟ150,000

How It Worksโ€‹

Customer Experience:

  1. Customer selects KakaoPay at checkout
  2. Redirected to KakaoPay authorization page
  3. KakaoTalk app opens automatically (deep link)
  4. Customer reviews payment in KakaoTalk
  5. Confirms with password/biometric/pattern
  6. Returns to merchant site
  7. Receives payment notification in KakaoTalk

Typical completion time: 30-90 seconds

Payment Flow Examplesโ€‹

Mobile Payment Flow:

Mobile Payment Flow

Lightning-fast mobile experience:

  • โถ Select KakaoPay - Customer chooses KakaoPay at checkout
  • โท KakaoTalk opens - Deep link instantly launches KakaoTalk app
  • โธ Payment prompt - Transaction details appear in KakaoTalk
  • โน Review details - Merchant name and payment amount shown
  • โบ Authenticate - Confirm with password, pattern, or biometric
  • โป Payment complete - Instant processing (fastest in Asia - 20-40 seconds)
  • โผ Notification - Payment confirmation message in KakaoTalk
  • โฝ Return - Automatic redirect to merchant

Desktop Payment Flow:

Desktop Payment Flow

QR code method for desktop users:

  • โถ Choose KakaoPay - Customer selects KakaoPay on desktop
  • โท Generate QR - System creates KakaoPay payment QR code
  • โธ Display QR - QR code shown prominently on screen
  • โน Open KakaoTalk - Customer launches KakaoTalk on mobile
  • โบ Scan QR - Use KakaoTalk's built-in QR scanner
  • โป Payment loaded - Transaction details auto-populate in app
  • โผ Authenticate - Quick biometric or password confirmation
  • โฝ Success - Payment processed, desktop shows confirmation

Implementationโ€‹

Step 1: Create KakaoPay Sourceโ€‹

curl https://api.omise.co/sources \
-u skey_test_YOUR_SECRET_KEY: \
-d "type=kakaopay" \
-d "amount=1000000" \
-d "currency=KRW"

Response:

{
"object": "source",
"id": "src_test_5rt6s9vah5lkvi1rh9c",
"type": "kakaopay",
"flow": "redirect",
"amount": 1000000,
"currency": "KRW"
}

Step 2: Create Charge and Redirectโ€‹

app.post('/checkout/kakaopay', async (req, res) => {
try {
const { amount, order_id, customer_email, customer_name } = req.body;

// Validate amount
if (amount < 10000) { // โ‚ฉ100 minimum
return res.status(400).json({
error: 'Minimum amount is โ‚ฉ100'
});
}

if (amount > 1000000000) { // โ‚ฉ10,000,000 maximum
return res.status(400).json({
error: 'Maximum amount is โ‚ฉ10,000,000'
});
}

// Create source
const source = await omise.sources.create({
type: 'kakaopay',
amount: amount,
currency: 'KRW'
});

// Create charge
const charge = await omise.charges.create({
amount: amount,
currency: 'KRW',
source: source.id,
return_uri: `${process.env.BASE_URL}/payment/callback`,
metadata: {
order_id: order_id,
customer_email: customer_email,
customer_name: customer_name,
payment_method: 'kakaopay'
}
});

// Redirect to KakaoPay
res.redirect(charge.authorize_uri);

} catch (error) {
console.error('KakaoPay error:', error);
res.status(500).json({ error: error.message });
}
});

Step 3: Handle Return Callbackโ€‹

app.get('/payment/callback', async (req, res) => {
try {
const chargeId = req.query.charge_id;
const charge = await omise.charges.retrieve(chargeId);

if (charge.status === 'successful') {
await processOrder(charge.metadata.order_id);
res.redirect(`/order-success?order=${charge.metadata.order_id}`);
} else if (charge.status === 'failed') {
res.redirect(`/payment-failed?reason=${charge.failure_message}`);
} else {
res.redirect('/payment-pending');
}
} catch (error) {
res.redirect('/payment-error');
}
});

Step 4: Handle Webhookโ€‹

app.post('/webhooks/omise', async (req, res) => {
const event = req.body;

if (event.key === 'charge.complete') {
const charge = event.data;

if (charge.source.type === 'kakaopay') {
if (charge.status === 'successful') {
await fulfillOrder(charge.metadata.order_id);
await sendConfirmationEmail(charge.metadata.customer_email);
} else if (charge.status === 'failed') {
await handleFailedPayment(charge.metadata.order_id);
}
}
}

res.sendStatus(200);
});

Complete Implementation Exampleโ€‹

const express = require('express');
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY
});

const app = express();
app.use(express.json());

// KakaoPay configuration
const KAKAOPAY_CONFIG = {
currency: 'KRW',
minAmount: 10000, // โ‚ฉ100
maxAmount: 1000000000, // โ‚ฉ10,000,000
displayName: 'KakaoPay',
timeout: 10 * 60 * 1000 // 10 minutes
};

// Create KakaoPay payment
app.post('/checkout/kakaopay', async (req, res) => {
try {
const { amount, order_id, customer_email, customer_name, customer_phone } = req.body;

// Validate currency
if (req.body.currency && req.body.currency !== 'KRW') {
return res.status(400).json({
error: 'KakaoPay only supports KRW currency'
});
}

// Validate amount
if (amount < KAKAOPAY_CONFIG.minAmount) {
return res.status(400).json({
error: `Minimum amount is โ‚ฉ${KAKAOPAY_CONFIG.minAmount / 100}`
});
}

if (amount > KAKAOPAY_CONFIG.maxAmount) {
return res.status(400).json({
error: `Maximum amount is โ‚ฉ${KAKAOPAY_CONFIG.maxAmount / 100}`
});
}

// Create source
const source = await omise.sources.create({
type: 'kakaopay',
amount: amount,
currency: KAKAOPAY_CONFIG.currency
});

// Create charge
const charge = await omise.charges.create({
amount: amount,
currency: KAKAOPAY_CONFIG.currency,
source: source.id,
return_uri: `${process.env.BASE_URL}/payment/callback`,
metadata: {
order_id: order_id,
customer_email: customer_email,
customer_name: customer_name,
customer_phone: customer_phone,
payment_method: 'kakaopay'
}
});

// Log for tracking
console.log(`KakaoPay payment initiated: ${charge.id} for order ${order_id}`);

// Return authorization URL
res.json({
authorize_uri: charge.authorize_uri,
charge_id: charge.id
});

} catch (error) {
console.error('KakaoPay error:', error);
res.status(500).json({ error: error.message });
}
});

// Payment callback
app.get('/payment/callback', async (req, res) => {
try {
const charge = await omise.charges.retrieve(req.query.charge_id);

if (charge.status === 'successful') {
res.redirect(`/order-confirmation?order=${charge.metadata.order_id}`);
} else {
res.redirect(`/payment-failed?charge=${req.query.charge_id}`);
}
} catch (error) {
res.redirect('/payment-error');
}
});

// Webhook handler
app.post('/webhooks/omise', async (req, res) => {
const event = req.body;

if (event.key === 'charge.complete' && event.data.source.type === 'kakaopay') {
const charge = event.data;

if (charge.status === 'successful') {
await fulfillOrder(charge.metadata.order_id);
await sendReceipt(charge.metadata.customer_email, charge);
console.log(`KakaoPay payment successful: ${charge.id}`);
} else {
await cancelOrder(charge.metadata.order_id);
console.log(`KakaoPay payment failed: ${charge.id}`);
}
}

res.sendStatus(200);
});

app.listen(3000);

Refund Supportโ€‹

KakaoPay supports full and partial refunds within 365 days:

// Full refund
const fullRefund = await omise.charges.refund('chrg_test_...', {
amount: 1000000 // Full amount
});

// Partial refund
const partialRefund = await omise.charges.refund('chrg_test_...', {
amount: 500000 // Partial amount
});

console.log('Refund status:', fullRefund.status);
Refund Processing

Refunds are processed back to the customer's KakaoPay account within 3-5 business days.

Common Issues & Troubleshootingโ€‹

Issue: Customer doesn't have KakaoTalkโ€‹

Cause: Customer doesn't have KakaoTalk app (rare in South Korea)

Solution:

// Show KakaoTalk requirement
function checkKakaoTalk() {
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);

if (!isMobile) {
alert('์นด์นด์˜คํŽ˜์ด๋Š” ๋ชจ๋ฐ”์ผ์—์„œ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.');
return false;
}

// Show app links
const isAndroid = /Android/i.test(navigator.userAgent);
const appLink = isAndroid
? 'https://play.google.com/store/apps/details?id=com.kakao.talk'
: 'https://apps.apple.com/kr/app/kakaotalk/id362057947';

showMessage(`์นด์นด์˜คํ†ก ์•ฑ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. <a href="${appLink}">๋‹ค์šด๋กœ๋“œ</a>`);
return true;
}

Issue: Payment method not linkedโ€‹

Error: No payment method linked to KakaoPay

Solution:

if (charge.failure_code === 'no_payment_method') {
showMessage(
'์นด์นด์˜คํŽ˜์ด์— ๊ฒฐ์ œ์ˆ˜๋‹จ์ด ๋“ฑ๋ก๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ' +
'์นด์นด์˜คํ†ก์—์„œ ๊ฒฐ์ œ์ˆ˜๋‹จ์„ ๋“ฑ๋กํ•ด์ฃผ์„ธ์š”.'
);
showPaymentMethodSetupGuide();
}

Issue: Transaction limit exceededโ€‹

Error: Transaction exceeds account limit

Solution:

if (charge.failure_code === 'transaction_limit_exceeded') {
showMessage(
'๊ฑฐ๋ž˜ ํ•œ๋„๋ฅผ ์ดˆ๊ณผํ–ˆ์Šต๋‹ˆ๋‹ค. ' +
'๋ณธ์ธ ์ธ์ฆ ํ›„ ํ•œ๋„๋ฅผ ๋Š˜๋ฆฌ๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ๊ฒฐ์ œ์ˆ˜๋‹จ์„ ์ด์šฉํ•ด์ฃผ์„ธ์š”.'
);
}

Issue: Payment timeoutโ€‹

Cause: Customer didn't complete payment within 10 minutes

Solution:

const TIMEOUT = 10 * 60 * 1000;

setTimeout(() => {
if (!paymentCompleted) {
showMessage('๊ฒฐ์ œ ์‹œ๊ฐ„์ด ์ดˆ๊ณผ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.');
enableRetry();
}
}, TIMEOUT);

Best Practicesโ€‹

1. Display in Koreanโ€‹

<div class="kakaopay-payment">
<h3>์นด์นด์˜คํŽ˜์ด๋กœ ๊ฒฐ์ œ</h3>
<div class="logo">
<img src="/icons/kakaopay-logo.svg" alt="์นด์นด์˜คํŽ˜์ด">
</div>
<div class="instructions">
<ol>
<li>์นด์นด์˜คํ†ก ์•ฑ์ด ์„ค์น˜๋˜์–ด ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค</li>
<li>์นด์นด์˜คํŽ˜์ด์— ๊ฒฐ์ œ์ˆ˜๋‹จ์ด ๋“ฑ๋ก๋˜์–ด ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค</li>
<li>์นด์นด์˜คํ†ก ์•ฑ์œผ๋กœ ์ž๋™ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค</li>
<li>๋น„๋ฐ€๋ฒˆํ˜ธ ๋˜๋Š” ์ƒ์ฒด์ธ์ฆ์œผ๋กœ ๊ฒฐ์ œ๋ฅผ ์Šน์ธํ•ฉ๋‹ˆ๋‹ค</li>
</ol>
</div>
<p class="help-text">
์นด์นด์˜คํŽ˜์ด๊ฐ€ ์ฒ˜์Œ์ด์‹ ๊ฐ€์š”?
<a href="https://www.kakaopay.com" target="_blank">์ž์„ธํžˆ ๋ณด๊ธฐ</a>
</p>
</div>

2. Mobile-Only Detectionโ€‹

function validateKakaoPayPayment() {
const userAgent = navigator.userAgent;
const isMobile = /Android|iPhone|iPad|iPod/i.test(userAgent);

if (!isMobile) {
return {
valid: false,
message: '์นด์นด์˜คํŽ˜์ด๋Š” ๋ชจ๋ฐ”์ผ ๊ธฐ๊ธฐ์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค'
};
}

return { valid: true };
}
function openKakaoTalkApp(authorizeUri) {
// Redirect to KakaoPay
window.location = authorizeUri;

// Fallback if app doesn't open
setTimeout(() => {
if (!document.hidden) {
showKakaoTalkInstallPrompt();
}
}, 2000);
}

function showKakaoTalkInstallPrompt() {
const isAndroid = /Android/i.test(navigator.userAgent);
const downloadUrl = isAndroid
? 'https://play.google.com/store/apps/details?id=com.kakao.talk'
: 'https://apps.apple.com/kr/app/kakaotalk/id362057947';

if (confirm('์นด์นด์˜คํ†ก ์•ฑ์ด ์„ค์น˜๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๋‹ค์šด๋กœ๋“œ ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?')) {
window.location = downloadUrl;
}
}

4. Amount Formattingโ€‹

function formatKRW(amount) {
return new Intl.NumberFormat('ko-KR', {
style: 'currency',
currency: 'KRW',
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(amount / 100);
}

// Usage
const displayAmount = formatKRW(1000000); // "โ‚ฉ10,000"

5. Show Payment Method Setupโ€‹

<div class="kakaopay-setup-guide">
<h4>์นด์นด์˜คํŽ˜์ด ๊ฒฐ์ œ์ˆ˜๋‹จ ๋“ฑ๋ก ๋ฐฉ๋ฒ•:</h4>
<ol>
<li>์นด์นด์˜คํ†ก ์•ฑ ์‹คํ–‰</li>
<li>์šฐ์ธก ํ•˜๋‹จ ์  3๊ฐœ ๋ฉ”๋‰ด ์„ ํƒ</li>
<li>ํŽ˜์ด ์„ ํƒ</li>
<li>๊ฒฐ์ œ ์„ ํƒ</li>
<li>๊ฒฐ์ œ์ˆ˜๋‹จ ๊ด€๋ฆฌ</li>
<li>์นด๋“œ, ๊ณ„์ขŒ, ๋˜๋Š” ํŽ˜์ด๋จธ๋‹ˆ ๋“ฑ๋ก</li>
</ol>
</div>

Use Casesโ€‹

E-commerceโ€‹

  • Perfect for Korean online shopping
  • High conversion rates
  • Fast checkout experience

Digital Contentโ€‹

  • Music, videos, books
  • Game items and subscriptions
  • In-app purchases

Food Deliveryโ€‹

  • Quick payment for orders
  • Integrated with delivery apps
  • Popular for restaurant payments

Servicesโ€‹

  • Transportation (taxi, bus, parking)
  • Beauty and wellness bookings
  • Event tickets and reservations

Testingโ€‹

Test Credentialsโ€‹

Use Omise test mode credentials:

  • Secret Key: skey_test_YOUR_SECRET_KEY
  • Public Key: pkey_test_YOUR_PUBLIC_KEY

Test Amountsโ€‹

Amount (KRW)Expected Result
10000 - 999999Success
1000000Insufficient funds
1000001Transaction declined

Test Flowโ€‹

  1. Create charge with test credentials
  2. Use test amounts above
  3. Complete payment in KakaoPay test environment
  4. Verify webhook received
  5. Check charge status

FAQโ€‹

What is KakaoPay?

KakaoPay is South Korea's leading mobile wallet with 30+ million users, integrated directly into KakaoTalk, the country's most popular messaging app used by over 90% of South Koreans.

Do customers need KakaoTalk?

Yes, KakaoPay is integrated into KakaoTalk, so customers must have the KakaoTalk app installed. This is standard in South Korea where KakaoTalk has 90%+ penetration.

What are the transaction limits?
  • Minimum: โ‚ฉ100
  • Maximum: โ‚ฉ10,000,000 per transaction

Limits depend on customer's account verification level (Basic, Verified, Plus).

How long does settlement take?

KakaoPay settlements typically occur within 1-3 business days. Check your Omise dashboard for specific settlement schedules.

Can I refund KakaoPay payments?

Yes, KakaoPay supports both full and partial refunds within 365 days of the original transaction.

What if customer has no payment method linked?

Customers need to add a payment method (credit card, bank account, or Kakao Money) in KakaoTalk before they can use KakaoPay. Direct them to KakaoTalk settings to add a payment method.

Does KakaoPay work on desktop?

No, KakaoPay requires the KakaoTalk mobile app. Desktop users should be shown alternative payment methods like credit cards or bank transfer.

Should I display instructions in Korean?

Yes! Since KakaoPay is South Korea-specific, displaying all instructions and messages in Korean (ํ•œ๊ตญ์–ด) is essential for the best user experience.

Testingโ€‹

Test Modeโ€‹

KakaoPay can be tested using your test API keys. In test mode:

Test Credentials:

  • Use test API keys (skey_test_xxx)
  • Currency: KRW (Korean Won)
  • No actual KakaoPay account required for testing

Test Flow:

  1. Create source and charge with test API keys
  2. Customer redirects to test authorize_uri
  3. Test page simulates KakaoPay authorization
  4. Use Omise Dashboard Actions to mark charge as successful/failed
  5. Verify webhook and return_uri handling

Testing Implementation:

// Test KakaoPay payment
const source = await omise.sources.create({
type: 'kakaopay',
amount: 10000, // โ‚ฉ10,000
currency: 'KRW'
});

const charge = await omise.charges.create({
amount: 10000,
currency: 'KRW',
source: source.id,
return_uri: 'https://example.com/callback'
});

console.log('Test authorize URL:', charge.authorize_uri);

Test Scenarios:

  • Successful payment: Verify order completion workflow
  • Failed payment: Test error handling
  • Amount limits: Test minimum and maximum amounts
  • Mobile flow: Test deep-linking to KakaoTalk app
  • Insufficient balance: Simulate low wallet balance
  • Timeout: Test abandoned payment scenarios
  • Webhook delivery: Verify all webhook notifications

Important Notes:

  • Test mode doesn't connect to real KakaoPay servers
  • Use dashboard to simulate payment outcomes
  • Test mobile app flow (KakaoTalk integration)
  • Verify webhook handling for all charge statuses
  • Test amount validation for KRW currency

For comprehensive testing guidelines, see the Testing Documentation.

Next Stepsโ€‹

  1. Create KakaoPay source
  2. Implement redirect flow
  3. Handle return callback
  4. Set up webhook
  5. Test payment flow
  6. Go live