node-behind-api-client 2.0.48
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/.gitlab-ci.yml +20 -0
- package/README.md +65 -0
- package/docs/behind-api-client/easyjob/JobDescriptions/README.md +654 -0
- package/docs/behind-api-client/easyjob/README.md +647 -0
- package/docs/behind-api-client/easyjob/applicants/README.md +494 -0
- package/docs/behind-api-client/easyjob/applications/README.md +754 -0
- package/docs/behind-api-client/easyjob/candidateProfiles/README.md +940 -0
- package/docs/behind-api-client/easyjob/jd-candidate-questions/README.md +372 -0
- package/docs/behind-api-client/payments/payture/README.21.md +901 -0
- package/docs/behind-api-client/payments/payture/README.cards.md +1497 -0
- package/docs/behind-api-client/payments/payture/README.md +1497 -0
- package/docs/behind-api-client/payments/payture/README.rukitchen.md +396 -0
- package/docs/behind-api-client/payments/payture/README.subscriptions.md +1266 -0
- package/docs/behind-api-client/payments/stripe/README.flow.md +254 -0
- package/docs/behind-api-client/rag/storage/README.md +519 -0
- package/example.js +35 -0
- package/index.cjs +14 -0
- package/index.js +15 -0
- package/lib/behind-api-auth-client/BehindApiAuthClient.js +91 -0
- package/lib/behind-api-auth-client/authorisation/AuthorisationApp.js +9 -0
- package/lib/behind-api-auth-client/authorisation/AuthorisationV10.js +9 -0
- package/lib/behind-api-auth-client/authorisation/AuthorisationV10Code.js +30 -0
- package/lib/behind-api-auth-client/example.js +47 -0
- package/lib/behind-api-auth-client/package.json +9 -0
- package/lib/behind-api-client/BehindApiClient.js +137 -0
- package/lib/behind-api-client/chat/ChatApp.js +11 -0
- package/lib/behind-api-client/chat/ChatV10.js +13 -0
- package/lib/behind-api-client/chat/ChatV10Chat.js +87 -0
- package/lib/behind-api-client/chat/ChatV10Chats.js +14 -0
- package/lib/behind-api-client/chat/ChatV10Message.js +57 -0
- package/lib/behind-api-client/chat/ChatV20.js +11 -0
- package/lib/behind-api-client/chat/ChatV20Chat.js +14 -0
- package/lib/behind-api-client/chat/ChatV20Message.js +27 -0
- package/lib/behind-api-client/easyjob/EasyjobApp.js +9 -0
- package/lib/behind-api-client/easyjob/EasyjobV10.js +31 -0
- package/lib/behind-api-client/easyjob/EasyjobV10Answers.js +16 -0
- package/lib/behind-api-client/easyjob/EasyjobV10Applicants.js +29 -0
- package/lib/behind-api-client/easyjob/EasyjobV10Applications.js +39 -0
- package/lib/behind-api-client/easyjob/EasyjobV10CandidateProfileArtifacts.js +31 -0
- package/lib/behind-api-client/easyjob/EasyjobV10CandidateProfiles.js +99 -0
- package/lib/behind-api-client/easyjob/EasyjobV10Companies.js +36 -0
- package/lib/behind-api-client/easyjob/EasyjobV10Cv.js +15 -0
- package/lib/behind-api-client/easyjob/EasyjobV10Departments.js +37 -0
- package/lib/behind-api-client/easyjob/EasyjobV10JobDescriptionArtifacts.js +29 -0
- package/lib/behind-api-client/easyjob/EasyjobV10JobDescriptionCandidateQuestions.js +63 -0
- package/lib/behind-api-client/easyjob/EasyjobV10JobDescriptions.js +93 -0
- package/lib/behind-api-client/easyjob/EasyjobV10Reports.js +35 -0
- package/lib/behind-api-client/example.js +47 -0
- package/lib/behind-api-client/global/GlobalApp.js +9 -0
- package/lib/behind-api-client/global/GlobalV10.js +11 -0
- package/lib/behind-api-client/global/GlobalV10Sockets.js +16 -0
- package/lib/behind-api-client/global/GlobalV10Storage.js +29 -0
- package/lib/behind-api-client/gpt/GptApp.js +15 -0
- package/lib/behind-api-client/gpt/GptV10.js +15 -0
- package/lib/behind-api-client/gpt/GptV10Prompt.js +17 -0
- package/lib/behind-api-client/gpt/GptV10Request.js +16 -0
- package/lib/behind-api-client/gpt/GptV10Storedprompts.js +14 -0
- package/lib/behind-api-client/gpt/GptV10Whisper.js +23 -0
- package/lib/behind-api-client/gpt/GptV20.js +9 -0
- package/lib/behind-api-client/gpt/GptV20Prompt.js +15 -0
- package/lib/behind-api-client/gpt/GptV30.js +13 -0
- package/lib/behind-api-client/gpt/GptV30Chat.js +19 -0
- package/lib/behind-api-client/gpt/GptV30Prompt.js +41 -0
- package/lib/behind-api-client/gpt/GptV30Prompts.js +32 -0
- package/lib/behind-api-client/gpt/GptV40.js +11 -0
- package/lib/behind-api-client/gpt/GptV40Prompt.js +24 -0
- package/lib/behind-api-client/gpt/GptV40Prompts.js +30 -0
- package/lib/behind-api-client/mailer/MailerApp.js +11 -0
- package/lib/behind-api-client/mailer/MailerV10.js +15 -0
- package/lib/behind-api-client/mailer/MailerV10Bulk.js +21 -0
- package/lib/behind-api-client/mailer/MailerV10Message.js +83 -0
- package/lib/behind-api-client/mailer/MailerV10Settings.js +44 -0
- package/lib/behind-api-client/mailer/MailerV10Template.js +23 -0
- package/lib/behind-api-client/mailer/MailerV20.js +9 -0
- package/lib/behind-api-client/mailer/MailerV20Message.js +21 -0
- package/lib/behind-api-client/mastogram/MastogramApp.js +9 -0
- package/lib/behind-api-client/mastogram/MastogramV10.js +13 -0
- package/lib/behind-api-client/mastogram/MastogramV10Bluesky.js +42 -0
- package/lib/behind-api-client/mastogram/MastogramV10Mastodon.js +45 -0
- package/lib/behind-api-client/mastogram/MastogramV10Telegram.js +39 -0
- package/lib/behind-api-client/monitor/MonitorApp.js +9 -0
- package/lib/behind-api-client/monitor/MonitorV10.js +13 -0
- package/lib/behind-api-client/monitor/MonitorV10Finances.js +39 -0
- package/lib/behind-api-client/monitor/MonitorV10Record.js +15 -0
- package/lib/behind-api-client/monitor/MonitorV10Records.js +22 -0
- package/lib/behind-api-client/oauth/OauthApp.js +9 -0
- package/lib/behind-api-client/oauth/OauthV10.js +9 -0
- package/lib/behind-api-client/oauth/OauthV10Authorisation.js +15 -0
- package/lib/behind-api-client/package.json +9 -0
- package/lib/behind-api-client/payments/PaymentsApp.js +13 -0
- package/lib/behind-api-client/payments/PaymentsV10.js +15 -0
- package/lib/behind-api-client/payments/PaymentsV10Gift.js +32 -0
- package/lib/behind-api-client/payments/PaymentsV10Payture.js +30 -0
- package/lib/behind-api-client/payments/PaymentsV10Product.js +15 -0
- package/lib/behind-api-client/payments/PaymentsV10Telegram.js +44 -0
- package/lib/behind-api-client/payments/PaymentsV20.js +9 -0
- package/lib/behind-api-client/payments/PaymentsV20Payture.js +32 -0
- package/lib/behind-api-client/payments/PaymentsV21.js +15 -0
- package/lib/behind-api-client/payments/PaymentsV21Cards.js +14 -0
- package/lib/behind-api-client/payments/PaymentsV21Payture.js +29 -0
- package/lib/behind-api-client/payments/PaymentsV21Stripe.js +28 -0
- package/lib/behind-api-client/payments/PaymentsV21Subscriptions.js +21 -0
- package/lib/behind-api-client/questionnaire/QuestionnaireApp.js +9 -0
- package/lib/behind-api-client/questionnaire/QuestionnaireV10.js +9 -0
- package/lib/behind-api-client/questionnaire/QuestionnaireV10Form.js +22 -0
- package/lib/behind-api-client/raet/RaetApp.js +11 -0
- package/lib/behind-api-client/raet/RaetV10.js +21 -0
- package/lib/behind-api-client/raet/RaetV10Cv.js +87 -0
- package/lib/behind-api-client/raet/RaetV10Individual.js +43 -0
- package/lib/behind-api-client/raet/RaetV10Individuals.js +38 -0
- package/lib/behind-api-client/raet/RaetV10Jd.js +47 -0
- package/lib/behind-api-client/raet/RaetV10Project.js +61 -0
- package/lib/behind-api-client/raet/RaetV10Projects.js +14 -0
- package/lib/behind-api-client/raet/RaetV10Report.js +39 -0
- package/lib/behind-api-client/raet/RaetV20.js +11 -0
- package/lib/behind-api-client/raet/RaetV20Cv.js +31 -0
- package/lib/behind-api-client/raet/RaetV20Individuals.js +25 -0
- package/lib/behind-api-client/rag/RagApp.js +9 -0
- package/lib/behind-api-client/rag/RagV10.js +9 -0
- package/lib/behind-api-client/rag/RagV10Storage.js +27 -0
- package/lib/behind-api-client/ruKitchen/RuKitchenApp.js +9 -0
- package/lib/behind-api-client/ruKitchen/RuKitchenV10.js +11 -0
- package/lib/behind-api-client/ruKitchen/RuKitchenV10Importer.js +29 -0
- package/lib/behind-api-client/ruKitchen/RuKitchenV10SeoArticle.js +14 -0
- package/lib/behind-api-client/sales/SalesApp.js +11 -0
- package/lib/behind-api-client/sales/SalesV10.js +23 -0
- package/lib/behind-api-client/sales/SalesV10Catalogue.js +58 -0
- package/lib/behind-api-client/sales/SalesV10Categories.js +15 -0
- package/lib/behind-api-client/sales/SalesV10Companies.js +55 -0
- package/lib/behind-api-client/sales/SalesV10Company.js +120 -0
- package/lib/behind-api-client/sales/SalesV10Group.js +70 -0
- package/lib/behind-api-client/sales/SalesV10Groups.js +21 -0
- package/lib/behind-api-client/sales/SalesV10Logs.js +14 -0
- package/lib/behind-api-client/sales/SalesV10Notes.js +38 -0
- package/lib/behind-api-client/sales/SalesV20.js +11 -0
- package/lib/behind-api-client/sales/SalesV20Companies.js +26 -0
- package/lib/behind-api-client/sales/SalesV20Notes.js +17 -0
- package/lib/behind-api-client/sip/SipApp.js +9 -0
- package/lib/behind-api-client/sip/SipV10.js +15 -0
- package/lib/behind-api-client/sip/SipV10Call.js +83 -0
- package/lib/behind-api-client/sip/SipV10Phone.js +44 -0
- package/lib/behind-api-client/sip/SipV10Transcript.js +21 -0
- package/lib/behind-api-client/sip/SipV10Transcripts.js +21 -0
- package/lib/behind-api-client/storage/StorageApp.js +9 -0
- package/lib/behind-api-client/storage/StorageV10.js +11 -0
- package/lib/behind-api-client/storage/StorageV10File.js +35 -0
- package/lib/behind-api-client/storage/StorageV10Upload.js +21 -0
- package/lib/behind-api-client/tests/TestsApp.js +9 -0
- package/lib/behind-api-client/tests/TestsV10.js +15 -0
- package/lib/behind-api-client/tests/TestsV10Cases.js +29 -0
- package/lib/behind-api-client/tests/TestsV10CasesExtended.js +14 -0
- package/lib/behind-api-client/tests/TestsV10Core.js +14 -0
- package/lib/behind-api-client/tests/TestsV10Mail.js +14 -0
- package/lib/behind-api-client/tools/ToolsApp.js +9 -0
- package/lib/behind-api-client/tools/ToolsV10.js +9 -0
- package/lib/behind-api-client/tools/ToolsV10Pdf.js +30 -0
- package/package.json +25 -0
|
@@ -0,0 +1,1497 @@
|
|
|
1
|
+
# Payments API - Payture Extension
|
|
2
|
+
|
|
3
|
+
This document extends the main EasyJob JavaScript API Client Documentation with detailed information about the Payture payment gateway integration methods.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Payture Payments API provides methods for creating and managing payment bills through the Payture payment gateway. This includes creating bills for authorized and unauthorized users, checking bill status, and handling payment workflows for subscriptions and gift purchases.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Payments API - Payture
|
|
12
|
+
|
|
13
|
+
### behindAPI.V20.payments.payture.billCreate(subscriptionId)
|
|
14
|
+
Creates a payment bill for an authorized user subscribing to a product.
|
|
15
|
+
|
|
16
|
+
**Parameters:**
|
|
17
|
+
- `subscriptionId` (string, required) - The subscription product UUID to create a bill for
|
|
18
|
+
|
|
19
|
+
**Usage:**
|
|
20
|
+
```javascript
|
|
21
|
+
const result = await behindAPI.V20.payments.payture.billCreate(
|
|
22
|
+
"550e8400-e29b-41d4-a716-446655440001"
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// Response:
|
|
26
|
+
{
|
|
27
|
+
success: true,
|
|
28
|
+
data: {
|
|
29
|
+
bill_id: "cc0e8400-e29b-41d4-a716-446655440009",
|
|
30
|
+
bill: {
|
|
31
|
+
payUrl: "https://payture.com/apim/Pay?SessionId=abc123xyz"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Error Response:
|
|
37
|
+
{
|
|
38
|
+
success: false,
|
|
39
|
+
message: "Can't finish request"
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### behindAPI.V20.payments.payture.billCreateUnauthorised(subscriptionId, userId, redirectUrl, extended)
|
|
44
|
+
Creates a payment bill for an unauthorized user or allows specifying custom parameters. This method is useful for guest checkouts or when you need to pass additional data with the payment.
|
|
45
|
+
|
|
46
|
+
**Parameters:**
|
|
47
|
+
- `subscriptionId` (string, required) - The subscription product UUID to create a bill for
|
|
48
|
+
- `userId` (string, required) - The user UUID who is making the purchase
|
|
49
|
+
- `redirectUrl` (string, optional) - Custom URL to redirect after payment completion. Must be a valid HTTP/HTTPS URL
|
|
50
|
+
- `extended` (object, optional) - Additional data to store with the bill (e.g., gift recipient info, page access details)
|
|
51
|
+
|
|
52
|
+
**Usage:**
|
|
53
|
+
```javascript
|
|
54
|
+
const result = await behindAPI.V20.payments.payture.billCreateUnauthorised(
|
|
55
|
+
"550e8400-e29b-41d4-a716-446655440001", // subscription_id
|
|
56
|
+
"aa0e8400-e29b-41d4-a716-446655440005", // user_id
|
|
57
|
+
"https://example.com/payment-complete", // redirect_url
|
|
58
|
+
{
|
|
59
|
+
pageId: "ck4jrtawdA3Ed4",
|
|
60
|
+
giftRecipient: "john@example.com",
|
|
61
|
+
customNote: "Holiday special"
|
|
62
|
+
} // extended
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Response:
|
|
66
|
+
{
|
|
67
|
+
success: true,
|
|
68
|
+
data: {
|
|
69
|
+
bill_id: "cc0e8400-e29b-41d4-a716-446655440009",
|
|
70
|
+
bill: {
|
|
71
|
+
payUrl: "https://payture.com/apim/Pay?SessionId=abc123xyz"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Error Response:
|
|
77
|
+
{
|
|
78
|
+
success: false,
|
|
79
|
+
message: "Can't finish request"
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### behindAPI.V20.payments.payture.billStatus(billId)
|
|
84
|
+
Retrieves the current status of a payment bill and processes completed payments. This method checks the bill status with Payture and updates the local database accordingly.
|
|
85
|
+
|
|
86
|
+
**Parameters:**
|
|
87
|
+
- `billId` (string, required) - The bill UUID to check status for
|
|
88
|
+
|
|
89
|
+
**Usage:**
|
|
90
|
+
```javascript
|
|
91
|
+
const result = await behindAPI.V20.payments.payture.billStatus(
|
|
92
|
+
"cc0e8400-e29b-41d4-a716-446655440009"
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// Response (pending payment):
|
|
96
|
+
{
|
|
97
|
+
success: true,
|
|
98
|
+
data: {
|
|
99
|
+
bill_info: {
|
|
100
|
+
bill_status: "PENDING",
|
|
101
|
+
amount: 99900
|
|
102
|
+
},
|
|
103
|
+
status: "PENDING"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Response (successful payment):
|
|
108
|
+
{
|
|
109
|
+
success: true,
|
|
110
|
+
data: {
|
|
111
|
+
bill_info: {
|
|
112
|
+
bill_status: "PAID",
|
|
113
|
+
amount: 99900
|
|
114
|
+
},
|
|
115
|
+
status: "PAID"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Response (gift purchase - includes gift code):
|
|
120
|
+
{
|
|
121
|
+
success: true,
|
|
122
|
+
data: {
|
|
123
|
+
bill_info: {
|
|
124
|
+
bill_status: "PAID",
|
|
125
|
+
amount: 99900,
|
|
126
|
+
gift: {
|
|
127
|
+
code: "GIFT-ABC123"
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
status: "PAID"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Error Response (empty bill_id):
|
|
135
|
+
{
|
|
136
|
+
success: false,
|
|
137
|
+
message: "bill_id can not be empty"
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Error Response (amount mismatch):
|
|
141
|
+
{
|
|
142
|
+
success: false,
|
|
143
|
+
data: {
|
|
144
|
+
bill_info: {
|
|
145
|
+
bill_status: "PAID",
|
|
146
|
+
amount: 99900
|
|
147
|
+
},
|
|
148
|
+
status: "AMOUNT_MISMATCH"
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Data Structure Reference
|
|
156
|
+
|
|
157
|
+
### Bill Creation Response
|
|
158
|
+
The `billCreate()` and `billCreateUnauthorised()` methods return:
|
|
159
|
+
|
|
160
|
+
**Success Response:**
|
|
161
|
+
- `success` (boolean) - Always `true` for successful requests
|
|
162
|
+
- `data` (object) - Container for response data
|
|
163
|
+
- `bill_id` (UUID) - The newly created bill's unique identifier
|
|
164
|
+
- `bill` (object) - Bill details
|
|
165
|
+
- `payUrl` (string) - URL to redirect user for payment
|
|
166
|
+
|
|
167
|
+
**Error Response:**
|
|
168
|
+
- `success` (boolean) - Always `false` for failed requests
|
|
169
|
+
- `message` (string) - Error description
|
|
170
|
+
|
|
171
|
+
### Bill Status Response
|
|
172
|
+
The `billStatus()` method returns:
|
|
173
|
+
|
|
174
|
+
**Success Response:**
|
|
175
|
+
- `success` (boolean) - Always `true` for successful requests
|
|
176
|
+
- `data` (object) - Container for response data
|
|
177
|
+
- `bill_info` (object) - Payment information from Payture
|
|
178
|
+
- `bill_status` (string) - Current status: "PENDING", "PAID"
|
|
179
|
+
- `amount` (number) - Payment amount in kopecks (cents)
|
|
180
|
+
- `gift` (object, optional) - Only present for gift purchases after payment
|
|
181
|
+
- `code` (string) - Gift activation code
|
|
182
|
+
- `status` (string) - Current bill status: "PENDING", "PAID", "DONE", "AMOUNT_MISMATCH"
|
|
183
|
+
|
|
184
|
+
**Special Status Values:**
|
|
185
|
+
- `PENDING` - Payment not yet completed
|
|
186
|
+
- `PAID` - Payment completed on Payture side
|
|
187
|
+
- `DONE` - Payment processed and subscription/gift activated
|
|
188
|
+
- `AMOUNT_MISMATCH` - Payment amount doesn't match expected amount
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Complete Usage Examples
|
|
193
|
+
|
|
194
|
+
### Basic Payment Flow for Authorized Users
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
// Example: Create payment and redirect user
|
|
198
|
+
async function createPaymentForUser(subscriptionId) {
|
|
199
|
+
try {
|
|
200
|
+
console.log("Creating payment bill...");
|
|
201
|
+
|
|
202
|
+
const billResult = await behindAPI.V20.payments.payture.billCreate(
|
|
203
|
+
subscriptionId
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
if (billResult.success) {
|
|
207
|
+
console.log("✓ Bill created successfully!");
|
|
208
|
+
console.log(`Bill ID: ${billResult.data.bill_id}`);
|
|
209
|
+
console.log(`Payment URL: ${billResult.data.bill.payUrl}`);
|
|
210
|
+
|
|
211
|
+
// Redirect user to payment page
|
|
212
|
+
window.location.href = billResult.data.bill.payUrl;
|
|
213
|
+
|
|
214
|
+
return billResult.data;
|
|
215
|
+
} else {
|
|
216
|
+
console.log(`✗ Failed to create bill: ${billResult.message}`);
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error("Error creating payment:", error);
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Usage
|
|
226
|
+
const paymentData = await createPaymentForUser(
|
|
227
|
+
"550e8400-e29b-41d4-a716-446655440001"
|
|
228
|
+
);
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Guest Checkout with Custom Redirect
|
|
232
|
+
|
|
233
|
+
```javascript
|
|
234
|
+
// Example: Create payment for guest user with custom redirect
|
|
235
|
+
async function createGuestPayment(subscriptionId, userId, returnUrl) {
|
|
236
|
+
try {
|
|
237
|
+
console.log("Creating guest payment bill...");
|
|
238
|
+
|
|
239
|
+
const billResult = await behindAPI.V20.payments.payture.billCreateUnauthorised(
|
|
240
|
+
subscriptionId,
|
|
241
|
+
userId,
|
|
242
|
+
returnUrl,
|
|
243
|
+
{
|
|
244
|
+
source: "landing_page",
|
|
245
|
+
campaign: "spring_sale_2024"
|
|
246
|
+
}
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
if (billResult.success) {
|
|
250
|
+
console.log("✓ Guest bill created successfully!");
|
|
251
|
+
console.log(`Bill ID: ${billResult.data.bill_id}`);
|
|
252
|
+
console.log(`Payment URL: ${billResult.data.bill.payUrl}`);
|
|
253
|
+
|
|
254
|
+
// Store bill_id in session for later status checking
|
|
255
|
+
sessionStorage.setItem('pending_bill_id', billResult.data.bill_id);
|
|
256
|
+
|
|
257
|
+
// Redirect to payment
|
|
258
|
+
window.location.href = billResult.data.bill.payUrl;
|
|
259
|
+
|
|
260
|
+
return billResult.data;
|
|
261
|
+
} else {
|
|
262
|
+
console.log(`✗ Failed to create guest bill: ${billResult.message}`);
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
} catch (error) {
|
|
266
|
+
console.error("Error creating guest payment:", error);
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Usage
|
|
272
|
+
const guestPayment = await createGuestPayment(
|
|
273
|
+
"550e8400-e29b-41d4-a716-446655440001",
|
|
274
|
+
"aa0e8400-e29b-41d4-a716-446655440005",
|
|
275
|
+
"https://mysite.com/thank-you"
|
|
276
|
+
);
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Page Access Purchase with Extended Data
|
|
280
|
+
|
|
281
|
+
```javascript
|
|
282
|
+
// Example: Purchase subscription with page access
|
|
283
|
+
async function purchasePageAccess(subscriptionId, userId, pageId) {
|
|
284
|
+
try {
|
|
285
|
+
console.log("Creating page access purchase...");
|
|
286
|
+
|
|
287
|
+
// Create bill with page access information
|
|
288
|
+
const billResult = await behindAPI.V20.payments.payture.billCreateUnauthorised(
|
|
289
|
+
subscriptionId,
|
|
290
|
+
userId,
|
|
291
|
+
"https://mysite.com/access-granted",
|
|
292
|
+
{
|
|
293
|
+
pageId: "ck4jrtawdA3Ed4"
|
|
294
|
+
}
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
if (!billResult.success) {
|
|
298
|
+
console.log(`✗ Failed to create bill: ${billResult.message}`);
|
|
299
|
+
return { success: false, error: billResult.message };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
console.log("✓ Bill created with page access!");
|
|
303
|
+
console.log(`Bill ID: ${billResult.data.bill_id}`);
|
|
304
|
+
console.log(`Page ID: ck4jrtawdA3Ed4`);
|
|
305
|
+
|
|
306
|
+
// Store for later verification
|
|
307
|
+
sessionStorage.setItem('pending_bill_id', billResult.data.bill_id);
|
|
308
|
+
sessionStorage.setItem('pending_page_id', 'ck4jrtawdA3Ed4');
|
|
309
|
+
|
|
310
|
+
// Redirect to payment
|
|
311
|
+
window.location.href = billResult.data.bill.payUrl;
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
success: true,
|
|
315
|
+
billId: billResult.data.bill_id,
|
|
316
|
+
pageId: 'ck4jrtawdA3Ed4',
|
|
317
|
+
paymentUrl: billResult.data.bill.payUrl
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
} catch (error) {
|
|
321
|
+
console.error("Error purchasing page access:", error);
|
|
322
|
+
return { success: false, error: error.message };
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Usage
|
|
327
|
+
const pageAccessPurchase = await purchasePageAccess(
|
|
328
|
+
"550e8400-e29b-41d4-a716-446655440001",
|
|
329
|
+
"aa0e8400-e29b-41d4-a716-446655440005",
|
|
330
|
+
"ck4jrtawdA3Ed4"
|
|
331
|
+
);
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Gift Purchase Workflow
|
|
335
|
+
|
|
336
|
+
```javascript
|
|
337
|
+
// Example: Purchase a gift subscription
|
|
338
|
+
async function purchaseGift(giftSubscriptionId, buyerId, recipientEmail) {
|
|
339
|
+
try {
|
|
340
|
+
console.log("Creating gift purchase...");
|
|
341
|
+
|
|
342
|
+
// Create bill with gift recipient information
|
|
343
|
+
const billResult = await behindAPI.V20.payments.payture.billCreateUnauthorised(
|
|
344
|
+
giftSubscriptionId,
|
|
345
|
+
buyerId,
|
|
346
|
+
"https://mysite.com/gift-complete",
|
|
347
|
+
{
|
|
348
|
+
giftRecipient: recipientEmail,
|
|
349
|
+
giftType: "subscription",
|
|
350
|
+
giftMessage: "Enjoy your subscription!"
|
|
351
|
+
}
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
if (!billResult.success) {
|
|
355
|
+
console.log(`✗ Failed to create gift bill: ${billResult.message}`);
|
|
356
|
+
return { success: false, error: billResult.message };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
console.log("✓ Gift bill created!");
|
|
360
|
+
console.log(`Bill ID: ${billResult.data.bill_id}`);
|
|
361
|
+
|
|
362
|
+
// Redirect to payment
|
|
363
|
+
window.location.href = billResult.data.bill.payUrl;
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
success: true,
|
|
367
|
+
billId: billResult.data.bill_id,
|
|
368
|
+
paymentUrl: billResult.data.bill.payUrl
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
} catch (error) {
|
|
372
|
+
console.error("Error purchasing gift:", error);
|
|
373
|
+
return { success: false, error: error.message };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Usage
|
|
378
|
+
const giftPurchase = await purchaseGift(
|
|
379
|
+
"550e8400-e29b-41d4-a716-446655440001",
|
|
380
|
+
"aa0e8400-e29b-41d4-a716-446655440005",
|
|
381
|
+
"recipient@example.com"
|
|
382
|
+
);
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Payment Status Monitoring
|
|
386
|
+
|
|
387
|
+
```javascript
|
|
388
|
+
// Example: Check payment status with polling
|
|
389
|
+
async function monitorPaymentStatus(billId, maxAttempts = 10, intervalMs = 3000) {
|
|
390
|
+
let attempts = 0;
|
|
391
|
+
|
|
392
|
+
const checkStatus = async () => {
|
|
393
|
+
try {
|
|
394
|
+
console.log(`Checking payment status (attempt ${attempts + 1}/${maxAttempts})...`);
|
|
395
|
+
|
|
396
|
+
const statusResult = await behindAPI.V20.payments.payture.billStatus(billId);
|
|
397
|
+
|
|
398
|
+
if (!statusResult.success) {
|
|
399
|
+
console.log(`✗ Status check failed: ${statusResult.message}`);
|
|
400
|
+
return { success: false, error: statusResult.message };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const status = statusResult.data.status;
|
|
404
|
+
console.log(`Current status: ${status}`);
|
|
405
|
+
|
|
406
|
+
if (status === "PAID" || status === "DONE") {
|
|
407
|
+
console.log("✓ Payment completed successfully!");
|
|
408
|
+
|
|
409
|
+
// Check for gift code
|
|
410
|
+
if (statusResult.data.bill_info.gift) {
|
|
411
|
+
console.log(`Gift code: ${statusResult.data.bill_info.gift.code}`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return {
|
|
415
|
+
success: true,
|
|
416
|
+
status: status,
|
|
417
|
+
billInfo: statusResult.data.bill_info
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (status === "AMOUNT_MISMATCH") {
|
|
422
|
+
console.log("✗ Payment amount mismatch detected!");
|
|
423
|
+
return {
|
|
424
|
+
success: false,
|
|
425
|
+
error: "Amount mismatch",
|
|
426
|
+
status: status
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Still pending, continue polling
|
|
431
|
+
attempts++;
|
|
432
|
+
if (attempts < maxAttempts) {
|
|
433
|
+
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
|
434
|
+
return checkStatus();
|
|
435
|
+
} else {
|
|
436
|
+
console.log("⚠ Maximum attempts reached, payment still pending");
|
|
437
|
+
return {
|
|
438
|
+
success: false,
|
|
439
|
+
error: "Timeout",
|
|
440
|
+
status: "PENDING"
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
} catch (error) {
|
|
445
|
+
console.error("Error checking payment status:", error);
|
|
446
|
+
return { success: false, error: error.message };
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
return checkStatus();
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Usage
|
|
454
|
+
const billId = "cc0e8400-e29b-41d4-a716-446655440009";
|
|
455
|
+
const paymentResult = await monitorPaymentStatus(billId);
|
|
456
|
+
|
|
457
|
+
if (paymentResult.success) {
|
|
458
|
+
console.log("Payment confirmed!");
|
|
459
|
+
if (paymentResult.billInfo.gift) {
|
|
460
|
+
console.log(`Gift code: ${paymentResult.billInfo.gift.code}`);
|
|
461
|
+
}
|
|
462
|
+
} else {
|
|
463
|
+
console.log(`Payment not completed: ${paymentResult.error}`);
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Payment Return Handler with Page Access
|
|
468
|
+
|
|
469
|
+
```javascript
|
|
470
|
+
// Example: Handle user return from payment gateway
|
|
471
|
+
async function handlePaymentReturn() {
|
|
472
|
+
try {
|
|
473
|
+
// Get bill_id from URL or session
|
|
474
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
475
|
+
const billId = urlParams.get('bill_id') ||
|
|
476
|
+
sessionStorage.getItem('pending_bill_id');
|
|
477
|
+
const pageId = sessionStorage.getItem('pending_page_id');
|
|
478
|
+
|
|
479
|
+
if (!billId) {
|
|
480
|
+
console.log("No bill ID found");
|
|
481
|
+
return { success: false, error: "No bill ID" };
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
console.log("Checking payment status for return...");
|
|
485
|
+
|
|
486
|
+
// Check the bill status
|
|
487
|
+
const statusResult = await behindAPI.V20.payments.payture.billStatus(billId);
|
|
488
|
+
|
|
489
|
+
if (!statusResult.success) {
|
|
490
|
+
console.log(`✗ Failed to check status: ${statusResult.message}`);
|
|
491
|
+
return {
|
|
492
|
+
success: false,
|
|
493
|
+
error: statusResult.message
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const status = statusResult.data.status;
|
|
498
|
+
const billInfo = statusResult.data.bill_info;
|
|
499
|
+
|
|
500
|
+
// Clear stored data
|
|
501
|
+
sessionStorage.removeItem('pending_bill_id');
|
|
502
|
+
sessionStorage.removeItem('pending_page_id');
|
|
503
|
+
|
|
504
|
+
if (status === "PAID" || status === "DONE") {
|
|
505
|
+
console.log("✓ Payment successful!");
|
|
506
|
+
|
|
507
|
+
// Handle gift code if present
|
|
508
|
+
if (billInfo.gift) {
|
|
509
|
+
return {
|
|
510
|
+
success: true,
|
|
511
|
+
status: status,
|
|
512
|
+
paymentType: "gift",
|
|
513
|
+
giftCode: billInfo.gift.code,
|
|
514
|
+
message: "Payment successful! Your gift code is ready."
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Handle page access if present
|
|
519
|
+
if (pageId) {
|
|
520
|
+
return {
|
|
521
|
+
success: true,
|
|
522
|
+
status: status,
|
|
523
|
+
paymentType: "page_access",
|
|
524
|
+
pageId: pageId,
|
|
525
|
+
message: "Payment successful! Your page access has been granted."
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return {
|
|
530
|
+
success: true,
|
|
531
|
+
status: status,
|
|
532
|
+
paymentType: "subscription",
|
|
533
|
+
message: "Payment successful! Your subscription is now active."
|
|
534
|
+
};
|
|
535
|
+
} else if (status === "PENDING") {
|
|
536
|
+
console.log("⚠ Payment still pending");
|
|
537
|
+
return {
|
|
538
|
+
success: false,
|
|
539
|
+
status: "PENDING",
|
|
540
|
+
message: "Payment is still being processed. Please wait..."
|
|
541
|
+
};
|
|
542
|
+
} else if (status === "AMOUNT_MISMATCH") {
|
|
543
|
+
console.log("✗ Payment amount mismatch");
|
|
544
|
+
return {
|
|
545
|
+
success: false,
|
|
546
|
+
status: status,
|
|
547
|
+
message: "Payment amount doesn't match. Please contact support."
|
|
548
|
+
};
|
|
549
|
+
} else {
|
|
550
|
+
console.log(`⚠ Unknown status: ${status}`);
|
|
551
|
+
return {
|
|
552
|
+
success: false,
|
|
553
|
+
status: status,
|
|
554
|
+
message: "Unknown payment status. Please contact support."
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
} catch (error) {
|
|
559
|
+
console.error("Error handling payment return:", error);
|
|
560
|
+
return {
|
|
561
|
+
success: false,
|
|
562
|
+
error: error.message
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Usage on return page
|
|
568
|
+
window.addEventListener('load', async () => {
|
|
569
|
+
const result = await handlePaymentReturn();
|
|
570
|
+
|
|
571
|
+
if (result.success) {
|
|
572
|
+
document.getElementById('status').textContent = result.message;
|
|
573
|
+
|
|
574
|
+
if (result.paymentType === "gift" && result.giftCode) {
|
|
575
|
+
document.getElementById('gift-code').textContent = result.giftCode;
|
|
576
|
+
document.getElementById('gift-section').style.display = 'block';
|
|
577
|
+
} else if (result.paymentType === "page_access" && result.pageId) {
|
|
578
|
+
document.getElementById('page-info').textContent =
|
|
579
|
+
`You now have access to page: ${result.pageId}`;
|
|
580
|
+
document.getElementById('page-section').style.display = 'block';
|
|
581
|
+
}
|
|
582
|
+
} else {
|
|
583
|
+
document.getElementById('status').textContent =
|
|
584
|
+
result.message || "Payment not completed";
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
### Complete Purchase Flow with Error Handling
|
|
590
|
+
|
|
591
|
+
```javascript
|
|
592
|
+
// Example: Complete payment flow from creation to confirmation
|
|
593
|
+
async function completePurchaseFlow(subscriptionId, userId, options = {}) {
|
|
594
|
+
const flow = {
|
|
595
|
+
billId: null,
|
|
596
|
+
paymentUrl: null,
|
|
597
|
+
status: null,
|
|
598
|
+
error: null
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
try {
|
|
602
|
+
console.log("=== STARTING PURCHASE FLOW ===");
|
|
603
|
+
|
|
604
|
+
// Step 1: Prepare extended data
|
|
605
|
+
const extendedData = {};
|
|
606
|
+
if (options.pageId) {
|
|
607
|
+
extendedData.pageId = options.pageId;
|
|
608
|
+
}
|
|
609
|
+
if (options.giftRecipient) {
|
|
610
|
+
extendedData.giftRecipient = options.giftRecipient;
|
|
611
|
+
}
|
|
612
|
+
if (options.customData) {
|
|
613
|
+
Object.assign(extendedData, options.customData);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Step 2: Create bill
|
|
617
|
+
console.log("\n1. Creating payment bill...");
|
|
618
|
+
let billResult;
|
|
619
|
+
|
|
620
|
+
if (options.isGuest) {
|
|
621
|
+
billResult = await behindAPI.V20.payments.payture.billCreateUnauthorised(
|
|
622
|
+
subscriptionId,
|
|
623
|
+
userId,
|
|
624
|
+
options.redirectUrl || "https://mysite.com/payment-return",
|
|
625
|
+
extendedData
|
|
626
|
+
);
|
|
627
|
+
} else {
|
|
628
|
+
billResult = await behindAPI.V20.payments.payture.billCreate(
|
|
629
|
+
subscriptionId
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (!billResult.success) {
|
|
634
|
+
console.log(`✗ Bill creation failed: ${billResult.message}`);
|
|
635
|
+
flow.error = billResult.message;
|
|
636
|
+
return flow;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
flow.billId = billResult.data.bill_id;
|
|
640
|
+
flow.paymentUrl = billResult.data.bill.payUrl;
|
|
641
|
+
|
|
642
|
+
console.log(`✓ Bill created: ${flow.billId}`);
|
|
643
|
+
console.log(`Payment URL: ${flow.paymentUrl}`);
|
|
644
|
+
|
|
645
|
+
// Step 3: Store bill_id for later verification
|
|
646
|
+
sessionStorage.setItem('current_bill_id', flow.billId);
|
|
647
|
+
sessionStorage.setItem('bill_created_at', Date.now());
|
|
648
|
+
|
|
649
|
+
if (options.pageId) {
|
|
650
|
+
sessionStorage.setItem('pending_page_id', options.pageId);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Step 4: Open payment in new window or redirect
|
|
654
|
+
console.log("\n2. Opening payment gateway...");
|
|
655
|
+
const paymentWindow = window.open(flow.paymentUrl, '_blank');
|
|
656
|
+
|
|
657
|
+
if (!paymentWindow) {
|
|
658
|
+
console.log("⚠ Popup blocked, redirecting in current window...");
|
|
659
|
+
window.location.href = flow.paymentUrl;
|
|
660
|
+
return flow;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Step 5: Monitor payment status
|
|
664
|
+
console.log("\n3. Monitoring payment status...");
|
|
665
|
+
const monitoringResult = await monitorPaymentWindow(
|
|
666
|
+
flow.billId,
|
|
667
|
+
paymentWindow
|
|
668
|
+
);
|
|
669
|
+
|
|
670
|
+
flow.status = monitoringResult.status;
|
|
671
|
+
|
|
672
|
+
if (monitoringResult.success) {
|
|
673
|
+
console.log("✓ Payment completed successfully!");
|
|
674
|
+
sessionStorage.removeItem('current_bill_id');
|
|
675
|
+
sessionStorage.removeItem('bill_created_at');
|
|
676
|
+
sessionStorage.removeItem('pending_page_id');
|
|
677
|
+
} else {
|
|
678
|
+
console.log(`✗ Payment not completed: ${monitoringResult.error}`);
|
|
679
|
+
flow.error = monitoringResult.error;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
return flow;
|
|
683
|
+
|
|
684
|
+
} catch (error) {
|
|
685
|
+
console.error("Error in purchase flow:", error);
|
|
686
|
+
flow.error = error.message;
|
|
687
|
+
return flow;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// Helper function to monitor payment window
|
|
692
|
+
async function monitorPaymentWindow(billId, paymentWindow) {
|
|
693
|
+
return new Promise((resolve) => {
|
|
694
|
+
let attempts = 0;
|
|
695
|
+
const maxAttempts = 60;
|
|
696
|
+
|
|
697
|
+
const interval = setInterval(async () => {
|
|
698
|
+
attempts++;
|
|
699
|
+
|
|
700
|
+
if (paymentWindow.closed) {
|
|
701
|
+
clearInterval(interval);
|
|
702
|
+
const statusResult = await behindAPI.V20.payments.payture.billStatus(billId);
|
|
703
|
+
|
|
704
|
+
if (statusResult.success &&
|
|
705
|
+
(statusResult.data.status === "PAID" || statusResult.data.status === "DONE")) {
|
|
706
|
+
resolve({
|
|
707
|
+
success: true,
|
|
708
|
+
status: statusResult.data.status,
|
|
709
|
+
billInfo: statusResult.data.bill_info
|
|
710
|
+
});
|
|
711
|
+
} else {
|
|
712
|
+
resolve({
|
|
713
|
+
success: false,
|
|
714
|
+
error: "Payment window closed without completion",
|
|
715
|
+
status: statusResult.data?.status || "UNKNOWN"
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (attempts % 2 === 0) {
|
|
722
|
+
const statusResult = await behindAPI.V20.payments.payture.billStatus(billId);
|
|
723
|
+
|
|
724
|
+
if (statusResult.success &&
|
|
725
|
+
(statusResult.data.status === "PAID" || statusResult.data.status === "DONE")) {
|
|
726
|
+
clearInterval(interval);
|
|
727
|
+
paymentWindow.close();
|
|
728
|
+
resolve({
|
|
729
|
+
success: true,
|
|
730
|
+
status: statusResult.data.status,
|
|
731
|
+
billInfo: statusResult.data.bill_info
|
|
732
|
+
});
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
if (attempts >= maxAttempts) {
|
|
738
|
+
clearInterval(interval);
|
|
739
|
+
resolve({
|
|
740
|
+
success: false,
|
|
741
|
+
error: "Payment monitoring timeout",
|
|
742
|
+
status: "PENDING"
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
}, 5000);
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Usage examples
|
|
750
|
+
|
|
751
|
+
// Standard purchase
|
|
752
|
+
const standardPurchase = await completePurchaseFlow(
|
|
753
|
+
"550e8400-e29b-41d4-a716-446655440001",
|
|
754
|
+
"aa0e8400-e29b-41d4-a716-446655440005",
|
|
755
|
+
{
|
|
756
|
+
isGuest: true
|
|
757
|
+
}
|
|
758
|
+
);
|
|
759
|
+
|
|
760
|
+
// Purchase with page access
|
|
761
|
+
const pageAccessPurchase = await completePurchaseFlow(
|
|
762
|
+
"550e8400-e29b-41d4-a716-446655440001",
|
|
763
|
+
"aa0e8400-e29b-41d4-a716-446655440005",
|
|
764
|
+
{
|
|
765
|
+
isGuest: true,
|
|
766
|
+
pageId: "ck4jrtawdA3Ed4",
|
|
767
|
+
redirectUrl: "https://mysite.com/access-granted"
|
|
768
|
+
}
|
|
769
|
+
);
|
|
770
|
+
|
|
771
|
+
// Gift purchase
|
|
772
|
+
const giftPurchase = await completePurchaseFlow(
|
|
773
|
+
"550e8400-e29b-41d4-a716-446655440001",
|
|
774
|
+
"aa0e8400-e29b-41d4-a716-446655440005",
|
|
775
|
+
{
|
|
776
|
+
isGuest: false,
|
|
777
|
+
giftRecipient: "recipient@example.com",
|
|
778
|
+
redirectUrl: "https://mysite.com/gift-sent"
|
|
779
|
+
}
|
|
780
|
+
);
|
|
781
|
+
|
|
782
|
+
console.log("=== PURCHASE RESULT ===");
|
|
783
|
+
console.log(pageAccessPurchase);
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
---
|
|
787
|
+
|
|
788
|
+
## Best Practices
|
|
789
|
+
|
|
790
|
+
1. **Bill ID Storage**: Always store the bill_id after creation for status verification and troubleshooting.
|
|
791
|
+
|
|
792
|
+
2. **Status Polling**: Implement reasonable polling intervals (3-5 seconds) when monitoring payment status to avoid overwhelming the API.
|
|
793
|
+
|
|
794
|
+
3. **Error Handling**: Always check the `success` property and handle various status values (PENDING, PAID, DONE, AMOUNT_MISMATCH).
|
|
795
|
+
|
|
796
|
+
4. **Redirect URLs**: When using custom redirect URLs with `billCreateUnauthorised`, ensure they are properly formatted and accessible.
|
|
797
|
+
|
|
798
|
+
5. **Extended Data**: Use the `extended` parameter to store additional context about the purchase:
|
|
799
|
+
- `pageId` (e.g., "ck4jrtawdA3Ed4") for granting page access after payment
|
|
800
|
+
- Gift recipient information for gift purchases
|
|
801
|
+
- Source tracking and campaign data
|
|
802
|
+
- Any custom metadata needed for your business logic
|
|
803
|
+
|
|
804
|
+
6. **Amount Verification**: Always verify the payment amount matches your expected amount to prevent fraudulent transactions.
|
|
805
|
+
|
|
806
|
+
7. **Gift Codes**: For gift purchases, ensure proper handling of the gift code returned after successful payment.
|
|
807
|
+
|
|
808
|
+
8. **Page Access**: When including `pageId` in extended data, the system automatically grants user access to the specified page upon successful payment (for direct subscription types).
|
|
809
|
+
|
|
810
|
+
9. **Session Management**: Clear stored bill IDs and session data after successful payment completion.
|
|
811
|
+
|
|
812
|
+
10. **User Experience**: Provide clear feedback during payment processing and handle popup blockers gracefully.
|
|
813
|
+
|
|
814
|
+
11. **Security**: Never store sensitive payment information locally. Always use the Payture gateway for payment processing.
|
|
815
|
+
|
|
816
|
+
---
|
|
817
|
+
|
|
818
|
+
## Payment Status Flow
|
|
819
|
+
|
|
820
|
+
```
|
|
821
|
+
PENDING → PAID → DONE
|
|
822
|
+
↓
|
|
823
|
+
AMOUNT_MISMATCH
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
- **PENDING**: Initial state after bill creation, payment not completed
|
|
827
|
+
- **PAID**: Payment completed on Payture side, amount verified
|
|
828
|
+
- **DONE**: Payment processed and subscription/gift activated
|
|
829
|
+
- **AMOUNT_MISMATCH**: Payment completed but amount doesn't match expected value
|
|
830
|
+
|
|
831
|
+
---
|
|
832
|
+
|
|
833
|
+
## Extended Data Use Cases
|
|
834
|
+
|
|
835
|
+
The `extended` parameter in `billCreateUnauthorised` supports various use cases:
|
|
836
|
+
|
|
837
|
+
### Page Access Purchase
|
|
838
|
+
|
|
839
|
+
```javascript
|
|
840
|
+
// Grant access to specific page after payment
|
|
841
|
+
const result = await behindAPI.V20.payments.payture.billCreateUnauthorised(
|
|
842
|
+
subscriptionId,
|
|
843
|
+
userId,
|
|
844
|
+
redirectUrl,
|
|
845
|
+
{
|
|
846
|
+
pageId: "ck4jrtawdA3Ed4"
|
|
847
|
+
}
|
|
848
|
+
);
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
### Gift Purchase with Recipient Info
|
|
852
|
+
|
|
853
|
+
```javascript
|
|
854
|
+
// Store gift recipient information
|
|
855
|
+
const result = await behindAPI.V20.payments.payture.billCreateUnauthorised(
|
|
856
|
+
subscriptionId,
|
|
857
|
+
userId,
|
|
858
|
+
redirectUrl,
|
|
859
|
+
{
|
|
860
|
+
giftRecipient: "friend@example.com",
|
|
861
|
+
giftMessage: "Happy Birthday!",
|
|
862
|
+
giftOccasion: "birthday"
|
|
863
|
+
}
|
|
864
|
+
);
|
|
865
|
+
```
|
|
866
|
+
|
|
867
|
+
### Marketing Campaign Tracking
|
|
868
|
+
|
|
869
|
+
```javascript
|
|
870
|
+
// Track purchase source and campaign
|
|
871
|
+
const result = await behindAPI.V20.payments.payture.billCreateUnauthorised(
|
|
872
|
+
subscriptionId,
|
|
873
|
+
userId,
|
|
874
|
+
redirectUrl,
|
|
875
|
+
{
|
|
876
|
+
source: "facebook_ad",
|
|
877
|
+
campaign: "spring_sale_2024",
|
|
878
|
+
referrer: "influencer_abc",
|
|
879
|
+
utm_source: "social",
|
|
880
|
+
utm_medium: "paid",
|
|
881
|
+
utm_campaign: "spring24"
|
|
882
|
+
}
|
|
883
|
+
);
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
---
|
|
887
|
+
|
|
888
|
+
## Authorization Requirements
|
|
889
|
+
|
|
890
|
+
- `billCreate`: Requires valid user session (`need_authorization: true`)
|
|
891
|
+
- `billCreateUnauthorised`: No authorization required (`need_authorization: false`)
|
|
892
|
+
- `billStatus`: No authorization required (`need_authorization: false`)
|
|
893
|
+
|
|
894
|
+
The `billCreateUnauthorised` and `billStatus` methods can be called without user authentication, making them suitable for guest checkouts and status verification.
|
|
895
|
+
|
|
896
|
+
---
|
|
897
|
+
|
|
898
|
+
## Integration Notes
|
|
899
|
+
|
|
900
|
+
The Payture Payments API integrates with:
|
|
901
|
+
|
|
902
|
+
- **Subscription System**: Bills are created for subscription products and activate subscriptions upon payment
|
|
903
|
+
- **Gift System**: Supports gift purchases with automatic code generation and email notifications
|
|
904
|
+
- **Email System**: Sends invoices and gift notifications through the mailer API
|
|
905
|
+
- **Woololo API**: Can grant page access for specific purchase types when `pageId` is provided in extended data
|
|
906
|
+
- **Database**: Stores bill records, tracks status, and manages payment history
|
|
907
|
+
|
|
908
|
+
### Page Access Integration
|
|
909
|
+
|
|
910
|
+
When creating a bill with `pageId` in the extended data:
|
|
911
|
+
1. The bill is created with the page access information stored
|
|
912
|
+
2. Upon successful payment (PAID status)
|
|
913
|
+
3. For "direct" subscription product types, the system automatically grants user access to the specified page
|
|
914
|
+
4. Access is granted using the Woololo API with access_type 'ALL_USERS'
|
|
915
|
+
|
|
916
|
+
---
|
|
917
|
+
|
|
918
|
+
## Error Handling
|
|
919
|
+
|
|
920
|
+
All methods return a response object with a `success` boolean property. When `success` is `false`, a `message` property contains the error description.
|
|
921
|
+
|
|
922
|
+
```javascript
|
|
923
|
+
// Comprehensive error handling example
|
|
924
|
+
async function safePaymentOperation(subscriptionId, userId, pageId = null) {
|
|
925
|
+
try {
|
|
926
|
+
// Validate input parameters
|
|
927
|
+
if (!subscriptionId || !userId) {
|
|
928
|
+
return {
|
|
929
|
+
success: false,
|
|
930
|
+
error: "Missing required parameters"
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// Validate UUID format
|
|
935
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
936
|
+
if (!uuidRegex.test(subscriptionId) || !uuidRegex.test(userId)) {
|
|
937
|
+
return {
|
|
938
|
+
success: false,
|
|
939
|
+
error: "Invalid UUID format"
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// Prepare extended data
|
|
944
|
+
const extendedData = {};
|
|
945
|
+
if (pageId) {
|
|
946
|
+
extendedData.pageId = pageId;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// Create bill
|
|
950
|
+
const billResult = await behindAPI.V20.payments.payture.billCreateUnauthorised(
|
|
951
|
+
subscriptionId,
|
|
952
|
+
userId,
|
|
953
|
+
"https://mysite.com/return",
|
|
954
|
+
extendedData
|
|
955
|
+
);
|
|
956
|
+
|
|
957
|
+
if (!billResult.success) {
|
|
958
|
+
console.error("API Error:", billResult.message);
|
|
959
|
+
return {
|
|
960
|
+
success: false,
|
|
961
|
+
error: billResult.message
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
return {
|
|
966
|
+
success: true,
|
|
967
|
+
billId: billResult.data.bill_id,
|
|
968
|
+
paymentUrl: billResult.data.bill.payUrl,
|
|
969
|
+
pageId: pageId
|
|
970
|
+
};
|
|
971
|
+
|
|
972
|
+
} catch (error) {
|
|
973
|
+
console.error("Unexpected error:", error);
|
|
974
|
+
return {
|
|
975
|
+
success: false,
|
|
976
|
+
error: "An unexpected error occurred"
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// Usage with comprehensive error handling
|
|
982
|
+
const result = await safePaymentOperation(
|
|
983
|
+
"550e8400-e29b-41d4-a716-446655440001",
|
|
984
|
+
"aa0e8400-e29b-41d4-a716-446655440005",
|
|
985
|
+
"ck4jrtawdA3Ed4"
|
|
986
|
+
);
|
|
987
|
+
|
|
988
|
+
if (result.success) {
|
|
989
|
+
console.log("Bill created:", result.billId);
|
|
990
|
+
if (result.pageId) {
|
|
991
|
+
console.log("Page access will be granted:", result.pageId);
|
|
992
|
+
}
|
|
993
|
+
window.location.href = result.paymentUrl;
|
|
994
|
+
} else {
|
|
995
|
+
console.error("Operation failed:", result.error);
|
|
996
|
+
// Show user-friendly error message
|
|
997
|
+
alert("Unable to create payment. Please try again.");
|
|
998
|
+
}
|
|
999
|
+
```
|
|
1000
|
+
|
|
1001
|
+
---
|
|
1002
|
+
|
|
1003
|
+
## Common Error Scenarios
|
|
1004
|
+
|
|
1005
|
+
### Empty Bill ID
|
|
1006
|
+
```javascript
|
|
1007
|
+
const result = await behindAPI.V20.payments.payture.billStatus("");
|
|
1008
|
+
|
|
1009
|
+
// Response:
|
|
1010
|
+
{
|
|
1011
|
+
success: false,
|
|
1012
|
+
message: "bill_id can not be empty"
|
|
1013
|
+
}
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
### Invalid UUID Format
|
|
1017
|
+
```javascript
|
|
1018
|
+
// Always validate UUIDs before making API calls
|
|
1019
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
1020
|
+
|
|
1021
|
+
if (!uuidRegex.test(subscriptionId)) {
|
|
1022
|
+
console.error("Invalid subscription ID format");
|
|
1023
|
+
return { success: false, error: "Invalid UUID format" };
|
|
1024
|
+
}
|
|
1025
|
+
```
|
|
1026
|
+
|
|
1027
|
+
### Payment Amount Mismatch
|
|
1028
|
+
```javascript
|
|
1029
|
+
// The system detects when payment amount doesn't match expected amount
|
|
1030
|
+
const statusResult = await behindAPI.V20.payments.payture.billStatus(billId);
|
|
1031
|
+
|
|
1032
|
+
if (statusResult.data?.status === "AMOUNT_MISMATCH") {
|
|
1033
|
+
console.error("Payment amount verification failed");
|
|
1034
|
+
// Contact support or retry payment
|
|
1035
|
+
}
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
### Network Timeout
|
|
1039
|
+
```javascript
|
|
1040
|
+
// Implement timeout handling for API calls
|
|
1041
|
+
async function createBillWithTimeout(subscriptionId, userId, timeoutMs = 30000) {
|
|
1042
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
1043
|
+
setTimeout(() => reject(new Error("Request timeout")), timeoutMs);
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
const billPromise = behindAPI.V20.payments.payture.billCreateUnauthorised(
|
|
1047
|
+
subscriptionId,
|
|
1048
|
+
userId,
|
|
1049
|
+
"https://mysite.com/return"
|
|
1050
|
+
);
|
|
1051
|
+
|
|
1052
|
+
try {
|
|
1053
|
+
const result = await Promise.race([billPromise, timeoutPromise]);
|
|
1054
|
+
return result;
|
|
1055
|
+
} catch (error) {
|
|
1056
|
+
console.error("Timeout or error:", error);
|
|
1057
|
+
return { success: false, error: error.message };
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
```
|
|
1061
|
+
|
|
1062
|
+
---
|
|
1063
|
+
|
|
1064
|
+
## Advanced Usage Patterns
|
|
1065
|
+
|
|
1066
|
+
### Multi-Currency Support
|
|
1067
|
+
```javascript
|
|
1068
|
+
// Handle different currencies based on user location
|
|
1069
|
+
async function createInternationalPayment(subscriptionId, userId, userCountry) {
|
|
1070
|
+
const currencyMap = {
|
|
1071
|
+
'RU': { currency: 'RUB', redirectUrl: 'https://mysite.ru/return' },
|
|
1072
|
+
'US': { currency: 'USD', redirectUrl: 'https://mysite.com/return' },
|
|
1073
|
+
'EU': { currency: 'EUR', redirectUrl: 'https://mysite.eu/return' }
|
|
1074
|
+
};
|
|
1075
|
+
|
|
1076
|
+
const config = currencyMap[userCountry] || currencyMap['RU'];
|
|
1077
|
+
|
|
1078
|
+
const result = await behindAPI.V20.payments.payture.billCreateUnauthorised(
|
|
1079
|
+
subscriptionId,
|
|
1080
|
+
userId,
|
|
1081
|
+
config.redirectUrl,
|
|
1082
|
+
{
|
|
1083
|
+
currency: config.currency,
|
|
1084
|
+
country: userCountry
|
|
1085
|
+
}
|
|
1086
|
+
);
|
|
1087
|
+
|
|
1088
|
+
return result;
|
|
1089
|
+
}
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
### Retry Logic for Failed Payments
|
|
1093
|
+
```javascript
|
|
1094
|
+
// Implement retry mechanism with exponential backoff
|
|
1095
|
+
async function createBillWithRetry(subscriptionId, userId, maxRetries = 3) {
|
|
1096
|
+
let lastError;
|
|
1097
|
+
|
|
1098
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
1099
|
+
try {
|
|
1100
|
+
console.log(`Payment creation attempt ${attempt}/${maxRetries}`);
|
|
1101
|
+
|
|
1102
|
+
const result = await behindAPI.V20.payments.payture.billCreateUnauthorised(
|
|
1103
|
+
subscriptionId,
|
|
1104
|
+
userId,
|
|
1105
|
+
"https://mysite.com/return"
|
|
1106
|
+
);
|
|
1107
|
+
|
|
1108
|
+
if (result.success) {
|
|
1109
|
+
console.log("✓ Payment created successfully");
|
|
1110
|
+
return result;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
lastError = result.message;
|
|
1114
|
+
|
|
1115
|
+
} catch (error) {
|
|
1116
|
+
lastError = error.message;
|
|
1117
|
+
console.error(`Attempt ${attempt} failed:`, error);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// Wait before retrying (exponential backoff)
|
|
1121
|
+
if (attempt < maxRetries) {
|
|
1122
|
+
const waitTime = Math.pow(2, attempt) * 1000;
|
|
1123
|
+
console.log(`Waiting ${waitTime}ms before retry...`);
|
|
1124
|
+
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
return {
|
|
1129
|
+
success: false,
|
|
1130
|
+
error: `Failed after ${maxRetries} attempts: ${lastError}`
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
```
|
|
1134
|
+
|
|
1135
|
+
### Payment Analytics Tracking
|
|
1136
|
+
```javascript
|
|
1137
|
+
// Track payment events for analytics
|
|
1138
|
+
async function createTrackedPayment(subscriptionId, userId, analyticsData) {
|
|
1139
|
+
const startTime = Date.now();
|
|
1140
|
+
|
|
1141
|
+
const result = await behindAPI.V20.payments.payture.billCreateUnauthorised(
|
|
1142
|
+
subscriptionId,
|
|
1143
|
+
userId,
|
|
1144
|
+
"https://mysite.com/return",
|
|
1145
|
+
{
|
|
1146
|
+
...analyticsData,
|
|
1147
|
+
sessionId: generateSessionId(),
|
|
1148
|
+
deviceType: getDeviceType(),
|
|
1149
|
+
referrer: document.referrer
|
|
1150
|
+
}
|
|
1151
|
+
);
|
|
1152
|
+
|
|
1153
|
+
const duration = Date.now() - startTime;
|
|
1154
|
+
|
|
1155
|
+
// Send analytics event
|
|
1156
|
+
trackEvent('payment_initiated', {
|
|
1157
|
+
billId: result.data?.bill_id,
|
|
1158
|
+
subscriptionId: subscriptionId,
|
|
1159
|
+
success: result.success,
|
|
1160
|
+
duration: duration,
|
|
1161
|
+
...analyticsData
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
return result;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
function trackEvent(eventName, eventData) {
|
|
1168
|
+
// Your analytics implementation
|
|
1169
|
+
console.log(`Analytics: ${eventName}`, eventData);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
function generateSessionId() {
|
|
1173
|
+
return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
function getDeviceType() {
|
|
1177
|
+
const ua = navigator.userAgent;
|
|
1178
|
+
if (/mobile/i.test(ua)) return 'mobile';
|
|
1179
|
+
if (/tablet/i.test(ua)) return 'tablet';
|
|
1180
|
+
return 'desktop';
|
|
1181
|
+
}
|
|
1182
|
+
```
|
|
1183
|
+
|
|
1184
|
+
### Webhook Handler for Payment Notifications
|
|
1185
|
+
```javascript
|
|
1186
|
+
// Server-side webhook handler example (Node.js/Express)
|
|
1187
|
+
app.post('/webhooks/payture', async (req, res) => {
|
|
1188
|
+
try {
|
|
1189
|
+
const { bill_id, status, amount } = req.body;
|
|
1190
|
+
|
|
1191
|
+
// Verify webhook authenticity (implement your verification logic)
|
|
1192
|
+
if (!verifyWebhookSignature(req)) {
|
|
1193
|
+
return res.status(401).json({ error: 'Invalid signature' });
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
// Check payment status
|
|
1197
|
+
const statusResult = await behindAPI.V20.payments.payture.billStatus(bill_id);
|
|
1198
|
+
|
|
1199
|
+
if (statusResult.success && statusResult.data.status === "DONE") {
|
|
1200
|
+
// Payment completed, update your systems
|
|
1201
|
+
await updateUserSubscription(bill_id);
|
|
1202
|
+
|
|
1203
|
+
// Send confirmation email
|
|
1204
|
+
await sendPaymentConfirmation(bill_id);
|
|
1205
|
+
|
|
1206
|
+
res.status(200).json({ success: true });
|
|
1207
|
+
} else {
|
|
1208
|
+
res.status(200).json({ success: false, status: statusResult.data?.status });
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
} catch (error) {
|
|
1212
|
+
console.error('Webhook error:', error);
|
|
1213
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
1214
|
+
}
|
|
1215
|
+
});
|
|
1216
|
+
```
|
|
1217
|
+
|
|
1218
|
+
### Subscription Management Dashboard
|
|
1219
|
+
```javascript
|
|
1220
|
+
// Build a dashboard showing all user payments
|
|
1221
|
+
async function buildPaymentDashboard(userId) {
|
|
1222
|
+
const dashboard = {
|
|
1223
|
+
activeSubscriptions: [],
|
|
1224
|
+
pendingPayments: [],
|
|
1225
|
+
completedPayments: [],
|
|
1226
|
+
giftCodes: [],
|
|
1227
|
+
totalSpent: 0
|
|
1228
|
+
};
|
|
1229
|
+
|
|
1230
|
+
try {
|
|
1231
|
+
// Fetch user's bills from your database
|
|
1232
|
+
const userBills = await fetchUserBills(userId);
|
|
1233
|
+
|
|
1234
|
+
for (const bill of userBills) {
|
|
1235
|
+
const statusResult = await behindAPI.V20.payments.payture.billStatus(
|
|
1236
|
+
bill.bill_id
|
|
1237
|
+
);
|
|
1238
|
+
|
|
1239
|
+
if (!statusResult.success) continue;
|
|
1240
|
+
|
|
1241
|
+
const billData = {
|
|
1242
|
+
billId: bill.bill_id,
|
|
1243
|
+
amount: statusResult.data.bill_info.amount / 100,
|
|
1244
|
+
status: statusResult.data.status,
|
|
1245
|
+
createdAt: bill.created_at
|
|
1246
|
+
};
|
|
1247
|
+
|
|
1248
|
+
switch (statusResult.data.status) {
|
|
1249
|
+
case "PENDING":
|
|
1250
|
+
dashboard.pendingPayments.push(billData);
|
|
1251
|
+
break;
|
|
1252
|
+
case "DONE":
|
|
1253
|
+
case "PAID":
|
|
1254
|
+
dashboard.completedPayments.push(billData);
|
|
1255
|
+
dashboard.totalSpent += billData.amount;
|
|
1256
|
+
|
|
1257
|
+
if (statusResult.data.bill_info.gift) {
|
|
1258
|
+
dashboard.giftCodes.push({
|
|
1259
|
+
...billData,
|
|
1260
|
+
giftCode: statusResult.data.bill_info.gift.code
|
|
1261
|
+
});
|
|
1262
|
+
} else {
|
|
1263
|
+
dashboard.activeSubscriptions.push(billData);
|
|
1264
|
+
}
|
|
1265
|
+
break;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
return dashboard;
|
|
1270
|
+
|
|
1271
|
+
} catch (error) {
|
|
1272
|
+
console.error("Error building dashboard:", error);
|
|
1273
|
+
return dashboard;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
// Usage
|
|
1278
|
+
const dashboard = await buildPaymentDashboard("aa0e8400-e29b-41d4-a716-446655440005");
|
|
1279
|
+
console.log("Payment Dashboard:", dashboard);
|
|
1280
|
+
```
|
|
1281
|
+
|
|
1282
|
+
---
|
|
1283
|
+
|
|
1284
|
+
## Testing
|
|
1285
|
+
|
|
1286
|
+
### Test Environment Setup
|
|
1287
|
+
```javascript
|
|
1288
|
+
// Configure test environment
|
|
1289
|
+
const TEST_CONFIG = {
|
|
1290
|
+
subscriptionId: "550e8400-e29b-41d4-a716-446655440001",
|
|
1291
|
+
testUserId: "test-user-000000000000000000000001",
|
|
1292
|
+
testPageId: "ck4jrtawdA3Ed4",
|
|
1293
|
+
testRedirectUrl: "https://test.mysite.com/return"
|
|
1294
|
+
};
|
|
1295
|
+
|
|
1296
|
+
// Run payment flow test
|
|
1297
|
+
async function testPaymentFlow() {
|
|
1298
|
+
console.log("=== TESTING PAYMENT FLOW ===\n");
|
|
1299
|
+
|
|
1300
|
+
// Test 1: Create bill
|
|
1301
|
+
console.log("Test 1: Creating test bill...");
|
|
1302
|
+
const billResult = await behindAPI.V20.payments.payture.billCreateUnauthorised(
|
|
1303
|
+
TEST_CONFIG.subscriptionId,
|
|
1304
|
+
TEST_CONFIG.testUserId,
|
|
1305
|
+
TEST_CONFIG.testRedirectUrl,
|
|
1306
|
+
{
|
|
1307
|
+
pageId: TEST_CONFIG.testPageId,
|
|
1308
|
+
test: true
|
|
1309
|
+
}
|
|
1310
|
+
);
|
|
1311
|
+
|
|
1312
|
+
console.log(billResult.success ? "✓ PASS" : "✗ FAIL");
|
|
1313
|
+
if (!billResult.success) return;
|
|
1314
|
+
|
|
1315
|
+
const billId = billResult.data.bill_id;
|
|
1316
|
+
console.log(`Bill ID: ${billId}\n`);
|
|
1317
|
+
|
|
1318
|
+
// Test 2: Check initial status
|
|
1319
|
+
console.log("Test 2: Checking initial bill status...");
|
|
1320
|
+
const initialStatus = await behindAPI.V20.payments.payture.billStatus(billId);
|
|
1321
|
+
console.log(initialStatus.success ? "✓ PASS" : "✗ FAIL");
|
|
1322
|
+
console.log(`Status: ${initialStatus.data?.status}\n`);
|
|
1323
|
+
|
|
1324
|
+
// Test 3: Check with empty bill_id
|
|
1325
|
+
console.log("Test 3: Testing empty bill_id validation...");
|
|
1326
|
+
const emptyTest = await behindAPI.V20.payments.payture.billStatus("");
|
|
1327
|
+
console.log(!emptyTest.success ? "✓ PASS" : "✗ FAIL");
|
|
1328
|
+
console.log(`Error message: ${emptyTest.message}\n`);
|
|
1329
|
+
|
|
1330
|
+
console.log("=== TEST COMPLETE ===");
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// Run tests
|
|
1334
|
+
testPaymentFlow();
|
|
1335
|
+
```
|
|
1336
|
+
|
|
1337
|
+
---
|
|
1338
|
+
|
|
1339
|
+
## Troubleshooting
|
|
1340
|
+
|
|
1341
|
+
### Common Issues and Solutions
|
|
1342
|
+
|
|
1343
|
+
**Issue: Payment window blocked by popup blocker**
|
|
1344
|
+
```javascript
|
|
1345
|
+
// Solution: Provide fallback to direct redirect
|
|
1346
|
+
const paymentWindow = window.open(paymentUrl, '_blank');
|
|
1347
|
+
|
|
1348
|
+
if (!paymentWindow || paymentWindow.closed) {
|
|
1349
|
+
alert("Please allow popups for this site to complete payment");
|
|
1350
|
+
// Fallback to same-window redirect
|
|
1351
|
+
window.location.href = paymentUrl;
|
|
1352
|
+
}
|
|
1353
|
+
```
|
|
1354
|
+
|
|
1355
|
+
**Issue: Payment status remains PENDING after successful payment**
|
|
1356
|
+
```javascript
|
|
1357
|
+
// Solution: Implement proper status polling with logging
|
|
1358
|
+
async function debugPaymentStatus(billId) {
|
|
1359
|
+
console.log(`Debugging bill: ${billId}`);
|
|
1360
|
+
|
|
1361
|
+
const result = await behindAPI.V20.payments.payture.billStatus(billId);
|
|
1362
|
+
|
|
1363
|
+
console.log("API Response:", JSON.stringify(result, null, 2));
|
|
1364
|
+
console.log("Status:", result.data?.status);
|
|
1365
|
+
console.log("Bill Status:", result.data?.bill_info?.bill_status);
|
|
1366
|
+
console.log("Amount:", result.data?.bill_info?.amount);
|
|
1367
|
+
|
|
1368
|
+
return result;
|
|
1369
|
+
}
|
|
1370
|
+
```
|
|
1371
|
+
|
|
1372
|
+
**Issue: Amount mismatch errors**
|
|
1373
|
+
```javascript
|
|
1374
|
+
// Solution: Log and verify amounts at each step
|
|
1375
|
+
async function verifyPaymentAmount(billId, expectedAmount) {
|
|
1376
|
+
const result = await behindAPI.V20.payments.payture.billStatus(billId);
|
|
1377
|
+
|
|
1378
|
+
if (result.success) {
|
|
1379
|
+
const actualAmount = result.data.bill_info.amount / 100; // Convert from kopecks
|
|
1380
|
+
console.log(`Expected: ${expectedAmount}, Actual: ${actualAmount}`);
|
|
1381
|
+
|
|
1382
|
+
if (Math.abs(actualAmount - expectedAmount) > 0.01) {
|
|
1383
|
+
console.error("Amount mismatch detected!");
|
|
1384
|
+
return false;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
return true;
|
|
1389
|
+
}
|
|
1390
|
+
```
|
|
1391
|
+
|
|
1392
|
+
**Issue: Lost bill_id after page refresh**
|
|
1393
|
+
```javascript
|
|
1394
|
+
// Solution: Implement persistent storage
|
|
1395
|
+
function saveBillId(billId) {
|
|
1396
|
+
// Store in multiple places for redundancy
|
|
1397
|
+
sessionStorage.setItem('pending_bill_id', billId);
|
|
1398
|
+
localStorage.setItem('last_bill_id', billId);
|
|
1399
|
+
|
|
1400
|
+
// Store with timestamp for cleanup
|
|
1401
|
+
const billData = {
|
|
1402
|
+
billId: billId,
|
|
1403
|
+
timestamp: Date.now()
|
|
1404
|
+
};
|
|
1405
|
+
localStorage.setItem('bill_data', JSON.stringify(billData));
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
function retrieveBillId() {
|
|
1409
|
+
// Try session storage first
|
|
1410
|
+
let billId = sessionStorage.getItem('pending_bill_id');
|
|
1411
|
+
|
|
1412
|
+
// Fallback to local storage
|
|
1413
|
+
if (!billId) {
|
|
1414
|
+
billId = localStorage.getItem('last_bill_id');
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
// Check if bill is recent (within 24 hours)
|
|
1418
|
+
const billData = JSON.parse(localStorage.getItem('bill_data') || '{}');
|
|
1419
|
+
const hoursSinceCreation = (Date.now() - billData.timestamp) / (1000 * 60 * 60);
|
|
1420
|
+
|
|
1421
|
+
if (hoursSinceCreation > 24) {
|
|
1422
|
+
console.warn("Bill ID is more than 24 hours old");
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
return billId;
|
|
1426
|
+
}
|
|
1427
|
+
```
|
|
1428
|
+
|
|
1429
|
+
---
|
|
1430
|
+
|
|
1431
|
+
## Security Considerations
|
|
1432
|
+
|
|
1433
|
+
1. **Never expose sensitive data**: Don't log or store payment card details
|
|
1434
|
+
2. **Validate all inputs**: Always validate UUIDs and URLs before API calls
|
|
1435
|
+
3. **Use HTTPS**: Ensure all redirect URLs use HTTPS protocol
|
|
1436
|
+
4. **Implement CSRF protection**: Use tokens for payment initiation
|
|
1437
|
+
5. **Rate limiting**: Implement rate limiting to prevent abuse
|
|
1438
|
+
6. **Verify webhooks**: Always verify webhook signatures
|
|
1439
|
+
7. **Session security**: Use secure session storage mechanisms
|
|
1440
|
+
8. **Audit logging**: Log all payment operations for security audits
|
|
1441
|
+
|
|
1442
|
+
```javascript
|
|
1443
|
+
// Example: Secure payment initiation
|
|
1444
|
+
async function securePaymentInit(subscriptionId, userId, csrfToken) {
|
|
1445
|
+
// Verify CSRF token
|
|
1446
|
+
if (!verifyCsrfToken(csrfToken)) {
|
|
1447
|
+
return { success: false, error: "Invalid security token" };
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
// Sanitize inputs
|
|
1451
|
+
const sanitizedSubId = sanitizeUuid(subscriptionId);
|
|
1452
|
+
const sanitizedUserId = sanitizeUuid(userId);
|
|
1453
|
+
|
|
1454
|
+
// Check rate limit
|
|
1455
|
+
if (isRateLimited(userId)) {
|
|
1456
|
+
return { success: false, error: "Too many requests. Please try again later." };
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// Create bill with audit log
|
|
1460
|
+
const result = await behindAPI.V20.payments.payture.billCreateUnauthorised(
|
|
1461
|
+
sanitizedSubId,
|
|
1462
|
+
sanitizedUserId,
|
|
1463
|
+
"https://secure.mysite.com/return"
|
|
1464
|
+
);
|
|
1465
|
+
|
|
1466
|
+
// Log the operation
|
|
1467
|
+
auditLog({
|
|
1468
|
+
action: 'payment_initiated',
|
|
1469
|
+
userId: sanitizedUserId,
|
|
1470
|
+
billId: result.data?.bill_id,
|
|
1471
|
+
success: result.success,
|
|
1472
|
+
timestamp: new Date().toISOString()
|
|
1473
|
+
});
|
|
1474
|
+
|
|
1475
|
+
return result;
|
|
1476
|
+
}
|
|
1477
|
+
```
|
|
1478
|
+
|
|
1479
|
+
---
|
|
1480
|
+
|
|
1481
|
+
## API Reference Summary
|
|
1482
|
+
|
|
1483
|
+
| Method | Authorization Required | Parameters | Returns |
|
|
1484
|
+
|--------|----------------------|------------|---------|
|
|
1485
|
+
| `billCreate` | Yes | subscriptionId | bill_id, payUrl |
|
|
1486
|
+
| `billCreateUnauthorised` | No | subscriptionId, userId, redirectUrl, extended | bill_id, payUrl |
|
|
1487
|
+
| `billStatus` | No | billId | bill_info, status |
|
|
1488
|
+
|
|
1489
|
+
---
|
|
1490
|
+
|
|
1491
|
+
## Support and Resources
|
|
1492
|
+
|
|
1493
|
+
For additional help:
|
|
1494
|
+
- **API Documentation**: Refer to the main EasyJob API documentation
|
|
1495
|
+
- **Error Codes**: Check Payture's official error code documentation
|
|
1496
|
+
- **Support**: Contact your technical support team for integration assistance
|
|
1497
|
+
- **Testing**: Use test environment before production deployment
|