@paynext/sdk 0.0.165 → 0.0.167
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -3
- package/dist/checkout.html +244 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
TypeScript SDK for integrating PayNext payment processing with full type safety and multiple payment methods support.
|
|
4
4
|
|
|
5
|
+
## 🛡️ NEW: PCI-Compliant Iframe Mode
|
|
6
|
+
|
|
7
|
+
Enable iframe mode for enhanced PCI compliance by adding one parameter:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
await checkout.mount('checkout-container', {
|
|
11
|
+
clientToken: 'your-token',
|
|
12
|
+
environment: 'production',
|
|
13
|
+
apiVersion: 'v1',
|
|
14
|
+
iframeMode: true, // 🛡️ Isolates payment data in secure iframe
|
|
15
|
+
})
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Benefits:** Reduced PCI scope, isolated processing, XSS protection. See [Iframe Quick Start](./IFRAME_QUICKSTART.md) for details.
|
|
19
|
+
|
|
5
20
|
## Installation
|
|
6
21
|
|
|
7
22
|
```bash
|
|
@@ -101,10 +116,11 @@ export const CheckoutComponent: FC<Readonly<IProps>> = (props) => {
|
|
|
101
116
|
## Configuration
|
|
102
117
|
|
|
103
118
|
```typescript
|
|
104
|
-
interface PayNextConfig {
|
|
119
|
+
interface PayNextConfigExtended extends PayNextConfig {
|
|
105
120
|
clientToken: string
|
|
106
121
|
apiVersion: string
|
|
107
|
-
environment: 'sandbox' | 'production'
|
|
122
|
+
environment: 'develop' | 'staging' | 'sandbox' | 'production'
|
|
123
|
+
iframeMode?: boolean // 🛡️ Enable PCI-compliant iframe mode (default: false)
|
|
108
124
|
variant?: 'default' | 'compact'
|
|
109
125
|
locale?: Locale
|
|
110
126
|
translate?: CheckoutTranslate
|
|
@@ -194,4 +210,31 @@ The SDK supports recent versions of all major browsers:
|
|
|
194
210
|
|
|
195
211
|
## Security
|
|
196
212
|
|
|
197
|
-
PCI
|
|
213
|
+
### PCI Compliance
|
|
214
|
+
|
|
215
|
+
**Iframe Mode (Recommended)**: Enables PCI-compliant iframe isolation for reduced compliance scope.
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
await checkout.mount('checkout-container', {
|
|
219
|
+
iframeMode: true, // Isolates payment data processing
|
|
220
|
+
// ... other config
|
|
221
|
+
})
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Content Security Policy
|
|
225
|
+
|
|
226
|
+
For iframe mode, add to your HTML:
|
|
227
|
+
|
|
228
|
+
```html
|
|
229
|
+
<meta http-equiv="Content-Security-Policy"
|
|
230
|
+
content="frame-src https://cdn-sdk.paynext.com https://cdn-sdk-dev.paynext.com;">
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Security Features
|
|
234
|
+
|
|
235
|
+
- PCI DSS Level 1 compliant
|
|
236
|
+
- End-to-end encryption (TLS 1.3)
|
|
237
|
+
- Tokenization of sensitive data
|
|
238
|
+
- No sensitive payment data stored locally
|
|
239
|
+
- Optional iframe isolation for reduced PCI scope
|
|
240
|
+
- Sandbox iframe security attributes (when iframe mode enabled)
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>PayNext Checkout - Iframe Mode</title>
|
|
7
|
+
|
|
8
|
+
<!-- Security headers -->
|
|
9
|
+
<meta http-equiv="X-Content-Type-Options" content="nosniff">
|
|
10
|
+
<meta http-equiv="X-Frame-Options" content="ALLOW-FROM *">
|
|
11
|
+
<meta http-equiv="Referrer-Policy" content="strict-origin-when-cross-origin">
|
|
12
|
+
|
|
13
|
+
<!-- CSP for iframe -->
|
|
14
|
+
<meta http-equiv="Content-Security-Policy"
|
|
15
|
+
content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src *; frame-ancestors *;">
|
|
16
|
+
|
|
17
|
+
<style>
|
|
18
|
+
* {
|
|
19
|
+
margin: 0;
|
|
20
|
+
padding: 0;
|
|
21
|
+
box-sizing: border-box;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
html, body {
|
|
25
|
+
width: 100%;
|
|
26
|
+
height: 100%;
|
|
27
|
+
overflow: hidden;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
body {
|
|
31
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#checkout-container {
|
|
35
|
+
width: 100%;
|
|
36
|
+
min-height: 100%;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Loading state */
|
|
40
|
+
.loading {
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
justify-content: center;
|
|
44
|
+
min-height: 400px;
|
|
45
|
+
color: #666;
|
|
46
|
+
font-size: 14px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.loading::after {
|
|
50
|
+
content: '...';
|
|
51
|
+
animation: dots 1.5s steps(4, end) infinite;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@keyframes dots {
|
|
55
|
+
0%, 20% { content: '.'; }
|
|
56
|
+
40% { content: '..'; }
|
|
57
|
+
60%, 100% { content: '...'; }
|
|
58
|
+
}
|
|
59
|
+
</style>
|
|
60
|
+
</head>
|
|
61
|
+
<body>
|
|
62
|
+
<div id="checkout-container">
|
|
63
|
+
<div class="loading">Loading checkout</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<script type="module">
|
|
67
|
+
// Message types for parent communication
|
|
68
|
+
const MessageType = {
|
|
69
|
+
INIT: 'paynext:init',
|
|
70
|
+
MOUNT: 'paynext:mount',
|
|
71
|
+
UNMOUNT: 'paynext:unmount',
|
|
72
|
+
UPDATE_CONFIG: 'paynext:updateConfig',
|
|
73
|
+
READY: 'paynext:ready',
|
|
74
|
+
LOADED: 'paynext:loaded',
|
|
75
|
+
ATTEMPT: 'paynext:attempt',
|
|
76
|
+
COMPLETE: 'paynext:complete',
|
|
77
|
+
FAIL: 'paynext:fail',
|
|
78
|
+
ERROR: 'paynext:error',
|
|
79
|
+
RESIZE: 'paynext:resize'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let checkout = null
|
|
83
|
+
let parentOrigin = null
|
|
84
|
+
let config = null
|
|
85
|
+
|
|
86
|
+
// Send message to parent
|
|
87
|
+
function sendToParent(type, payload, requestId) {
|
|
88
|
+
if (!parentOrigin) {
|
|
89
|
+
console.error('[Iframe] Parent origin not set')
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
window.parent.postMessage(
|
|
94
|
+
{ type, payload, requestId },
|
|
95
|
+
parentOrigin
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Handle messages from parent
|
|
100
|
+
window.addEventListener('message', async (event) => {
|
|
101
|
+
const data = event.data
|
|
102
|
+
|
|
103
|
+
// Store parent origin on first message
|
|
104
|
+
if (!parentOrigin) {
|
|
105
|
+
parentOrigin = event.origin
|
|
106
|
+
console.log('[Iframe] Parent origin set to:', parentOrigin)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Validate origin
|
|
110
|
+
if (event.origin !== parentOrigin) {
|
|
111
|
+
console.warn('[Iframe] Message from unauthorized origin:', event.origin)
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!data.type || !data.type.startsWith('paynext:')) {
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
switch (data.type) {
|
|
121
|
+
case MessageType.MOUNT:
|
|
122
|
+
console.log('[Iframe] Mount request received')
|
|
123
|
+
|
|
124
|
+
if (data.payload?.config) {
|
|
125
|
+
config = data.payload.config
|
|
126
|
+
|
|
127
|
+
// Import and initialize SDK
|
|
128
|
+
const { PayNextCheckout } = await import('./index.cdn.js')
|
|
129
|
+
|
|
130
|
+
checkout = new PayNextCheckout()
|
|
131
|
+
|
|
132
|
+
// Mount checkout
|
|
133
|
+
await checkout.mount('checkout-container', {
|
|
134
|
+
...config,
|
|
135
|
+
// Override callbacks to send messages to parent
|
|
136
|
+
onCheckoutLoaded: (result) => {
|
|
137
|
+
sendToParent(MessageType.LOADED, result, data.requestId)
|
|
138
|
+
notifyResize()
|
|
139
|
+
},
|
|
140
|
+
onCheckoutAttempt: (result) => {
|
|
141
|
+
sendToParent(MessageType.ATTEMPT, result)
|
|
142
|
+
},
|
|
143
|
+
onCheckoutComplete: (result) => {
|
|
144
|
+
sendToParent(MessageType.COMPLETE, result)
|
|
145
|
+
},
|
|
146
|
+
onCheckoutFail: (error) => {
|
|
147
|
+
sendToParent(MessageType.FAIL, error)
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// Send success response
|
|
152
|
+
sendToParent(MessageType.READY, { success: true }, data.requestId)
|
|
153
|
+
|
|
154
|
+
// Setup resize observer
|
|
155
|
+
setupResizeObserver()
|
|
156
|
+
}
|
|
157
|
+
break
|
|
158
|
+
|
|
159
|
+
case MessageType.UNMOUNT:
|
|
160
|
+
console.log('[Iframe] Unmount request received')
|
|
161
|
+
|
|
162
|
+
if (checkout) {
|
|
163
|
+
await checkout.unmount()
|
|
164
|
+
checkout = null
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
sendToParent(MessageType.READY, { success: true }, data.requestId)
|
|
168
|
+
break
|
|
169
|
+
|
|
170
|
+
case MessageType.UPDATE_CONFIG:
|
|
171
|
+
console.log('[Iframe] Update config request received')
|
|
172
|
+
|
|
173
|
+
if (checkout && data.payload?.config) {
|
|
174
|
+
await checkout.updateConfig(data.payload.config)
|
|
175
|
+
sendToParent(MessageType.READY, { success: true }, data.requestId)
|
|
176
|
+
}
|
|
177
|
+
break
|
|
178
|
+
|
|
179
|
+
default:
|
|
180
|
+
console.warn('[Iframe] Unknown message type:', data.type)
|
|
181
|
+
}
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error('[Iframe] Error handling message:', error)
|
|
184
|
+
sendToParent(MessageType.ERROR, {
|
|
185
|
+
message: error.message,
|
|
186
|
+
stack: error.stack
|
|
187
|
+
}, data.requestId)
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
// Setup resize observer to notify parent of height changes
|
|
192
|
+
function setupResizeObserver() {
|
|
193
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
194
|
+
for (const entry of entries) {
|
|
195
|
+
const height = entry.contentRect.height
|
|
196
|
+
sendToParent(MessageType.RESIZE, { height })
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
resizeObserver.observe(document.body)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Notify parent of current height
|
|
204
|
+
function notifyResize() {
|
|
205
|
+
const height = document.body.scrollHeight
|
|
206
|
+
sendToParent(MessageType.RESIZE, { height })
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Send ready message when iframe loads
|
|
210
|
+
window.addEventListener('load', () => {
|
|
211
|
+
console.log('[Iframe] Iframe loaded and ready')
|
|
212
|
+
|
|
213
|
+
// Wait a bit for parent to setup message listener
|
|
214
|
+
setTimeout(() => {
|
|
215
|
+
// Send ready to any origin initially
|
|
216
|
+
window.parent.postMessage(
|
|
217
|
+
{ type: MessageType.READY, payload: { success: true } },
|
|
218
|
+
'*'
|
|
219
|
+
)
|
|
220
|
+
}, 100)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
// Log errors
|
|
224
|
+
window.addEventListener('error', (event) => {
|
|
225
|
+
console.error('[Iframe] Runtime error:', event.error)
|
|
226
|
+
sendToParent(MessageType.ERROR, {
|
|
227
|
+
message: event.error?.message || 'Unknown error',
|
|
228
|
+
stack: event.error?.stack
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
window.addEventListener('unhandledrejection', (event) => {
|
|
233
|
+
console.error('[Iframe] Unhandled promise rejection:', event.reason)
|
|
234
|
+
sendToParent(MessageType.ERROR, {
|
|
235
|
+
message: event.reason?.message || 'Unhandled promise rejection',
|
|
236
|
+
stack: event.reason?.stack
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
console.log('[Iframe] PayNext Checkout iframe initialized')
|
|
241
|
+
</script>
|
|
242
|
+
</body>
|
|
243
|
+
</html>
|
|
244
|
+
|