@waffo/waffo-integrate 1.0.0
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/LICENSE +21 -0
- package/README.md +98 -0
- package/SKILL.md +462 -0
- package/bin/install.js +85 -0
- package/docs/INDEX.md +50 -0
- package/package.json +29 -0
- package/references/api-contract.md +539 -0
- package/references/go.md +688 -0
- package/references/java.md +615 -0
- package/references/node.md +576 -0
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
# Node.js Integration Templates
|
|
2
|
+
|
|
3
|
+
## Package
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
@waffo/waffo-node
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## SDK Initialization
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// src/config/waffo.ts
|
|
13
|
+
import { Waffo, Environment } from '@waffo/waffo-node';
|
|
14
|
+
|
|
15
|
+
let waffoInstance: Waffo | null = null;
|
|
16
|
+
|
|
17
|
+
export function getWaffo(): Waffo {
|
|
18
|
+
if (!waffoInstance) {
|
|
19
|
+
waffoInstance = new Waffo({
|
|
20
|
+
apiKey: process.env.WAFFO_API_KEY!,
|
|
21
|
+
privateKey: process.env.WAFFO_PRIVATE_KEY!,
|
|
22
|
+
waffoPublicKey: process.env.WAFFO_PUBLIC_KEY!,
|
|
23
|
+
environment: process.env.NODE_ENV === 'production'
|
|
24
|
+
? Environment.PRODUCTION
|
|
25
|
+
: Environment.SANDBOX,
|
|
26
|
+
merchantId: process.env.WAFFO_MERCHANT_ID!,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return waffoInstance;
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Alternatively, use the built-in env loader:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { Waffo } from '@waffo/waffo-node';
|
|
37
|
+
const waffo = Waffo.fromEnv();
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Environment variables for `fromEnv()`:
|
|
41
|
+
- `WAFFO_API_KEY`
|
|
42
|
+
- `WAFFO_PRIVATE_KEY`
|
|
43
|
+
- `WAFFO_PUBLIC_KEY`
|
|
44
|
+
- `WAFFO_ENVIRONMENT` (SANDBOX or PRODUCTION)
|
|
45
|
+
- `WAFFO_MERCHANT_ID`
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Order Payment Service
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// src/services/payment-service.ts
|
|
53
|
+
import { getWaffo } from '../config/waffo';
|
|
54
|
+
import { randomUUID } from 'crypto';
|
|
55
|
+
|
|
56
|
+
/** Generate a 32-char request ID (UUID without dashes, max length 32) */
|
|
57
|
+
function genRequestId(): string {
|
|
58
|
+
return randomUUID().replace(/-/g, '');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface CreatePaymentInput {
|
|
62
|
+
merchantOrderId: string;
|
|
63
|
+
amount: string;
|
|
64
|
+
currency: string;
|
|
65
|
+
description: string;
|
|
66
|
+
notifyUrl: string;
|
|
67
|
+
successRedirectUrl: string; // URL to redirect after payment
|
|
68
|
+
userId: string;
|
|
69
|
+
userEmail: string;
|
|
70
|
+
userTerminal?: string; // WEB | APP | WAP | SYSTEM (default: WEB)
|
|
71
|
+
payMethodType?: string; // e.g., 'CREDITCARD', 'EWALLET'
|
|
72
|
+
payMethodName?: string; // e.g., 'CC_VISA', 'DANA'
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function createPayment(input: CreatePaymentInput) {
|
|
76
|
+
const waffo = getWaffo();
|
|
77
|
+
const response = await waffo.order().create({
|
|
78
|
+
paymentRequestId: genRequestId(),
|
|
79
|
+
merchantOrderId: input.merchantOrderId,
|
|
80
|
+
orderCurrency: input.currency,
|
|
81
|
+
orderAmount: input.amount,
|
|
82
|
+
orderDescription: input.description,
|
|
83
|
+
notifyUrl: input.notifyUrl,
|
|
84
|
+
successRedirectUrl: input.successRedirectUrl,
|
|
85
|
+
userInfo: {
|
|
86
|
+
userId: input.userId,
|
|
87
|
+
userEmail: input.userEmail,
|
|
88
|
+
userTerminal: input.userTerminal || 'WEB',
|
|
89
|
+
},
|
|
90
|
+
paymentInfo: {
|
|
91
|
+
productName: 'ONE_TIME_PAYMENT',
|
|
92
|
+
...(input.payMethodType && { payMethodType: input.payMethodType }),
|
|
93
|
+
...(input.payMethodName && { payMethodName: input.payMethodName }),
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (!response.isSuccess()) {
|
|
98
|
+
throw new Error(`Payment creation failed: ${response.getCode()} - ${response.getMessage()}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return response.getData();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function queryOrder(paymentRequestId: string) {
|
|
105
|
+
const waffo = getWaffo();
|
|
106
|
+
const response = await waffo.order().inquiry({ paymentRequestId });
|
|
107
|
+
|
|
108
|
+
if (!response.isSuccess()) {
|
|
109
|
+
throw new Error(`Order inquiry failed: ${response.getCode()} - ${response.getMessage()}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return response.getData();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function cancelOrder(paymentRequestId: string) {
|
|
116
|
+
const waffo = getWaffo();
|
|
117
|
+
const response = await waffo.order().cancel({ paymentRequestId });
|
|
118
|
+
|
|
119
|
+
if (!response.isSuccess()) {
|
|
120
|
+
throw new Error(`Order cancel failed: ${response.getCode()} - ${response.getMessage()}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return response.getData();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function captureOrder(paymentRequestId: string, amount: string, currency: string) {
|
|
127
|
+
const waffo = getWaffo();
|
|
128
|
+
const response = await waffo.order().capture({
|
|
129
|
+
paymentRequestId,
|
|
130
|
+
captureAmount: amount,
|
|
131
|
+
captureCurrency: currency,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (!response.isSuccess()) {
|
|
135
|
+
throw new Error(`Order capture failed: ${response.getCode()} - ${response.getMessage()}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return response.getData();
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Refund Service
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
// src/services/refund-service.ts
|
|
148
|
+
import { getWaffo } from '../config/waffo';
|
|
149
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
150
|
+
|
|
151
|
+
export async function refundOrder(
|
|
152
|
+
origPaymentRequestId: string,
|
|
153
|
+
refundAmount: string,
|
|
154
|
+
refundReason?: string,
|
|
155
|
+
) {
|
|
156
|
+
const waffo = getWaffo();
|
|
157
|
+
const response = await waffo.order().refund({
|
|
158
|
+
refundRequestId: uuidv4(),
|
|
159
|
+
origPaymentRequestId,
|
|
160
|
+
refundAmount,
|
|
161
|
+
refundReason,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
if (!response.isSuccess()) {
|
|
165
|
+
throw new Error(`Refund failed: ${response.getCode()} - ${response.getMessage()}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return response.getData();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export async function queryRefund(refundRequestId: string) {
|
|
172
|
+
const waffo = getWaffo();
|
|
173
|
+
const response = await waffo.refund().inquiry({ refundRequestId });
|
|
174
|
+
|
|
175
|
+
if (!response.isSuccess()) {
|
|
176
|
+
throw new Error(`Refund inquiry failed: ${response.getCode()} - ${response.getMessage()}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return response.getData();
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Subscription Service
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
// src/services/subscription-service.ts
|
|
189
|
+
import { getWaffo } from '../config/waffo';
|
|
190
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
191
|
+
|
|
192
|
+
export interface CreateSubscriptionInput {
|
|
193
|
+
merchantSubscriptionId: string;
|
|
194
|
+
amount: string;
|
|
195
|
+
currency: string;
|
|
196
|
+
description: string;
|
|
197
|
+
notifyUrl: string;
|
|
198
|
+
userId: string;
|
|
199
|
+
userEmail: string;
|
|
200
|
+
productId: string;
|
|
201
|
+
productName: string;
|
|
202
|
+
periodType: 'DAILY' | 'WEEKLY' | 'MONTHLY';
|
|
203
|
+
periodInterval: string; // e.g., '1' for every period
|
|
204
|
+
goodsUrl: string;
|
|
205
|
+
successRedirectUrl: string;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function createSubscription(input: CreateSubscriptionInput) {
|
|
209
|
+
const waffo = getWaffo();
|
|
210
|
+
const response = await waffo.subscription().create({
|
|
211
|
+
subscriptionRequest: uuidv4(),
|
|
212
|
+
merchantSubscriptionId: input.merchantSubscriptionId,
|
|
213
|
+
currency: input.currency,
|
|
214
|
+
amount: input.amount,
|
|
215
|
+
orderDescription: input.description,
|
|
216
|
+
notifyUrl: input.notifyUrl,
|
|
217
|
+
successRedirectUrl: input.successRedirectUrl,
|
|
218
|
+
productInfo: {
|
|
219
|
+
description: input.productName,
|
|
220
|
+
periodType: input.periodType,
|
|
221
|
+
periodInterval: input.periodInterval,
|
|
222
|
+
},
|
|
223
|
+
userInfo: {
|
|
224
|
+
userId: input.userId,
|
|
225
|
+
userEmail: input.userEmail,
|
|
226
|
+
userTerminal: input.userTerminal || 'WEB', // WEB for PC, APP for mobile/tablet
|
|
227
|
+
},
|
|
228
|
+
goodsInfo: {
|
|
229
|
+
goodsId: input.productId,
|
|
230
|
+
goodsName: input.productName,
|
|
231
|
+
goodsUrl: input.goodsUrl,
|
|
232
|
+
},
|
|
233
|
+
paymentInfo: {
|
|
234
|
+
productName: 'SUBSCRIPTION',
|
|
235
|
+
payMethodType: 'CREDITCARD,DEBITCARD,APPLEPAY,GOOGLEPAY',
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
if (!response.isSuccess()) {
|
|
240
|
+
throw new Error(`Subscription creation failed: ${response.getCode()} - ${response.getMessage()}`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return response.getData();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export async function querySubscription(subscriptionRequest: string) {
|
|
247
|
+
const waffo = getWaffo();
|
|
248
|
+
const response = await waffo.subscription().inquiry({ subscriptionRequest });
|
|
249
|
+
|
|
250
|
+
if (!response.isSuccess()) {
|
|
251
|
+
throw new Error(`Subscription inquiry failed: ${response.getCode()} - ${response.getMessage()}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return response.getData();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export async function cancelSubscription(subscriptionRequest: string) {
|
|
258
|
+
const waffo = getWaffo();
|
|
259
|
+
const response = await waffo.subscription().cancel({ subscriptionRequest });
|
|
260
|
+
|
|
261
|
+
if (!response.isSuccess()) {
|
|
262
|
+
throw new Error(`Subscription cancel failed: ${response.getCode()} - ${response.getMessage()}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return response.getData();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Get subscription management URL.
|
|
270
|
+
* Note: manage() only works when subscription is ACTIVE (after payment).
|
|
271
|
+
* Type assertion needed due to SDK type limitation.
|
|
272
|
+
*/
|
|
273
|
+
export async function manageSubscription(subscriptionRequest: string) {
|
|
274
|
+
const waffo = getWaffo();
|
|
275
|
+
const response = await waffo.subscription().manage({ subscriptionRequest } as any);
|
|
276
|
+
|
|
277
|
+
if (!response.isSuccess()) {
|
|
278
|
+
throw new Error(`Subscription manage failed: ${response.getCode()} - ${response.getMessage()}`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const data = response.getData() as any;
|
|
282
|
+
return data?.managementUrl;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export interface ChangeSubscriptionInput {
|
|
286
|
+
originSubscriptionRequest: string;
|
|
287
|
+
remainingAmount: string;
|
|
288
|
+
currency: string;
|
|
289
|
+
notifyUrl: string;
|
|
290
|
+
userId: string;
|
|
291
|
+
userEmail: string;
|
|
292
|
+
newProductName: string;
|
|
293
|
+
periodType: 'DAILY' | 'WEEKLY' | 'MONTHLY';
|
|
294
|
+
periodInterval: string;
|
|
295
|
+
newAmount: string;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export async function changeSubscription(input: ChangeSubscriptionInput) {
|
|
299
|
+
const waffo = getWaffo();
|
|
300
|
+
const response = await waffo.subscription().change({
|
|
301
|
+
subscriptionRequest: uuidv4(),
|
|
302
|
+
originSubscriptionRequest: input.originSubscriptionRequest,
|
|
303
|
+
remainingAmount: input.remainingAmount,
|
|
304
|
+
currency: input.currency,
|
|
305
|
+
notifyUrl: input.notifyUrl,
|
|
306
|
+
productInfoList: [{
|
|
307
|
+
description: input.newProductName,
|
|
308
|
+
periodType: input.periodType,
|
|
309
|
+
periodInterval: input.periodInterval,
|
|
310
|
+
amount: input.newAmount,
|
|
311
|
+
}],
|
|
312
|
+
userInfo: {
|
|
313
|
+
userId: input.userId,
|
|
314
|
+
userEmail: input.userEmail,
|
|
315
|
+
userTerminal: input.userTerminal || 'WEB',
|
|
316
|
+
},
|
|
317
|
+
goodsInfo: {
|
|
318
|
+
goodsId: 'subscription',
|
|
319
|
+
goodsName: input.newProductName,
|
|
320
|
+
},
|
|
321
|
+
paymentInfo: {
|
|
322
|
+
productName: 'SUBSCRIPTION',
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
if (!response.isSuccess()) {
|
|
327
|
+
throw new Error(`Subscription change failed: ${response.getCode()} - ${response.getMessage()}`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return response.getData();
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Webhook Handler
|
|
337
|
+
|
|
338
|
+
### Express
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
// src/webhooks/waffo-webhook.ts
|
|
342
|
+
import express from 'express';
|
|
343
|
+
import { getWaffo } from '../config/waffo';
|
|
344
|
+
|
|
345
|
+
const router = express.Router();
|
|
346
|
+
|
|
347
|
+
// Use raw body parser for webhook signature verification
|
|
348
|
+
router.post('/waffo/webhook',
|
|
349
|
+
express.raw({ type: 'application/json' }),
|
|
350
|
+
async (req, res) => {
|
|
351
|
+
const waffo = getWaffo();
|
|
352
|
+
const body = req.body.toString();
|
|
353
|
+
const signature = req.headers['x-signature'] as string;
|
|
354
|
+
|
|
355
|
+
const handler = waffo.webhook()
|
|
356
|
+
.onPayment((notification) => {
|
|
357
|
+
const result = notification.result;
|
|
358
|
+
console.log(`Payment ${result?.orderStatus}: orderId=${result?.acquiringOrderId}`);
|
|
359
|
+
// TODO: Update your order status in database
|
|
360
|
+
})
|
|
361
|
+
.onRefund((notification) => {
|
|
362
|
+
const result = notification.result;
|
|
363
|
+
console.log(`Refund ${result?.refundStatus}: refundId=${result?.acquiringRefundOrderId}`);
|
|
364
|
+
// TODO: Update your refund status in database
|
|
365
|
+
})
|
|
366
|
+
.onSubscriptionStatus((notification) => {
|
|
367
|
+
const result = notification.result;
|
|
368
|
+
console.log(`Subscription ${result?.subscriptionStatus}: subId=${result?.subscriptionId}`);
|
|
369
|
+
// TODO: Update your subscription status in database
|
|
370
|
+
})
|
|
371
|
+
.onSubscriptionPeriodChanged((notification) => {
|
|
372
|
+
const result = notification.result;
|
|
373
|
+
console.log(`Period changed: subId=${result?.subscriptionId}`);
|
|
374
|
+
// TODO: Record billing period result
|
|
375
|
+
})
|
|
376
|
+
.onSubscriptionChange((notification) => {
|
|
377
|
+
const result = notification.result;
|
|
378
|
+
console.log(`Subscription change ${result?.subscriptionChangeStatus}`);
|
|
379
|
+
// TODO: Handle subscription upgrade/downgrade result
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const webhookResult = await handler.handleWebhook(body, signature);
|
|
383
|
+
|
|
384
|
+
res.setHeader('X-SIGNATURE', webhookResult.responseSignature);
|
|
385
|
+
res.status(200).send(webhookResult.responseBody);
|
|
386
|
+
}
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
export default router;
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### NestJS
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
// src/webhooks/waffo-webhook.controller.ts
|
|
396
|
+
import { Controller, Post, Req, Res, RawBodyRequest } from '@nestjs/common';
|
|
397
|
+
import { Request, Response } from 'express';
|
|
398
|
+
import { getWaffo } from '../config/waffo';
|
|
399
|
+
|
|
400
|
+
@Controller('waffo')
|
|
401
|
+
export class WaffoWebhookController {
|
|
402
|
+
@Post('webhook')
|
|
403
|
+
async handleWebhook(
|
|
404
|
+
@Req() req: RawBodyRequest<Request>,
|
|
405
|
+
@Res() res: Response,
|
|
406
|
+
) {
|
|
407
|
+
const waffo = getWaffo();
|
|
408
|
+
const body = req.rawBody?.toString() ?? '';
|
|
409
|
+
const signature = req.headers['x-signature'] as string;
|
|
410
|
+
|
|
411
|
+
const handler = waffo.webhook()
|
|
412
|
+
.onPayment((notification) => {
|
|
413
|
+
// TODO: Handle payment notification
|
|
414
|
+
})
|
|
415
|
+
.onRefund((notification) => {
|
|
416
|
+
// TODO: Handle refund notification
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
const result = await handler.handleWebhook(body, signature);
|
|
420
|
+
|
|
421
|
+
res.setHeader('X-SIGNATURE', result.responseSignature);
|
|
422
|
+
res.status(200).send(result.responseBody);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Fastify
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
// src/webhooks/waffo-webhook.ts
|
|
431
|
+
import { FastifyInstance } from 'fastify';
|
|
432
|
+
import { getWaffo } from '../config/waffo';
|
|
433
|
+
|
|
434
|
+
export async function waffoWebhookRoute(fastify: FastifyInstance) {
|
|
435
|
+
fastify.addContentTypeParser(
|
|
436
|
+
'application/json',
|
|
437
|
+
{ parseAs: 'string' },
|
|
438
|
+
(req, body, done) => done(null, body),
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
fastify.post('/waffo/webhook', async (request, reply) => {
|
|
442
|
+
const waffo = getWaffo();
|
|
443
|
+
const body = request.body as string;
|
|
444
|
+
const signature = request.headers['x-signature'] as string;
|
|
445
|
+
|
|
446
|
+
const handler = waffo.webhook()
|
|
447
|
+
.onPayment((notification) => {
|
|
448
|
+
// TODO: Handle payment notification
|
|
449
|
+
})
|
|
450
|
+
.onRefund((notification) => {
|
|
451
|
+
// TODO: Handle refund notification
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
const result = await handler.handleWebhook(body, signature);
|
|
455
|
+
|
|
456
|
+
reply.header('X-SIGNATURE', result.responseSignature);
|
|
457
|
+
reply.status(200).send(result.responseBody);
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
## Test Template (Sandbox Integration)
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
// tests/payment.test.ts
|
|
468
|
+
import { describe, it, expect } from 'vitest'; // or jest
|
|
469
|
+
import { Waffo, Environment } from '@waffo/waffo-node';
|
|
470
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
471
|
+
|
|
472
|
+
const HAS_CREDENTIALS = !!(
|
|
473
|
+
process.env.WAFFO_API_KEY &&
|
|
474
|
+
process.env.WAFFO_PRIVATE_KEY &&
|
|
475
|
+
process.env.WAFFO_PUBLIC_KEY &&
|
|
476
|
+
process.env.WAFFO_MERCHANT_ID
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
const conditionalIt = HAS_CREDENTIALS ? it : it.skip;
|
|
480
|
+
|
|
481
|
+
function createWaffo(): Waffo {
|
|
482
|
+
return new Waffo({
|
|
483
|
+
apiKey: process.env.WAFFO_API_KEY!,
|
|
484
|
+
privateKey: process.env.WAFFO_PRIVATE_KEY!,
|
|
485
|
+
waffoPublicKey: process.env.WAFFO_PUBLIC_KEY!,
|
|
486
|
+
environment: Environment.SANDBOX,
|
|
487
|
+
merchantId: process.env.WAFFO_MERCHANT_ID!,
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
describe('Waffo Payment Integration', () => {
|
|
492
|
+
conditionalIt('creates a payment order', async () => {
|
|
493
|
+
const waffo = createWaffo();
|
|
494
|
+
const paymentRequestId = uuidv4();
|
|
495
|
+
|
|
496
|
+
const response = await waffo.order().create({
|
|
497
|
+
paymentRequestId,
|
|
498
|
+
merchantOrderId: `test-${Date.now()}`,
|
|
499
|
+
orderCurrency: 'USD',
|
|
500
|
+
orderAmount: '1.00',
|
|
501
|
+
orderDescription: 'Integration test order',
|
|
502
|
+
notifyUrl: 'https://example.com/webhook',
|
|
503
|
+
userInfo: { userId: 'test-user', userEmail: 'test@example.com' },
|
|
504
|
+
paymentInfo: { productName: 'Test' },
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
if (!response.isSuccess()) {
|
|
508
|
+
console.error(`Create order failed: paymentRequestId=${paymentRequestId}, ` +
|
|
509
|
+
`code=${response.getCode()}, message=${response.getMessage()}, ` +
|
|
510
|
+
`data=${JSON.stringify(response.getData())}`);
|
|
511
|
+
}
|
|
512
|
+
expect(response.isSuccess()).toBe(true);
|
|
513
|
+
|
|
514
|
+
const data = response.getData();
|
|
515
|
+
expect(data?.acquiringOrderId).toBeTruthy();
|
|
516
|
+
console.log(`Order created: acquiringOrderId=${data?.acquiringOrderId}`);
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
conditionalIt('queries an order', async () => {
|
|
520
|
+
const waffo = createWaffo();
|
|
521
|
+
const paymentRequestId = uuidv4();
|
|
522
|
+
|
|
523
|
+
// Create first
|
|
524
|
+
await waffo.order().create({
|
|
525
|
+
paymentRequestId,
|
|
526
|
+
merchantOrderId: `test-${Date.now()}`,
|
|
527
|
+
orderCurrency: 'USD',
|
|
528
|
+
orderAmount: '1.00',
|
|
529
|
+
orderDescription: 'Test',
|
|
530
|
+
notifyUrl: 'https://example.com/webhook',
|
|
531
|
+
userInfo: { userId: 'test-user', userEmail: 'test@example.com' },
|
|
532
|
+
paymentInfo: { productName: 'Test' },
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
// Then query
|
|
536
|
+
const response = await waffo.order().inquiry({ paymentRequestId });
|
|
537
|
+
|
|
538
|
+
if (!response.isSuccess()) {
|
|
539
|
+
console.error(`Inquiry failed: paymentRequestId=${paymentRequestId}, ` +
|
|
540
|
+
`code=${response.getCode()}, message=${response.getMessage()}`);
|
|
541
|
+
}
|
|
542
|
+
expect(response.isSuccess()).toBe(true);
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
---
|
|
548
|
+
|
|
549
|
+
## Merchant Config & Payment Method Query
|
|
550
|
+
|
|
551
|
+
```typescript
|
|
552
|
+
// src/services/config-service.ts
|
|
553
|
+
import { getWaffo } from '../config/waffo';
|
|
554
|
+
|
|
555
|
+
export async function getMerchantConfig() {
|
|
556
|
+
const waffo = getWaffo();
|
|
557
|
+
const response = await waffo.merchantConfig().inquiry({});
|
|
558
|
+
|
|
559
|
+
if (!response.isSuccess()) {
|
|
560
|
+
throw new Error(`Merchant config inquiry failed: ${response.getCode()} - ${response.getMessage()}`);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return response.getData();
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export async function getPaymentMethods() {
|
|
567
|
+
const waffo = getWaffo();
|
|
568
|
+
const response = await waffo.payMethodConfig().inquiry({});
|
|
569
|
+
|
|
570
|
+
if (!response.isSuccess()) {
|
|
571
|
+
throw new Error(`Pay method inquiry failed: ${response.getCode()} - ${response.getMessage()}`);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return response.getData();
|
|
575
|
+
}
|
|
576
|
+
```
|