@wiicode/youcanpay-sdk 1.0.1 → 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 +320 -21
- package/dist/client.d.ts +2 -0
- package/dist/client.js +53 -6
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces/payment.interface.d.ts +19 -2
- package/dist/interfaces/token.interface.d.ts +1 -1
- package/dist/interfaces/transaction.interface.d.ts +2 -0
- package/dist/nestjs/pipes/webhook.pipe.d.ts +1 -1
- package/dist/nestjs/pipes/webhook.pipe.js +13 -6
- package/dist/nestjs/pipes/webhook.pipe.js.map +1 -1
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +8 -1
- package/dist/security/index.js.map +1 -1
- package/dist/security/validators.d.ts +12 -0
- package/dist/security/validators.js +117 -1
- package/dist/security/validators.js.map +1 -1
- package/dist/security/webhook.js +21 -3
- package/dist/security/webhook.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -1,14 +1,31 @@
|
|
|
1
1
|
# YouCanPay SDK
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@wiicode/youcanpay-sdk)
|
|
4
|
+
[](https://www.npmjs.com/package/@wiicode/youcanpay-sdk)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://github.com/eeyuub/youcanpay)
|
|
7
|
+
|
|
3
8
|
Production-ready Node.js SDK for [YouCanPay](https://youcanpay.com) - Morocco's payment gateway.
|
|
4
9
|
|
|
5
10
|
Works with **any Node.js framework** (Express, Fastify, Hapi) and has first-class **NestJS integration**.
|
|
6
11
|
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **Credit Card Payments** - Redirect to YouCanPay checkout or direct server-side processing
|
|
15
|
+
- **CashPlus Payments** - Generate payment codes for cash payments at 1,800+ CashPlus locations in Morocco
|
|
16
|
+
- **Webhook Handling** - Secure webhook verification and parsing
|
|
17
|
+
- **NestJS Integration** - First-class module, guards, and pipes
|
|
18
|
+
- **TypeScript** - Full type safety and IntelliSense support
|
|
19
|
+
- **Validation** - Built-in input validation and sanitization
|
|
20
|
+
|
|
7
21
|
## Table of Contents
|
|
8
22
|
|
|
9
23
|
- [Installation](#installation)
|
|
10
24
|
- [Environment Setup](#environment-setup)
|
|
11
25
|
- [Quick Start](#quick-start)
|
|
26
|
+
- [Payment Methods](#payment-methods)
|
|
27
|
+
- [Credit Card](#credit-card-payment)
|
|
28
|
+
- [CashPlus](#cashplus-payment)
|
|
12
29
|
- [Complete Payment Flow](#complete-payment-flow)
|
|
13
30
|
- [API Reference](#api-reference)
|
|
14
31
|
- [Webhook Handling](#webhook-handling)
|
|
@@ -82,12 +99,12 @@ const client = new YouCanPayClient({
|
|
|
82
99
|
// Create a payment
|
|
83
100
|
async function createPayment() {
|
|
84
101
|
const { token } = await client.createToken({
|
|
85
|
-
orderId: 'order-123',
|
|
86
102
|
amount: 50000, // 500.00 MAD (in centimes)
|
|
87
103
|
currency: CurrencyCode.MAD,
|
|
88
104
|
customerIp: '192.168.1.1',
|
|
89
105
|
successUrl: 'https://myapp.com/payment/success',
|
|
90
106
|
errorUrl: 'https://myapp.com/payment/error',
|
|
107
|
+
// orderId: 'order-123', // Optional - auto-generated UUID if not provided
|
|
91
108
|
});
|
|
92
109
|
|
|
93
110
|
// Redirect user to YouCanPay checkout
|
|
@@ -172,6 +189,62 @@ export class PaymentsService {
|
|
|
172
189
|
|
|
173
190
|
---
|
|
174
191
|
|
|
192
|
+
## Payment Methods
|
|
193
|
+
|
|
194
|
+
### Credit Card Payment
|
|
195
|
+
|
|
196
|
+
Redirect users to YouCanPay's secure checkout page:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
// 1. Create token
|
|
200
|
+
const { token } = await client.createToken({
|
|
201
|
+
amount: 50000, // 500.00 MAD
|
|
202
|
+
currency: CurrencyCode.MAD,
|
|
203
|
+
customerIp: '192.168.1.1',
|
|
204
|
+
successUrl: 'https://myapp.com/success',
|
|
205
|
+
errorUrl: 'https://myapp.com/error',
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// 2. Redirect to checkout
|
|
209
|
+
const paymentUrl = client.getPaymentUrl(token.id);
|
|
210
|
+
// => https://youcanpay.com/payment/token-id?lang=fr
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### CashPlus Payment
|
|
214
|
+
|
|
215
|
+
Generate a payment code for cash payments at 1,800+ CashPlus locations in Morocco:
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// 1. Create token (same as card)
|
|
219
|
+
const { token } = await client.createToken({
|
|
220
|
+
amount: 50000,
|
|
221
|
+
currency: CurrencyCode.MAD,
|
|
222
|
+
customerIp: '192.168.1.1',
|
|
223
|
+
successUrl: 'https://myapp.com/success',
|
|
224
|
+
errorUrl: 'https://myapp.com/error',
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// 2. Initialize CashPlus payment
|
|
228
|
+
const cashplus = await client.payWithCashPlus({
|
|
229
|
+
tokenId: token.id,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// 3. Display the code to customer
|
|
233
|
+
console.log('Payment Code:', cashplus.token); // e.g., "cp862603980"
|
|
234
|
+
console.log('Transaction ID:', cashplus.transaction_id);
|
|
235
|
+
|
|
236
|
+
// Customer takes this code to any CashPlus location to pay in cash
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**CashPlus Flow:**
|
|
240
|
+
1. Customer sees payment code (e.g., `cp862603980`)
|
|
241
|
+
2. Customer visits any CashPlus location in Morocco
|
|
242
|
+
3. Customer provides the code and pays in cash
|
|
243
|
+
4. YouCanPay sends webhook notification to your server
|
|
244
|
+
5. Your app confirms the payment
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
175
248
|
## Complete Payment Flow
|
|
176
249
|
|
|
177
250
|
```
|
|
@@ -312,11 +385,11 @@ Create a payment token.
|
|
|
312
385
|
|
|
313
386
|
```typescript
|
|
314
387
|
const { token } = await client.createToken({
|
|
315
|
-
orderId: string, // Your unique order ID
|
|
316
388
|
amount: number, // Amount in centimes (5000 = 50.00 MAD)
|
|
317
389
|
currency: CurrencyCode, // 'MAD' | 'USD' | 'EUR'
|
|
318
390
|
customerIp: string, // Customer's IP address
|
|
319
391
|
successUrl: string, // Redirect URL on success
|
|
392
|
+
orderId?: string, // Optional - auto-generated UUID if not provided
|
|
320
393
|
errorUrl?: string, // Redirect URL on error
|
|
321
394
|
customer?: { // Optional customer info
|
|
322
395
|
name?: string,
|
|
@@ -366,14 +439,33 @@ const result = await client.payWithCreditCard({
|
|
|
366
439
|
|
|
367
440
|
#### `payWithCashPlus(params): Promise<CashPlusPaymentResponse>`
|
|
368
441
|
|
|
369
|
-
Initialize CashPlus payment.
|
|
442
|
+
Initialize CashPlus payment. Returns a payment code that customers can use at any CashPlus location in Morocco.
|
|
370
443
|
|
|
371
444
|
```typescript
|
|
372
445
|
const result = await client.payWithCashPlus({
|
|
373
|
-
tokenId: string,
|
|
446
|
+
tokenId: string, // Token from createToken()
|
|
374
447
|
});
|
|
448
|
+
|
|
449
|
+
// Response
|
|
450
|
+
{
|
|
451
|
+
transaction_id: string,
|
|
452
|
+
token: string, // Payment code for CashPlus location (e.g., "cp115705252")
|
|
453
|
+
success?: boolean,
|
|
454
|
+
amount?: number, // Amount in centimes
|
|
455
|
+
currency?: string, // Currency code
|
|
456
|
+
order_id?: string, // Order reference
|
|
457
|
+
expires_at?: string, // Token expiration (ISO date)
|
|
458
|
+
message?: string, // Message from YouCanPay
|
|
459
|
+
}
|
|
375
460
|
```
|
|
376
461
|
|
|
462
|
+
**CashPlus Payment Flow:**
|
|
463
|
+
1. Create a payment token with `createToken()`
|
|
464
|
+
2. Initialize CashPlus payment with `payWithCashPlus()`
|
|
465
|
+
3. Display the `cashplus_token` to the customer
|
|
466
|
+
4. Customer visits any CashPlus location and provides the code
|
|
467
|
+
5. Once paid, YouCanPay sends a webhook notification
|
|
468
|
+
|
|
377
469
|
### Standalone Functions
|
|
378
470
|
|
|
379
471
|
#### Webhook Functions
|
|
@@ -414,6 +506,13 @@ import {
|
|
|
414
506
|
validateOrderId,
|
|
415
507
|
validateIP,
|
|
416
508
|
validateEmail,
|
|
509
|
+
validateTokenId,
|
|
510
|
+
validateTimeout,
|
|
511
|
+
validateClientOptions,
|
|
512
|
+
validateCardNumber,
|
|
513
|
+
validateExpiryDate,
|
|
514
|
+
validateCVV,
|
|
515
|
+
validateCardHolderName,
|
|
417
516
|
validatePaymentInput,
|
|
418
517
|
} from '@wiicode/youcanpay-sdk';
|
|
419
518
|
|
|
@@ -427,6 +526,13 @@ validateCurrency('GBP'); // { valid: false, error: 'Currency mus
|
|
|
427
526
|
|
|
428
527
|
validateRedirectURL('https://app.com'); // { valid: true }
|
|
429
528
|
validateRedirectURL('javascript:...'); // { valid: false }
|
|
529
|
+
validateTokenId('tok_123'); // { valid: true }
|
|
530
|
+
validateTimeout(30000); // { valid: true }
|
|
531
|
+
validateClientOptions({ privateKey: 'pri_x', publicKey: 'pub_x' }); // { valid: true }
|
|
532
|
+
validateCardNumber('4111111111111111'); // { valid: true }
|
|
533
|
+
validateExpiryDate('12/30'); // { valid: true }
|
|
534
|
+
validateCVV('123'); // { valid: true }
|
|
535
|
+
validateCardHolderName('Jane Doe'); // { valid: true }
|
|
430
536
|
|
|
431
537
|
// Validate all at once
|
|
432
538
|
const result = validatePaymentInput({
|
|
@@ -508,7 +614,7 @@ console.log(webhook.transactionId); // 'txn-uuid'
|
|
|
508
614
|
console.log(webhook.orderId); // 'your-order-id'
|
|
509
615
|
console.log(webhook.amount); // 50000
|
|
510
616
|
console.log(webhook.isSuccess); // true
|
|
511
|
-
console.log(webhook.status); // 'paid' | 'failed' | 'refunded'
|
|
617
|
+
console.log(webhook.status); // 'paid' | 'failed' | 'refunded' | 'unknown'
|
|
512
618
|
console.log(webhook.eventName); // 'transaction.paid'
|
|
513
619
|
```
|
|
514
620
|
|
|
@@ -525,6 +631,7 @@ import {
|
|
|
525
631
|
} from '@nestjs/common';
|
|
526
632
|
import {
|
|
527
633
|
ParseWebhookPipe,
|
|
634
|
+
ParsedWebhook,
|
|
528
635
|
ParsedWebhookPayload,
|
|
529
636
|
verifyWebhookSecret,
|
|
530
637
|
} from '@wiicode/youcanpay-sdk';
|
|
@@ -554,6 +661,15 @@ export class PaymentsController {
|
|
|
554
661
|
}
|
|
555
662
|
```
|
|
556
663
|
|
|
664
|
+
You can also inject the parsed payload directly:
|
|
665
|
+
|
|
666
|
+
```typescript
|
|
667
|
+
@Post('webhook')
|
|
668
|
+
handleWebhook(@ParsedWebhook() webhook: ParsedWebhookPayload) {
|
|
669
|
+
return { orderId: webhook.orderId };
|
|
670
|
+
}
|
|
671
|
+
```
|
|
672
|
+
|
|
557
673
|
### Webhook Security Checklist
|
|
558
674
|
|
|
559
675
|
- [ ] Add secret to webhook URL: `https://myapp.com/webhook?secret=xxx`
|
|
@@ -620,43 +736,211 @@ const payment = await prisma.payment.create({
|
|
|
620
736
|
});
|
|
621
737
|
```
|
|
622
738
|
|
|
623
|
-
### TypeORM Example
|
|
739
|
+
### TypeORM Example (Complete NestJS Entity)
|
|
624
740
|
|
|
625
741
|
```typescript
|
|
742
|
+
// payment-status.enum.ts
|
|
743
|
+
export enum PaymentStatus {
|
|
744
|
+
PENDING = 'PENDING',
|
|
745
|
+
COMPLETED = 'COMPLETED',
|
|
746
|
+
FAILED = 'FAILED',
|
|
747
|
+
REFUNDED = 'REFUNDED',
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// payment.entity.ts
|
|
751
|
+
import {
|
|
752
|
+
Entity,
|
|
753
|
+
PrimaryGeneratedColumn,
|
|
754
|
+
Column,
|
|
755
|
+
CreateDateColumn,
|
|
756
|
+
UpdateDateColumn,
|
|
757
|
+
ManyToOne,
|
|
758
|
+
JoinColumn,
|
|
759
|
+
} from 'typeorm';
|
|
760
|
+
import { PaymentStatus } from './payment-status.enum';
|
|
761
|
+
import { User } from '../users/user.entity';
|
|
762
|
+
|
|
626
763
|
@Entity('payments')
|
|
627
764
|
export class Payment {
|
|
628
765
|
@PrimaryGeneratedColumn('uuid')
|
|
629
766
|
id: string;
|
|
630
767
|
|
|
631
|
-
@Column({ unique: true })
|
|
768
|
+
@Column({ unique: true, name: 'order_id' })
|
|
632
769
|
orderId: string;
|
|
633
770
|
|
|
634
|
-
@Column({ nullable: true })
|
|
771
|
+
@Column({ nullable: true, name: 'token_id' })
|
|
635
772
|
tokenId: string;
|
|
636
773
|
|
|
637
|
-
@Column({ nullable: true })
|
|
774
|
+
@Column({ nullable: true, name: 'transaction_id' })
|
|
638
775
|
transactionId: string;
|
|
639
776
|
|
|
640
|
-
@Column()
|
|
641
|
-
amount: number;
|
|
777
|
+
@Column({ type: 'integer' })
|
|
778
|
+
amount: number; // In centimes
|
|
642
779
|
|
|
643
|
-
@Column()
|
|
780
|
+
@Column({ length: 3 })
|
|
644
781
|
currency: string;
|
|
645
782
|
|
|
646
|
-
@Column({
|
|
647
|
-
|
|
783
|
+
@Column({
|
|
784
|
+
type: 'enum',
|
|
785
|
+
enum: PaymentStatus,
|
|
786
|
+
default: PaymentStatus.PENDING,
|
|
787
|
+
})
|
|
788
|
+
status: PaymentStatus;
|
|
648
789
|
|
|
649
|
-
@Column()
|
|
790
|
+
@Column({ name: 'user_id' })
|
|
650
791
|
userId: string;
|
|
651
792
|
|
|
652
|
-
@
|
|
793
|
+
@ManyToOne(() => User, { onDelete: 'CASCADE' })
|
|
794
|
+
@JoinColumn({ name: 'user_id' })
|
|
795
|
+
user: User;
|
|
796
|
+
|
|
797
|
+
@Column({ type: 'jsonb', nullable: true })
|
|
798
|
+
metadata: Record<string, any>;
|
|
799
|
+
|
|
800
|
+
@Column({ nullable: true })
|
|
801
|
+
errorMessage: string;
|
|
802
|
+
|
|
803
|
+
@CreateDateColumn({ name: 'created_at' })
|
|
653
804
|
createdAt: Date;
|
|
654
805
|
|
|
655
|
-
@UpdateDateColumn()
|
|
806
|
+
@UpdateDateColumn({ name: 'updated_at' })
|
|
656
807
|
updatedAt: Date;
|
|
657
808
|
}
|
|
658
809
|
```
|
|
659
810
|
|
|
811
|
+
### Complete Payment Service Example
|
|
812
|
+
|
|
813
|
+
```typescript
|
|
814
|
+
// payments.service.ts
|
|
815
|
+
import { Injectable, NotFoundException } from '@nestjs/common';
|
|
816
|
+
import { InjectRepository } from '@nestjs/typeorm';
|
|
817
|
+
import { Repository } from 'typeorm';
|
|
818
|
+
import { YouCanPayService, CurrencyCode, parseWebhookPayload, ParsedWebhookPayload } from '@wiicode/youcanpay-sdk';
|
|
819
|
+
import { Payment } from './payment.entity';
|
|
820
|
+
import { PaymentStatus } from './payment-status.enum';
|
|
821
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
822
|
+
|
|
823
|
+
@Injectable()
|
|
824
|
+
export class PaymentsService {
|
|
825
|
+
constructor(
|
|
826
|
+
@InjectRepository(Payment)
|
|
827
|
+
private readonly paymentRepo: Repository<Payment>,
|
|
828
|
+
private readonly youcanpay: YouCanPayService,
|
|
829
|
+
) {}
|
|
830
|
+
|
|
831
|
+
async createPayment(userId: string, amount: number, currency: string, customerIp: string) {
|
|
832
|
+
const orderId = uuidv4();
|
|
833
|
+
|
|
834
|
+
// 1. Save payment to database
|
|
835
|
+
const payment = this.paymentRepo.create({
|
|
836
|
+
orderId,
|
|
837
|
+
amount,
|
|
838
|
+
currency,
|
|
839
|
+
userId,
|
|
840
|
+
status: PaymentStatus.PENDING,
|
|
841
|
+
});
|
|
842
|
+
await this.paymentRepo.save(payment);
|
|
843
|
+
|
|
844
|
+
// 2. Create token with YouCanPay
|
|
845
|
+
const { token } = await this.youcanpay.createToken({
|
|
846
|
+
orderId,
|
|
847
|
+
amount,
|
|
848
|
+
currency: currency as CurrencyCode,
|
|
849
|
+
customerIp,
|
|
850
|
+
successUrl: `${process.env.APP_URL}/payments/success`,
|
|
851
|
+
errorUrl: `${process.env.APP_URL}/payments/error`,
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
// 3. Update payment with token ID
|
|
855
|
+
payment.tokenId = token.id;
|
|
856
|
+
await this.paymentRepo.save(payment);
|
|
857
|
+
|
|
858
|
+
return {
|
|
859
|
+
paymentId: payment.id,
|
|
860
|
+
paymentUrl: this.youcanpay.getPaymentUrl(token.id),
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
async handleWebhook(payload: unknown) {
|
|
865
|
+
const webhook = parseWebhookPayload(payload);
|
|
866
|
+
|
|
867
|
+
const payment = await this.paymentRepo.findOne({
|
|
868
|
+
where: { orderId: webhook.orderId },
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
if (!payment) {
|
|
872
|
+
throw new NotFoundException('Payment not found');
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// Idempotency check
|
|
876
|
+
if (payment.status === PaymentStatus.COMPLETED) {
|
|
877
|
+
return { received: true, already_processed: true };
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// Update payment status
|
|
881
|
+
payment.transactionId = webhook.transactionId;
|
|
882
|
+
payment.status = webhook.isSuccess ? PaymentStatus.COMPLETED : PaymentStatus.FAILED;
|
|
883
|
+
await this.paymentRepo.save(payment);
|
|
884
|
+
|
|
885
|
+
return { received: true };
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
async getPaymentByOrderId(orderId: string) {
|
|
889
|
+
return this.paymentRepo.findOne({ where: { orderId } });
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
async getUserPayments(userId: string) {
|
|
893
|
+
return this.paymentRepo.find({
|
|
894
|
+
where: { userId },
|
|
895
|
+
order: { createdAt: 'DESC' },
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
### Mongoose Example (MongoDB)
|
|
902
|
+
|
|
903
|
+
```typescript
|
|
904
|
+
// payment.schema.ts
|
|
905
|
+
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
|
906
|
+
import { Document } from 'mongoose';
|
|
907
|
+
|
|
908
|
+
export enum PaymentStatus {
|
|
909
|
+
PENDING = 'PENDING',
|
|
910
|
+
COMPLETED = 'COMPLETED',
|
|
911
|
+
FAILED = 'FAILED',
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
@Schema({ timestamps: true })
|
|
915
|
+
export class Payment extends Document {
|
|
916
|
+
@Prop({ required: true, unique: true })
|
|
917
|
+
orderId: string;
|
|
918
|
+
|
|
919
|
+
@Prop()
|
|
920
|
+
tokenId: string;
|
|
921
|
+
|
|
922
|
+
@Prop()
|
|
923
|
+
transactionId: string;
|
|
924
|
+
|
|
925
|
+
@Prop({ required: true })
|
|
926
|
+
amount: number;
|
|
927
|
+
|
|
928
|
+
@Prop({ required: true })
|
|
929
|
+
currency: string;
|
|
930
|
+
|
|
931
|
+
@Prop({ enum: PaymentStatus, default: PaymentStatus.PENDING })
|
|
932
|
+
status: PaymentStatus;
|
|
933
|
+
|
|
934
|
+
@Prop({ required: true })
|
|
935
|
+
userId: string;
|
|
936
|
+
|
|
937
|
+
@Prop({ type: Object })
|
|
938
|
+
metadata: Record<string, any>;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
export const PaymentSchema = SchemaFactory.createForClass(Payment);
|
|
942
|
+
```
|
|
943
|
+
|
|
660
944
|
### Status Flow
|
|
661
945
|
|
|
662
946
|
```
|
|
@@ -710,6 +994,15 @@ if (!validation.valid) {
|
|
|
710
994
|
6. **HTTPS only** - Never use HTTP in production
|
|
711
995
|
7. **Sanitize inputs** - Use `sanitizeString()` for user inputs
|
|
712
996
|
8. **URL whitelist** - Validate redirect URLs against allowed domains
|
|
997
|
+
9. **Pre-validate outbound requests** - The SDK rejects invalid client, token, and card input before any network call
|
|
998
|
+
|
|
999
|
+
### Production Checklist
|
|
1000
|
+
|
|
1001
|
+
- Complete a full sandbox checkout plus webhook round-trip before enabling live keys.
|
|
1002
|
+
- Restrict redirect URLs to your own domains with `validateRedirectURL(..., { allowedDomains: [...] })`.
|
|
1003
|
+
- Store webhook event IDs in your application database and enforce idempotency there.
|
|
1004
|
+
- Monitor failed `getTransaction()` and webhook-verification paths.
|
|
1005
|
+
- Rotate API keys and webhook secrets regularly.
|
|
713
1006
|
|
|
714
1007
|
---
|
|
715
1008
|
|
|
@@ -725,7 +1018,7 @@ try {
|
|
|
725
1018
|
} catch (error) {
|
|
726
1019
|
if (error instanceof YouCanPayError) {
|
|
727
1020
|
console.log(error.code); // ErrorCodes.VALIDATION_ERROR
|
|
728
|
-
console.log(error.
|
|
1021
|
+
console.log(error.statusCode); // 422
|
|
729
1022
|
console.log(error.message); // "The amount field is required"
|
|
730
1023
|
console.log(error.details); // { amount: ['required'] }
|
|
731
1024
|
}
|
|
@@ -752,7 +1045,7 @@ export class YouCanPayExceptionFilter implements ExceptionFilter {
|
|
|
752
1045
|
catch(exception: YouCanPayError, host: ArgumentsHost) {
|
|
753
1046
|
const response = host.switchToHttp().getResponse();
|
|
754
1047
|
|
|
755
|
-
response.status(exception.
|
|
1048
|
+
response.status(exception.statusCode || 500).json({
|
|
756
1049
|
error: exception.code,
|
|
757
1050
|
message: exception.message,
|
|
758
1051
|
});
|
|
@@ -840,9 +1133,15 @@ interface YouCanPayOptions {
|
|
|
840
1133
|
|
|
841
1134
|
## Support
|
|
842
1135
|
|
|
843
|
-
- [
|
|
1136
|
+
- [GitHub Repository](https://github.com/eeyuub/youcanpay)
|
|
1137
|
+
- [Report Issues](https://github.com/eeyuub/youcanpay/issues)
|
|
844
1138
|
- [npm Package](https://www.npmjs.com/package/@wiicode/youcanpay-sdk)
|
|
1139
|
+
- [YouCanPay Documentation](https://youcanpay.com/docs)
|
|
1140
|
+
|
|
1141
|
+
## Author
|
|
1142
|
+
|
|
1143
|
+
**WiiCode** - [@eeyuub](https://github.com/eeyuub)
|
|
845
1144
|
|
|
846
1145
|
## License
|
|
847
1146
|
|
|
848
|
-
MIT
|
|
1147
|
+
MIT - see [LICENSE](https://github.com/eeyuub/youcanpay/blob/master/LICENSE) for details.
|
package/dist/client.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Lang } from './enums';
|
|
|
2
2
|
import { YouCanPayOptions, CreateTokenParams, TokenResponse, PayCreditCardParams, PaymentResponse, PayCashPlusParams, CashPlusPaymentResponse, Transaction } from './interfaces';
|
|
3
3
|
export declare class YouCanPayClient {
|
|
4
4
|
private readonly http;
|
|
5
|
+
private readonly transactionHttp;
|
|
5
6
|
private readonly sandbox;
|
|
6
7
|
private readonly privateKey;
|
|
7
8
|
private readonly publicKey;
|
|
@@ -14,4 +15,5 @@ export declare class YouCanPayClient {
|
|
|
14
15
|
getTransaction(transactionId: string): Promise<Transaction>;
|
|
15
16
|
verifyWebhook(payload: unknown): boolean;
|
|
16
17
|
private handleError;
|
|
18
|
+
private assertValid;
|
|
17
19
|
}
|
package/dist/client.js
CHANGED
|
@@ -5,12 +5,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.YouCanPayClient = void 0;
|
|
7
7
|
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const crypto_1 = require("crypto");
|
|
8
9
|
const constants_1 = require("./constants");
|
|
9
10
|
const errors_1 = require("./errors");
|
|
10
11
|
const enums_1 = require("./enums");
|
|
11
12
|
const logger_1 = require("./logging/logger");
|
|
13
|
+
const webhook_1 = require("./security/webhook");
|
|
14
|
+
const validators_1 = require("./security/validators");
|
|
12
15
|
class YouCanPayClient {
|
|
13
16
|
constructor(options) {
|
|
17
|
+
this.assertValid((0, validators_1.validateClientOptions)({
|
|
18
|
+
privateKey: options.privateKey,
|
|
19
|
+
publicKey: options.publicKey,
|
|
20
|
+
timeout: options.timeout,
|
|
21
|
+
}));
|
|
14
22
|
this.sandbox = options.sandbox ?? false;
|
|
15
23
|
this.privateKey = options.privateKey;
|
|
16
24
|
this.publicKey = options.publicKey;
|
|
@@ -22,13 +30,27 @@ class YouCanPayClient {
|
|
|
22
30
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
23
31
|
},
|
|
24
32
|
});
|
|
33
|
+
this.transactionHttp = axios_1.default.create({
|
|
34
|
+
baseURL: this.sandbox ? constants_1.YOUCANPAY_SANDBOX_TRANSACTION_API_URL : constants_1.YOUCANPAY_TRANSACTION_API_URL,
|
|
35
|
+
timeout: options.timeout ?? 30000,
|
|
36
|
+
});
|
|
25
37
|
}
|
|
26
38
|
async createToken(params) {
|
|
27
39
|
const startTime = Date.now();
|
|
40
|
+
const orderId = params.orderId ?? (0, crypto_1.randomUUID)();
|
|
41
|
+
this.assertValid((0, validators_1.validatePaymentInput)({
|
|
42
|
+
amount: params.amount,
|
|
43
|
+
currency: params.currency,
|
|
44
|
+
orderId,
|
|
45
|
+
customerIp: params.customerIp,
|
|
46
|
+
successUrl: params.successUrl,
|
|
47
|
+
errorUrl: params.errorUrl,
|
|
48
|
+
customerEmail: params.customer?.email,
|
|
49
|
+
}));
|
|
28
50
|
const body = new URLSearchParams();
|
|
29
51
|
body.append('pri_key', this.privateKey);
|
|
30
52
|
body.append('pub_key', this.publicKey);
|
|
31
|
-
body.append('order_id',
|
|
53
|
+
body.append('order_id', orderId);
|
|
32
54
|
body.append('amount', String(params.amount));
|
|
33
55
|
body.append('currency', params.currency);
|
|
34
56
|
body.append('customer_ip', params.customerIp);
|
|
@@ -62,11 +84,11 @@ class YouCanPayClient {
|
|
|
62
84
|
try {
|
|
63
85
|
const response = await this.http.post('/tokenize', body);
|
|
64
86
|
const data = response.data;
|
|
65
|
-
await this.logger.log('createToken', { orderId
|
|
87
|
+
await this.logger.log('createToken', { orderId, amount: params.amount, currency: params.currency }, data, 'success', Date.now() - startTime, params.metadata);
|
|
66
88
|
return data;
|
|
67
89
|
}
|
|
68
90
|
catch (error) {
|
|
69
|
-
await this.logger.log('createToken', { orderId
|
|
91
|
+
await this.logger.log('createToken', { orderId, amount: params.amount, currency: params.currency }, undefined, 'error', Date.now() - startTime);
|
|
70
92
|
throw this.handleError(error);
|
|
71
93
|
}
|
|
72
94
|
}
|
|
@@ -76,6 +98,11 @@ class YouCanPayClient {
|
|
|
76
98
|
}
|
|
77
99
|
async payWithCreditCard(params) {
|
|
78
100
|
const startTime = Date.now();
|
|
101
|
+
this.assertValid((0, validators_1.validateTokenId)(params.tokenId));
|
|
102
|
+
this.assertValid((0, validators_1.validateCardNumber)(params.creditCard));
|
|
103
|
+
this.assertValid((0, validators_1.validateExpiryDate)(params.expireDate));
|
|
104
|
+
this.assertValid((0, validators_1.validateCVV)(params.cvv));
|
|
105
|
+
this.assertValid((0, validators_1.validateCardHolderName)(params.cardHolderName));
|
|
79
106
|
const body = new URLSearchParams();
|
|
80
107
|
body.append('pri_key', this.privateKey);
|
|
81
108
|
body.append('pub_key', this.publicKey);
|
|
@@ -97,13 +124,21 @@ class YouCanPayClient {
|
|
|
97
124
|
}
|
|
98
125
|
async payWithCashPlus(params) {
|
|
99
126
|
const startTime = Date.now();
|
|
127
|
+
this.assertValid((0, validators_1.validateTokenId)(params.tokenId));
|
|
100
128
|
const body = new URLSearchParams();
|
|
101
129
|
body.append('pri_key', this.privateKey);
|
|
102
130
|
body.append('pub_key', this.publicKey);
|
|
103
131
|
body.append('token_id', params.tokenId);
|
|
132
|
+
body.append('payment_method[type]', 'cashplus');
|
|
104
133
|
try {
|
|
105
134
|
const response = await this.http.post('/cashplus/init', body);
|
|
106
|
-
const
|
|
135
|
+
const rawData = response.data;
|
|
136
|
+
const cashplusToken = typeof rawData.token === 'string' ? rawData.token : rawData.token?.id;
|
|
137
|
+
const data = {
|
|
138
|
+
...rawData,
|
|
139
|
+
token: cashplusToken,
|
|
140
|
+
transaction_id: rawData.transaction_id || '',
|
|
141
|
+
};
|
|
107
142
|
await this.logger.log('payWithCashPlus', { tokenId: params.tokenId }, data, 'success', Date.now() - startTime);
|
|
108
143
|
return data;
|
|
109
144
|
}
|
|
@@ -114,8 +149,9 @@ class YouCanPayClient {
|
|
|
114
149
|
}
|
|
115
150
|
async getTransaction(transactionId) {
|
|
116
151
|
const startTime = Date.now();
|
|
152
|
+
this.assertValid((0, validators_1.validateTokenId)(transactionId));
|
|
117
153
|
try {
|
|
118
|
-
const response = await this.
|
|
154
|
+
const response = await this.transactionHttp.get(`/transactions/${transactionId}`, {
|
|
119
155
|
params: { pri_key: this.privateKey },
|
|
120
156
|
});
|
|
121
157
|
const data = response.data;
|
|
@@ -131,10 +167,16 @@ class YouCanPayClient {
|
|
|
131
167
|
if (!payload || typeof payload !== 'object') {
|
|
132
168
|
return false;
|
|
133
169
|
}
|
|
170
|
+
try {
|
|
171
|
+
(0, webhook_1.parseWebhookPayload)(payload);
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
}
|
|
134
176
|
const p = payload;
|
|
135
177
|
return (typeof p['transaction_id'] === 'string' &&
|
|
136
178
|
typeof p['order_id'] === 'string' &&
|
|
137
|
-
typeof p['amount'] === 'number' &&
|
|
179
|
+
(typeof p['amount'] === 'number' || typeof p['amount'] === 'string') &&
|
|
138
180
|
typeof p['status'] === 'string');
|
|
139
181
|
}
|
|
140
182
|
handleError(error) {
|
|
@@ -166,6 +208,11 @@ class YouCanPayClient {
|
|
|
166
208
|
const message = error instanceof Error ? error.message : 'An unknown error occurred';
|
|
167
209
|
return new errors_1.YouCanPayError(message, errors_1.ErrorCodes.UNKNOWN_ERROR);
|
|
168
210
|
}
|
|
211
|
+
assertValid(result) {
|
|
212
|
+
if (!result.valid) {
|
|
213
|
+
throw new errors_1.YouCanPayError(result.error ?? 'Invalid input', errors_1.ErrorCodes.VALIDATION_ERROR);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
169
216
|
}
|
|
170
217
|
exports.YouCanPayClient = YouCanPayClient;
|
|
171
218
|
//# sourceMappingURL=client.js.map
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":";;;;;;AAAA,kDAA6C;AAC7C,
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|