@smartsoft001/paypal 2.76.0 → 2.80.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/index.js +230 -0
- package/package.json +1 -1
- package/src/lib/paypal.config.d.ts +14 -0
- package/src/lib/paypal.service.d.ts +36 -0
- package/.eslintrc +0 -13
- package/jest.config.ts +0 -27
- package/project.json +0 -46
- package/src/lib/paypal.config.ts +0 -17
- package/src/lib/paypal.service.spec.ts +0 -192
- package/src/lib/paypal.service.ts +0 -266
- package/test-setup.ts +0 -0
- package/tsconfig.json +0 -13
- package/tsconfig.lib.json +0 -10
- package/tsconfig.spec.json +0 -20
- /package/src/{index.ts → index.d.ts} +0 -0
package/index.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result)
|
|
9
|
+
__defProp(target, key, result);
|
|
10
|
+
return result;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// packages/shared/paypal/src/lib/paypal.config.ts
|
|
14
|
+
var PaypalConfig = class {
|
|
15
|
+
constructor(clientId, clientSecret, currencyCode, returnUrl, apiUrl, cancelUrl, test) {
|
|
16
|
+
this.clientId = clientId;
|
|
17
|
+
this.clientSecret = clientSecret;
|
|
18
|
+
this.currencyCode = currencyCode;
|
|
19
|
+
this.returnUrl = returnUrl;
|
|
20
|
+
this.apiUrl = apiUrl;
|
|
21
|
+
this.cancelUrl = cancelUrl;
|
|
22
|
+
this.test = test;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
var PAYPAL_CONFIG_PROVIDER = "PAYPAL_CONFIG_PROVIDER";
|
|
26
|
+
var IPaypalConfigProvider = class {
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// packages/shared/paypal/src/lib/paypal.service.ts
|
|
30
|
+
import { Injectable, Logger } from "@nestjs/common";
|
|
31
|
+
import * as paypal from "paypal-rest-sdk";
|
|
32
|
+
var PaypalService = class {
|
|
33
|
+
constructor(httpService, config, moduleRef) {
|
|
34
|
+
this.httpService = httpService;
|
|
35
|
+
this.config = config;
|
|
36
|
+
this.moduleRef = moduleRef;
|
|
37
|
+
}
|
|
38
|
+
async create(obj) {
|
|
39
|
+
const config = await this.getConfig(obj.data);
|
|
40
|
+
const data = {
|
|
41
|
+
intent: "sale",
|
|
42
|
+
payer: {
|
|
43
|
+
payment_method: "paypal"
|
|
44
|
+
},
|
|
45
|
+
redirect_urls: {
|
|
46
|
+
return_url: config.apiUrl + "paypal/" + obj.id + "/confirm",
|
|
47
|
+
cancel_url: config.cancelUrl
|
|
48
|
+
},
|
|
49
|
+
transactions: [
|
|
50
|
+
{
|
|
51
|
+
item_list: {
|
|
52
|
+
items: [
|
|
53
|
+
{
|
|
54
|
+
name: obj.name,
|
|
55
|
+
sku: obj.id,
|
|
56
|
+
price: obj.amount / 100,
|
|
57
|
+
currency: config.currencyCode,
|
|
58
|
+
quantity: 1
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
amount: {
|
|
63
|
+
currency: config.currencyCode,
|
|
64
|
+
total: obj.amount / 100
|
|
65
|
+
},
|
|
66
|
+
description: obj.name
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
};
|
|
70
|
+
const result = await new Promise((res, rej) => {
|
|
71
|
+
paypal.payment.create(
|
|
72
|
+
data,
|
|
73
|
+
this.getEnv(config),
|
|
74
|
+
(error, payment2) => {
|
|
75
|
+
if (error) {
|
|
76
|
+
rej(error);
|
|
77
|
+
} else {
|
|
78
|
+
res(payment2);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
redirectUrl: result.links.find(
|
|
85
|
+
(l) => l.rel === "approval_url"
|
|
86
|
+
).href,
|
|
87
|
+
orderId: result.id
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
async confirm(payerId, paymentId, amount, externalData) {
|
|
91
|
+
const config = await this.getConfig(externalData);
|
|
92
|
+
const data = {
|
|
93
|
+
payer_id: payerId,
|
|
94
|
+
transactions: [
|
|
95
|
+
{
|
|
96
|
+
amount: {
|
|
97
|
+
currency: config.currencyCode,
|
|
98
|
+
total: amount
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
};
|
|
103
|
+
return await new Promise((res, rej) => {
|
|
104
|
+
paypal.payment.execute(
|
|
105
|
+
paymentId,
|
|
106
|
+
data,
|
|
107
|
+
this.getEnv(config),
|
|
108
|
+
(error, payment2) => {
|
|
109
|
+
if (error) {
|
|
110
|
+
rej(error);
|
|
111
|
+
} else {
|
|
112
|
+
res(payment2);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
async getStatus(trans) {
|
|
119
|
+
const orderId = this.getOrderId(trans) ?? "";
|
|
120
|
+
const config = await this.getConfig(trans.data);
|
|
121
|
+
const payment2 = await new Promise((res, rej) => {
|
|
122
|
+
paypal.payment.get(
|
|
123
|
+
orderId,
|
|
124
|
+
this.getEnv(config),
|
|
125
|
+
(error, result) => {
|
|
126
|
+
if (error) {
|
|
127
|
+
rej(error);
|
|
128
|
+
} else {
|
|
129
|
+
res(result);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
return {
|
|
135
|
+
status: this.getStatusFromExternal(payment2.state),
|
|
136
|
+
data: payment2
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
async refund(trans, comment) {
|
|
140
|
+
const orderId = this.getOrderId(trans);
|
|
141
|
+
const config = await this.getConfig(trans.data);
|
|
142
|
+
const transactionId = this.getTransactionId(trans);
|
|
143
|
+
if (!transactionId) {
|
|
144
|
+
throw new Error("Paypal transaction ID not found for refund");
|
|
145
|
+
}
|
|
146
|
+
const refundData = {
|
|
147
|
+
amount: {
|
|
148
|
+
total: (trans.amount / 100).toString(),
|
|
149
|
+
currency: config.currencyCode
|
|
150
|
+
},
|
|
151
|
+
description: comment
|
|
152
|
+
};
|
|
153
|
+
return new Promise((resolve, reject) => {
|
|
154
|
+
paypal.sale.refund(
|
|
155
|
+
transactionId,
|
|
156
|
+
refundData,
|
|
157
|
+
this.getEnv(config),
|
|
158
|
+
(error, refund) => {
|
|
159
|
+
if (error) {
|
|
160
|
+
reject(error);
|
|
161
|
+
} else {
|
|
162
|
+
resolve(refund);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
getOrderId(trans) {
|
|
169
|
+
const historyItem = trans.history.find((x) => x.status === "started");
|
|
170
|
+
if (!historyItem) {
|
|
171
|
+
console.warn("Transaction without start status");
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
return historyItem.data.orderId;
|
|
175
|
+
}
|
|
176
|
+
getStatusFromExternal(status) {
|
|
177
|
+
status = status.toUpperCase();
|
|
178
|
+
switch (status) {
|
|
179
|
+
case "COMPLETED":
|
|
180
|
+
return "completed";
|
|
181
|
+
case "VOIDED":
|
|
182
|
+
return "canceled";
|
|
183
|
+
case "CREATED":
|
|
184
|
+
return "pending";
|
|
185
|
+
case "SAVED":
|
|
186
|
+
return "pending";
|
|
187
|
+
case "APPROVED":
|
|
188
|
+
return "completed";
|
|
189
|
+
default:
|
|
190
|
+
return status;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async getConfig(data) {
|
|
194
|
+
try {
|
|
195
|
+
const provider = this.moduleRef.get(
|
|
196
|
+
PAYPAL_CONFIG_PROVIDER,
|
|
197
|
+
{ strict: false }
|
|
198
|
+
);
|
|
199
|
+
return await provider.get(data);
|
|
200
|
+
} catch (e) {
|
|
201
|
+
Logger.warn("PayPal config provider not found", PaypalService.name);
|
|
202
|
+
}
|
|
203
|
+
return this.config;
|
|
204
|
+
}
|
|
205
|
+
getEnv(config) {
|
|
206
|
+
return {
|
|
207
|
+
mode: config.test ? "sandbox" : "live",
|
|
208
|
+
client_id: config.clientId,
|
|
209
|
+
client_secret: config.clientSecret
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
getApiUrl(config) {
|
|
213
|
+
return config.test ? "https://api-m.sandbox.paypal.com/" : "https://api-m.paypal.com/";
|
|
214
|
+
}
|
|
215
|
+
getTransactionId(trans) {
|
|
216
|
+
const historyItem = trans.history.find(
|
|
217
|
+
(i) => i.status === "completed" && i.data && i.data.customData && i.data.customData.transactions && i.data.customData.transactions.length && i.data.customData.transactions[0].related_resources && i.data.customData.transactions[0].related_resources.length && i.data.customData.transactions[0].related_resources[0].sale
|
|
218
|
+
);
|
|
219
|
+
return historyItem ? historyItem.data.customData.transactions[0].related_resources[0].sale.id : null;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
PaypalService = __decorateClass([
|
|
223
|
+
Injectable()
|
|
224
|
+
], PaypalService);
|
|
225
|
+
export {
|
|
226
|
+
IPaypalConfigProvider,
|
|
227
|
+
PAYPAL_CONFIG_PROVIDER,
|
|
228
|
+
PaypalConfig,
|
|
229
|
+
PaypalService
|
|
230
|
+
};
|
package/package.json
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class PaypalConfig {
|
|
2
|
+
clientId: string;
|
|
3
|
+
clientSecret: string;
|
|
4
|
+
currencyCode: string;
|
|
5
|
+
returnUrl: string;
|
|
6
|
+
apiUrl: string;
|
|
7
|
+
cancelUrl: string;
|
|
8
|
+
test?: boolean;
|
|
9
|
+
constructor(clientId: string, clientSecret: string, currencyCode: string, returnUrl: string, apiUrl: string, cancelUrl: string, test?: boolean);
|
|
10
|
+
}
|
|
11
|
+
export declare const PAYPAL_CONFIG_PROVIDER = "PAYPAL_CONFIG_PROVIDER";
|
|
12
|
+
export declare abstract class IPaypalConfigProvider {
|
|
13
|
+
abstract get(data: any): Promise<PaypalConfig>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { HttpService } from '@nestjs/axios';
|
|
2
|
+
import { ModuleRef } from '@nestjs/core';
|
|
3
|
+
import { ITransPaymentSingleService, Trans, TransStatus } from '@smartsoft001/trans-domain';
|
|
4
|
+
import { PaypalConfig } from './paypal.config';
|
|
5
|
+
export declare class PaypalService implements ITransPaymentSingleService {
|
|
6
|
+
private readonly httpService;
|
|
7
|
+
private config;
|
|
8
|
+
private moduleRef;
|
|
9
|
+
constructor(httpService: HttpService, config: PaypalConfig, moduleRef: ModuleRef);
|
|
10
|
+
create(obj: {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
amount: number;
|
|
14
|
+
firstName?: string;
|
|
15
|
+
lastName?: string;
|
|
16
|
+
email?: string;
|
|
17
|
+
contactPhone?: string;
|
|
18
|
+
clientIp: string;
|
|
19
|
+
data: any;
|
|
20
|
+
}): Promise<{
|
|
21
|
+
orderId: string;
|
|
22
|
+
redirectUrl: string;
|
|
23
|
+
}>;
|
|
24
|
+
confirm(payerId: any, paymentId: any, amount: number, externalData: any): Promise<any>;
|
|
25
|
+
getStatus<T>(trans: Trans<T>): Promise<{
|
|
26
|
+
status: TransStatus;
|
|
27
|
+
data: any;
|
|
28
|
+
}>;
|
|
29
|
+
refund(trans: Trans<any>, comment: string): Promise<any>;
|
|
30
|
+
private getOrderId;
|
|
31
|
+
private getStatusFromExternal;
|
|
32
|
+
private getConfig;
|
|
33
|
+
private getEnv;
|
|
34
|
+
private getApiUrl;
|
|
35
|
+
private getTransactionId;
|
|
36
|
+
}
|
package/.eslintrc
DELETED
package/jest.config.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/* eslint-disable */
|
|
2
|
-
export default {
|
|
3
|
-
displayName: 'shared-paypal',
|
|
4
|
-
globals: {},
|
|
5
|
-
testEnvironment: 'node',
|
|
6
|
-
transform: {
|
|
7
|
-
'^.+\\.[tj]sx?$': [
|
|
8
|
-
'ts-jest',
|
|
9
|
-
{
|
|
10
|
-
tsConfig: '<rootDir>/tsconfig.spec.json',
|
|
11
|
-
},
|
|
12
|
-
],
|
|
13
|
-
},
|
|
14
|
-
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
|
15
|
-
coverageDirectory: '../../../coverage/libs/shared/paypal',
|
|
16
|
-
preset: '../../../jest.preset.js',
|
|
17
|
-
/* TODO: Update to latest Jest snapshotFormat
|
|
18
|
-
* By default Nx has kept the older style of Jest Snapshot formats
|
|
19
|
-
* to prevent breaking of any existing tests with snapshots.
|
|
20
|
-
* It's recommend you update to the latest format.
|
|
21
|
-
* You can do this by removing snapshotFormat property
|
|
22
|
-
* and running tests with --update-snapshot flag.
|
|
23
|
-
* Example: From within the project directory, run "nx test --update-snapshot"
|
|
24
|
-
* More info: https://jestjs.io/docs/upgrading-to-jest29#snapshot-format
|
|
25
|
-
*/
|
|
26
|
-
snapshotFormat: { escapeString: true, printBasicPrototype: true },
|
|
27
|
-
};
|
package/project.json
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "shared-paypal",
|
|
3
|
-
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
-
"sourceRoot": "packages/shared/paypal/src",
|
|
5
|
-
"projectType": "library",
|
|
6
|
-
"tags": ["scope:shared", "type:util"],
|
|
7
|
-
"generators": {},
|
|
8
|
-
"targets": {
|
|
9
|
-
"lint": {
|
|
10
|
-
"executor": "@nx/eslint:lint",
|
|
11
|
-
"outputs": ["{options.outputFile}"],
|
|
12
|
-
"options": {
|
|
13
|
-
"lintFilePatterns": [
|
|
14
|
-
"packages/shared/paypal/**/*.{ts,tsx,js,jsx}",
|
|
15
|
-
"packages/shared/paypal/package.json"
|
|
16
|
-
]
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
"test": {
|
|
20
|
-
"executor": "@nx/jest:jest",
|
|
21
|
-
"options": {
|
|
22
|
-
"jestConfig": "packages/shared/paypal/jest.config.ts"
|
|
23
|
-
},
|
|
24
|
-
"outputs": ["{workspaceRoot}/coverage/packages/shared/paypal"]
|
|
25
|
-
},
|
|
26
|
-
"build": {
|
|
27
|
-
"executor": "@nx/esbuild:esbuild",
|
|
28
|
-
"outputs": ["{options.outputPath}"],
|
|
29
|
-
"options": {
|
|
30
|
-
"outputPath": "dist/packages/shared/paypal",
|
|
31
|
-
"tsConfig": "packages/shared/paypal/tsconfig.lib.json",
|
|
32
|
-
"packageJson": "packages/shared/paypal/package.json",
|
|
33
|
-
"main": "packages/shared/paypal/src/index.ts",
|
|
34
|
-
"assets": ["packages/shared/paypal/*.md"]
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
"deploy": {
|
|
38
|
-
"executor": "ngx-deploy-npm:deploy",
|
|
39
|
-
"options": {
|
|
40
|
-
"access": "public",
|
|
41
|
-
"distFolderPath": "dist/packages/shared/paypal"
|
|
42
|
-
},
|
|
43
|
-
"dependsOn": ["build"]
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
package/src/lib/paypal.config.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export class PaypalConfig {
|
|
2
|
-
constructor(
|
|
3
|
-
public clientId: string,
|
|
4
|
-
public clientSecret: string,
|
|
5
|
-
public currencyCode: string,
|
|
6
|
-
public returnUrl: string,
|
|
7
|
-
public apiUrl: string,
|
|
8
|
-
public cancelUrl: string,
|
|
9
|
-
public test?: boolean,
|
|
10
|
-
) {}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const PAYPAL_CONFIG_PROVIDER = 'PAYPAL_CONFIG_PROVIDER';
|
|
14
|
-
|
|
15
|
-
export abstract class IPaypalConfigProvider {
|
|
16
|
-
abstract get(data: any): Promise<PaypalConfig>;
|
|
17
|
-
}
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import { HttpService } from '@nestjs/axios';
|
|
2
|
-
import { ModuleRef } from '@nestjs/core';
|
|
3
|
-
import { Test, TestingModule } from '@nestjs/testing';
|
|
4
|
-
|
|
5
|
-
import { Trans } from '@smartsoft001/trans-domain';
|
|
6
|
-
|
|
7
|
-
import { PaypalConfig } from './paypal.config';
|
|
8
|
-
import { PaypalService } from './paypal.service';
|
|
9
|
-
|
|
10
|
-
// Mock the module before importing it
|
|
11
|
-
jest.mock('paypal-rest-sdk', () => ({
|
|
12
|
-
payment: {
|
|
13
|
-
create: jest.fn(),
|
|
14
|
-
execute: jest.fn(),
|
|
15
|
-
get: jest.fn(),
|
|
16
|
-
},
|
|
17
|
-
sale: {
|
|
18
|
-
refund: jest.fn(),
|
|
19
|
-
},
|
|
20
|
-
configure: jest.fn(),
|
|
21
|
-
sdkVersion: '1.8.1',
|
|
22
|
-
userAgent: 'PayPalSDK/PayPal-node-SDK 1.8.1',
|
|
23
|
-
}));
|
|
24
|
-
|
|
25
|
-
//eslint-disable-next-line
|
|
26
|
-
import * as paypal from 'paypal-rest-sdk'; // Needs to be mocked first
|
|
27
|
-
|
|
28
|
-
const mockHttpService = {
|
|
29
|
-
post: jest.fn(),
|
|
30
|
-
get: jest.fn(),
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const mockModuleRef = {
|
|
34
|
-
get: jest.fn(),
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const mockPaypalConfig = {
|
|
38
|
-
clientId: 'mock-client-id',
|
|
39
|
-
clientSecret: 'mock-client-secret',
|
|
40
|
-
currencyCode: 'USD',
|
|
41
|
-
apiUrl: 'https://mock.api.url',
|
|
42
|
-
cancelUrl: 'https://mock.cancel.url',
|
|
43
|
-
test: true,
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
describe('paypal: PaypalService', () => {
|
|
47
|
-
let service: PaypalService;
|
|
48
|
-
|
|
49
|
-
beforeEach(async () => {
|
|
50
|
-
const module: TestingModule = await Test.createTestingModule({
|
|
51
|
-
providers: [
|
|
52
|
-
PaypalService,
|
|
53
|
-
{ provide: PaypalConfig, useValue: mockPaypalConfig },
|
|
54
|
-
{ provide: HttpService, useValue: mockHttpService },
|
|
55
|
-
{ provide: ModuleRef, useValue: mockModuleRef },
|
|
56
|
-
],
|
|
57
|
-
}).compile();
|
|
58
|
-
|
|
59
|
-
service = module.get<PaypalService>(PaypalService);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
afterEach(() => {
|
|
63
|
-
jest.clearAllMocks();
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
describe('create', () => {
|
|
67
|
-
it('should create a payment and return orderId and redirectUrl', async () => {
|
|
68
|
-
const mockPayment = {
|
|
69
|
-
id: 'test-payment-id',
|
|
70
|
-
links: [{ rel: 'approval_url', href: 'https://approval.url' }],
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
(paypal.payment.create as jest.Mock).mockImplementation(
|
|
74
|
-
(data, config, callback) => {
|
|
75
|
-
callback(null, mockPayment);
|
|
76
|
-
},
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
const result = await service.create({
|
|
80
|
-
id: 'test-id',
|
|
81
|
-
name: 'test-name',
|
|
82
|
-
amount: 1000,
|
|
83
|
-
email: 'test@example.com',
|
|
84
|
-
clientIp: '127.0.0.1',
|
|
85
|
-
data: {},
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
expect(result).toEqual({
|
|
89
|
-
orderId: 'test-payment-id',
|
|
90
|
-
redirectUrl: 'https://approval.url',
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe('confirm', () => {
|
|
96
|
-
it('should execute a payment and return the result', async () => {
|
|
97
|
-
const mockPayment = { id: 'test-payment-id', state: 'approved' };
|
|
98
|
-
|
|
99
|
-
(paypal.payment.execute as jest.Mock).mockImplementation(
|
|
100
|
-
(paymentId, data, config, callback) => {
|
|
101
|
-
callback(null, mockPayment);
|
|
102
|
-
},
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
const result = await service.confirm(
|
|
106
|
-
'test-payer-id',
|
|
107
|
-
'test-payment-id',
|
|
108
|
-
10,
|
|
109
|
-
{},
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
expect(result).toEqual(mockPayment);
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
describe('getStatus', () => {
|
|
117
|
-
it('should return the status and data of a payment', async () => {
|
|
118
|
-
const mockPayment = { state: 'COMPLETED' };
|
|
119
|
-
|
|
120
|
-
(paypal.payment.get as jest.Mock).mockImplementation(
|
|
121
|
-
(paymentId, config, callback) => {
|
|
122
|
-
callback(null, mockPayment);
|
|
123
|
-
},
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
const result = await service.getStatus({
|
|
127
|
-
data: {},
|
|
128
|
-
history: [
|
|
129
|
-
{
|
|
130
|
-
status: 'started',
|
|
131
|
-
data: { orderId: 'test-order-id' },
|
|
132
|
-
},
|
|
133
|
-
],
|
|
134
|
-
} as Trans<any>);
|
|
135
|
-
|
|
136
|
-
expect(result).toEqual({ status: 'completed', data: mockPayment });
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
describe('refund', () => {
|
|
141
|
-
it('should process a refund and return the result', async () => {
|
|
142
|
-
const mockRefund = { id: 'test-refund-id', state: 'completed' };
|
|
143
|
-
|
|
144
|
-
(paypal.sale.refund as jest.Mock).mockImplementation(
|
|
145
|
-
(saleId, data, config, callback) => {
|
|
146
|
-
callback(null, mockRefund);
|
|
147
|
-
},
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
const result = await service.refund(
|
|
151
|
-
{
|
|
152
|
-
amount: 1000,
|
|
153
|
-
data: {},
|
|
154
|
-
history: [
|
|
155
|
-
{
|
|
156
|
-
status: 'completed',
|
|
157
|
-
data: {
|
|
158
|
-
customData: {
|
|
159
|
-
transactions: [
|
|
160
|
-
{
|
|
161
|
-
related_resources: [
|
|
162
|
-
{
|
|
163
|
-
sale: { id: 'test-sale-id' },
|
|
164
|
-
},
|
|
165
|
-
],
|
|
166
|
-
},
|
|
167
|
-
],
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
],
|
|
172
|
-
} as Trans<any>,
|
|
173
|
-
'test comment',
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
expect(result).toEqual(mockRefund);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it('should throw an error when transaction ID is not found', async () => {
|
|
180
|
-
await expect(
|
|
181
|
-
service.refund(
|
|
182
|
-
{
|
|
183
|
-
amount: 1000,
|
|
184
|
-
data: {},
|
|
185
|
-
history: [],
|
|
186
|
-
} as unknown as Trans<any>,
|
|
187
|
-
'test comment',
|
|
188
|
-
),
|
|
189
|
-
).rejects.toThrow('Paypal transaction ID not found for refund');
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
});
|
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
import { HttpService } from '@nestjs/axios';
|
|
2
|
-
import { Injectable, Logger } from '@nestjs/common';
|
|
3
|
-
import { ModuleRef } from '@nestjs/core';
|
|
4
|
-
import * as paypal from 'paypal-rest-sdk';
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
ITransPaymentSingleService,
|
|
8
|
-
Trans,
|
|
9
|
-
TransStatus,
|
|
10
|
-
} from '@smartsoft001/trans-domain';
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
IPaypalConfigProvider,
|
|
14
|
-
PAYPAL_CONFIG_PROVIDER,
|
|
15
|
-
PaypalConfig,
|
|
16
|
-
} from './paypal.config';
|
|
17
|
-
|
|
18
|
-
@Injectable()
|
|
19
|
-
export class PaypalService implements ITransPaymentSingleService {
|
|
20
|
-
constructor(
|
|
21
|
-
private readonly httpService: HttpService,
|
|
22
|
-
private config: PaypalConfig,
|
|
23
|
-
private moduleRef: ModuleRef,
|
|
24
|
-
) {}
|
|
25
|
-
|
|
26
|
-
async create(obj: {
|
|
27
|
-
id: string;
|
|
28
|
-
name: string;
|
|
29
|
-
amount: number;
|
|
30
|
-
firstName?: string;
|
|
31
|
-
lastName?: string;
|
|
32
|
-
email?: string;
|
|
33
|
-
contactPhone?: string;
|
|
34
|
-
clientIp: string;
|
|
35
|
-
data: any;
|
|
36
|
-
}): Promise<{ orderId: string; redirectUrl: string }> {
|
|
37
|
-
const config = await this.getConfig(obj.data);
|
|
38
|
-
|
|
39
|
-
const data = {
|
|
40
|
-
intent: 'sale',
|
|
41
|
-
payer: {
|
|
42
|
-
payment_method: 'paypal',
|
|
43
|
-
},
|
|
44
|
-
redirect_urls: {
|
|
45
|
-
return_url: config.apiUrl + 'paypal/' + obj.id + '/confirm',
|
|
46
|
-
cancel_url: config.cancelUrl,
|
|
47
|
-
},
|
|
48
|
-
transactions: [
|
|
49
|
-
{
|
|
50
|
-
item_list: {
|
|
51
|
-
items: [
|
|
52
|
-
{
|
|
53
|
-
name: obj.name,
|
|
54
|
-
sku: obj.id,
|
|
55
|
-
price: obj.amount / 100,
|
|
56
|
-
currency: config.currencyCode,
|
|
57
|
-
quantity: 1,
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
},
|
|
61
|
-
amount: {
|
|
62
|
-
currency: config.currencyCode,
|
|
63
|
-
total: obj.amount / 100,
|
|
64
|
-
},
|
|
65
|
-
description: obj.name,
|
|
66
|
-
},
|
|
67
|
-
],
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const result: { id: any; links: any[] } = await new Promise((res, rej) => {
|
|
71
|
-
paypal.payment.create(
|
|
72
|
-
data as any,
|
|
73
|
-
this.getEnv(config) as any,
|
|
74
|
-
(error: any, payment: any) => {
|
|
75
|
-
if (error) {
|
|
76
|
-
rej(error);
|
|
77
|
-
} else {
|
|
78
|
-
res(payment);
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
redirectUrl: result.links.find(
|
|
86
|
-
(l: { rel: string }) => l.rel === 'approval_url',
|
|
87
|
-
).href,
|
|
88
|
-
orderId: result.id,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async confirm(
|
|
93
|
-
payerId: any,
|
|
94
|
-
paymentId: any,
|
|
95
|
-
amount: number,
|
|
96
|
-
externalData: any,
|
|
97
|
-
): Promise<any> {
|
|
98
|
-
const config = await this.getConfig(externalData);
|
|
99
|
-
|
|
100
|
-
const data = {
|
|
101
|
-
payer_id: payerId,
|
|
102
|
-
transactions: [
|
|
103
|
-
{
|
|
104
|
-
amount: {
|
|
105
|
-
currency: config.currencyCode,
|
|
106
|
-
total: amount,
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
],
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
return await new Promise<void>((res, rej) => {
|
|
113
|
-
paypal.payment.execute(
|
|
114
|
-
paymentId,
|
|
115
|
-
data,
|
|
116
|
-
this.getEnv(config) as any,
|
|
117
|
-
(error: any, payment: any) => {
|
|
118
|
-
if (error) {
|
|
119
|
-
rej(error);
|
|
120
|
-
} else {
|
|
121
|
-
res(payment);
|
|
122
|
-
}
|
|
123
|
-
},
|
|
124
|
-
);
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async getStatus<T>(
|
|
129
|
-
trans: Trans<T>,
|
|
130
|
-
): Promise<{ status: TransStatus; data: any }> {
|
|
131
|
-
const orderId = this.getOrderId(trans) ?? '';
|
|
132
|
-
const config = await this.getConfig(trans.data);
|
|
133
|
-
|
|
134
|
-
const payment: { state: string } = await new Promise((res, rej) => {
|
|
135
|
-
paypal.payment.get(
|
|
136
|
-
orderId,
|
|
137
|
-
this.getEnv(config) as any,
|
|
138
|
-
(error: any, result: any) => {
|
|
139
|
-
if (error) {
|
|
140
|
-
rej(error);
|
|
141
|
-
} else {
|
|
142
|
-
res(result);
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
|
-
);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
status: this.getStatusFromExternal(payment.state),
|
|
150
|
-
data: payment,
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
async refund(trans: Trans<any>, comment: string): Promise<any> {
|
|
155
|
-
const orderId = this.getOrderId(trans);
|
|
156
|
-
const config = await this.getConfig(trans.data);
|
|
157
|
-
const transactionId = this.getTransactionId(trans);
|
|
158
|
-
|
|
159
|
-
if (!transactionId) {
|
|
160
|
-
throw new Error('Paypal transaction ID not found for refund');
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const refundData = {
|
|
164
|
-
amount: {
|
|
165
|
-
total: (trans.amount / 100).toString(),
|
|
166
|
-
currency: config.currencyCode,
|
|
167
|
-
},
|
|
168
|
-
description: comment,
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
return new Promise((resolve, reject) => {
|
|
172
|
-
paypal.sale.refund(
|
|
173
|
-
transactionId,
|
|
174
|
-
refundData,
|
|
175
|
-
this.getEnv(config) as any,
|
|
176
|
-
(error: any, refund: any) => {
|
|
177
|
-
if (error) {
|
|
178
|
-
reject(error);
|
|
179
|
-
} else {
|
|
180
|
-
resolve(refund);
|
|
181
|
-
}
|
|
182
|
-
},
|
|
183
|
-
);
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
private getOrderId(trans: Trans<any>): string | null {
|
|
188
|
-
const historyItem = trans.history.find((x) => x.status === 'started');
|
|
189
|
-
|
|
190
|
-
if (!historyItem) {
|
|
191
|
-
console.warn('Transaction without start status');
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return historyItem.data.orderId;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
private getStatusFromExternal(status: string): any {
|
|
199
|
-
status = status.toUpperCase();
|
|
200
|
-
|
|
201
|
-
switch (status) {
|
|
202
|
-
case 'COMPLETED':
|
|
203
|
-
return 'completed';
|
|
204
|
-
case 'VOIDED':
|
|
205
|
-
return 'canceled';
|
|
206
|
-
case 'CREATED':
|
|
207
|
-
return 'pending';
|
|
208
|
-
case 'SAVED':
|
|
209
|
-
return 'pending';
|
|
210
|
-
case 'APPROVED':
|
|
211
|
-
return 'completed';
|
|
212
|
-
default:
|
|
213
|
-
return status;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
private async getConfig(data: any): Promise<PaypalConfig> {
|
|
218
|
-
try {
|
|
219
|
-
const provider: IPaypalConfigProvider = this.moduleRef.get(
|
|
220
|
-
PAYPAL_CONFIG_PROVIDER,
|
|
221
|
-
{ strict: false },
|
|
222
|
-
);
|
|
223
|
-
return await provider.get(data);
|
|
224
|
-
} catch (e) {
|
|
225
|
-
Logger.warn('PayPal config provider not found', PaypalService.name);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return this.config;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
private getEnv(config: PaypalConfig): {
|
|
232
|
-
mode: any;
|
|
233
|
-
client_id: any;
|
|
234
|
-
client_secret: any;
|
|
235
|
-
} {
|
|
236
|
-
return {
|
|
237
|
-
mode: config.test ? 'sandbox' : 'live',
|
|
238
|
-
client_id: config.clientId,
|
|
239
|
-
client_secret: config.clientSecret,
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
private getApiUrl(config: PaypalConfig): string {
|
|
244
|
-
return config.test
|
|
245
|
-
? 'https://api-m.sandbox.paypal.com/'
|
|
246
|
-
: 'https://api-m.paypal.com/';
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
private getTransactionId(trans: Trans<any>): string {
|
|
250
|
-
const historyItem = trans.history.find(
|
|
251
|
-
(i) =>
|
|
252
|
-
i.status === 'completed' &&
|
|
253
|
-
i.data &&
|
|
254
|
-
i.data.customData &&
|
|
255
|
-
i.data.customData.transactions &&
|
|
256
|
-
i.data.customData.transactions.length &&
|
|
257
|
-
i.data.customData.transactions[0].related_resources &&
|
|
258
|
-
i.data.customData.transactions[0].related_resources.length &&
|
|
259
|
-
i.data.customData.transactions[0].related_resources[0].sale,
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
return historyItem
|
|
263
|
-
? historyItem.data.customData.transactions[0].related_resources[0].sale.id
|
|
264
|
-
: null;
|
|
265
|
-
}
|
|
266
|
-
}
|
package/test-setup.ts
DELETED
|
File without changes
|
package/tsconfig.json
DELETED
package/tsconfig.lib.json
DELETED
package/tsconfig.spec.json
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "./tsconfig.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"outDir": "../../../dist/out-tsc",
|
|
5
|
-
"module": "commonjs",
|
|
6
|
-
"types": ["jest", "node"]
|
|
7
|
-
},
|
|
8
|
-
"include": [
|
|
9
|
-
"**/*.spec.ts",
|
|
10
|
-
"**/*.test.ts",
|
|
11
|
-
"**/*.spec.tsx",
|
|
12
|
-
"**/*.test.tsx",
|
|
13
|
-
"**/*.spec.js",
|
|
14
|
-
"**/*.test.js",
|
|
15
|
-
"**/*.spec.jsx",
|
|
16
|
-
"**/*.test.jsx",
|
|
17
|
-
"**/*.d.ts",
|
|
18
|
-
"jest.config.ts"
|
|
19
|
-
]
|
|
20
|
-
}
|
|
File without changes
|