Skip to main content

PayPay

Accept payments from 60+ million PayPay users in Japan, the country's dominant QR code payment platform with the highest adoption rate among Japanese mobile payment solutions.

Overview

User Statistics

User numbers are approximate and based on publicly available information. Actual active user counts may vary.

PayPay is Japan's largest cashless payment service with over 60 million users (nearly half of Japan's population). Launched in 2018 as a joint venture between SoftBank and Yahoo Japan, PayPay quickly became the market leader through aggressive cashback campaigns and widespread merchant adoption, making it essential for any business targeting Japanese consumers.

Key Features:

  • 60+ million users - one of Japan's leading payment apps
  • 4.4+ million locations - Accepted nationwide
  • No fees for customers - Free to use for payments
  • Instant confirmation - Real-time payment processing
  • Rewards program - PayPay Bonus points on purchases
  • Multiple funding sources - Bank, credit card, convenience store

Supported Regions

RegionCurrencyMin AmountMax AmountDaily Limit
JapanJPY¥100¥500,000Varies by funding source*

*Daily limits depend on the customer's funding source and verification level:

  • PayPay balance: Up to ¥500,000/day (verified users)
  • Linked bank account: Up to ¥500,000/day
  • Credit card: Subject to card limits

How It Works

Customer Experience:

  1. Customer selects "PayPay" at checkout
  2. Redirected to PayPay payment page
  3. Opens PayPay app automatically (on mobile)
  4. Reviews transaction details
  5. Authenticates if required (PIN or biometric)
  6. Confirms payment with one tap
  7. Earns PayPay Bonus points
  8. Returns to merchant website

Typical completion time: 30-90 seconds

Implementation

Step 1: Create PayPay Source

curl https://api.omise.co/sources \
-u skey_test_YOUR_SECRET_KEY: \
-d "type=paypay" \
-d "amount=100000" \
-d "currency=JPY"

Response:

{
"object": "source",
"id": "src_test_5rt6s9vah5lkvi1rh9c",
"type": "paypay",
"flow": "redirect",
"amount": 100000,
"currency": "JPY"
}

Step 2: Create Charge

curl https://api.omise.co/charges \
-u skey_test_YOUR_SECRET_KEY: \
-d "amount=100000" \
-d "currency=JPY" \
-d "source=src_test_5rt6s9vah5lkvi1rh9c" \
-d "return_uri=https://yourdomain.com/payment/callback"

Step 3: Redirect Customer

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

// Validate amount (¥100 - ¥500,000)
if (amount < 100 || amount > 500000) {
return res.status(400).json({
error: 'Amount must be between ¥100 and ¥500,000'
});
}

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

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

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

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

Step 4: Handle Return

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('/payment-success');
} 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 5: Handle Webhook

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

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

if (charge.status === 'successful') {
processOrder(charge.metadata.order_id);
sendConfirmationEmail(charge.metadata.customer_email);

// Log successful payment
console.log(`PayPay payment successful: ${charge.id}`);
} else if (charge.status === 'failed') {
handleFailedPayment(charge.metadata.order_id);
}
}

res.sendStatus(200);
});

Complete Implementation Example

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

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

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

// Validate amount (¥100 - ¥500,000)
if (amount < 100 || amount > 500000) {
return res.status(400).json({
error: 'Amount must be between ¥100 and ¥500,000'
});
}

// Calculate estimated PayPay Bonus points (example: 0.5%)
const estimatedBonus = Math.floor(amount * 0.005);

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

// Create charge
const charge = await omise.charges.create({
amount: amount,
currency: 'JPY',
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: 'paypay',
estimated_bonus: estimatedBonus
}
});

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

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

// Callback handler
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') {
const bonus = charge.metadata.estimated_bonus || 0;
res.redirect(`/order-success?order=${charge.metadata.order_id}&bonus=${bonus}`);
} else {
res.redirect(`/payment-failed?charge=${chargeId}`);
}
} catch (error) {
res.redirect('/payment-error');
}
});

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

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

if (charge.source.type === 'paypay') {
if (charge.status === 'successful') {
updateOrderStatus(charge.metadata.order_id, 'paid');
sendConfirmation(charge.metadata.customer_email, {
amount: charge.amount,
bonus: charge.metadata.estimated_bonus
});

console.log(`PayPay payment successful: ${charge.id}`);
} else {
updateOrderStatus(charge.metadata.order_id, 'failed');
console.log(`PayPay payment failed: ${charge.id}`);
}
}
}

res.sendStatus(200);
});

// Helper functions
async function updateOrderStatus(orderId, status) {
await db.orders.update({ id: orderId }, { status: status });
}

async function sendConfirmation(email, details) {
// Send email confirmation with PayPay Bonus info
}

app.listen(3000);

Refund Support

PayPay supports full and partial refunds within 60 days:

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

// Partial refund
const partialRefund = await omise.charges.refund('chrg_test_...', {
amount: 50000 // Half refund
});
Refund Timeline
  • Refunds are processed within 1-2 business days
  • Customers receive refunds in their PayPay balance
  • PayPay Bonus points are automatically adjusted
  • Supported within 60 days of original transaction
