--- title: Embedded SDK permalink: /en/notes/integrate/sdk-v4/ createTime: '2025/03/07 16:01:51' description: >- Use Embedded SDK as the unified entry for Web / WAP, iOS, and Android checkout integrations, with platform switching and direct access to the complete guide for each platform. outline: [2, 4] --- PingPong Checkout Embedded SDK is intended for teams that want shoppers to complete payment without leaving the merchant site or app. Use this page as the unified entry for Web / WAP embedded checkout and native app integrations. ## Platform Coverage | Platform | Entry | Description | |---|---|---| | Web / WAP | Current page | Integrate the JavaScript-based embedded checkout on your website or mobile web page | | iOS App | [iOS Integration Guide](/en/notes/integrate/native-sdk-ios/) | Build the native bottom-sheet checkout experience for iOS | | Android App | [Android Integration Guide](/en/notes/integrate/native-sdk-android/) | Build the native bottom-sheet checkout experience for Android | | App submission and privacy | [App Submission & Compliance](/en/notes/integrate/app-compliance/overview/) | Review App Store / Google Play submission and privacy requirements | ::: tip Recommendation If your implementation includes app-side checkout, start with this page and switch between the Web / WAP, iOS, and Android tabs based on the platform you are integrating. ::: ## Platform Selection ## Web/WAP ## Integration Summary Embedded SDK is a low-code checkout option for merchants that want to keep shoppers on the merchant site while still using PingPong Checkout components. Your server first creates the payment session through [prePay](/en/notes/checkout/api/reserve/), and your frontend then renders the checkout in-page through the JavaScript SDK. ## Payment Experience ### Web Payment In the Embedded SDK model, you can place the checkout experience directly inside your own website. On desktop and standard web pages, the checkout is rendered within your site so shoppers can complete payment without being redirected to a separate hosted page. ![Web embedded checkout payment experience](/images/integrate/sdk-experience/jssdk_web_ui.png) ### Mobile Payment In the Embedded SDK model, you can also embed the checkout experience into your mobile web page or in-app web view. The UI is optimized for smaller screens so shoppers can complete payment in a more seamless mobile flow without leaving your page. ![Mobile embedded checkout payment experience](/images/integrate/sdk-experience/jssdk_mobile_ui.png) ## Payment Flow ```mermaid %%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#E3F2FD', 'primaryTextColor': '#0D47A1', 'primaryBorderColor': '#1976D2', 'lineColor': '#1565C0', 'secondaryColor': '#BBDEFB', 'tertiaryColor': '#90CAF9', 'background': '#F8FBFF', 'mainBkg': '#E3F2FD', 'secondBkg': '#BBDEFB', 'tertiaryBkg': '#90CAF9', 'actorBkg': '#2196F3', 'actorBorder': '#1976D2', 'actorTextColor': '#FFFFFF', 'actorLineColor': '#1565C0', 'signalColor': '#0D47A1', 'signalTextColor': '#0D47A1', 'noteBkgColor': '#E1F5FE', 'noteTextColor': '#01579B', 'noteBorderColor': '#0288D1', 'loopTextColor': '#0D47A1', 'activationBkgColor': '#B3E5FC', 'activationBorderColor': '#0277BD' } }}%% sequenceDiagram participant Cardholder as πŸ’³ Cardholder participant UserTerminal as πŸ“± User Terminal participant MerchantPlatform as πŸͺ Merchant Platform participant PingPong as πŸ”„ PingPongCheckout participant ISSUER as 🏦 ISSUER Note over Cardholder,ISSUER: πŸ“‹ Order Creation Phase Cardholder->>UserTerminal: 1. Initiate checkout activate UserTerminal UserTerminal->>+MerchantPlatform: 2. Submit order MerchantPlatform->>+PingPong: 3. Request order interface PingPong-->>-MerchantPlatform: 4. Return checkout URL (contains JS URL) MerchantPlatform-->>-UserTerminal: 5. Return checkout info Note over UserTerminal,PingPong: 🎨 JS-SDK Rendering Phase UserTerminal->>+PingPong: 6. Render via JS-SDK PingPong-->>-UserTerminal: 7. Render checkout page UserTerminal-->>Cardholder: 8. Display checkout page Note over Cardholder,ISSUER: πŸ’³ Payment Phase Cardholder->>UserTerminal: 9. Select payment method and fill card info UserTerminal->>+PingPong: 10. Confirm payment PingPong->>+ISSUER: 11. Request payment ISSUER-->>-PingPong: 12. Return result alt πŸ”’ 3D Process PingPong-->>UserTerminal: 13. Redirect to 3D verification page UserTerminal->>Cardholder: 14. 8.1.1 Fill verification code Cardholder->>UserTerminal: 15. Submit verification UserTerminal->>+ISSUER: 16. 8.1.2 issuer verifies data ISSUER-->>-UserTerminal: 17. 8.1.3 Return verification result page UserTerminal->>UserTerminal: 18. 8.1.4 Fill required info to complete payment
Redirect to payResultUrl Note over MerchantPlatform: Process business logic based
on verification result else βœ… Non-3D Process PingPong-->>UserTerminal: 19. Return payment result page UserTerminal->>UserTerminal: 20. 8.2.1 Fill required info to complete payment
Redirect to payResultUrl Note over MerchantPlatform: Process business logic based
on verification result end deactivate UserTerminal Note over MerchantPlatform,PingPong: πŸ“‘ Asynchronous Notification Phase PingPong--)MerchantPlatform: 9.1 πŸ“‘ Asynchronous notification MerchantPlatform-->>PingPong: 21. 🟒 Response 200 ``` ### 1. Import Javascript-SDK Copy the following code to import PingPongCheckout Javascript-SDK via CDN address ::: code-tabs @tab πŸ§ͺ Sandbox Environment ```js ``` @tab πŸ‡ͺπŸ‡Ί FRA Production Environment ```js ``` @tab πŸ‡ΈπŸ‡¬ SG Production Environment ```js ``` @tab πŸ‡ΊπŸ‡Έ US Production Environment ```js ``` ::: ### Usage Method ::: note Note When switching from sandbox environment to production environment, please make sure to check and complete the following operations, otherwise the checkout page will not render properly. - Switch the CDN address of the imported Javascript-SDK to the URL specified for the production environment ::: 1. When you are debugging the **sandbox environment**, you need to import the PingPongCheckout Javascript-SDK address for the **sandbox environment** (remember to switch to the **production environment** address when deploying to production) 2. Insert the `pp-checkout` tag into the html body ```html:line-numbers title="index.html" ``` 3. Pass in the `accessToken` obtained from pre-order, see Order Interface (Hosted Mode) API documentation for `get accessToken` ```html:line-numbers title="index.html" ``` 4. You can pass the language to be displayed by the checkout page (default is English, see more languages in Locale) to `pp-checkout` through tag attributes, as follows: ```html:line-numbers title="index.html" ``` Through the above three steps, you have successfully rendered the Javascript-SDK checkout page. ### Global Variables and Hooks Before using global variables, please ensure that the Javascript-SDK has loaded successfully. #### customizeConfig Layout and Interface Configuration Customize the layout and interface elements of the checkout page through `PingPong.Checkout.customizeConfig`: | Configuration | Type | Default | Description | |-------|------|-------|------| | `layout` | `'tab'\|'accordion'` | `'tab'` | Page layout style. Options: `"tab"` (tab style), `"accordion"` (flat style) | | `displayCheckoutHeader` | `boolean` | `true` | Whether to display the checkout header | | `originalPay` | `boolean` | `true` | Whether to display the native payment button | | `toPingPongResult` | `boolean` | `true` | Whether to redirect to PingPong result page after payment. `false` means redirect directly to the merchant configured result page | | `hideStoredCards` | `boolean` | `false` | Whether to hide COF (Card On File) list | | `onlyDisplaySavedCard` | `boolean` | `false` | When set to `true`, the checkout only displays the stored card area (hides new card input). For the [checkout repeat purchase (stored card + CVV) scenario](/en/notes/onlinePayment/features/tokenization/cardOnFileCVV/) | | `disableCardRemoval` | `boolean` | `false` | When set to `true`, prevents users from removing stored card information. For the [checkout repeat purchase (stored card + CVV) scenario](/en/notes/onlinePayment/features/tokenization/cardOnFileCVV/) | | `displayCardPrompt` | `boolean` | `true` | Whether to display card payment prompt | | `localizationErrorMsg` | `boolean` | `false` | Whether to translate payment error messages | | `displayCardsLogo` | `boolean` | `true` | Whether to display card brand logo list | | `isChallengeIframe` | `boolean` | `false` | Whether to display the 3DS Challenge page in an iframe. When set to `true`, the Challenge flow is rendered in an iframe | ::: warning Note `toPingPongResult` configuration needs to be set when **creating payment session on server side**. Client-side settings may be overridden by server-side configuration. To disable PingPong result page redirect, please ensure the server is configured correctly. ::: ```js:line-numbers title="src/config/payment.js" // [!code highlight:1] customizeConfig layout and interface configuration PingPong.Checkout.customizeConfig = { // [!code focus] layout: "accordion", // Flat style displayCheckoutHeader: false, // Hide header originalPay: false, // Hide native payment button, requires custom button to trigger payment toPingPongResult: false, // Do not redirect to PingPong result page after payment hideStoredCards: false, // Show saved card list displayCardPrompt: true, // Show card payment prompt localizationErrorMsg: false, // Do not translate error messages displayCardsLogo: true, // Show card brand logos isChallengeIframe: true // Render the 3DS Challenge page in an iframe }; ``` #### Custom Payment Button (Optional) Users can customize buttons and bind pingpong's payment functionality through click events. ```js:line-numbers title="src/config/payment.js" // [!code highlight:2] Custom payment button configuration PingPong.Checkout.customizeConfig = { originalPay: false // [!code focus] } // When originalPay is false in initialization parameters, you need to customize the payment button click event document.querySelector('#pay').onclick = function () { // [!code focus] PingPong.Checkout.pay.run() // [!code focus] } ``` #### PingPong.Checkout.beforeCheckoutHook type: ```js (() => void) | (() => Promise) ``` `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 tracking points, checking inventory, etc., you can set this hook function. This function can return a `Promise`, and the subsequent payment process will wait for the Promise state to become Fulfilled before continuing execution. If you want to interrupt the payment process when the Promise state is Rejected or the asynchronous result does not meet your business conditions, you can throw an exception, and the SDK will interrupt the payment process after capturing the exception. ```js:line-numbers title="src/hooks/beforeCheckout.js" // [!code highlight] Pre-payment hook: Check inventory PingPong.Checkout.beforeCheckoutHook = () => { // [!code focus] return fetch('/api/requestInventory').then(res => { // [!code focus] const { inventoryQuantity } = res; if(inventoryQuantity < MIN_QUANTITY) { throw new Error('Insufficient inventory, transaction needs to be interrupted') // [!code error] } }).catch((error) => { throw new Error('Interface exception, transaction needs to be interrupted') // [!code error] }) }; ``` #### PingPong.Checkout.checkoutFailedHook type: ```js (() => void) | (() => Promise) ``` checkoutFailedHook receives the following parameters: ```js (code: string, message: string) => void | Promise; // code: string - Error code // message: string - Error message ``` `checkoutFailedHook` is used to customize error logic When user payment fails, PingPong will pop up a dialog box to prompt the user of the failure reason by default. If you want to customize the dialog UI or text, you can set this hook function. This function can return a Promise. If it returns a Promise, the subsequent process will wait for the Promise state to become Fulfilled before continuing execution ```js:line-numbers title="src/hooks/checkoutFailed.js" // [!code highlight] Payment failure hook: Custom error prompt PingPong.Checkout.checkoutFailedHook = (code: string, message: string) => { // [!code focus] notification.open({ // [!code focus] message: 'Error title', description: `${code}: ${message}` // [!code warning] }) }; ``` ### Usage Examples #### Native JavaScript Complete Example The following example shows how to integrate the SDK in a native JavaScript project, including complete project structure, API calls, and error handling. ::: code-tree title="Native JavaScript Integration Example" height="600px" entry="index.html" ```html:line-numbers title="index.html" :active PingPong Checkout SDK - Native JS Example

