@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.
Files changed (3) hide show
  1. package/README.md +44 -11
  2. package/index.mjs +88 -22
  3. 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 as well to make the entire payment process as simple and straight forward as possible.
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
- ### PayPal
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
- ### Coinbase
149
- * `GET /payments/coinbase/verify?code=xxx`
150
- * `POST /payments/webhook/coinbase` (if webhookSecret set)
151
- * `GET /payments/cancel`
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 Base Path
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
- const orderResponse = await this.parent.request(
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
- const orderStatus = orderResponse.status;
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: purchaseUnit.custom_id,
313
+ transactionId: customId,
292
314
  orderId: orderResponse.id,
293
- amount: parseFloat(purchaseUnit.amount.value),
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
- registerRoutes(basePath = '/payments') {
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
- res.json({ok: true, ...result});
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.status(500).json({ok: false, error: error.message});
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
- res.json({ok: true, ...result});
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.status(500).json({ok: false, error: error.message});
759
+ return res.redirect(this.redirects.subscriptionError);
705
760
  }
706
761
  });
707
762
 
708
763
  this.app.get(`${basePath}/cancel`, async (req, res) => {
709
- res.send('payment cancelled');
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
- res.json({ok: true, ...result});
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.status(500).json({ok: false, error: error.message});
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
- const result = await this.coinbase.verifyCharge(chargeId);
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.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": {