Refund Policy

Refund windows and policies are subject to change. Always verify current refund capabilities via the Omise API documentation or your merchant dashboard.

Common Issues & Troubleshooting

Issue: PayPay app not installed

Cause: Customer doesn't have PayPay app installed

Solution:

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

if (isMobile) {
return `
<div class="paypay-app-check">
<p>PayPay app is required for payment.</p>
<div class="download-links">
<a href="https://play.google.com/store/apps/details?id=jp.ne.paypay.android.app">
<img src="/images/google-play-badge-jp.png" alt="Google Playで入手">
</a>
<a href="https://apps.apple.com/jp/app/paypay/id1435783608">
<img src="/images/app-store-badge-jp.png" alt="App Storeからダウンロード">
</a>
</div>
</div>
`;
}
}

Issue: Insufficient balance

Cause: Customer's PayPay balance is too low and no backup payment method

Solution:

<div class="paypay-balance-info">
<h3>PayPayの残高が不足しています</h3>
<p>以下の方法でチャージしてください:</p>

<ul class="charge-methods">
<li>🏦 <strong>銀行口座</strong> - リアルタイムチャージ</li>
<li>🏧 <strong>セブン銀行ATM</strong> - 現金チャージ</li>
<li>🏪 <strong>ローソン</strong> - レジでチャージ</li>
<li>💳 <strong>クレジットカード</strong> - 登録して自動チャージ</li>
<li>👥 <strong>PayPay残高の送受信</strong> - 友達から受け取る</li>
</ul>

<p class="amount-needed">必要金額: <strong>¥{{amount}}</strong></p>
</div>

Issue: Daily limit exceeded

Cause: Customer reached their daily or monthly limit

Solution:

function handlePayPayLimitExceeded() {
return {
error: '利用限度額を超過しました',
message: 'PayPayの1日または1か月の利用限度額に達しました。',
solutions: [
{
title: '本人確認を完了する',
description: '本人確認済みアカウントは¥500,000/日まで利用可能',
steps: [
'PayPayアプリを開く',
'アカウント > 本人確認',
'身分証明書をアップロード',
'審査完了まで数時間待つ'
]
},
{
title: '別の支払い方法を使用',
description: 'クレジットカードや他の決済方法をご利用ください'
},
{
title: '明日再試行',
description: '利用限度額は毎日0時にリセットされます'
}
]
};
}

Issue: Payment authentication failed

Cause: Customer failed PIN or biometric authentication

Solution:

function handleAuthenticationError() {
return `
<div class="auth-error">
<h3>認証に失敗しました</h3>
<p>以下をお試しください:</p>
<ul>
<li>PINコードを再入力してください</li>
<li>生体認証を再試行してください</li>
<li>PayPayアプリを再起動してください</li>
<li>PINをお忘れの場合は、PayPayアプリで再設定してください</li>
</ul>
<button onclick="retryPayment()">再試行</button>
</div>
`;
}

Issue: Payment timeout

Cause: Customer didn't complete payment within time limit

Solution:

// 5-minute payment window
const PAYMENT_TIMEOUT = 5 * 60 * 1000;

setTimeout(() => {
if (!paymentConfirmed) {
showMessage('お支払いの有効期限が切れました。もう一度やり直してください。');
enableRetryButton();
}
}, PAYMENT_TIMEOUT);

Best Practices

1. Display PayPay Benefits (Japanese UI)

<div class="paypay-benefits">
<img src="/images/paypay-logo.svg" alt="PayPay" height="40">
<h3>PayPayで支払う</h3>
<ul class="benefits-list">
<li>✓ 即時決済確認</li>
<li>✓ PayPayボーナスポイント獲得</li>
<li>✓ 安全なPIN/生体認証</li>
<li>✓ 手数料無料</li>
<li>✓ 日本全国440万店舗以上で利用可能</li>
</ul>
<p class="bonus-note">
🎁 この購入で<strong>約{{points}}円相当</strong>のPayPayボーナスを獲得
</p>
</div>

2. Provide Bilingual Support

function getPayPayInstructions(language = 'ja') {
const instructions = {
'ja': {
title: 'PayPayでのお支払い方法',
steps: [
'「PayPayで支払う」をクリック',
'PayPayアプリが自動的に開きます',
'取引内容を確認',
'PINまたは生体認証で認証',
'お支払いを確定'
]
},
'en': {
title: 'How to pay with PayPay',
steps: [
'Click "Pay with PayPay"',
'PayPay app will open automatically',
'Review transaction details',
'Authenticate with PIN or biometric',
'Confirm payment'
]
}
};

return instructions[language] || instructions['ja'];
}
function openPayPayApp(authorizeUri) {
const isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);

if (isMobile) {
// Try to open PayPay app
window.location = authorizeUri;

// Fallback after 2 seconds
setTimeout(() => {
if (!document.hidden) {
showInstallAppMessage();
}
}, 2000);
} else {
// Desktop: Show QR code or web interface
window.location = authorizeUri;
}
}

