@yosle/tropipayjs 0.2.0 → 0.2.2

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 CHANGED
@@ -133,6 +133,33 @@ console.log(paylink.shortUrl);
133
133
 
134
134
  For more examples, please refer to the [Documentation](https://github.com/yosle/tropipayjs/blob/master/docs/)
135
135
 
136
+ ### Refunding a Transaction
137
+
138
+ The library provides two methods for handling refunds with 2FA confirmation:
139
+
140
+ #### Request a Security Code (Production only)
141
+
142
+ In **Production** mode, you must first request a 2FA code via SMS:
143
+
144
+ ```javascript
145
+ await tpp.requestSecurityCode();
146
+ // An SMS with the code will be sent to your phone
147
+ ```
148
+
149
+ #### Refund a Movement
150
+
151
+ Once you have the security code (or in Development mode), you can refund a transaction:
152
+
153
+ ```javascript
154
+ // Development mode — use the default test code
155
+ const result = await tpp.refundMovement("ORD-123456", 5000, "123456");
156
+
157
+ // Production mode — pass the code received via SMS
158
+ const result = await tpp.refundMovement("ORD-123456", 5000, "847291");
159
+ ```
160
+
161
+ **Note:** The refund endpoint requires the `ALLOW_REFUND` scope and 2FA to be enabled on your Tropipay account. In Development mode the default 2FA code is `123456` unless you have configured the Authenticator app.
162
+
136
163
  # Contributing
137
164
 
138
165
  If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!
@@ -5,7 +5,7 @@
5
5
  *
6
6
  */
7
7
  import { Axios } from "axios";
8
- import { TropipayConfig, AccountBalance, Country, PaymentLink, MediationPaymentCardConfig, LoginResponse } from "../interfaces";
8
+ import { TropipayConfig, AccountBalance, Country, PaymentLink, MediationPaymentCardConfig, LoginResponse, RefundResponse } from "../interfaces";
9
9
  type ServerMode = "Development" | "Production";
10
10
  import TropipayHooks from "../hooks/TropipayHooks";
11
11
  import PaymentCard from "../paymentcard/PaymentCard";
@@ -16,7 +16,6 @@ export declare class Tropipay {
16
16
  readonly clientSecret: string;
17
17
  readonly scopes: String[];
18
18
  request: Axios;
19
- loginRequest: Axios;
20
19
  static accessToken: string | null;
21
20
  static refreshToken: string | null;
22
21
  static expiresIn: number | null;
@@ -83,10 +82,39 @@ export declare class Tropipay {
83
82
  * belonging or not to the TropiPay platform with the particularity
84
83
  * that the payment will be held in custody or retained until it is
85
84
  * released with the approval of the payer.
85
+ * @deprecated This method is no longer supported and may be removed in a future release.
86
86
  * @see https://tpp.stoplight.io/docs/tropipay-api-doc/12a128ff971e4-creating-a-mediation-payment-card
87
87
  * @param config Payload with the payment details
88
88
  */
89
89
  createMediationPaymentCard(config: MediationPaymentCardConfig): Promise<PaymentLink>;
90
+ /**
91
+ * Refund a completed transaction.
92
+ * Requires 2FA enabled and ALLOW_REFUND permission on the Tropipay account.
93
+ *
94
+ * In Development mode, `securityCode` defaults to `"123456"` when omitted.
95
+ * In Production mode, you must first call `requestSecurityCode()` to receive
96
+ * the 2FA code via SMS, then pass it as the `securityCode` parameter.
97
+ *
98
+ * @see https://doc.tropipay.com/docs/api-reference/movements#refund-a-transaction
99
+ * @param orderCode Code of the order/transaction to refund (e.g. "ORD-123456")
100
+ * @param amount Amount to refund in cents (e.g. 5000 = 50.00 USD/EUR)
101
+ * @param securityCode 2FA confirmation code. Use "123456" in Development or
102
+ * the code received via SMS in Production (obtained via requestSecurityCode()).
103
+ */
104
+ refundMovement(orderCode: string, amount: number, securityCode: string): Promise<RefundResponse>;
105
+ /**
106
+ * Request a 2FA security code to be sent via SMS for confirming sensitive
107
+ * operations like refunds.
108
+ *
109
+ * In Production mode, you must call this method first to trigger an SMS with
110
+ * the code, then use the received code when calling `refundMovement()`.
111
+ * In Development mode this is not required as the default code "123456"
112
+ * is used automatically.
113
+ *
114
+ * @see https://doc.tropipay.com/docs/api-reference/movements#refund-a-transaction
115
+ * @returns The API response confirming the code was sent
116
+ */
117
+ requestSecurityCode(type?: "sms" | "email"): Promise<any>;
90
118
  }
91
119
  export declare class ClientSideUtils {
92
120
  constructor(tropipayInstance: Tropipay);
@@ -100,5 +128,6 @@ export declare const Scopes: {
100
128
  ALLOW_GET_BALANCE: string;
101
129
  ALLOW_GET_MOVEMENT_LIST: string;
102
130
  ALLOW_GET_CREDENTIAL: string;
131
+ ALLOW_REFUND: string;
103
132
  };
104
133
  export {};
package/index.d.ts CHANGED
@@ -40,7 +40,6 @@ interface UserHookSubscribed extends UserHook {
40
40
  updatedAt: string;
41
41
  }
42
42
  interface PaymentLinkPayload {
43
- accountId: number;
44
43
  reference: string;
45
44
  concept: string;
46
45
  favorite: boolean;
@@ -169,6 +168,16 @@ type AccountDeposits = {
169
168
  count: number;
170
169
  rows: Deposit[];
171
170
  };
171
+ type RefundResponse = {
172
+ id: number;
173
+ orderCode: string;
174
+ amount: number;
175
+ currency: string;
176
+ state: string;
177
+ type: string;
178
+ createdAt: string;
179
+ completedAt: string;
180
+ };
172
181
 
173
182
  declare class TropipayHooks {
174
183
  private tropipay;
@@ -334,7 +343,6 @@ declare class Tropipay {
334
343
  readonly clientSecret: string;
335
344
  readonly scopes: String[];
336
345
  request: Axios;
337
- loginRequest: Axios;
338
346
  static accessToken: string | null;
339
347
  static refreshToken: string | null;
340
348
  static expiresIn: number | null;
@@ -401,10 +409,39 @@ declare class Tropipay {
401
409
  * belonging or not to the TropiPay platform with the particularity
402
410
  * that the payment will be held in custody or retained until it is
403
411
  * released with the approval of the payer.
412
+ * @deprecated This method is no longer supported and may be removed in a future release.
404
413
  * @see https://tpp.stoplight.io/docs/tropipay-api-doc/12a128ff971e4-creating-a-mediation-payment-card
405
414
  * @param config Payload with the payment details
406
415
  */
407
416
  createMediationPaymentCard(config: MediationPaymentCardConfig): Promise<PaymentLink>;
417
+ /**
418
+ * Refund a completed transaction.
419
+ * Requires 2FA enabled and ALLOW_REFUND permission on the Tropipay account.
420
+ *
421
+ * In Development mode, `securityCode` defaults to `"123456"` when omitted.
422
+ * In Production mode, you must first call `requestSecurityCode()` to receive
423
+ * the 2FA code via SMS, then pass it as the `securityCode` parameter.
424
+ *
425
+ * @see https://doc.tropipay.com/docs/api-reference/movements#refund-a-transaction
426
+ * @param orderCode Code of the order/transaction to refund (e.g. "ORD-123456")
427
+ * @param amount Amount to refund in cents (e.g. 5000 = 50.00 USD/EUR)
428
+ * @param securityCode 2FA confirmation code. Use "123456" in Development or
429
+ * the code received via SMS in Production (obtained via requestSecurityCode()).
430
+ */
431
+ refundMovement(orderCode: string, amount: number, securityCode: string): Promise<RefundResponse>;
432
+ /**
433
+ * Request a 2FA security code to be sent via SMS for confirming sensitive
434
+ * operations like refunds.
435
+ *
436
+ * In Production mode, you must call this method first to trigger an SMS with
437
+ * the code, then use the received code when calling `refundMovement()`.
438
+ * In Development mode this is not required as the default code "123456"
439
+ * is used automatically.
440
+ *
441
+ * @see https://doc.tropipay.com/docs/api-reference/movements#refund-a-transaction
442
+ * @returns The API response confirming the code was sent
443
+ */
444
+ requestSecurityCode(type?: "sms" | "email"): Promise<any>;
408
445
  }
409
446
  declare class ClientSideUtils {
410
447
  constructor(tropipayInstance: Tropipay);
@@ -414,9 +451,7 @@ declare class ServerSideUtils {
414
451
  private tropipay;
415
452
  constructor(tropipayInstance: Tropipay);
416
453
  /**
417
- * DEPRECATED Verify Topipay's signature on API V2 webhooks.
418
- * @deprecated This method is deprecated and will be removed in future versions.
419
- * Use the `verifySignatureV3` method from the `ServerSideUtils` class instead.
454
+ * Verify Topipay's signature on webhooks.
420
455
  * @param credentials Credential object or Tropipay instance
421
456
  * @param {String} originalCurrencyAmount
422
457
  * @param bankOrderCode
@@ -455,19 +490,8 @@ declare class ServerSideUtils {
455
490
  * @return {Promise<string>} the valid base64 image
456
491
  */
457
492
  static isValidImage(base64Image: string): boolean;
458
- /**
459
- * Verify Topipay's signature on API V3 webhooks.
460
- * @param {string} originalCurrencyAmount
461
- * @param {string} bankOrderCode
462
- * @param {string} signature signaturev3 field to be verified
463
- * @returns {boolean}
464
- */
465
- static verifySignatureV3(credentials: {
466
- clientId: string;
467
- clientSecret: string;
468
- } | Tropipay, originalCurrencyAmount: string, bankOrderCode: string, signature: string): boolean;
469
493
  }
470
494
 
471
495
  declare const SERVER_MODE: ServerMode$1;
472
496
 
473
- export { AccountBalance, AccountDeposits, ClientSideUtils, Country, Deposit, HookEventType, HookTargetType, LoginError, LoginResponse, MAX_IMAGE_SIZE_MB, MediationPaymentCardConfig, PaymentLink, PaymentLinkPayload, SERVER_MODE, ServerMode$1 as ServerMode, ServerSideUtils, Tropipay, TropipayConfig, TropipayCredentials, UserHook, UserHookSubscribed };
497
+ export { AccountBalance, AccountDeposits, ClientSideUtils, Country, Deposit, HookEventType, HookTargetType, LoginError, LoginResponse, MAX_IMAGE_SIZE_MB, MediationPaymentCardConfig, PaymentLink, PaymentLinkPayload, RefundResponse, SERVER_MODE, ServerMode$1 as ServerMode, ServerSideUtils, Tropipay, TropipayConfig, TropipayCredentials, UserHook, UserHookSubscribed };
package/index.js CHANGED
@@ -220,7 +220,7 @@ class PaymentCard {
220
220
  await this.tropipay.login();
221
221
  }
222
222
  try {
223
- const paylink = await this.tropipay.request.post("/api/v3/paymentcards", payload, {
223
+ const paylink = await this.tropipay.request.post("/api/v2/paymentcards", payload, {
224
224
  headers: {
225
225
  "Content-Type": "application/json",
226
226
  Authorization: `Bearer ${Tropipay.accessToken}`,
@@ -243,7 +243,7 @@ class PaymentCard {
243
243
  await this.tropipay.login();
244
244
  }
245
245
  try {
246
- const paymentcards = await this.tropipay.request.get(`/api/v3/paymentcards`, {
246
+ const paymentcards = await this.tropipay.request.get(`/api/v2/paymentcards`, {
247
247
  headers: {
248
248
  "Content-Type": "application/json",
249
249
  Authorization: `Bearer ${Tropipay.accessToken}`,
@@ -268,7 +268,7 @@ class PaymentCard {
268
268
  await this.tropipay.login();
269
269
  }
270
270
  try {
271
- const paymentcard = await this.tropipay.request.get(`/api/v3/paymentcards/${id}`, {
271
+ const paymentcard = await this.tropipay.request.get(`/api/v2/paymentcards/${id}`, {
272
272
  headers: {
273
273
  "Content-Type": "application/json",
274
274
  Authorization: `Bearer ${Tropipay.accessToken}`,
@@ -294,7 +294,7 @@ class PaymentCard {
294
294
  await this.tropipay.login();
295
295
  }
296
296
  try {
297
- const paymentcard = await this.tropipay.request.delete(`/api/v3/paymentcards/`, {
297
+ const paymentcard = await this.tropipay.request.delete(`/api/v2/paymentcards/`, {
298
298
  headers: {
299
299
  "Content-Type": "application/json",
300
300
  Authorization: `Bearer ${Tropipay.accessToken}`,
@@ -360,7 +360,7 @@ class DepositAccounts {
360
360
  await this.tropipay.login();
361
361
  }
362
362
  try {
363
- const deposit = await this.tropipay.request.get(`/api/v3/deposit_accounts`, {
363
+ const deposit = await this.tropipay.request.get(`/api/v2/deposit_accounts`, {
364
364
  headers: {
365
365
  "Content-Type": "application/json",
366
366
  Authorization: `Bearer ${Tropipay.accessToken}`,
@@ -383,7 +383,7 @@ class DepositAccounts {
383
383
  await this.tropipay.login();
384
384
  }
385
385
  try {
386
- const deposit = await this.tropipay.request.post("/api/v3/deposit_accounts", depositAccountObj, {
386
+ const deposit = await this.tropipay.request.post("/api/v2/deposit_accounts", depositAccountObj, {
387
387
  headers: {
388
388
  "Content-Type": "application/json",
389
389
  Authorization: `Bearer ${Tropipay.accessToken}`,
@@ -407,7 +407,7 @@ class DepositAccounts {
407
407
  await this.tropipay.login();
408
408
  }
409
409
  try {
410
- const deposit = await this.tropipay.request.get(`/api/v3/deposit_accounts/${id}`, {
410
+ const deposit = await this.tropipay.request.get(`/api/v2/deposit_accounts/${id}`, {
411
411
  headers: {
412
412
  "Content-Type": "application/json",
413
413
  Authorization: `Bearer ${Tropipay.accessToken}`,
@@ -430,7 +430,7 @@ class DepositAccounts {
430
430
  await this.tropipay.login();
431
431
  }
432
432
  try {
433
- const deposit = await this.tropipay.request.put(`/api/v3/deposit_accounts/`, {
433
+ const deposit = await this.tropipay.request.put(`/api/v2/deposit_accounts/`, {
434
434
  headers: {
435
435
  "Content-Type": "application/json",
436
436
  Authorization: `Bearer ${Tropipay.accessToken}`,
@@ -453,7 +453,7 @@ class DepositAccounts {
453
453
  await this.tropipay.login();
454
454
  }
455
455
  try {
456
- const deposit = await this.tropipay.request.delete(`/api/v3/deposit_accounts/${id}`, {
456
+ const deposit = await this.tropipay.request.delete(`/api/v2/deposit_accounts/${id}`, {
457
457
  headers: {
458
458
  "Content-Type": "application/json",
459
459
  Authorization: `Bearer ${Tropipay.accessToken}`,
@@ -479,7 +479,6 @@ class Tropipay {
479
479
  clientSecret;
480
480
  scopes;
481
481
  request;
482
- loginRequest;
483
482
  static accessToken;
484
483
  static refreshToken;
485
484
  static expiresIn;
@@ -506,6 +505,7 @@ class Tropipay {
506
505
  "ALLOW_GET_BALANCE",
507
506
  "ALLOW_GET_MOVEMENT_LIST",
508
507
  "ALLOW_GET_CREDENTIAL",
508
+ "ALLOW_REFUND",
509
509
  ];
510
510
  }
511
511
  else {
@@ -519,7 +519,7 @@ class Tropipay {
519
519
  this.serverMode = config.serverMode || "Development";
520
520
  const tpp_env = this.serverMode === "Production"
521
521
  ? "https://www.tropipay.com"
522
- : "https://sandbox.tropipay.me";
522
+ : "https://tropipay-dev.herokuapp.com";
523
523
  this.request = axios__default["default"].create({
524
524
  baseURL: config.customTropipayUrl || tpp_env,
525
525
  headers: {
@@ -528,28 +528,21 @@ class Tropipay {
528
528
  Authorization: `Bearer ${Tropipay.accessToken}`,
529
529
  },
530
530
  });
531
- // Create a separate instance for login requests
532
- this.loginRequest = axios__default["default"].create({
533
- baseURL: config.customTropipayUrl || tpp_env,
534
- headers: {
535
- "Content-Type": "application/json",
536
- Accept: "application/json",
537
- },
538
- });
539
531
  // Add request interceptor for Token expired
540
532
  this.request.interceptors.request.use(async (config) => {
541
- const currentTimestamp = Math.floor(Date.now() / 1000);
533
+ const currentTimestamp = Math.floor(Date.now() / 1000); // Current time in seconds
542
534
  if (Tropipay.expiresIn && Tropipay.expiresIn < currentTimestamp) {
543
- console.debug("Token expired, attempting to log in");
535
+ // Token has expired, attempt to refresh it
544
536
  try {
545
537
  await this.login();
546
538
  }
547
539
  catch (error) {
540
+ // Handle token refresh error
541
+ Tropipay.accessToken = null;
542
+ Tropipay.refreshToken = null;
548
543
  throw handleExceptions(error);
549
544
  }
550
545
  }
551
- // Update the Authorization header in the config
552
- config.headers.Authorization = `Bearer ${Tropipay.accessToken}`;
553
546
  return config;
554
547
  }, (error) => {
555
548
  return Promise.reject(error);
@@ -561,8 +554,22 @@ class Tropipay {
561
554
  }
562
555
  async login() {
563
556
  try {
557
+ if (Tropipay.refreshToken) {
558
+ const { data } = await this.request.post("/api/v2/access/token", {
559
+ client_id: this.clientId,
560
+ client_secret: this.clientSecret,
561
+ grant_type: "refresh_token",
562
+ refresh_token: Tropipay.refreshToken,
563
+ }, {
564
+ headers: {
565
+ "Content-Type": "application/json",
566
+ Accept: "application/json",
567
+ },
568
+ });
569
+ return data;
570
+ }
564
571
  // normal credetials login
565
- const { data } = await this.loginRequest.post("/api/v3/access/token", {
572
+ const { data } = await this.request.post("/api/v2/access/token", {
566
573
  client_id: this.clientId,
567
574
  client_secret: this.clientSecret,
568
575
  grant_type: "client_credentials",
@@ -574,6 +581,7 @@ class Tropipay {
574
581
  },
575
582
  });
576
583
  Tropipay.accessToken = data.access_token;
584
+ Tropipay.refreshToken = data.refresh_token;
577
585
  Tropipay.expiresIn = data.expires_in;
578
586
  return data;
579
587
  }
@@ -581,7 +589,6 @@ class Tropipay {
581
589
  Tropipay.accessToken = null;
582
590
  Tropipay.refreshToken = null;
583
591
  Tropipay.expiresIn = null;
584
- Tropipay.expiresIn = null;
585
592
  throw handleExceptions(error);
586
593
  }
587
594
  }
@@ -733,6 +740,7 @@ class Tropipay {
733
740
  * belonging or not to the TropiPay platform with the particularity
734
741
  * that the payment will be held in custody or retained until it is
735
742
  * released with the approval of the payer.
743
+ * @deprecated This method is no longer supported and may be removed in a future release.
736
744
  * @see https://tpp.stoplight.io/docs/tropipay-api-doc/12a128ff971e4-creating-a-mediation-payment-card
737
745
  * @param config Payload with the payment details
738
746
  */
@@ -753,6 +761,71 @@ class Tropipay {
753
761
  throw handleExceptions(error);
754
762
  }
755
763
  }
764
+ /**
765
+ * Refund a completed transaction.
766
+ * Requires 2FA enabled and ALLOW_REFUND permission on the Tropipay account.
767
+ *
768
+ * In Development mode, `securityCode` defaults to `"123456"` when omitted.
769
+ * In Production mode, you must first call `requestSecurityCode()` to receive
770
+ * the 2FA code via SMS, then pass it as the `securityCode` parameter.
771
+ *
772
+ * @see https://doc.tropipay.com/docs/api-reference/movements#refund-a-transaction
773
+ * @param orderCode Code of the order/transaction to refund (e.g. "ORD-123456")
774
+ * @param amount Amount to refund in cents (e.g. 5000 = 50.00 USD/EUR)
775
+ * @param securityCode 2FA confirmation code. Use "123456" in Development or
776
+ * the code received via SMS in Production (obtained via requestSecurityCode()).
777
+ */
778
+ async refundMovement(orderCode, amount, securityCode) {
779
+ if (!Tropipay.accessToken) {
780
+ await this.login();
781
+ }
782
+ if (!this.scopes.includes("ALLOW_REFUND")) {
783
+ throw new TropipayJSException("The credential does not have the ALLOW_REFUND scope required to perform refunds. If you believe your account should have this permission, please contact Tropipay support.", 403, null);
784
+ }
785
+ try {
786
+ const response = await this.request.post("/api/v3/movements/in/refund", { orderCode, amount, securityCode }, {
787
+ headers: {
788
+ "Content-Type": "application/json",
789
+ Accept: "application/json",
790
+ Authorization: `Bearer ${Tropipay.accessToken}`,
791
+ },
792
+ });
793
+ return response.data;
794
+ }
795
+ catch (error) {
796
+ throw handleExceptions(error);
797
+ }
798
+ }
799
+ /**
800
+ * Request a 2FA security code to be sent via SMS for confirming sensitive
801
+ * operations like refunds.
802
+ *
803
+ * In Production mode, you must call this method first to trigger an SMS with
804
+ * the code, then use the received code when calling `refundMovement()`.
805
+ * In Development mode this is not required as the default code "123456"
806
+ * is used automatically.
807
+ *
808
+ * @see https://doc.tropipay.com/docs/api-reference/movements#refund-a-transaction
809
+ * @returns The API response confirming the code was sent
810
+ */
811
+ async requestSecurityCode(type = "sms") {
812
+ if (!Tropipay.accessToken) {
813
+ await this.login();
814
+ }
815
+ try {
816
+ const response = await this.request.post("/api/v3/users/sendSecurityCode", { type }, {
817
+ headers: {
818
+ "Content-Type": "application/json",
819
+ Accept: "application/json",
820
+ Authorization: `Bearer ${Tropipay.accessToken}`,
821
+ },
822
+ });
823
+ return response.data;
824
+ }
825
+ catch (error) {
826
+ throw handleExceptions(error);
827
+ }
828
+ }
756
829
  }
757
830
  class ClientSideUtils {
758
831
  constructor(tropipayInstance) {
@@ -768,9 +841,7 @@ class ServerSideUtils {
768
841
  this.tropipay = tropipayInstance;
769
842
  }
770
843
  /**
771
- * DEPRECATED Verify Topipay's signature on API V2 webhooks.
772
- * @deprecated This method is deprecated and will be removed in future versions.
773
- * Use the `verifySignatureV3` method from the `ServerSideUtils` class instead.
844
+ * Verify Topipay's signature on webhooks.
774
845
  * @param credentials Credential object or Tropipay instance
775
846
  * @param {String} originalCurrencyAmount
776
847
  * @param bankOrderCode
@@ -860,28 +931,6 @@ class ServerSideUtils {
860
931
  throw new Error(`Error checking base64 image`);
861
932
  }
862
933
  }
863
- /**
864
- * Verify Topipay's signature on API V3 webhooks.
865
- * @param {string} originalCurrencyAmount
866
- * @param {string} bankOrderCode
867
- * @param {string} signature signaturev3 field to be verified
868
- * @returns {boolean}
869
- */
870
- static verifySignatureV3(credentials, originalCurrencyAmount, bankOrderCode, signature) {
871
- const sha1Secret = crypto__namespace
872
- .createHash("sha1")
873
- .update(credentials.clientSecret)
874
- .digest("hex");
875
- const dataToHash = bankOrderCode +
876
- credentials.clientId +
877
- sha1Secret +
878
- originalCurrencyAmount;
879
- const localSignature = crypto__namespace
880
- .createHash("sha256")
881
- .update(dataToHash)
882
- .digest("hex");
883
- return localSignature === signature;
884
- }
885
934
  }
886
935
 
887
936
  const SERVER_MODE = "Development"; // Move the constant here
@@ -36,7 +36,6 @@ export interface UserHookSubscribed extends UserHook {
36
36
  updatedAt: string;
37
37
  }
38
38
  export interface PaymentLinkPayload {
39
- accountId: number;
40
39
  reference: string;
41
40
  concept: string;
42
41
  favorite: boolean;
@@ -165,3 +164,13 @@ export type AccountDeposits = {
165
164
  count: number;
166
165
  rows: Deposit[];
167
166
  };
167
+ export type RefundResponse = {
168
+ id: number;
169
+ orderCode: string;
170
+ amount: number;
171
+ currency: string;
172
+ state: string;
173
+ type: string;
174
+ createdAt: string;
175
+ completedAt: string;
176
+ };
@@ -46,7 +46,6 @@ declare enum Reasons {
46
46
  SENDING_YOU_A_KISS = 80
47
47
  }
48
48
  export interface PaymentLinkPayload {
49
- accountId: number;
50
49
  reference: string;
51
50
  concept: string;
52
51
  favorite: boolean;
@@ -61,6 +60,9 @@ export interface PaymentLinkPayload {
61
60
  urlFailed: string;
62
61
  urlNotification: string;
63
62
  serviceDate: string;
63
+ /**
64
+ * fgfsgdfgdfgd
65
+ */
64
66
  client?: {
65
67
  name: string;
66
68
  lastName: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yosle/tropipayjs",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Javascript / Typescript SDK for the Tropipay API",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -12,7 +12,10 @@
12
12
  },
13
13
  "repository": {
14
14
  "type": "git",
15
- "url": "https://github.com/yosle/tropipayjs"
15
+ "url": "git+https://github.com/yosle/tropipayjs.git"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
16
19
  },
17
20
  "keywords": [
18
21
  "Javascript",
@@ -0,0 +1 @@
1
+ declare function sum(a: number, b: number): number;
@@ -15,6 +15,7 @@ import {
15
15
  MediationPaymentCardConfig,
16
16
  LoginResponse,
17
17
  AccountDeposits,
18
+ RefundResponse,
18
19
  } from "../interfaces";
19
20
  type ServerMode = "Development" | "Production";
20
21
  import TropipayHooks from "../hooks/TropipayHooks";
@@ -27,7 +28,6 @@ export class Tropipay {
27
28
  readonly clientSecret: string;
28
29
  readonly scopes: String[];
29
30
  public request: Axios;
30
- public loginRequest: Axios;
31
31
  public static accessToken: string | null;
32
32
  public static refreshToken: string | null;
33
33
  public static expiresIn: number | null;
@@ -56,6 +56,7 @@ export class Tropipay {
56
56
  "ALLOW_GET_BALANCE",
57
57
  "ALLOW_GET_MOVEMENT_LIST",
58
58
  "ALLOW_GET_CREDENTIAL",
59
+ "ALLOW_REFUND",
59
60
  ];
60
61
  } else {
61
62
  this.scopes = config.scopes;
@@ -73,12 +74,11 @@ export class Tropipay {
73
74
  this.clientSecret = config.clientSecret;
74
75
  this.serverMode = config.serverMode || "Development";
75
76
 
76
- const tpp_env =
77
- this.serverMode === "Production"
78
- ? "https://www.tropipay.com"
79
- : "https://sandbox.tropipay.me";
77
+ const tpp_env = this.serverMode === "Production"
78
+ ? "https://www.tropipay.com"
79
+ : "https://tropipay-dev.herokuapp.com";
80
80
  this.request = axios.create({
81
- baseURL: config.customTropipayUrl || tpp_env,
81
+ baseURL: config.customTropipayUrl || tpp_env,
82
82
  headers: {
83
83
  "Content-Type": "application/json",
84
84
  Accept: "application/json",
@@ -86,33 +86,27 @@ export class Tropipay {
86
86
  },
87
87
  });
88
88
 
89
- // Create a separate instance for login requests
90
- this.loginRequest = axios.create({
91
- baseURL: config.customTropipayUrl || tpp_env,
92
- headers: {
93
- "Content-Type": "application/json",
94
- Accept: "application/json",
95
- },
96
- });
97
89
 
98
- // Add request interceptor for Token expired
99
- this.request.interceptors.request.use(
100
- async (config) => {
101
- const currentTimestamp = Math.floor(Date.now() / 1000);
90
+ // Add request interceptor for Token expired
91
+ this.request.interceptors.request.use(
92
+ async (config: any) => {
93
+ const currentTimestamp = Math.floor(Date.now() / 1000); // Current time in seconds
102
94
 
103
95
  if (Tropipay.expiresIn && Tropipay.expiresIn < currentTimestamp) {
104
- console.debug("Token expired, attempting to log in");
96
+ // Token has expired, attempt to refresh it
105
97
  try {
106
- await this.login();
98
+ await this.login();
107
99
  } catch (error) {
108
- throw handleExceptions(error as Error);
100
+ // Handle token refresh error
101
+ Tropipay.accessToken = null;
102
+ Tropipay.refreshToken = null;
103
+ throw handleExceptions(error as any);
109
104
  }
110
105
  }
111
- // Update the Authorization header in the config
112
- config.headers.Authorization = `Bearer ${Tropipay.accessToken}`;
106
+
113
107
  return config;
114
108
  },
115
- (error) => {
109
+ (error: AxiosError) => {
116
110
  return Promise.reject(error);
117
111
  }
118
112
  );
@@ -122,13 +116,34 @@ export class Tropipay {
122
116
  this.mediationPaymentCard = new MediationPaymentCard(this);
123
117
 
124
118
  this.depositAccounts = new DepositAccounts(this);
119
+
120
+
125
121
  }
126
122
 
127
123
  public async login() {
128
124
  try {
125
+ if (Tropipay.refreshToken) {
126
+ const { data } = await this.request.post<LoginResponse>(
127
+ "/api/v2/access/token",
128
+ {
129
+ client_id: this.clientId,
130
+ client_secret: this.clientSecret,
131
+ grant_type: "refresh_token",
132
+ refresh_token: Tropipay.refreshToken,
133
+ },
134
+ {
135
+ headers: {
136
+ "Content-Type": "application/json",
137
+ Accept: "application/json",
138
+ },
139
+ }
140
+ );
141
+ return data;
142
+ }
143
+
129
144
  // normal credetials login
130
- const { data } = await this.loginRequest.post<LoginResponse>(
131
- "/api/v3/access/token",
145
+ const { data } = await this.request.post<LoginResponse>(
146
+ "/api/v2/access/token",
132
147
  {
133
148
  client_id: this.clientId,
134
149
  client_secret: this.clientSecret,
@@ -144,13 +159,13 @@ export class Tropipay {
144
159
  );
145
160
 
146
161
  Tropipay.accessToken = data.access_token;
162
+ Tropipay.refreshToken = data.refresh_token;
147
163
  Tropipay.expiresIn = data.expires_in;
148
164
  return data;
149
165
  } catch (error) {
150
166
  Tropipay.accessToken = null;
151
167
  Tropipay.refreshToken = null;
152
168
  Tropipay.expiresIn = null;
153
- Tropipay.expiresIn = null;
154
169
  throw handleExceptions(error as any);
155
170
  }
156
171
  }
@@ -313,6 +328,7 @@ export class Tropipay {
313
328
  * belonging or not to the TropiPay platform with the particularity
314
329
  * that the payment will be held in custody or retained until it is
315
330
  * released with the approval of the payer.
331
+ * @deprecated This method is no longer supported and may be removed in a future release.
316
332
  * @see https://tpp.stoplight.io/docs/tropipay-api-doc/12a128ff971e4-creating-a-mediation-payment-card
317
333
  * @param config Payload with the payment details
318
334
  */
@@ -337,6 +353,91 @@ export class Tropipay {
337
353
  throw handleExceptions(error as any);
338
354
  }
339
355
  }
356
+
357
+ /**
358
+ * Refund a completed transaction.
359
+ * Requires 2FA enabled and ALLOW_REFUND permission on the Tropipay account.
360
+ *
361
+ * In Development mode, `securityCode` defaults to `"123456"` when omitted.
362
+ * In Production mode, you must first call `requestSecurityCode()` to receive
363
+ * the 2FA code via SMS, then pass it as the `securityCode` parameter.
364
+ *
365
+ * @see https://doc.tropipay.com/docs/api-reference/movements#refund-a-transaction
366
+ * @param orderCode Code of the order/transaction to refund (e.g. "ORD-123456")
367
+ * @param amount Amount to refund in cents (e.g. 5000 = 50.00 USD/EUR)
368
+ * @param securityCode 2FA confirmation code. Use "123456" in Development or
369
+ * the code received via SMS in Production (obtained via requestSecurityCode()).
370
+ */
371
+ async refundMovement(
372
+ orderCode: string,
373
+ amount: number,
374
+ securityCode: string
375
+ ): Promise<RefundResponse> {
376
+ if (!Tropipay.accessToken) {
377
+ await this.login();
378
+ }
379
+
380
+ if (!this.scopes.includes("ALLOW_REFUND")) {
381
+ throw new TropipayJSException(
382
+ "The credential does not have the ALLOW_REFUND scope required to perform refunds. If you believe your account should have this permission, please contact Tropipay support.",
383
+ 403,
384
+ null
385
+ );
386
+ }
387
+
388
+ try {
389
+ const response = await this.request.post(
390
+ "/api/v3/movements/in/refund",
391
+ { orderCode, amount, securityCode },
392
+ {
393
+ headers: {
394
+ "Content-Type": "application/json",
395
+ Accept: "application/json",
396
+ Authorization: `Bearer ${Tropipay.accessToken}`,
397
+ },
398
+ }
399
+ );
400
+ return response.data as RefundResponse;
401
+ } catch (error) {
402
+ throw handleExceptions(error as any);
403
+ }
404
+ }
405
+
406
+ /**
407
+ * Request a 2FA security code to be sent via SMS for confirming sensitive
408
+ * operations like refunds.
409
+ *
410
+ * In Production mode, you must call this method first to trigger an SMS with
411
+ * the code, then use the received code when calling `refundMovement()`.
412
+ * In Development mode this is not required as the default code "123456"
413
+ * is used automatically.
414
+ *
415
+ * @see https://doc.tropipay.com/docs/api-reference/movements#refund-a-transaction
416
+ * @returns The API response confirming the code was sent
417
+ */
418
+ async requestSecurityCode(type: "sms" | "email" = "sms"): Promise<any> {
419
+ if (!Tropipay.accessToken) {
420
+ await this.login();
421
+ }
422
+
423
+ try {
424
+ const response = await this.request.post(
425
+ "/api/v3/users/sendSecurityCode",
426
+ { type },
427
+ {
428
+ headers: {
429
+ "Content-Type": "application/json",
430
+ Accept: "application/json",
431
+ Authorization: `Bearer ${Tropipay.accessToken}`,
432
+ },
433
+ }
434
+ );
435
+ return response.data;
436
+ } catch (error) {
437
+ throw handleExceptions(error as any);
438
+ }
439
+ }
440
+
340
441
  }
341
442
 
342
443
  export class ClientSideUtils {
@@ -354,4 +455,5 @@ export const Scopes = {
354
455
  ALLOW_GET_BALANCE: "ALLOW_GET_BALANCE",
355
456
  ALLOW_GET_MOVEMENT_LIST: "ALLOW_GET_MOVEMENT_LIST",
356
457
  ALLOW_GET_CREDENTIAL: "ALLOW_GET_CREDENTIAL",
458
+ ALLOW_REFUND: "ALLOW_REFUND",
357
459
  };
@@ -18,7 +18,7 @@ export default class DepositAccounts {
18
18
  }
19
19
  try {
20
20
  const deposit = await this.tropipay.request.get(
21
- `/api/v3/deposit_accounts`,
21
+ `/api/v2/deposit_accounts`,
22
22
  {
23
23
  headers: {
24
24
  "Content-Type": "application/json",
@@ -43,7 +43,7 @@ export default class DepositAccounts {
43
43
  }
44
44
  try {
45
45
  const deposit = await this.tropipay.request.post(
46
- "/api/v3/deposit_accounts",
46
+ "/api/v2/deposit_accounts",
47
47
  depositAccountObj,
48
48
  {
49
49
  headers: {
@@ -70,7 +70,7 @@ export default class DepositAccounts {
70
70
  }
71
71
  try {
72
72
  const deposit = await this.tropipay.request.get(
73
- `/api/v3/deposit_accounts/${id}`,
73
+ `/api/v2/deposit_accounts/${id}`,
74
74
  {
75
75
  headers: {
76
76
  "Content-Type": "application/json",
@@ -96,7 +96,7 @@ export default class DepositAccounts {
96
96
  }
97
97
  try {
98
98
  const deposit = await this.tropipay.request.put(
99
- `/api/v3/deposit_accounts/`,
99
+ `/api/v2/deposit_accounts/`,
100
100
  {
101
101
  headers: {
102
102
  "Content-Type": "application/json",
@@ -122,7 +122,7 @@ export default class DepositAccounts {
122
122
  }
123
123
  try {
124
124
  const deposit = await this.tropipay.request.delete(
125
- `/api/v3/deposit_accounts/${id}`,
125
+ `/api/v2/deposit_accounts/${id}`,
126
126
  {
127
127
  headers: {
128
128
  "Content-Type": "application/json",
package/src/index.ts CHANGED
@@ -26,3 +26,4 @@ export * from "./depositAccount/depositAccounts";
26
26
  export * from "./interfaces/index";
27
27
  export { SERVER_MODE } from "./config/TropipayConfig";
28
28
  export * from "./constants/TropipayConstants";
29
+ export type { RefundResponse } from "./interfaces/index";
@@ -64,7 +64,6 @@ export interface UserHookSubscribed extends UserHook {
64
64
  }
65
65
 
66
66
  export interface PaymentLinkPayload {
67
- accountId: number;
68
67
  reference: string;
69
68
  concept: string;
70
69
  favorite: boolean;
@@ -200,3 +199,14 @@ export type AccountDeposits = {
200
199
  count: number;
201
200
  rows: Deposit[];
202
201
  };
202
+
203
+ export type RefundResponse = {
204
+ id: number;
205
+ orderCode: string;
206
+ amount: number;
207
+ currency: string;
208
+ state: string;
209
+ type: string;
210
+ createdAt: string;
211
+ completedAt: string;
212
+ };
@@ -1,37 +1,36 @@
1
1
  export interface PaymentLinkPayload {
2
- accountId: number;
3
- reference: string;
4
- concept: string;
5
- favorite: boolean;
6
- amount: number;
7
- currency: string;
8
- description: string;
9
- singleUse: boolean;
10
- reasonId: number;
11
- expirationDays: number;
12
- lang: string;
13
- urlSuccess: string;
14
- urlFailed: string;
15
- urlNotification: string;
16
- serviceDate: string;
17
- client: {
18
- name: string;
19
- lastName: string;
20
- address: string;
21
- phone: string;
22
- email: string;
23
- countryId: number;
24
- termsAndConditions: boolean;
25
- };
26
- directPayment: boolean;
27
- paymentMethods?: string[];
2
+ reference: string;
3
+ concept: string;
4
+ favorite: boolean;
5
+ amount: number;
6
+ currency: string;
7
+ description: string;
8
+ singleUse: boolean;
9
+ reasonId: number;
10
+ expirationDays: number;
11
+ lang: string;
12
+ urlSuccess: string;
13
+ urlFailed: string;
14
+ urlNotification: string;
15
+ serviceDate: string;
16
+ client: {
17
+ name: string;
18
+ lastName: string;
19
+ address: string;
20
+ phone: string;
21
+ email: string;
22
+ countryId: number;
23
+ termsAndConditions: boolean;
24
+ };
25
+ directPayment: boolean;
26
+ paymentMethods?: string[];
28
27
  }
29
28
  export interface PaymentLink extends PaymentLinkPayload {
30
- expirationDate: string;
31
- hasClient: boolean;
32
- updatedAt: string;
33
- createdAt: string;
34
- qrImage: string;
35
- shortUrl: string;
36
- paymentUrl: string;
29
+ expirationDate: string;
30
+ hasClient: boolean;
31
+ updatedAt: string;
32
+ createdAt: string;
33
+ qrImage: string;
34
+ shortUrl: string;
35
+ paymentUrl: string;
37
36
  }
@@ -50,7 +50,6 @@ enum Reasons {
50
50
  }
51
51
 
52
52
  export interface PaymentLinkPayload {
53
- accountId: number;
54
53
  reference: string;
55
54
  concept: string;
56
55
  favorite: boolean;
@@ -65,6 +64,9 @@ export interface PaymentLinkPayload {
65
64
  urlFailed: string;
66
65
  urlNotification: string;
67
66
  serviceDate: string;
67
+ /**
68
+ * fgfsgdfgdfgd
69
+ */
68
70
  client?: {
69
71
  name: string;
70
72
  lastName: string;
@@ -20,7 +20,7 @@ export default class PaymentCard {
20
20
  }
21
21
  try {
22
22
  const paylink = await this.tropipay.request.post(
23
- "/api/v3/paymentcards",
23
+ "/api/v2/paymentcards",
24
24
  payload,
25
25
  {
26
26
  headers: {
@@ -46,7 +46,7 @@ export default class PaymentCard {
46
46
  }
47
47
  try {
48
48
  const paymentcards = await this.tropipay.request.get(
49
- `/api/v3/paymentcards`,
49
+ `/api/v2/paymentcards`,
50
50
  {
51
51
  headers: {
52
52
  "Content-Type": "application/json",
@@ -74,7 +74,7 @@ export default class PaymentCard {
74
74
  }
75
75
  try {
76
76
  const paymentcard = await this.tropipay.request.get(
77
- `/api/v3/paymentcards/${id}`,
77
+ `/api/v2/paymentcards/${id}`,
78
78
  {
79
79
  headers: {
80
80
  "Content-Type": "application/json",
@@ -103,7 +103,7 @@ export default class PaymentCard {
103
103
  }
104
104
  try {
105
105
  const paymentcard = await this.tropipay.request.delete(
106
- `/api/v3/paymentcards/`,
106
+ `/api/v2/paymentcards/`,
107
107
  {
108
108
  headers: {
109
109
  "Content-Type": "application/json",
@@ -11,9 +11,7 @@ export class ServerSideUtils {
11
11
  }
12
12
 
13
13
  /**
14
- * DEPRECATED Verify Topipay's signature on API V2 webhooks.
15
- * @deprecated This method is deprecated and will be removed in future versions.
16
- * Use the `verifySignatureV3` method from the `ServerSideUtils` class instead.
14
+ * Verify Topipay's signature on webhooks.
17
15
  * @param credentials Credential object or Tropipay instance
18
16
  * @param {String} originalCurrencyAmount
19
17
  * @param bankOrderCode
@@ -125,41 +123,4 @@ export class ServerSideUtils {
125
123
  throw new Error(`Error checking base64 image`);
126
124
  }
127
125
  }
128
-
129
- /**
130
- * Verify Topipay's signature on API V3 webhooks.
131
- * @param {string} originalCurrencyAmount
132
- * @param {string} bankOrderCode
133
- * @param {string} signature signaturev3 field to be verified
134
- * @returns {boolean}
135
- */
136
- public static verifySignatureV3(
137
- credentials:
138
- | {
139
- clientId: string;
140
- clientSecret: string;
141
- }
142
- | Tropipay,
143
- originalCurrencyAmount: string,
144
- bankOrderCode: string,
145
- signature: string
146
- ): boolean {
147
- const sha1Secret = crypto
148
- .createHash("sha1")
149
- .update(credentials.clientSecret)
150
- .digest("hex");
151
-
152
- const dataToHash =
153
- bankOrderCode +
154
- credentials.clientId +
155
- sha1Secret +
156
- originalCurrencyAmount;
157
-
158
- const localSignature = crypto
159
- .createHash("sha256")
160
- .update(dataToHash)
161
- .digest("hex");
162
-
163
- return localSignature === signature;
164
- }
165
126
  }
@@ -3,9 +3,7 @@ export declare class ServerSideUtils {
3
3
  private tropipay;
4
4
  constructor(tropipayInstance: Tropipay);
5
5
  /**
6
- * DEPRECATED Verify Topipay's signature on API V2 webhooks.
7
- * @deprecated This method is deprecated and will be removed in future versions.
8
- * Use the `verifySignatureV3` method from the `ServerSideUtils` class instead.
6
+ * Verify Topipay's signature on webhooks.
9
7
  * @param credentials Credential object or Tropipay instance
10
8
  * @param {String} originalCurrencyAmount
11
9
  * @param bankOrderCode
@@ -44,15 +42,4 @@ export declare class ServerSideUtils {
44
42
  * @return {Promise<string>} the valid base64 image
45
43
  */
46
44
  static isValidImage(base64Image: string): boolean;
47
- /**
48
- * Verify Topipay's signature on API V3 webhooks.
49
- * @param {string} originalCurrencyAmount
50
- * @param {string} bankOrderCode
51
- * @param {string} signature signaturev3 field to be verified
52
- * @returns {boolean}
53
- */
54
- static verifySignatureV3(credentials: {
55
- clientId: string;
56
- clientSecret: string;
57
- } | Tropipay, originalCurrencyAmount: string, bankOrderCode: string, signature: string): boolean;
58
45
  }