Package Exports
- @paypercut/checkout-js
- @paypercut/checkout-js/cdn
- @paypercut/checkout-js/package.json
Readme
Paypercut Checkout JavaScript SDK
A lightweight, framework-agnostic JavaScript SDK for embedding Paypercut Checkout into your web application. Works seamlessly with vanilla JavaScript, TypeScript, React, Vue, Angular, and other modern frameworks.
Table of Contents
- Features
- How It Works
- Installation
- Quick Start
- API Reference
- Events
- Usage Examples
- Security
- Troubleshooting
- Performance Optimization
- Best Practices
- FAQ
- Support
Installation
Via NPM
npm install @paypercut/checkout-jsVia Yarn
yarn add @paypercut/checkout-jsVia PNPM
pnpm add @paypercut/checkout-jsVia CDN
<!-- jsDelivr (recommended with SRI) -->
<script src="https://cdn.jsdelivr.net/npm/@paypercut/checkout-js@1.0.10/dist/paypercut-checkout.iife.min.js"
integrity="sha384-..."
crossorigin="anonymous"></script>
<!-- or UNPKG -->
<script src="https://unpkg.com/@paypercut/checkout-js@1.0.10/dist/paypercut-checkout.iife.min.js"></script>Quick Start
This returns a checkout session ID like CHK_12345.
1. Embed the Checkout
Use the checkout ID to initialize the checkout on your frontend:
import { PaypercutCheckout } from '@paypercut/checkout-js';
// Initialize the checkout
const checkout = PaypercutCheckout({
id: 'CHK_12345', // Your checkout session ID from step 1
containerId: '#checkout' // CSS selector or HTMLElement
});
// Listen for payment events
checkout.on('success', () => {
console.log('Payment successful!');
});
checkout.on('error', () => {
console.error('Payment failed');
});
// Optional: Listen for when iframe finishes loading (useful for modal implementations)
checkout.on('loaded', () => {
console.log('Checkout loaded and ready');
});
// Render the checkout
checkout.render();That's it! The checkout is now embedded in your page.
2. Display Mode
The SDK supports one display mode:
| Mode | When to Use | Configuration |
|---|---|---|
| Embedded | Checkout is part of your page layout | Provide containerId |
API Reference
PaypercutCheckout(options)
Creates a new checkout instance.
Options
| Option | Type | Required | Description |
|---|---|---|---|
id |
string |
Yes | Checkout session identifier |
containerId |
string | HTMLElement |
Yes | CSS selector or element where iframe mounts. |
Example
const checkout = PaypercutCheckout({
id: 'CHK_12345',
containerId: '#checkout-container'
});Instance Methods
render()
Mounts and displays the checkout iframe.
checkout.render();destroy()
Destroys the instance and cleans up all event listeners. Call this when you're done with the checkout instance.
checkout.destroy();on(event, handler)
Subscribes to checkout events. Returns an unsubscribe function.
const unsubscribe = checkout.on('success', () => {
console.log('Payment successful!');
});
// Later, to unsubscribe
unsubscribe();once(event, handler)
Subscribes to a checkout event that automatically unsubscribes after the first emission. Returns an unsubscribe function.
checkout.once('loaded', () => {
console.log('Checkout loaded - this will only fire once');
});off(event, handler)
Unsubscribes from checkout events.
const handler = () => console.log('Payment successful!');
checkout.on('success', handler);
checkout.off('success', handler);isMounted()
Returns whether the checkout is currently mounted.
if (checkout.isMounted()) {
console.log('Checkout is visible');
}Events
Subscribe to events using the on() method:
| Event | Description | Payload |
|---|---|---|
loaded |
Checkout iframe has finished loading | void |
success |
Payment completed successfully | PaymentSuccessPayload |
error |
Terminal failure (tokenize, confirm, or 3DS) | ApiErrorPayload (see details below) |
expired |
Checkout session expired | void |
threeds_started |
3DS challenge flow started; SDK modal opened | object (flow context) |
threeds_complete |
3DS challenge completed | object (auth result context) |
threeds_canceled |
3DS challenge canceled by user | object (flow context) |
threeds_error |
3DS challenge error | ApiErrorPayload (if provided) or object |
Success payload example
checkout.on('success', (payload) => {
// payload: PaymentSuccessPayload
// Example:
// {
// payment_method: {
// brand: 'visa',
// last4: '4242',
// exp_month: 12,
// exp_year: 2030,
// }
// }
console.log('PM last4', payload.payment_method.last4);
});Error payloads overview
checkout.on('error', (err) => {
switch (err.code) {
case 'card_validation_error':
// err.errors[] has field-level issues. Messages are localized.
break;
case 'card_declined':
// Optional err.decline_code and user-friendly err.message
break;
case 'threeds_error':
case 'threeds_authentication_failed':
case 'threeds_canceled':
// 3DS issue. Some servers use status_code 424 with a detailed message.
break;
default:
// Other terminal errors (e.g., authentication_failed, session_expired)
break;
}
});3DS Events (enum usage recommended)
You can subscribe using string event names or the SdkEvent enum:
import { PaypercutCheckout, SdkEvent } from '@paypercut/checkout-js';
const checkout = PaypercutCheckout({ id: 'CHK_12345', containerId: '#checkout' });
checkout.on(SdkEvent.ThreeDSStarted, (ctx) => {
// e.g., show your own UI indicator or log ctx
});
checkout.on(SdkEvent.ThreeDSComplete, (payload) => {
// 3DS completed successfully; Hosted Checkout will continue the flow
});
checkout.on(SdkEvent.ThreeDSCanceled, (payload) => {
// User canceled the 3DS challenge
});
checkout.on(SdkEvent.ThreeDSError, (err) => {
// Normalized ApiErrorPayload when available; fallback may be a raw object
console.error('3DS error:', err);
});Payload notes:
error: the SDK forwards a normalizedApiErrorPayloadwhen provided by Hosted Checkout.threeds_error: forwardspayload.errorwhen available; otherwise the raw message data.threeds_*non-error events: payload is forwarded as-is from Hosted Checkout (shape may evolve).
Core events (loaded, success, error, expired)
Using enums:
import { PaypercutCheckout, SdkEvent } from '@paypercut/checkout-js';
const checkout = PaypercutCheckout({ id: 'CHK_12345', containerId: '#checkout' });
checkout.on(SdkEvent.Loaded, () => {
console.log('Checkout loaded');
});
checkout.on(SdkEvent.Success, (payload) => {
console.log('Payment successful', payload.payment_method);
});
checkout.on(SdkEvent.Error, (err) => {
// err: ApiErrorPayload
console.error('Payment error:', err.code, err.message);
});
checkout.on(SdkEvent.Expired, () => {
console.warn('Checkout session expired');
});Or with string event names:
checkout.on('loaded', () => {});
checkout.on('success', (payload) => {
console.log('Payment successful', payload.payment_method);
});
checkout.on('error', (err) => {
console.error('Payment error', err);
});
checkout.on('expired', () => {});Types
Payload types
PaymentSuccessPayload
// Payload for `checkout.on('success', (payload))`
type PaymentSuccessPayload = {
payment_method: {
brand: string; // e.g., 'visa', 'mastercard'
last4: string;
exp_month: number;
exp_year: number;
};
};ApiErrorPayload (common shapes)
The SDK forwards the error payload provided by Hosted Checkout. Common shapes include:
// Card validation error with per-field details (messages are localized)
type CardField = 'card_number' | 'expiration_date' | 'cvc' | 'cardholder_name';
type CardValidationErrorCode =
| 'invalid_number' | 'incomplete_number'
| 'invalid_expiry' | 'incomplete_expiry'
| 'invalid_cvc' | 'incomplete_cvc'
| 'required';
type VendorValidationType = 'invalid' | 'incomplete' | 'required';
type CardValidationErrorItem = {
field: CardField;
code: CardValidationErrorCode;
message: string; // localized per current checkout locale
vendor_type: VendorValidationType;
};
type ApiErrorPayload =
| {
checkoutId: string;
code: 'card_validation_error';
message: string; // e.g., 'Card details are incomplete or invalid'
status_code: number; // typically 400
timestamp: string; // ISO8601
errors: CardValidationErrorItem[];
}
| {
code:
| 'threeds_error'
| 'threeds_authentication_failed'
| 'threeds_canceled'
| 'card_declined'
| 'authentication_failed'
| 'session_expired'
| string; // other server codes may appear, e.g. 'permission_denied'
type?: 'card_error' | string;
message?: string;
status_code?: number; // some sources use `status`
status?: number; // compatibility with some payloads
decline_code?: string; // Optional decline reason
trace_id?: string;
timestamp?: string; // ISO8601
[key: string]: any; // passthrough
};Error payload examples
{
"checkoutId": "CHK_12345",
"code": "card_validation_error",
"message": "Card details are incomplete or invalid",
"status_code": 400,
"timestamp": "2025-01-01T12:00:00.000Z",
"errors": [
{ "field": "card_number", "code": "invalid_number", "message": "Card number is invalid", "vendor_type": "invalid" },
{ "field": "expiration_date", "code": "incomplete_expiry", "message": "Incomplete date", "vendor_type": "incomplete" }
]
}{
"checkoutId": "01KA8EG3KA2A61YXX4XVD4FYPT",
"code": "permission_denied",
"message": "forbidden",
"status_code": 403,
"trace_id": "00761104aff14f33adb84d7437d7e320",
"timestamp": "2025-12-02T09:31:22.779Z"
}{
"checkoutId": "01KA8EG3KA2A61YXX4XVD4FYPT",
"code": "threeds_error",
"type": "card_error",
"message": "Card authentication failed.",
"status": 400,
"timestamp": "2025-12-02T09:31:44.250Z"
}Note: Some payloads provide
status_codewhile others providestatus. Treat them interchangeably.
Error codes explained
- authentication_failed — General authentication failure during confirm or 3DS. Usually recoverable by retrying or using another card.
- card_declined — Issuer declined the card. Optional
decline_codemay be present (e.g.,insufficient_funds). - wallet_authentication_canceled — User canceled Apple/Google Pay authentication sheet.
- wallet_not_available — Apple/Google Pay not available on the device/browser.
- card_validation_error — Client-side validation of card fields failed.
errors[]contains localized, per-field issues. - threeds_error — 3DS flow encountered an error (e.g., ACS unavailable). Often accompanied by a server message.
- threeds_authentication_failed — 3DS authentication failed (e.g., challenged and consumer failed/denied).
- threeds_canceled — Customer canceled the 3DS challenge.
- session_expired — Checkout session is no longer valid; create a new session.
- permission_denied — Server responded with permission error (e.g., session not allowed). Not enumerated above; SDK forwards unknown codes unchanged.
Notes:
- For 3DS issues, you may receive
threeds_errorwithstatus_code424 and a server-provided message. - For declines, expect
code: 'card_declined', optionaldecline_code, and a user-friendlymessage. - The validation error
messagefields are already localized based on the checkout’s locale.
3DS payloads (representative samples)
The SDK forwards whatever Hosted Checkout sends for 3DS events. Shapes may evolve.
// threeds_started
{
type: 'THREEDS_START_FLOW',
checkoutId: 'CHK_12345',
// additional context as provided by Hosted Checkout (e.g., method, acsUrl)
}
// threeds_complete
{
type: 'THREEDS_COMPLETE',
checkoutId: 'CHK_12345',
// authentication result context (e.g., status: 'Y' | 'A' | 'C' | 'D')
}
// threeds_canceled
{
type: 'THREEDS_CANCELED',
checkoutId: 'CHK_12345',
}
// threeds_error (non-terminal)
// Note: terminal failures will also be emitted on `error` with ApiErrorPayload
{
type: 'THREEDS_ERROR',
checkoutId: 'CHK_12345',
error?: ApiErrorPayload,
}Tip: Prefer subscribing with the SdkEvent enum for stronger typing.
Usage Examples
Vanilla JavaScript (CDN)
<!DOCTYPE html>
<html>
<head>
<title>Paypercut Checkout</title>
</head>
<body>
<div id="checkout"></div>
<script src="https://cdn.jsdelivr.net/npm/@paypercut/checkout-js@1.0.10/dist/paypercut-checkout.iife.min.js"></script>
<script>
const checkout = PaypercutCheckout({
id: 'CHK_12345',
containerId: '#checkout'
});
checkout.on('success', function() {
alert('Payment successful!');
});
checkout.on('error', function(error) {
alert('Payment failed: ' + error.message);
});
checkout.render();
</script>
</body>
</html>TypeScript / ESM
import { PaypercutCheckout } from '@paypercut/checkout-js';
const checkout = PaypercutCheckout({
id: 'CHK_12345',
containerId: '#checkout'
});
checkout.on('success', () => {
console.log('Payment successful');
// Redirect to success page
window.location.href = '/success';
});
checkout.on('error', (error) => {
console.error('Payment error:', error);
// Show error message to user
alert(`Payment failed: ${error.message}`);
});
checkout.render();React
import { useEffect, useRef, useState } from 'react';
import { PaypercutCheckout, CheckoutInstance } from '@paypercut/checkout-js';
export function CheckoutComponent({ checkoutId }: { checkoutId: string }) {
const containerRef = useRef<HTMLDivElement>(null);
const checkoutRef = useRef<CheckoutInstance | null>(null);
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
useEffect(() => {
if (!containerRef.current) return;
const checkout = PaypercutCheckout({
id: checkoutId,
containerId: containerRef.current
});
checkout.on('loaded', () => {
setStatus('idle');
console.log('Checkout loaded');
});
checkout.on('success', () => {
setStatus('success');
console.log('Payment successful');
});
checkout.on('error', (error) => {
setStatus('error');
console.error('Payment error:', error);
});
checkout.render();
checkoutRef.current = checkout;
return () => {
checkout.destroy();
};
}, [checkoutId]);
return (
<div>
<div ref={containerRef} style={{ width: '100%', height: '600px' }} />
{status === 'loading' && <p>Processing payment...</p>}
{status === 'success' && <p>✅ Payment successful!</p>}
{status === 'error' && <p>❌ Payment failed. Please try again.</p>}
</div>
);
}Modal-like Implementation
If you want to create a modal-like experience, you can use the loaded event to show/hide a loading overlay:
import { useEffect, useRef, useState } from 'react';
import { PaypercutCheckout, CheckoutInstance } from '@paypercut/checkout-js';
export function ModalCheckout({ checkoutId, onClose }: { checkoutId: string; onClose: () => void }) {
const containerRef = useRef<HTMLDivElement>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
if (!containerRef.current) return;
const checkout = PaypercutCheckout({
id: checkoutId,
containerId: containerRef.current
});
checkout.on('loaded', () => {
setIsLoading(false);
});
checkout.on('success', () => {
console.log('Payment successful');
onClose();
});
checkout.on('error', (error) => {
console.error('Payment error:', error);
});
checkout.render();
return () => {
checkout.destroy();
};
}, [checkoutId, onClose]);
return (
<div style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 9999
}}>
<div style={{
position: 'relative',
width: '90%',
maxWidth: '500px',
height: '90%',
maxHeight: '700px',
backgroundColor: '#fff',
borderRadius: '12px',
overflow: 'hidden'
}}>
{isLoading && (
<div style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#fff',
zIndex: 1
}}>
<p>Loading checkout...</p>
</div>
)}
<div ref={containerRef} style={{ width: '100%', height: '100%' }} />
<button
onClick={onClose}
style={{
position: 'absolute',
top: '10px',
right: '10px',
zIndex: 2
}}
>
✕
</button>
</div>
</div>
);
}Security
Origin Validation
The SDK automatically validates all postMessage communications against the configured checkoutHost origin. This prevents unauthorized messages from being processed.
Content Security Policy
For enhanced security, configure your Content Security Policy headers:
Content-Security-Policy:
frame-src https://buy.paypercut.io OR something else;
script-src 'self' https://cdn.jsdelivr.net https://unpkg.com;
connect-src https://buy.paypercut.io OR something else;HTTPS Only
Always use HTTPS in production. The SDK is designed for secure communication over HTTPS.
Troubleshooting
Checkout not displaying
Problem: The iframe doesn't appear after calling render().
Solutions:
- Ensure the container element exists in the DOM before calling
render() - Check that the
idis a valid checkout session ID - Verify there are no CSP errors in the browser console
- Enable debug mode:
debug: trueto see detailed logs
Events not firing
Problem: Event handlers are not being called.
Solutions:
- Ensure you subscribe to events before calling
render() - Check that the checkout session is active and not expired
- Enable debug mode to see message logs
TypeScript errors
Problem: TypeScript shows type errors when using the SDK.
Solutions:
- Ensure you're importing types:
import { PaypercutCheckout, CheckoutInstance } from '@paypercut/checkout-js' - Update to the latest version of the SDK
- Check that your
tsconfig.jsonincludes the SDK's type definitions
Performance Optimization
Preconnect to Checkout Host
Add DNS prefetch and preconnect hints to your HTML for faster loading:
<link rel="preconnect" href="https://buy.paypercut.io ">
<link rel="dns-prefetch" href="https://buy.paypercut.io ">Lazy Loading
Load the SDK only when needed to reduce initial bundle size:
async function loadCheckout() {
const { PaypercutCheckout } = await import('@paypercut/checkout-js');
return PaypercutCheckout;
}
// Load when user clicks pay button
payButton.addEventListener('click', async () => {
const PaypercutCheckout = await loadCheckout();
const checkout = PaypercutCheckout({ id: 'CHK_12345' });
checkout.render();
});Best Practices
1. Always Verify Payments on Your Backend
Never rely solely on frontend events for payment confirmation. Always verify payments using webhooks on your backend:
// ✅ Good: Use frontend events for UI updates only
checkout.on('success', () => {
// Show success message
showSuccessMessage();
// Redirect to order confirmation page
window.location.href = '/orders/confirmation';
});
// ❌ Bad: Don't grant access based on frontend events alone
checkout.on('success', () => {
// This can be manipulated by users!
grantPremiumAccess(userId); // Don't do this!
});2. Handle All Event Types
Always handle both success and error events:
checkout.on('success', () => {
// Handle success
showSuccessMessage();
});
checkout.on('error', () => {
// Show user-friendly error message
alert('Payment failed. Please try again.');
});3. Clean Up on Component Unmount
Always call destroy() when your component unmounts to prevent memory leaks:
// React example
useEffect(() => {
const checkout = PaypercutCheckout({ id: checkoutId });
checkout.render();
return () => {
checkout.destroy(); // Clean up!
};
}, []);FAQ For Internal Validation
Q: How do I handle successful payments?
Listen to the success event and redirect users or update your UI accordingly. Always verify the payment on your backend using webhooks.
Q: Can I use this with server-side rendering (SSR)?
Yes, but ensure the SDK is only initialized on the client side. For Next.js, use dynamic imports with ssr: false.
Q: What happens if the user closes the browser during payment?
Q: Can I have multiple checkouts on one page? Maybe Yes, but only one should be active at a time to avoid user confusion. If no, we destroy the previous one and create a new one.
Q: How do I handle errors?
Listen to the error event and display user-friendly error messages. Log errors for debugging.