@hackthedev/dsync-pay 1.0.0 → 1.0.5
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 +44 -11
- package/index.mjs +88 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,11 +4,12 @@ As another part of the dSync library family this library is responsible for paym
|
|
|
4
4
|
|
|
5
5
|
> [!NOTE]
|
|
6
6
|
>
|
|
7
|
-
> Payment Providers may take a cut from your money or have other fees that are outside of this library's control.
|
|
7
|
+
> Payment Providers may take a cut from your money or have other fees that are outside of this library's control.
|
|
8
8
|
|
|
9
9
|
------
|
|
10
10
|
|
|
11
11
|
## Setup
|
|
12
|
+
|
|
12
13
|
```js
|
|
13
14
|
import dSyncPay from '@hackthedev/dsync-pay';
|
|
14
15
|
|
|
@@ -17,6 +18,13 @@ const payments = new dSyncPay({
|
|
|
17
18
|
app,
|
|
18
19
|
domain: 'https://domain.com',
|
|
19
20
|
basePath: '/payments', // optional, default is '/payments'
|
|
21
|
+
redirects: { // optional, customize redirect pages
|
|
22
|
+
success: '/payment-success',
|
|
23
|
+
error: '/payment-error',
|
|
24
|
+
cancelled: '/payment-cancelled',
|
|
25
|
+
subscriptionSuccess: '/subscription-success',
|
|
26
|
+
subscriptionError: '/subscription-error'
|
|
27
|
+
},
|
|
20
28
|
paypal: {
|
|
21
29
|
clientId: 'xxx',
|
|
22
30
|
clientSecret: 'xxx',
|
|
@@ -138,17 +146,33 @@ const result = await payments.coinbase.verifyCharge(chargeCode);
|
|
|
138
146
|
|
|
139
147
|
## Routes
|
|
140
148
|
|
|
141
|
-
dSyncPay automatically creates verification routes for handling payment returns
|
|
149
|
+
dSyncPay automatically creates verification routes and redirect pages for handling payment returns to make the entire payment process as simple and straight forward as possible.
|
|
150
|
+
|
|
151
|
+
### Verification Routes
|
|
152
|
+
|
|
153
|
+
#### PayPal
|
|
154
|
+
|
|
155
|
+
- `GET /payments/paypal/verify?token=xxx`
|
|
156
|
+
- `GET /payments/paypal/subscription/verify?subscription_id=xxx`
|
|
157
|
+
- `GET /payments/cancel`
|
|
142
158
|
|
|
143
|
-
|
|
144
|
-
* `GET /payments/paypal/verify?token=xxx`
|
|
145
|
-
* `GET /payments/paypal/subscription/verify?subscription_id=xxx`
|
|
146
|
-
* `GET /payments/cancel`
|
|
159
|
+
#### Coinbase
|
|
147
160
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
161
|
+
- `GET /payments/coinbase/verify?code=xxx`
|
|
162
|
+
- `POST /payments/webhook/coinbase` (if webhookSecret set)
|
|
163
|
+
- `GET /payments/cancel`
|
|
164
|
+
|
|
165
|
+
### Auto-Generated Redirect Pages
|
|
166
|
+
|
|
167
|
+
The library automatically creates simple redirect pages at:
|
|
168
|
+
|
|
169
|
+
- `GET /payment-success` - shown after successful payment
|
|
170
|
+
- `GET /payment-error` - shown when payment fails
|
|
171
|
+
- `GET /payment-cancelled` - shown when user cancels payment
|
|
172
|
+
- `GET /subscription-success` - shown after successful subscription
|
|
173
|
+
- `GET /subscription-error` - shown when subscription fails
|
|
174
|
+
|
|
175
|
+
You can customize these redirect URLs in the constructor with the `redirects` parameter.
|
|
152
176
|
|
|
153
177
|
### Usage Example
|
|
154
178
|
|
|
@@ -161,18 +185,27 @@ const order = await payments.paypal.createOrder({
|
|
|
161
185
|
// redirect user to order.approvalUrl
|
|
162
186
|
// paypal redirects back to /payments/paypal/verify?token=XXX
|
|
163
187
|
// route automatically verifies and triggers onPaymentCompleted
|
|
188
|
+
// then redirects to /payment-success
|
|
164
189
|
```
|
|
165
190
|
|
|
166
|
-
### Custom
|
|
191
|
+
### Custom Configuration
|
|
167
192
|
|
|
168
193
|
```javascript
|
|
169
194
|
const payments = new dSyncPay({
|
|
170
195
|
app,
|
|
171
196
|
domain: 'https://domain.com',
|
|
172
197
|
basePath: '/api/pay', // default is '/payments'
|
|
198
|
+
redirects: {
|
|
199
|
+
success: '/custom/success',
|
|
200
|
+
error: '/custom/error',
|
|
201
|
+
cancelled: '/custom/cancelled',
|
|
202
|
+
subscriptionSuccess: '/custom/sub-success',
|
|
203
|
+
subscriptionError: '/custom/sub-error'
|
|
204
|
+
},
|
|
173
205
|
paypal: { ... }
|
|
174
206
|
});
|
|
175
207
|
|
|
176
208
|
// routes: /api/pay/paypal/verify, /api/pay/coinbase/verify, etc.
|
|
177
209
|
// auto urls: https://domain.com/api/pay/paypal/verify
|
|
210
|
+
// redirect pages: /custom/success, /custom/error, etc.
|
|
178
211
|
```
|
package/index.mjs
CHANGED
|
@@ -5,6 +5,13 @@ export default class dSyncPay {
|
|
|
5
5
|
app = null,
|
|
6
6
|
domain = null,
|
|
7
7
|
basePath = '/payments',
|
|
8
|
+
redirects = {
|
|
9
|
+
success: '/payment-success',
|
|
10
|
+
error: '/payment-error',
|
|
11
|
+
cancelled: '/payment-cancelled',
|
|
12
|
+
subscriptionSuccess: '/subscription-success',
|
|
13
|
+
subscriptionError: '/subscription-error'
|
|
14
|
+
},
|
|
8
15
|
paypal = null,
|
|
9
16
|
coinbase = null,
|
|
10
17
|
onPaymentCreated = null,
|
|
@@ -22,6 +29,7 @@ export default class dSyncPay {
|
|
|
22
29
|
this.app = app;
|
|
23
30
|
this.domain = domain.endsWith('/') ? domain.slice(0, -1) : domain;
|
|
24
31
|
this.basePath = basePath;
|
|
32
|
+
this.redirects = redirects;
|
|
25
33
|
|
|
26
34
|
this.callbacks = {
|
|
27
35
|
onPaymentCreated,
|
|
@@ -46,17 +54,18 @@ export default class dSyncPay {
|
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
this.registerRoutes(basePath);
|
|
57
|
+
this.registerRedirectRoutes();
|
|
49
58
|
}
|
|
50
59
|
|
|
51
60
|
getUrl(path) {
|
|
52
61
|
return `${this.domain}${this.basePath}${path}`;
|
|
53
62
|
}
|
|
54
63
|
|
|
55
|
-
emit(event, data) {
|
|
64
|
+
async emit(event, data) {
|
|
56
65
|
const callback = this.callbacks[event];
|
|
57
66
|
if (callback) {
|
|
58
67
|
try {
|
|
59
|
-
callback(data);
|
|
68
|
+
await callback(data);
|
|
60
69
|
} catch (err) {
|
|
61
70
|
console.error("callback error:", err);
|
|
62
71
|
}
|
|
@@ -258,7 +267,7 @@ export default class dSyncPay {
|
|
|
258
267
|
const accessToken = await this.getAccessToken();
|
|
259
268
|
|
|
260
269
|
try {
|
|
261
|
-
|
|
270
|
+
let orderResponse = await this.parent.request(
|
|
262
271
|
`${this.baseUrl}/v2/checkout/orders/${orderId}`,
|
|
263
272
|
{
|
|
264
273
|
headers: {
|
|
@@ -267,10 +276,10 @@ export default class dSyncPay {
|
|
|
267
276
|
}
|
|
268
277
|
);
|
|
269
278
|
|
|
270
|
-
|
|
279
|
+
let orderStatus = orderResponse.status;
|
|
271
280
|
|
|
272
281
|
if (orderStatus === "APPROVED") {
|
|
273
|
-
await this.parent.request(
|
|
282
|
+
const captureResponse = await this.parent.request(
|
|
274
283
|
`${this.baseUrl}/v2/checkout/orders/${orderId}/capture`,
|
|
275
284
|
{
|
|
276
285
|
method: 'POST',
|
|
@@ -280,27 +289,41 @@ export default class dSyncPay {
|
|
|
280
289
|
body: {}
|
|
281
290
|
}
|
|
282
291
|
);
|
|
292
|
+
|
|
293
|
+
orderStatus = captureResponse.status;
|
|
294
|
+
orderResponse = captureResponse;
|
|
283
295
|
}
|
|
284
296
|
|
|
285
|
-
const purchaseUnit = orderResponse.purchase_units[0];
|
|
297
|
+
const purchaseUnit = orderResponse.purchase_units?.[0] || {};
|
|
298
|
+
|
|
299
|
+
let amount = 0;
|
|
300
|
+
let customId = purchaseUnit.custom_id;
|
|
301
|
+
|
|
302
|
+
if (purchaseUnit.payments?.captures?.[0]) {
|
|
303
|
+
amount = parseFloat(purchaseUnit.payments.captures[0].amount.value);
|
|
304
|
+
customId = purchaseUnit.payments.captures[0].custom_id || purchaseUnit.custom_id;
|
|
305
|
+
} else if (purchaseUnit.amount?.value) {
|
|
306
|
+
amount = parseFloat(purchaseUnit.amount.value);
|
|
307
|
+
}
|
|
286
308
|
|
|
287
309
|
const result = {
|
|
288
310
|
provider: 'paypal',
|
|
289
311
|
type: 'order',
|
|
290
312
|
status: orderStatus,
|
|
291
|
-
transactionId:
|
|
313
|
+
transactionId: customId,
|
|
292
314
|
orderId: orderResponse.id,
|
|
293
|
-
amount:
|
|
294
|
-
currency: purchaseUnit.amount.currency_code,
|
|
315
|
+
amount: amount,
|
|
316
|
+
currency: purchaseUnit.payments?.captures?.[0]?.amount?.currency_code || purchaseUnit.amount?.currency_code || 'EUR',
|
|
295
317
|
rawResponse: orderResponse
|
|
296
318
|
};
|
|
297
319
|
|
|
298
320
|
if (orderStatus === 'COMPLETED') {
|
|
299
|
-
this.parent.emit('onPaymentCompleted', result);
|
|
300
|
-
} else if (orderStatus === 'VOIDED') {
|
|
301
|
-
this.parent.emit('onPaymentCancelled', result);
|
|
321
|
+
await this.parent.emit('onPaymentCompleted', result);
|
|
322
|
+
} else if (orderStatus === 'VOIDED' || orderStatus === 'CANCELLED') {
|
|
323
|
+
await this.parent.emit('onPaymentCancelled', result);
|
|
324
|
+
} else {
|
|
325
|
+
await this.parent.emit('onPaymentFailed', result);
|
|
302
326
|
}
|
|
303
|
-
|
|
304
327
|
return result;
|
|
305
328
|
} catch (error) {
|
|
306
329
|
this.parent.emit('onError', {
|
|
@@ -679,7 +702,29 @@ export default class dSyncPay {
|
|
|
679
702
|
}
|
|
680
703
|
}
|
|
681
704
|
|
|
682
|
-
|
|
705
|
+
registerRedirectRoutes() {
|
|
706
|
+
this.app.get(this.redirects.success, (req, res) => {
|
|
707
|
+
res.send('payment successful! thank you.');
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
this.app.get(this.redirects.error, (req, res) => {
|
|
711
|
+
res.send('payment failed. please try again.');
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
this.app.get(this.redirects.cancelled, (req, res) => {
|
|
715
|
+
res.send('payment cancelled.');
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
this.app.get(this.redirects.subscriptionSuccess, (req, res) => {
|
|
719
|
+
res.send('subscription activated!');
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
this.app.get(this.redirects.subscriptionError, (req, res) => {
|
|
723
|
+
res.send('subscription failed.');
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
registerRoutes(basePath = '/payments') {
|
|
683
728
|
if (this.paypal) {
|
|
684
729
|
this.app.get(`${basePath}/paypal/verify`, async (req, res) => {
|
|
685
730
|
try {
|
|
@@ -687,9 +732,14 @@ export default class dSyncPay {
|
|
|
687
732
|
if (!orderId) return res.status(400).json({ok: false, error: 'missing_token'});
|
|
688
733
|
|
|
689
734
|
const result = await this.paypal.verifyOrder(orderId);
|
|
690
|
-
|
|
735
|
+
|
|
736
|
+
if (result.status === 'COMPLETED') {
|
|
737
|
+
return res.redirect(this.redirects.success);
|
|
738
|
+
} else {
|
|
739
|
+
return res.redirect(this.redirects.error);
|
|
740
|
+
}
|
|
691
741
|
} catch (error) {
|
|
692
|
-
res.
|
|
742
|
+
return res.redirect(this.redirects.error);
|
|
693
743
|
}
|
|
694
744
|
});
|
|
695
745
|
|
|
@@ -699,14 +749,25 @@ export default class dSyncPay {
|
|
|
699
749
|
if (!subscriptionId) return res.status(400).json({ok: false, error: 'missing_subscription_id'});
|
|
700
750
|
|
|
701
751
|
const result = await this.paypal.verifySubscription(subscriptionId);
|
|
702
|
-
|
|
752
|
+
|
|
753
|
+
if (result.status === 'ACTIVE') {
|
|
754
|
+
return res.redirect(this.redirects.subscriptionSuccess);
|
|
755
|
+
} else {
|
|
756
|
+
return res.redirect(this.redirects.subscriptionError);
|
|
757
|
+
}
|
|
703
758
|
} catch (error) {
|
|
704
|
-
res.
|
|
759
|
+
return res.redirect(this.redirects.subscriptionError);
|
|
705
760
|
}
|
|
706
761
|
});
|
|
707
762
|
|
|
708
763
|
this.app.get(`${basePath}/cancel`, async (req, res) => {
|
|
709
|
-
|
|
764
|
+
const orderId = req.query.token;
|
|
765
|
+
if (orderId) {
|
|
766
|
+
try {
|
|
767
|
+
await this.paypal.verifyOrder(orderId);
|
|
768
|
+
} catch (e) {}
|
|
769
|
+
}
|
|
770
|
+
return res.redirect(this.redirects.cancelled);
|
|
710
771
|
});
|
|
711
772
|
}
|
|
712
773
|
|
|
@@ -717,9 +778,14 @@ export default class dSyncPay {
|
|
|
717
778
|
if (!chargeCode) return res.status(400).json({ok: false, error: 'missing_code'});
|
|
718
779
|
|
|
719
780
|
const result = await this.coinbase.verifyCharge(chargeCode);
|
|
720
|
-
|
|
781
|
+
|
|
782
|
+
if (result.status === 'COMPLETED') {
|
|
783
|
+
return res.redirect(this.redirects.success);
|
|
784
|
+
} else {
|
|
785
|
+
return res.redirect(this.redirects.error);
|
|
786
|
+
}
|
|
721
787
|
} catch (error) {
|
|
722
|
-
res.
|
|
788
|
+
return res.redirect(this.redirects.error);
|
|
723
789
|
}
|
|
724
790
|
});
|
|
725
791
|
|
|
@@ -739,7 +805,7 @@ export default class dSyncPay {
|
|
|
739
805
|
|
|
740
806
|
if (event.event.type === 'charge:confirmed') {
|
|
741
807
|
const chargeId = event.event.data.id;
|
|
742
|
-
|
|
808
|
+
await this.coinbase.verifyCharge(chargeId);
|
|
743
809
|
}
|
|
744
810
|
|
|
745
811
|
res.status(200).json({ok: true});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hackthedev/dsync-pay",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "As another part of the dSync library family this library is responsible for payment handling currently supporting PayPal and Coinbase Crypto payments. Its works independently and without any percentage cuts by using your own API keys.",
|
|
5
5
|
"homepage": "https://github.com/NETWORK-Z-Dev/dSyncPay#readme",
|
|
6
6
|
"bugs": {
|