@zyadfayed96/hpp-sdk 1.0.0
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 +91 -0
- package/hpp-callback.html +315 -0
- package/hpp-sdk.js +341 -0
- package/index.d.ts +35 -0
- package/package.json +17 -0
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# HPP SDK
|
|
2
|
+
|
|
3
|
+
Official JavaScript SDK for HPP Payment Integration.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install via npm:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install hpp-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### 1. Import the SDK
|
|
16
|
+
|
|
17
|
+
**Using ES Modules (React, Vue, etc.):**
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
import { HPP } from 'hpp-sdk';
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Using CommonJS (Node.js):**
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
const HPP = require('hpp-sdk');
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Using Script Tag (Browser):**
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<script src="path/to/node_modules/hpp-sdk/hpp-sdk.js"></script>
|
|
33
|
+
<!-- OR -->
|
|
34
|
+
<script src="hpp-sdk.js"></script>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Initialize and Open Payment
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
const hpp = new HPP({
|
|
41
|
+
IDAccount: 'your-account-id',
|
|
42
|
+
PaymentIdentifier: 'payment-session-id',
|
|
43
|
+
mode: 'production', // or 'sandbox'
|
|
44
|
+
viewType: 'popup', // 'popup', 'redirect', or 'embed'
|
|
45
|
+
|
|
46
|
+
// Callback for success
|
|
47
|
+
onSuccess: (data) => {
|
|
48
|
+
console.log('Payment Successful:', data);
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// Callback for error
|
|
52
|
+
onError: (error) => {
|
|
53
|
+
console.error('Payment Failed:', error);
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
// Callback for dialog close (popup mode only)
|
|
57
|
+
onClose: () => {
|
|
58
|
+
console.log('Payment dialog closed');
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Open the payment interface
|
|
63
|
+
hpp.open();
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 3. Handle Callback (For Redirect Mode)
|
|
67
|
+
|
|
68
|
+
If you use `viewType: 'redirect'`, you need to host a callback page (e.g., `hpp-callback.html`) to handle the user returning from the payment gateway.
|
|
69
|
+
|
|
70
|
+
1. Create a file named `hpp-callback.html` on your server.
|
|
71
|
+
2. Copy the content from the example `hpp-callback.html` provided in this package (or implement your own logic).
|
|
72
|
+
3. The callback page should parse the URL parameters and communicate back to your main application or display the result.
|
|
73
|
+
|
|
74
|
+
## Configuration Options
|
|
75
|
+
|
|
76
|
+
| Option | Type | Required | Description |
|
|
77
|
+
|--------|------|----------|-------------|
|
|
78
|
+
| `IDAccount` | String | Yes | Your Merchant Account ID. |
|
|
79
|
+
| `PaymentIdentifier` | String | Yes | Unique identifier for the payment session. |
|
|
80
|
+
| `mode` | String | Yes | Environment: `'production'` or `'sandbox'`. |
|
|
81
|
+
| `viewType` | String | No | `'popup'` (default), `'redirect'`, or `'embed'`. |
|
|
82
|
+
| `openInDialog` | Boolean | No | Legacy option for popup mode (default: `true`). |
|
|
83
|
+
| `dialogWidth` | Number | No | Width of the popup window (default: `1000`). |
|
|
84
|
+
| `dialogHeight` | Number | No | Height of the popup window (default: `700`). |
|
|
85
|
+
| `onSuccess` | Function | No | Callback when payment is successful. |
|
|
86
|
+
| `onError` | Function | No | Callback when payment fails. |
|
|
87
|
+
| `onClose` | Function | No | Callback when the popup is closed by the user. |
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
ISC
|
|
@@ -0,0 +1,315 @@
|
|
|
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>Payment Processing...</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
body {
|
|
15
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
16
|
+
background: linear-gradient(135deg, #FEFEFE 0%, #EEEEEE 100%);
|
|
17
|
+
min-height: 100vh;
|
|
18
|
+
display: flex;
|
|
19
|
+
justify-content: center;
|
|
20
|
+
align-items: center;
|
|
21
|
+
padding: 20px;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.container {
|
|
25
|
+
background: white;
|
|
26
|
+
border-radius: 12px;
|
|
27
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
|
28
|
+
padding: 40px;
|
|
29
|
+
max-width: 500px;
|
|
30
|
+
width: 100%;
|
|
31
|
+
text-align: center;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.spinner {
|
|
35
|
+
border: 4px solid #f0f0f0;
|
|
36
|
+
border-top: 4px solid #667eea;
|
|
37
|
+
border-radius: 50%;
|
|
38
|
+
width: 50px;
|
|
39
|
+
height: 50px;
|
|
40
|
+
animation: spin 1s linear infinite;
|
|
41
|
+
margin: 0 auto 20px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@keyframes spin {
|
|
45
|
+
0% { transform: rotate(0deg); }
|
|
46
|
+
100% { transform: rotate(360deg); }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
h1 {
|
|
50
|
+
color: #333;
|
|
51
|
+
margin-bottom: 10px;
|
|
52
|
+
font-size: 24px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
p {
|
|
56
|
+
color: #666;
|
|
57
|
+
margin-bottom: 10px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.result-section {
|
|
61
|
+
display: none;
|
|
62
|
+
margin-top: 30px;
|
|
63
|
+
padding-top: 20px;
|
|
64
|
+
border-top: 2px solid #f0f0f0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.result-section.show {
|
|
68
|
+
display: block;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.success {
|
|
72
|
+
color: #155724;
|
|
73
|
+
background: #d4edda;
|
|
74
|
+
border: 1px solid #c3e6cb;
|
|
75
|
+
padding: 15px;
|
|
76
|
+
border-radius: 6px;
|
|
77
|
+
margin-bottom: 15px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.error {
|
|
81
|
+
color: #721c24;
|
|
82
|
+
background: #f8d7da;
|
|
83
|
+
border: 1px solid #f5c6cb;
|
|
84
|
+
padding: 15px;
|
|
85
|
+
border-radius: 6px;
|
|
86
|
+
margin-bottom: 15px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.data-display {
|
|
90
|
+
background: #f8f9fa;
|
|
91
|
+
padding: 15px;
|
|
92
|
+
border-radius: 6px;
|
|
93
|
+
font-family: 'Courier New', monospace;
|
|
94
|
+
font-size: 12px;
|
|
95
|
+
text-align: left;
|
|
96
|
+
overflow-x: auto;
|
|
97
|
+
max-height: 300px;
|
|
98
|
+
overflow-y: auto;
|
|
99
|
+
margin-bottom: 15px;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.button-group {
|
|
103
|
+
display: flex;
|
|
104
|
+
gap: 10px;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
button {
|
|
109
|
+
padding: 10px 20px;
|
|
110
|
+
border: none;
|
|
111
|
+
border-radius: 6px;
|
|
112
|
+
font-weight: 600;
|
|
113
|
+
cursor: pointer;
|
|
114
|
+
transition: all 0.3s;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.btn-primary {
|
|
118
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
119
|
+
color: white;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.btn-primary:hover {
|
|
123
|
+
transform: translateY(-2px);
|
|
124
|
+
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.4);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.btn-secondary {
|
|
128
|
+
background: #f0f0f0;
|
|
129
|
+
color: #333;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.btn-secondary:hover {
|
|
133
|
+
background: #e0e0e0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.loading-text {
|
|
137
|
+
color: #666;
|
|
138
|
+
font-size: 14px;
|
|
139
|
+
}
|
|
140
|
+
</style>
|
|
141
|
+
</head>
|
|
142
|
+
<body>
|
|
143
|
+
<div class="container">
|
|
144
|
+
<div id="processingSection" class="processing-section">
|
|
145
|
+
<div class="spinner"></div>
|
|
146
|
+
<h1>Processing Payment</h1>
|
|
147
|
+
<p class="loading-text">Please wait while we process your payment...</p>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<div id="resultSection" class="result-section">
|
|
151
|
+
<h1 id="resultTitle">Payment Status</h1>
|
|
152
|
+
<div id="resultMessage" class="success"></div>
|
|
153
|
+
<div id="resultData" class="data-display" style="display: none;"></div>
|
|
154
|
+
<div class="button-group">
|
|
155
|
+
<button class="btn-primary" id="backBtn">← Back to Payment Form</button>
|
|
156
|
+
<button class="btn-secondary" id="closeBtn">Close</button>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
<script>
|
|
162
|
+
let paymentResult = null
|
|
163
|
+
|
|
164
|
+
// Get URL parameters
|
|
165
|
+
function getURLParams() {
|
|
166
|
+
const params = new URLSearchParams(window.location.search)
|
|
167
|
+
return {
|
|
168
|
+
status: params.get('status'),
|
|
169
|
+
message: params.get('message'),
|
|
170
|
+
reference: params.get('reference'),
|
|
171
|
+
amount: params.get('amount'),
|
|
172
|
+
code: params.get('code'),
|
|
173
|
+
data: params.get('data')
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Parse result from URL
|
|
178
|
+
function parsePaymentResult() {
|
|
179
|
+
const params = getURLParams()
|
|
180
|
+
|
|
181
|
+
if (params.status === 'success') {
|
|
182
|
+
return {
|
|
183
|
+
status: 'success',
|
|
184
|
+
reference: params.reference,
|
|
185
|
+
amount: params.amount,
|
|
186
|
+
message: params.message || 'Payment successful!'
|
|
187
|
+
}
|
|
188
|
+
} else if (params.status === 'reference_code') {
|
|
189
|
+
// Handle reference code payment
|
|
190
|
+
return {
|
|
191
|
+
status: 'success',
|
|
192
|
+
reference: params.reference,
|
|
193
|
+
amount: params.amount,
|
|
194
|
+
message: 'Reference Code Generated Successfully!'
|
|
195
|
+
}
|
|
196
|
+
} else if (params.status === 'error') {
|
|
197
|
+
// Handle error status
|
|
198
|
+
return {
|
|
199
|
+
status: 'error',
|
|
200
|
+
code: params.code,
|
|
201
|
+
message: params.message || 'Payment failed'
|
|
202
|
+
}
|
|
203
|
+
} else if (params.code) {
|
|
204
|
+
// Fallback for code-based error detection
|
|
205
|
+
return {
|
|
206
|
+
status: 'error',
|
|
207
|
+
code: params.code,
|
|
208
|
+
message: params.message || 'Payment failed'
|
|
209
|
+
}
|
|
210
|
+
} else if (params.data) {
|
|
211
|
+
try {
|
|
212
|
+
return JSON.parse(decodeURIComponent(params.data))
|
|
213
|
+
} catch (e) {
|
|
214
|
+
return {
|
|
215
|
+
status: 'pending',
|
|
216
|
+
message: 'Payment is being processed'
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return null
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Display result
|
|
225
|
+
function displayResult(result) {
|
|
226
|
+
const resultSection = document.getElementById('resultSection')
|
|
227
|
+
const resultTitle = document.getElementById('resultTitle')
|
|
228
|
+
const resultMessage = document.getElementById('resultMessage')
|
|
229
|
+
const resultData = document.getElementById('resultData')
|
|
230
|
+
const processingSection = document.getElementById('processingSection')
|
|
231
|
+
|
|
232
|
+
processingSection.style.display = 'none'
|
|
233
|
+
|
|
234
|
+
if (result.status === 'success') {
|
|
235
|
+
resultTitle.textContent = '✅ Payment Successful'
|
|
236
|
+
resultMessage.className = 'success'
|
|
237
|
+
resultMessage.innerHTML = `
|
|
238
|
+
<strong>${result.message}</strong>
|
|
239
|
+
${result.reference ? `<br>Reference: ${result.reference}` : ''}
|
|
240
|
+
${result.amount ? `<br>Amount: ${result.amount}` : ''}
|
|
241
|
+
`
|
|
242
|
+
} else if (result.status === 'error') {
|
|
243
|
+
resultTitle.textContent = '❌ Payment Failed'
|
|
244
|
+
resultMessage.className = 'error'
|
|
245
|
+
resultMessage.innerHTML = `
|
|
246
|
+
<strong>${result.message}</strong>
|
|
247
|
+
${result.code ? `<br>Error Code: ${result.code}` : ''}
|
|
248
|
+
`
|
|
249
|
+
} else {
|
|
250
|
+
resultTitle.textContent = '⏳ Payment Pending'
|
|
251
|
+
resultMessage.className = 'success'
|
|
252
|
+
resultMessage.textContent = result.message || 'Payment is being processed. Please check your email for updates.'
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Show data if available
|
|
256
|
+
if (Object.keys(result).length > 2) {
|
|
257
|
+
resultData.style.display = 'block'
|
|
258
|
+
resultData.textContent = JSON.stringify(result, null, 2)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
resultSection.classList.add('show')
|
|
262
|
+
|
|
263
|
+
// Send result to opener (if opened from popup)
|
|
264
|
+
sendToOpener(result)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Send result back to opener window
|
|
268
|
+
function sendToOpener(result) {
|
|
269
|
+
if (window.opener && !window.opener.closed) {
|
|
270
|
+
const message = {
|
|
271
|
+
type: 'hpp-callback-result',
|
|
272
|
+
data: result
|
|
273
|
+
}
|
|
274
|
+
window.opener.postMessage(message, '*')
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Go back to payment form
|
|
279
|
+
document.getElementById('backBtn').addEventListener('click', () => {
|
|
280
|
+
window.history.back()
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
// Close window
|
|
284
|
+
document.getElementById('closeBtn').addEventListener('click', () => {
|
|
285
|
+
if (window.opener) {
|
|
286
|
+
window.close()
|
|
287
|
+
} else {
|
|
288
|
+
window.history.back()
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
// Initialize
|
|
293
|
+
window.addEventListener('load', () => {
|
|
294
|
+
paymentResult = parsePaymentResult()
|
|
295
|
+
|
|
296
|
+
if (paymentResult) {
|
|
297
|
+
displayResult(paymentResult)
|
|
298
|
+
} else {
|
|
299
|
+
// No result found, wait a bit then show error
|
|
300
|
+
setTimeout(() => {
|
|
301
|
+
const errorMsg = document.getElementById('resultMessage')
|
|
302
|
+
errorMsg.className = 'error'
|
|
303
|
+
errorMsg.textContent = 'Unable to retrieve payment result. Please contact support.'
|
|
304
|
+
document.getElementById('resultSection').classList.add('show')
|
|
305
|
+
document.getElementById('processingSection').style.display = 'none'
|
|
306
|
+
}, 2000)
|
|
307
|
+
}
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
// Log callback
|
|
311
|
+
console.log('%c📋 HPP Callback Page', 'font-size: 16px; font-weight: bold; color: #667eea;')
|
|
312
|
+
console.log('Waiting for payment result...')
|
|
313
|
+
</script>
|
|
314
|
+
</body>
|
|
315
|
+
</html>
|
package/hpp-sdk.js
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HPP SDK - JavaScript Version (Compiled)
|
|
3
|
+
*
|
|
4
|
+
* Ready-to-use JavaScript file for browser and Node.js environments
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* ```html
|
|
8
|
+
* <script src="hpp-sdk.js"></script>
|
|
9
|
+
* <script>
|
|
10
|
+
* const hpp = new HPP({
|
|
11
|
+
* IDAccount: 'your-account-id',
|
|
12
|
+
* PaymentIdentifier: 'payment-123',
|
|
13
|
+
* mode: 'production',
|
|
14
|
+
* onSuccess: (data) => console.log('Success:', data),
|
|
15
|
+
* onError: (error) => console.error('Error:', error)
|
|
16
|
+
* })
|
|
17
|
+
* hpp.open()
|
|
18
|
+
* </script>
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Or in modules:
|
|
22
|
+
* ```javascript
|
|
23
|
+
* import { HPP } from './hpp-sdk.js'
|
|
24
|
+
* const hpp = new HPP({...})
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// HPP Communication Utility
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
class HPPCommunication {
|
|
33
|
+
/**
|
|
34
|
+
* Check if running in SDK mode (opened from parent window)
|
|
35
|
+
*/
|
|
36
|
+
static isSDKMode() {
|
|
37
|
+
return window.opener !== null && window.opener !== undefined
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Send payment success status to parent window
|
|
42
|
+
*/
|
|
43
|
+
static sendSuccess(data) {
|
|
44
|
+
if (!this.isSDKMode()) return
|
|
45
|
+
|
|
46
|
+
const message = {
|
|
47
|
+
type: 'hpp-status',
|
|
48
|
+
data: data,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
window.opener?.postMessage(message, '*')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Send payment error status to parent window
|
|
56
|
+
*/
|
|
57
|
+
static sendError(error) {
|
|
58
|
+
if (!this.isSDKMode()) return
|
|
59
|
+
|
|
60
|
+
const message = {
|
|
61
|
+
type: 'hpp-status',
|
|
62
|
+
data: error,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
window.opener?.postMessage(message, '*')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Notify parent that HPP is ready
|
|
70
|
+
*/
|
|
71
|
+
static sendReady() {
|
|
72
|
+
if (!this.isSDKMode()) return
|
|
73
|
+
|
|
74
|
+
const message = {
|
|
75
|
+
type: 'hpp-ready',
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
window.opener?.postMessage(message, '*')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Send close signal to parent window
|
|
83
|
+
*/
|
|
84
|
+
static sendClose() {
|
|
85
|
+
if (!this.isSDKMode()) return
|
|
86
|
+
|
|
87
|
+
const message = {
|
|
88
|
+
type: 'hpp-close',
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
window.opener?.postMessage(message, '*')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Close the HPP window and notify parent
|
|
96
|
+
*/
|
|
97
|
+
static closeHPP() {
|
|
98
|
+
this.sendClose()
|
|
99
|
+
window.close()
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// HPP Main Class
|
|
105
|
+
// ============================================================================
|
|
106
|
+
|
|
107
|
+
class HPP {
|
|
108
|
+
constructor(config) {
|
|
109
|
+
// Validate required fields
|
|
110
|
+
if (!config.IDAccount || !config.PaymentIdentifier || !config.mode) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
'HPP SDK: Missing required config parameters (IDAccount, PaymentIdentifier, mode)',
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.config = {
|
|
117
|
+
openInDialog: true,
|
|
118
|
+
dialogWidth: 1000,
|
|
119
|
+
dialogHeight: 700,
|
|
120
|
+
...config,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
this.dialogWindow = null
|
|
124
|
+
this.isDialogOpen = false
|
|
125
|
+
this.messageHandler = null
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Open the HPP payment interface
|
|
130
|
+
*/
|
|
131
|
+
open() {
|
|
132
|
+
if (this.isDialogOpen) {
|
|
133
|
+
console.warn('HPP: Dialog is already open')
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const { openInDialog } = this.config
|
|
138
|
+
|
|
139
|
+
if (openInDialog) {
|
|
140
|
+
this.openDialog()
|
|
141
|
+
} else {
|
|
142
|
+
this.openPage()
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Open HPP in a dialog/modal window
|
|
148
|
+
*/
|
|
149
|
+
openDialog() {
|
|
150
|
+
const url = this.buildHppUrl()
|
|
151
|
+
const width = typeof this.config.dialogWidth === 'number' ? this.config.dialogWidth : 1000
|
|
152
|
+
const height = typeof this.config.dialogHeight === 'number' ? this.config.dialogHeight : 700
|
|
153
|
+
|
|
154
|
+
// Center the dialog
|
|
155
|
+
const left = window.screenLeft + (window.outerWidth - width) / 2
|
|
156
|
+
const top = window.screenTop + (window.outerHeight - height) / 2
|
|
157
|
+
|
|
158
|
+
const windowFeatures = `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`
|
|
159
|
+
|
|
160
|
+
this.dialogWindow = window.open(url, 'hpp-dialog', windowFeatures)
|
|
161
|
+
|
|
162
|
+
if (!this.dialogWindow) {
|
|
163
|
+
console.error('HPP: Failed to open dialog. Popup blocker may be active.')
|
|
164
|
+
if (this.config.onError) {
|
|
165
|
+
this.config.onError({
|
|
166
|
+
code: 'DIALOG_OPEN_FAILED',
|
|
167
|
+
message: 'Failed to open payment dialog. Please check if popups are blocked.',
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
this.isDialogOpen = true
|
|
174
|
+
this.setupMessageListener()
|
|
175
|
+
|
|
176
|
+
// Check if window is closed periodically
|
|
177
|
+
const checkInterval = setInterval(() => {
|
|
178
|
+
if (!this.dialogWindow || this.dialogWindow.closed) {
|
|
179
|
+
clearInterval(checkInterval)
|
|
180
|
+
this.isDialogOpen = false
|
|
181
|
+
this.cleanup()
|
|
182
|
+
if (this.config.onClose) {
|
|
183
|
+
this.config.onClose()
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}, 500)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Open HPP in a full page (redirect)
|
|
191
|
+
*/
|
|
192
|
+
openPage() {
|
|
193
|
+
const url = this.buildHppUrl()
|
|
194
|
+
window.location.href = url
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Build the HPP URL with all parameters
|
|
199
|
+
*/
|
|
200
|
+
buildHppUrl() {
|
|
201
|
+
const { baseUrl, IDAccount, PaymentIdentifier, CustomerRequestID, mode, viewType } = this.config
|
|
202
|
+
|
|
203
|
+
let url = `${baseUrl}/hpp/${IDAccount}/${PaymentIdentifier}`
|
|
204
|
+
|
|
205
|
+
if (CustomerRequestID) {
|
|
206
|
+
url += `/${CustomerRequestID}`
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Add query parameters
|
|
210
|
+
const params = new URLSearchParams({
|
|
211
|
+
Mode: mode,
|
|
212
|
+
sdk: 'true', // Indicate that HPP is opened via SDK
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
// Add viewType if provided
|
|
216
|
+
if (viewType) {
|
|
217
|
+
params.append('viewType', viewType)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return `${url}?${params.toString()}`
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Setup message listener for postMessage communication
|
|
225
|
+
*/
|
|
226
|
+
setupMessageListener() {
|
|
227
|
+
if (this.messageHandler) {
|
|
228
|
+
window.removeEventListener('message', this.messageHandler)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
this.messageHandler = (event) => {
|
|
232
|
+
// Verify message format
|
|
233
|
+
const message = event.data
|
|
234
|
+
|
|
235
|
+
if (typeof message !== 'object' || !message.type || !message.type.startsWith('hpp-')) {
|
|
236
|
+
return
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
switch (message.type) {
|
|
240
|
+
case 'hpp-status':
|
|
241
|
+
this.handlePaymentStatus(message.data)
|
|
242
|
+
break
|
|
243
|
+
|
|
244
|
+
case 'hpp-ready':
|
|
245
|
+
// HPP is ready, can be used for additional setup
|
|
246
|
+
break
|
|
247
|
+
|
|
248
|
+
case 'hpp-close':
|
|
249
|
+
this.close()
|
|
250
|
+
break
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
window.addEventListener('message', this.messageHandler)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Handle payment status from HPP
|
|
259
|
+
*/
|
|
260
|
+
handlePaymentStatus(data) {
|
|
261
|
+
if (
|
|
262
|
+
data.status === 'success' ||
|
|
263
|
+
data.status === 'pending' ||
|
|
264
|
+
data.status === 'reference_code'
|
|
265
|
+
) {
|
|
266
|
+
if (this.config.onSuccess) {
|
|
267
|
+
this.config.onSuccess(data)
|
|
268
|
+
}
|
|
269
|
+
} else if (data.code) {
|
|
270
|
+
// It's an error response
|
|
271
|
+
if (this.config.onError) {
|
|
272
|
+
this.config.onError(data)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Close the dialog/window
|
|
279
|
+
*/
|
|
280
|
+
close() {
|
|
281
|
+
if (this.dialogWindow && !this.dialogWindow.closed) {
|
|
282
|
+
this.dialogWindow.close()
|
|
283
|
+
}
|
|
284
|
+
this.isDialogOpen = false
|
|
285
|
+
this.cleanup()
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Check if dialog is open
|
|
290
|
+
*/
|
|
291
|
+
isOpen() {
|
|
292
|
+
return this.isDialogOpen && this.dialogWindow !== null && !this.dialogWindow.closed
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Get dialog window reference
|
|
297
|
+
*/
|
|
298
|
+
get window() {
|
|
299
|
+
return this.dialogWindow
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Cleanup resources
|
|
304
|
+
*/
|
|
305
|
+
cleanup() {
|
|
306
|
+
if (this.messageHandler) {
|
|
307
|
+
window.removeEventListener('message', this.messageHandler)
|
|
308
|
+
this.messageHandler = null
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Destructor to clean up resources
|
|
314
|
+
*/
|
|
315
|
+
destroy() {
|
|
316
|
+
this.close()
|
|
317
|
+
this.cleanup()
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ============================================================================
|
|
322
|
+
// Exports
|
|
323
|
+
// ============================================================================
|
|
324
|
+
|
|
325
|
+
// For browser global (script tag usage)
|
|
326
|
+
if (typeof window !== 'undefined') {
|
|
327
|
+
window.HPP = HPP
|
|
328
|
+
window.HPPCommunication = HPPCommunication
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// For CommonJS (if loaded as module)
|
|
332
|
+
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
|
333
|
+
module.exports = HPP
|
|
334
|
+
module.exports.HPP = HPP
|
|
335
|
+
module.exports.HPPCommunication = HPPCommunication
|
|
336
|
+
module.exports.default = HPP
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// For ES6 modules (comment out export statements for script tag compatibility)
|
|
340
|
+
// export { HPP, HPPCommunication }
|
|
341
|
+
// export default HPP
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
declare module 'hpp-sdk' {
|
|
2
|
+
export interface HPPConfig {
|
|
3
|
+
IDAccount: string;
|
|
4
|
+
PaymentIdentifier: string;
|
|
5
|
+
mode: 'production' | 'sandbox';
|
|
6
|
+
viewType?: 'popup' | 'redirect' | 'embed';
|
|
7
|
+
openInDialog?: boolean;
|
|
8
|
+
dialogWidth?: number;
|
|
9
|
+
dialogHeight?: number;
|
|
10
|
+
CustomerRequestID?: string;
|
|
11
|
+
onSuccess?: (data: any) => void;
|
|
12
|
+
onError?: (error: any) => void;
|
|
13
|
+
onClose?: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class HPP {
|
|
17
|
+
constructor(config: HPPConfig);
|
|
18
|
+
open(): void;
|
|
19
|
+
openDialog(): void;
|
|
20
|
+
openPage(): void;
|
|
21
|
+
close(): void;
|
|
22
|
+
destroy(): void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class HPPCommunication {
|
|
26
|
+
static isSDKMode(): boolean;
|
|
27
|
+
static sendSuccess(data: any): void;
|
|
28
|
+
static sendError(error: any): void;
|
|
29
|
+
static sendReady(): void;
|
|
30
|
+
static sendClose(): void;
|
|
31
|
+
static closeHPP(): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default HPP;
|
|
35
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zyadfayed96/hpp-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "HPP Payment SDK for JavaScript",
|
|
5
|
+
"main": "hpp-sdk.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"hpp",
|
|
12
|
+
"payment",
|
|
13
|
+
"sdk"
|
|
14
|
+
],
|
|
15
|
+
"author": "",
|
|
16
|
+
"license": "ISC"
|
|
17
|
+
}
|