OnePage Checkout
About 3591 wordsAbout 12 min
2025-03-07
What is OnePage Checkout?
OnePage Checkout is a modern e-commerce checkout model that consolidates the traditional multi-page checkout process (such as: shopping cart → fill in delivery information → select payment method → confirm order) onto a single page. Users can complete all checkout steps within one page without page jumps, greatly simplifying the shopping process.
Design philosophy of this solution
This SDK solution is specially designed to adapt to OnePage Checkout pages. If your e-commerce website has already adopted the one-page checkout model, this solution can seamlessly embed PingPong payment components into your existing checkout page, displaying alongside modules such as address filling and shipping selection, maintaining user experience consistency without requiring reconstruction of your checkout flow.
Main Advantages
Enhanced User Experience
- Reduces page loading and jumping, improving checkout efficiency
- Users can view and modify order information in real-time
- Clean and intuitive interface, reducing cart abandonment rate
Improved Conversion Rate
- Simplified process means fewer operational steps and potential drop-off points
- Shorter and more direct path for users to complete purchases
- Particularly suitable for mobile users with more friendly responsive design
Core Function Modules
Typical one-page checkout usually includes the following modules:
- Address Information: Delivery address filling and selection
- Shipping Method: Available shipping options and fees
- Payment Method: Various payment options (credit card, PayPal, Alipay, etc.)
- Order Summary: Product list, prices, totals, etc.
- Coupons/Discount Codes: Input discount information
Implementation Suggestions
- Progressive Design: Use collapsible design, expand modules as needed
- Form Validation: Real-time validation of user input, reducing errors
- Guest Checkout: Allow users to purchase without registration
- Save Information: Automatically save address and payment information for logged-in users
- Security Authentication: Ensure PCI DSS compliance, display security indicators
Applicable Scenarios
- Mobile shopping platforms
- E-commerce websites pursuing high conversion rates
- Subscription services
- Digital product sales
SDK Integration Process
Integration Process
1. Get sdkAccessToken API
The merchant backend system obtains the JS-SDK access credential (token) through the Get sdkAccessToken API, which is used for the frontend JS-SDK initialization call.
2. Import Javascript-SDK
Copy the following code to import the PingPongCheckout Javascript-SDK via CDN address
<script type="module" src="https://payssr-cdn.pingpongx.com/production-fra/acquirer-checkout-onepage/sandbox/pp-checkout.js"></script><script type="module" src="https://payssr-cdn.pingpongx.com/production-fra/acquirer-checkout-onepage/pp-checkout.js"></script><script type="module" src="https://acquirer-cdn.pingpongx.com/acquirer/checkout-onepage/production-sg/pp-checkout.js"></script>3. Initialize Cashier
Insert the pp-checkout tag into the html body
<!-- Insert PingPong Cashier Component -->
<pp-checkout locale="en"></pp-checkout>Call PingPong.Checkout.create to complete initialization
Parameters
amountRequiredString
Transaction amount
This transaction is only for cashier display. Subsequent amount changes need to call the updateCheckoutHook function to update
currencyRequiredString
Transaction currency
tradeCountryRequiredString
Used to specify the PingPong cashier country
Subsequent country changes need to call the updateCheckoutHook function to update
sdkAccessTokenRequiredString
JS-SDK access credential
originalPayOptionalBoolean
true
Controls whether to use the default PingPong payment button
If set to false, it means not using the built-in payment button. In this case, you need to call the PingPong.Checkout.pay.run() method in the custom payment button click event to trigger the payment process.
paymentMethodsOptionalString[]
Specify payment method list, embedded cashier will display according to specified payment methods
The passed payment methods will perform intersection operation with configured payment methods under this accId. The display order of payment methods will be arranged according to the order of the passed array.
useTabModeOptionalBoolean
true
Controls whether to display payment method option box
When set to false, if the merchant only has card payment configuration, the option box will not be displayed and the payment form will be directly shown. Suitable for scenarios to simplify user experience.
goodsOptionalArray
Product list, containing the following sub-fields
goods.descriptionOptionalString
Product description
goods.imgUrlOptionalString
Product image URL
goods.nameRequiredString
Product name
goods.numberRequiredString
Product quantity
goods.skuOptionalString
Product SKU code
goods.unitPriceRequiredString
Product unit price
goods.virtualProductOptionalString
N
Whether it's a virtual product
Y - Virtual product, N - Physical product
bizTypeOptionalString
ApplePay card binding business parameter
Required for ApplePay transactions, fixed value: CodeGrant
recurringInfoDTOOptionalObject
Configure Recurring payment (required for ApplePay transactions), containing the following sub-fields
recurringInfoDTO.recurringPaymentStartDateOptionalDate
First payment date
eg: "2024-06-01 00:00:00"
recurringInfoDTO.recurringPaymentIntervalUnitOptionalString
Type representing calendar units such as year, month, day, hour, etc.
enum: year / month / day / hour / minute, eg: "month"
recurringInfoDTO.recurringPaymentIntervalCountOptionalString
Number of interval units constituting the total payment interval
eg: "6"
recurringInfoDTO.recurringPaymentEndDateOptionalDate
Last payment date
eg: "2024-12-01 00:00:00"
// Create PingPong Cashier
PingPong.Checkout.create({
amount: '1.08', // Transaction amount
currency: 'USD', // Transaction currency
tradeCountry: 'US', // Transaction country
originalPay: true,
useTabMode: false, // Hide option box when only card payment available
sdkAccessToken, // SDK access token
paymentMethods: ['VISA', 'Klarna'],
goods: [{
description: 'short legs',
imgUrl: 'http://pic.bizhi360.com/bpic/30/5230.jpg',
name: '한국어/English',
number: '1',
sku: '20230524001',
unitPrice: '1',
virtualProduct: 'N'
}],
// ApplePay payment parameters
bizType: 'CodeGrant', // Required for ApplePay transactions
recurringInfoDTO: {
recurringPaymentStartDate: "2024-06-01 00:00:00",
recurringPaymentIntervalUnit: "month",
recurringPaymentIntervalCount: "6",
recurringPaymentEndDate: "2024-12-01 00:00:00"
},
})Custom Payment Button (Optional)
// Custom payment button configuration
// When originalPay is false in initialization parameters, custom payment button click event is needed
document.querySelector('#pay').onclick = function () {
PingPong.Checkout.pay.run() // Manually trigger payment
}Event Listeners (Optional)
SDK supports listening to ready and error events during initialization, facilitating external state management and error handling.
// Listen to SDK initialization events
// Listen to initialization success event
document.querySelector('pp-checkout').addEventListener('ready', (e) => {
console.log('SDK initialization successful'); // Initialization success callback
// You can execute post-initialization logic here
});
// Listen to initialization failure event
document.querySelector('pp-checkout').addEventListener('error', (e) => {
console.log('SDK initialization failed', e.detail); // Error information
// You can execute error handling logic here, such as displaying error prompts, retrying, etc.
});Event Description:
ready: Triggered when SDK initialization is successful, indicating cashier is readyerror: Triggered when SDK initialization fails, event details contained ine.detail
4. Modify Amount, Country and Products
Before using global variables, please ensure the Javascript-SDK has loaded completely.
updateCheckoutHook (Optional)
updateCheckoutHook is used to update cashier elements.
ts types
// updateCheckoutHook type definition
PingPong.Checkout.updateCheckoutHook:
({ amount?: string, tradeCountry?: string, goods?: Goods[] }) => voidUsage Instructions
⚠️ Important: This method uses a partial update mechanism. Only pass in the fields that have changed, no need to pass in other fields.
| Scenario | Parameter | Example Code |
|---|---|---|
| Country changes | tradeCountry | PingPong.Checkout.updateCheckoutHook({ tradeCountry: newCountry }); |
| Amount changes | amount | PingPong.Checkout.updateCheckoutHook({ amount: newAmount }); |
| Goods changes | goods | PingPong.Checkout.updateCheckoutHook({ goods: newGoods }); |
Correct Examples
// Scenario 1: User switches country - only pass tradeCountry
const newCountry = 'US';
PingPong.Checkout.updateCheckoutHook({ tradeCountry: newCountry });
// Scenario 2: Amount changes after user applies coupon - only pass amount
const newAmount = '99.99';
PingPong.Checkout.updateCheckoutHook({ amount: newAmount });
// Scenario 3: Product information changes - only pass goods
const newGoods = [{
description: 'updated description',
imgUrl: 'http://pic.bizhi360.com/bpic/30/5230.jpg',
name: 'Updated Product Name',
number: '2',
sku: '20230524001',
unitPrice: '2',
virtualProduct: 'N'
}];
PingPong.Checkout.updateCheckoutHook({ goods: newGoods });❌ Incorrect Examples
The following approaches are not recommended - passing unnecessary fields or null values:
// ❌ Incorrect: No need to pass unchanged fields or null values
PingPong.Checkout.updateCheckoutHook({
amount: newAmount ? newAmount : null,
tradeCountry: newCountry ? newCountry : null,
goods: newGoods ? newGoods : null
});
// ❌ Incorrect: When updating amount only, no need to pass country and goods
PingPong.Checkout.updateCheckoutHook({
amount: '99.99',
tradeCountry: 'US', // Unchanged field, not needed
goods: [] // Unchanged field, not needed
});5. Pre-order Verification
beforeCheckoutHook (Optional)
type:
// beforeCheckoutHook type definition
(() => void) | (() => Promise<void>)beforeCheckoutHook is used to set the hook function before initiating payment request.
When you need to execute your own business logic before the user clicks the payment button and initiates the payment request, such as reporting analytics, checking inventory, etc., you can set this hook function.
This function can return a Promise, and subsequent payment processes will wait until the Promise status becomes Fulfilled before continuing execution. If you want to interrupt the payment process when the Promise status is Rejected or the asynchronous result doesn't meet your business conditions, you can throw an exception, and the SDK will interrupt the payment process after capturing the exception.
PingPong.Checkout.beforeCheckoutHook = () => {
return fetch('/api/requestInventory').then(res => {
const {inventoryQuantity} = res;
if (inventoryQuantity < MIN_QUANTITY) {
throw new Error('Insufficient inventory, transaction needs to be interrupted')
}
}).catch((error) => {
throw new Error('Interface exception, transaction needs to be interrupted')
})
};ts types
// beforeCheckoutHook return type definition
PingPong.Checkout.beforeCheckoutHook:
(() => string) | (() => Promise<string>)6. Place Order
After clicking the payment button, call the Place Order API to complete the payment
7. Error Handling
checkoutFailedHook (Optional)
type:
// checkoutFailedHook type definition
(() => void) | (() => Promise<void>)checkoutFailedHook receives the following parameters:
// checkoutFailedHook parameter type definition
(code: string, message: string) => void | Promise<void>;
// code: string - Error code
// message: string - Error messagecheckoutFailedHook is used to customize error logic
When user payment fails, PingPong will default to showing a popup with the failure reason. If you want to customize the popup UI or text, you can set this hook function.
This function can return a Promise. If returning Promise, subsequent processes will wait until the Promise status becomes Fulfilled before continuing execution
PingPong.Checkout.checkoutFailedHook = (code: string, message: string) => {
notification.open({
message: 'Error title',
description: `${code}: ${message}`
})
};Usage Example:
examples
onepage
pages
checkout.html
assets
css
checkout.css
js
config
app-config.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PingPong Checkout Demo</title>
<link rel="stylesheet" href="../assets/css/checkout.css">
</head>
<body>
<div class="checkout-container">
<div class="checkout-section">
<!-- billing form -->
<form class="billing-form">
<h3>Billing Information</h3>
<div class="form-group">
<label for="amount">Amount:</label>
<input type="number" id="amount" value="1.08" step="0.01" min="0">
</div>
<div class="form-group">
<label for="country">Country:</label>
<select id="country">
<option value="US">United States</option>
<option value="GB">United Kingdom</option>
<option value="DE">Germany</option>
<option value="FR">France</option>
</select>
</div>
<!-- billing form fields -->
</form>
<!-- PingPong embedded cashier -->
<div class="pingpong-checkout">
<pp-checkout locale="en"></pp-checkout>
</div>
<!-- shipping form -->
<form class="shipping-form">
<h3>Shipping Information</h3>
<!-- shipping form fields -->
</form>
</div>
<div class="payment-section">
<!-- coupon code -->
<div class="coupon-section">
<label for="coupon">Coupon Code:</label>
<input type="text" id="coupon" placeholder="Enter coupon code">
</div>
<!-- custom payment button -->
<button id="customPay" class="pay-button">Pay Now</button>
</div>
</div>
<!-- Load PingPong JS-SDK -->
<script type="module" src="https://payssr-cdn.pingpongx.com/production-fra/acquirer-checkout-onepage/sandbox/pp-checkout.js"></script>
<!-- Load application scripts -->
<script type="module" src="../js/main.js"></script>
</body>
</html>/**
* PingPong Checkout Style File
* Responsible for all style definitions of the checkout page
*/
/* ========== Variable Definitions ========== */
:root {
--primary-color: #007bff;
--primary-hover: #0056b3;
--success-color: #28a745;
--error-color: #dc3545;
--warning-color: #ffc107;
--border-color: #e1e5e9;
--background-light: #f8f9fa;
--text-primary: #333;
--text-secondary: #666;
--shadow-sm: 0 2px 4px rgba(0,0,0,0.1);
--shadow-md: 0 2px 8px rgba(0,0,0,0.1);
--shadow-lg: 0 4px 12px rgba(0,123,255,0.3);
--border-radius: 8px;
--border-radius-sm: 4px;
--border-radius-lg: 12px;
--transition: all 0.3s ease;
}
/* ========== Basic Style Reset ========== */
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background-color: var(--background-light);
color: var(--text-primary);
line-height: 1.6;
}
/* ========== Main Container ========== */
.checkout-container {
max-width: 800px;
margin: 0 auto;
padding: 24px;
background: white;
border-radius: var(--border-radius);
box-shadow: var(--shadow-md);
}
/* ========== Checkout Section ========== */
.checkout-section {
margin-bottom: 32px;
}
.billing-form,
.shipping-form {
margin-bottom: 24px;
}
.billing-form h3,
.shipping-form h3 {
margin: 0 0 16px 0;
color: var(--text-primary);
font-size: 18px;
font-weight: 600;
}
/* ========== Form Components ========== */
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
margin-bottom: 6px;
font-weight: 600;
color: var(--text-primary);
font-size: 14px;
}
.form-group input,
.form-group select {
width: 100%;
max-width: 320px;
padding: 10px 12px;
border: 1px solid var(--border-color);
border-radius: var(--border-radius-sm);
font-size: 14px;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
background: white;
}
.form-group input:focus,
.form-group select:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(0,123,255,0.1);
}
.form-group input::placeholder {
color: #999;
}
/* ========== PingPong Cashier ========== */
.pingpong-checkout {
margin: 24px 0;
padding: 20px;
background: white;
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
box-shadow: var(--shadow-sm);
}
/* ========== Payment Section ========== */
.payment-section {
text-align: center;
padding: 24px;
background: linear-gradient(135deg, #f8f9ff 0%, #ffffff 100%);
border-radius: var(--border-radius);
border: 1px solid var(--border-color);
}
/* ========== Coupon Section ========== */
.coupon-section {
margin-bottom: 20px;
}
.coupon-section label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: var(--text-primary);
font-size: 14px;
}
.coupon-section input {
width: 100%;
max-width: 280px;
padding: 12px 16px;
border: 2px solid var(--border-color);
border-radius: var(--border-radius);
font-size: 14px;
transition: var(--transition);
background: white;
}
.coupon-section input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(0,123,255,0.1);
}
/* ========== Payment Button ========== */
.pay-button {
background: linear-gradient(135deg, var(--primary-color) 0%, #0056b3 100%);
color: white;
border: none;
padding: 16px 48px;
border-radius: var(--border-radius);
cursor: pointer;
font-size: 16px;
font-weight: 700;
transition: var(--transition);
min-width: 200px;
position: relative;
overflow: hidden;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.pay-button:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
background: linear-gradient(135deg, #0056b3 0%, var(--primary-hover) 100%);
}
.pay-button:active {
transform: translateY(0);
box-shadow: var(--shadow-md);
}
.pay-button:disabled {
background: #6c757d;
cursor: not-allowed;
transform: none;
box-shadow: none;
opacity: 0.7;
}
.pay-button.loading {
color: transparent;
pointer-events: none;
}
.pay-button.loading::after {
content: '';
position: absolute;
width: 20px;
height: 20px;
top: 50%;
left: 50%;
margin-left: -10px;
margin-top: -10px;
border: 2px solid white;
border-radius: 50%;
border-top-color: transparent;
animation: spin 1s ease-in-out infinite;
}
/* ========== Message Prompts ========== */
.message {
padding: 12px 16px;
border-radius: var(--border-radius-sm);
margin: 16px 0;
font-size: 14px;
font-weight: 500;
border-left: 4px solid;
}
.message.error {
background: #f8d7da;
border-left-color: var(--error-color);
color: #721c24;
}
.message.success {
background: #d4edda;
border-left-color: var(--success-color);
color: #155724;
}
.message.warning {
background: #fff3cd;
border-left-color: var(--warning-color);
color: #856404;
}
.message.info {
background: #d1ecf1;
border-left-color: var(--primary-color);
color: #0c5460;
}
/* ========== Loading State ========== */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255,255,255,0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.loading-overlay .spinner {
width: 40px;
height: 40px;
border: 4px solid var(--border-color);
border-top-color: var(--primary-color);
border-radius: 50%;
animation: spin 1s linear infinite;
}
/* ========== Animation Definitions ========== */
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slideIn {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
/* ========== Responsive Design ========== */
@media (max-width: 768px) {
body {
padding: 10px;
}
.checkout-container {
padding: 16px;
border-radius: var(--border-radius-sm);
}
.form-group input,
.form-group select {
max-width: 100%;
}
.coupon-section input {
max-width: 100%;
}
.pay-button {
width: 100%;
min-width: unset;
padding: 14px 24px;
}
.pingpong-checkout {
padding: 16px;
}
}
@media (max-width: 480px) {
.checkout-container {
padding: 12px;
}
.payment-section {
padding: 20px 16px;
}
}
/* ========== Print Styles ========== */
@media print {
.pay-button,
.loading-overlay {
display: none !important;
}
.checkout-container {
box-shadow: none;
border: 1px solid #ddd;
}
}
/* ========== Accessibility Enhancement ========== */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* High contrast mode support */
@media (prefers-contrast: high) {
:root {
--border-color: #000;
--text-primary: #000;
--background-light: #fff;
}
}
/* ========== Utility Classes ========== */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
white-space: nowrap;
border: 0;
}
.text-center {
text-align: center !important;
}
.text-left {
text-align: left !important;
}
.text-right {
text-align: right !important;
}
.mb-0 { margin-bottom: 0 !important; }
.mb-1 { margin-bottom: 8px !important; }
.mb-2 { margin-bottom: 16px !important; }
.mb-3 { margin-bottom: 24px !important; }
.mb-4 { margin-bottom: 32px !important; }/**
* Application Configuration File
* Contains all application-level configuration constants
*/
/**
* PingPong SDK Configuration
*/
export const PINGPONG_CONFIG = {
// SDK basic configuration
amount: '1.08',
currency: 'USD',
tradeCountry: 'US',
originalPay: false,
useTabMode: true,
sdkAccessToken: 'your-sdk-access-token-here',
paymentMethods: ['VISA', 'MasterCard', 'Klarna'],
goods: [{
description: 'short legs',
imgUrl: 'http://pic.bizhi360.com/bpic/30/5230.jpg',
name: '한국어/English',
number: '1',
sku: '20230524001',
unitPrice: '1',
virtualProduct: 'N'
}],
// ApplePay configuration (optional)
applePay: {
bizType: 'CodeGrant',
recurringInfoDTO: {
recurringPaymentStartDate: '',
recurringPaymentIntervalUnit: 'month',
recurringPaymentIntervalCount: '',
recurringPaymentEndDate: ''
}
}
};
/**
* API Endpoint Configuration
*/
export const API_ENDPOINTS = {
// PingPong API
getAccessToken: '/api/auth/sdk-token',
createOrder: '/api/orders/create',
validateInventory: '/api/inventory/validate',
// Business API
validateCoupon: '/api/coupons/validate',
logError: '/api/logs/error',
// Payment related
paymentStatus: '/api/payments/status',
refundPayment: '/api/payments/refund'
};
/**
* Country Currency Mapping Configuration
*/
export const COUNTRY_CURRENCY_MAP = {
'US': 'USD',
'GB': 'GBP',
'DE': 'EUR',
'FR': 'EUR',
'IT': 'EUR',
'ES': 'EUR',
'CA': 'CAD',
'AU': 'AUD',
'JP': 'JPY',
'CN': 'CNY'
};
/**
* Error Code Mapping Configuration
*/
export const ERROR_MESSAGES = {
'PAYMENT_DECLINED': 'Payment declined, please check card information or try another payment method',
'INSUFFICIENT_FUNDS': 'Insufficient funds, please use another payment method',
'INVALID_CARD': 'Invalid card information, please check and retry',
'CARD_EXPIRED': 'Card expired, please use another card',
'NETWORK_ERROR': 'Network connection abnormal, please check network and retry',
'TIMEOUT': 'Payment timeout, please retry',
'FRAUD_DETECTED': 'Security verification failed, please contact customer service',
'CURRENCY_NOT_SUPPORTED': 'Currency not supported, please choose another currency',
'AMOUNT_INVALID': 'Invalid payment amount',
'AMOUNT_TOO_LOW': 'Payment amount too low',
'AMOUNT_TOO_HIGH': 'Payment amount too high',
'MERCHANT_ERROR': 'Merchant system error, please contact customer service',
'SDK_TOKEN_EXPIRED': 'Access credential expired, please refresh page and retry',
'SDK_TOKEN_INVALID': 'Access credential invalid, please contact customer service'
};
/**
* Application Constants Configuration
*/
export const APP_CONSTANTS = {
// Debounce delay time (milliseconds)
DEBOUNCE_DELAY: {
AMOUNT_INPUT: 500,
COUNTRY_CHANGE: 0,
COUPON_INPUT: 1000
},
// Validation rules
VALIDATION: {
MIN_AMOUNT: 0.01,
MAX_AMOUNT: 999999.99,
COUPON_LENGTH_MIN: 3,
COUPON_LENGTH_MAX: 50
},
// Timeout configuration (milliseconds)
TIMEOUTS: {
PAYMENT: 300000, // 5 minutes
API_CALL: 30000, // 30