PingPong Payment Checkout

Initializing checkout...

``` ```css:line-numbers title="styles.css" * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background: #f5f5f5; padding: 20px; } .container { max-width: 1200px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } h1 { color: #333; margin-bottom: 30px; text-align: center; } .loading { text-align: center; padding: 40px; } .spinner { width: 40px; height: 40px; margin: 0 auto 20px; border: 4px solid #f3f3f3; border-top: 4px solid #1890ff; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .error { padding: 20px; background: #fff2f0; border: 1px solid #ffccc7; border-radius: 4px; color: #ff4d4f; text-align: center; } .error button { margin-top: 15px; padding: 8px 20px; background: #1890ff; color: white; border: none; border-radius: 4px; cursor: pointer; } .error button:hover { background: #40a9ff; } #checkout-wrap { display: none; } ``` ```js:line-numbers title="config.js" // SDK Configuration const CONFIG = { // API endpoint configuration apiEndpoint: '/api/reserve', // [!code focus] // SDK CDN address (switch based on environment) sdkUrl: { sandbox: 'https://payssr-cdn.pingpongx.com/production-fra/acquirer-checkout-web/sandbox/pp-checkout.js', production: 'https://payssr-cdn.pingpongx.com/production-fra/acquirer-checkout-web/pp-checkout.js' }, // Default language defaultLocale: 'zh', // [!code focus] // Request timeout (milliseconds) timeout: 30000, // Minimum inventory quantity (for demonstrating beforeCheckoutHook) minInventory: 1 }; ``` ```js:line-numbers title="api.js" // API call encapsulation const API = { /** * Get AccessToken * @returns {Promise} AccessToken */ async getAccessToken() { // [!code focus] try { const response = await fetch(CONFIG.apiEndpoint, { // [!code focus] method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ // Order information amount: 100.00, currency: 'USD', // ... other necessary parameters }) }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); // [!code warning] } const data = await response.json(); if (!data.accessToken) { throw new Error('accessToken missing from response'); // [!code error] } return data.accessToken; // [!code focus] } catch (error) { console.error('Failed to get AccessToken:', error); throw error; } }, /** * Check inventory (example) * @returns {Promise<{inventoryQuantity: number}>} */ async checkInventory() { // Simulate API call return new Promise((resolve) => { setTimeout(() => { resolve({ inventoryQuantity: 10 }); }, 500); }); } }; ``` ```js:line-numbers title="main.js" // Initialize after page loads document.addEventListener('DOMContentLoaded', async () => { await initCheckout(); // [!code focus] }); /** * Initialize checkout */ async function initCheckout() { const loadingEl = document.getElementById('loading'); const errorEl = document.getElementById('error'); const errorMessageEl = document.getElementById('error-message'); const checkoutWrap = document.getElementById('checkout-wrap'); const ppCheckout = document.querySelector('pp-checkout'); try { // 1. Wait for SDK to load await waitForSDK(); // [!code focus] // 2. Configure SDK hooks setupHooks(); // [!code focus] // 3. Get AccessToken const accessToken = await API.getAccessToken(); // [!code focus] // 4. Set AccessToken to SDK ppCheckout.setAttribute('accessToken', accessToken); // [!code focus] ppCheckout.setAttribute('locale', CONFIG.defaultLocale); // 5. Show checkout loadingEl.style.display = 'none'; checkoutWrap.style.display = 'block'; console.log('Checkout initialized successfully'); } catch (error) { console.error('Initialization failed:', error); loadingEl.style.display = 'none'; errorMessageEl.textContent = `Initialization failed: ${error.message}`; errorEl.style.display = 'block'; } } /** * Wait for SDK to load */ function waitForSDK() { return new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('SDK loading timeout')); }, CONFIG.timeout); const checkSDK = () => { if (window.PingPong && window.PingPong.Checkout) { // [!code focus] clearTimeout(timeout); resolve(); } else { setTimeout(checkSDK, 100); } }; checkSDK(); }); } /** * Configure SDK Hooks */ function setupHooks() { // Pre-payment hook: Check inventory PingPong.Checkout.beforeCheckoutHook = async () => { // [!code focus] try { const { inventoryQuantity } = await API.checkInventory(); if (inventoryQuantity < CONFIG.minInventory) { throw new Error('Insufficient inventory, cannot complete payment'); // [!code error] } console.log('Inventory check passed, inventory quantity:', inventoryQuantity); } catch (error) { console.error('Inventory check failed:', error); throw error; } }; // Payment failed hook: Custom error prompt PingPong.Checkout.checkoutFailedHook = (code, message) => { // [!code focus] console.error('Payment failed:', { code, message }); // Custom error prompt alert(`Payment failed\nError code: ${code}\nError message: ${message}`); }; } ``` ::: #### Vue 3 Complete Example The following example shows how to integrate the SDK in a Vue 3 project, using Composition API to implement reactive state management. ::: code-tree title="Vue 3 Integration Example" height="600px" entry="App.vue" ```vue:line-numbers title="App.vue" :active