4. Validate Amount Limits

function validatePayPayAmount(amount) {
const MIN = 100; // ¥100
const MAX = 500000; // ¥500,000

if (amount < MIN) {
return `最小金額は¥${MIN}です`;
}

if (amount > MAX) {
return `最大金額は¥${MAX}です`;
}

return null; // Valid
}

5. Display PayPay Bonus Estimate

function calculatePayPayBonus(amount) {
// Base rate: 0.5% (varies by campaign)
const baseRate = 0.005;
const bonusAmount = Math.floor(amount * baseRate);

return {
amount: bonusAmount,
formatted: `¥${bonusAmount.toLocaleString('ja-JP')}`,
message: `この購入で約${bonusAmount}円相当のPayPayボーナスを獲得できます`
};
}

6. Use Webhooks for Reliability

// Webhook is primary notification method
app.post('/webhooks/omise', handleWebhook);

// Callback is backup for UI updates
app.get('/payment/callback', handleCallback);

// Never rely solely on callback for order fulfillment

FAQ

What is PayPay?

PayPay is Japan's largest mobile payment service with over 60 million users (nearly half of Japan's population). Launched in 2018, it allows users to make cashless payments via QR codes, send money to friends, and earn PayPay Bonus points. It's accepted at 4.4+ million locations across Japan.

Do customers need a Japanese bank account?

Not necessarily. Customers can fund their PayPay account through:

  • Japanese bank account (most common)
  • Credit card (Visa, Mastercard, JCB)
  • Cash at convenience stores or ATMs
  • Receiving transfers from other PayPay users

However, most PayPay users are in Japan with Japanese bank accounts.

What are the transaction limits?

Limits vary by funding source and verification level:

  • Per transaction: ¥100 - ¥500,000
  • Unverified account: ¥100,000/day, ¥500,000/month
  • Verified account: ¥500,000/day, ¥2,000,000/month
  • Blue Badge (corporate): Higher limits available
How do customers earn PayPay Bonus?

Customers automatically earn PayPay Bonus points on purchases. The base rate is typically 0.5%, but PayPay frequently runs campaigns offering higher bonus rates (sometimes up to 20-30%). Bonus amounts depend on:

  • Base payment bonus (0.5%)
  • Campaign bonuses
  • Payment method (PayPay balance vs credit card)
  • Merchant-specific campaigns
Can I refund PayPay payments?

Yes, PayPay supports both full and partial refunds within 60 days of the original transaction. Refunds are returned to the customer's PayPay balance within 1-2 business days, and any PayPay Bonus earned is automatically adjusted.

How long does settlement take?

PayPay payments typically settle within 2-3 business days after the transaction. Check your Omise dashboard for specific settlement schedules and dates.

Is PayPay available 24/7?

Yes, PayPay payments can be processed 24/7 including weekends and holidays. However, settlement to your merchant account follows business day schedules.

Should I provide Japanese language support?

Yes, highly recommended. While many younger Japanese users can navigate English interfaces, providing Japanese language support significantly improves conversion rates. At minimum, provide:

  • Payment button in Japanese (「PayPayで支払う」)
  • Error messages in Japanese
  • Basic instructions in Japanese
What's the difference between PayPay and LINE Pay?

Both are popular in Japan, but PayPay has significantly higher market share:

  • PayPay: 60M+ users, #1 market share, QR code focused, widespread acceptance
  • LINE Pay: 40M+ users, integrated with LINE messaging, good for LINE ecosystem

For maximum reach in Japan, consider accepting both.

Testing

Test Mode

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

Test Credentials:

  • Use test API keys (skey_test_xxx)
  • Currency: JPY (Japanese Yen)
  • No actual PayPay 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 PayPay authorization
  4. Use Omise Dashboard Actions to mark charge as successful/failed
  5. Verify webhook and return_uri handling

Testing Implementation:

// Test PayPay payment
const source = await omise.sources.create({
type: 'paypay',
amount: 100000, // ¥100,000
currency: 'JPY'
});

const charge = await omise.charges.create({
amount: 100000,
currency: 'JPY',
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 (¥1) and maximum amounts
  • Mobile flow: Test deep-linking to PayPay app
  • Insufficient balance: Simulate low wallet balance
  • Timeout: Test abandoned payment scenarios
  • Webhook delivery: Verify all webhook notifications
  • Japanese UI: Test Japanese language support

Important Notes:

  • Test mode doesn't connect to real PayPay servers
  • Use dashboard to simulate payment outcomes
  • Test mobile app flow thoroughly (PayPay app integration)
  • Verify webhook handling for all charge statuses
  • Test Japanese language support in UI
  • Verify JPY currency handling (no decimal places)

For comprehensive testing guidelines, see the Testing Documentation.

Next Steps

  1. Create PayPay source
  2. Implement redirect flow
  3. Set up webhook handling
  4. Add Japanese language support
  5. Test payment flow
  6. Go live