@deenruv/phone-number-validation 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 +23 -0
- package/README.md +50 -0
- package/dist/plugin-server/api.d.ts +15 -0
- package/dist/plugin-server/api.js +48 -0
- package/dist/plugin-server/extension.d.ts +1 -0
- package/dist/plugin-server/extension.js +18 -0
- package/dist/plugin-server/index.d.ts +5 -0
- package/dist/plugin-server/index.js +47 -0
- package/dist/plugin-server/phone-number-validation-process.d.ts +3 -0
- package/dist/plugin-server/phone-number-validation-process.js +16 -0
- package/dist/plugin-server/service.d.ts +11 -0
- package/dist/plugin-server/service.e2e.test.d.ts +1 -0
- package/dist/plugin-server/service.e2e.test.js +75 -0
- package/dist/plugin-server/service.js +87 -0
- package/dist/plugin-server/symbol.d.ts +1 -0
- package/dist/plugin-server/symbol.js +4 -0
- package/dist/plugin-server/types.d.ts +9 -0
- package/dist/plugin-server/types.js +2 -0
- package/dist/plugin-server/utils.d.ts +2 -0
- package/dist/plugin-server/utils.js +93 -0
- package/package.json +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# License 1
|
|
2
|
+
|
|
3
|
+
The MIT License
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2025-present Aexol
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
8
|
+
|
|
9
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
10
|
+
|
|
11
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
12
|
+
|
|
13
|
+
# License 2
|
|
14
|
+
|
|
15
|
+
The MIT License
|
|
16
|
+
|
|
17
|
+
Copyright (c) 2018-2025 Michael Bromley
|
|
18
|
+
|
|
19
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# @deenruv/phone-number-validation
|
|
2
|
+
|
|
3
|
+
Plugin that validates phone numbers on orders using the `libphonenumber-js` library. It can enforce phone number validation during order state transitions and exposes a Shop API query for on-demand validation.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @deenruv/phone-number-validation
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Configuration
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { PhoneNumberValidationPlugin } from '@deenruv/phone-number-validation';
|
|
15
|
+
|
|
16
|
+
// In your Deenruv server config:
|
|
17
|
+
plugins: [
|
|
18
|
+
PhoneNumberValidationPlugin.init({
|
|
19
|
+
// Disable automatic validation on order state transitions
|
|
20
|
+
disableTransitionValidation: false,
|
|
21
|
+
// Order state to validate at (default behavior)
|
|
22
|
+
stateCheck: 'ArrangingPayment',
|
|
23
|
+
// Require a phone number to be present
|
|
24
|
+
requirePhoneNumber: true,
|
|
25
|
+
// Default country code for parsing (string or async function)
|
|
26
|
+
defaultCountryCode: 'PL',
|
|
27
|
+
// Allowed country codes (array or async function)
|
|
28
|
+
allowedCountryCodes: ['PL', 'DE', 'GB'],
|
|
29
|
+
}),
|
|
30
|
+
]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- Phone number validation using `libphonenumber-js`
|
|
36
|
+
- Automatic validation during order state transitions (configurable)
|
|
37
|
+
- Shop API query for on-demand phone number validation
|
|
38
|
+
- Configurable default country code (static or dynamic per request/order)
|
|
39
|
+
- Configurable allowed country codes whitelist (static or dynamic per request)
|
|
40
|
+
- Optional phone number requirement enforcement
|
|
41
|
+
|
|
42
|
+
## Admin UI
|
|
43
|
+
|
|
44
|
+
This plugin is server-only and does not add any admin UI extensions.
|
|
45
|
+
|
|
46
|
+
## API Extensions
|
|
47
|
+
|
|
48
|
+
### Shop API
|
|
49
|
+
|
|
50
|
+
- **Query** `validateCurrentOrderPhoneNumber: PhoneNumberValidationResult!` — Validates the phone number on the current active order. Returns either `PhoneNumberValidationSuccess` or `PhoneNumberValidationError` with a message.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { RequestContext } from "@deenruv/core";
|
|
2
|
+
import { PhoneNumberValidationService } from "./service";
|
|
3
|
+
export declare class ValidatePhoneNumberAPIResolver {
|
|
4
|
+
private readonly phoneNumberValidationService;
|
|
5
|
+
constructor(phoneNumberValidationService: PhoneNumberValidationService);
|
|
6
|
+
validateCurrentOrderPhoneNumber(ctx: RequestContext): Promise<{
|
|
7
|
+
__typename: string;
|
|
8
|
+
success: boolean;
|
|
9
|
+
message?: undefined;
|
|
10
|
+
} | {
|
|
11
|
+
__typename: string;
|
|
12
|
+
message: string;
|
|
13
|
+
success?: undefined;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.ValidatePhoneNumberAPIResolver = void 0;
|
|
16
|
+
const graphql_1 = require("@nestjs/graphql");
|
|
17
|
+
const core_1 = require("@deenruv/core");
|
|
18
|
+
const service_1 = require("./service");
|
|
19
|
+
let ValidatePhoneNumberAPIResolver = class ValidatePhoneNumberAPIResolver {
|
|
20
|
+
constructor(phoneNumberValidationService) {
|
|
21
|
+
this.phoneNumberValidationService = phoneNumberValidationService;
|
|
22
|
+
}
|
|
23
|
+
async validateCurrentOrderPhoneNumber(ctx) {
|
|
24
|
+
const message = await this.phoneNumberValidationService.validatePhoneNumberForCurrentOrder(ctx);
|
|
25
|
+
if (!message) {
|
|
26
|
+
return {
|
|
27
|
+
__typename: "PhoneNumberValidationSuccess",
|
|
28
|
+
success: true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
__typename: "PhoneNumberValidationError",
|
|
33
|
+
message,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
exports.ValidatePhoneNumberAPIResolver = ValidatePhoneNumberAPIResolver;
|
|
38
|
+
__decorate([
|
|
39
|
+
(0, graphql_1.Query)(),
|
|
40
|
+
__param(0, (0, core_1.Ctx)()),
|
|
41
|
+
__metadata("design:type", Function),
|
|
42
|
+
__metadata("design:paramtypes", [core_1.RequestContext]),
|
|
43
|
+
__metadata("design:returntype", Promise)
|
|
44
|
+
], ValidatePhoneNumberAPIResolver.prototype, "validateCurrentOrderPhoneNumber", null);
|
|
45
|
+
exports.ValidatePhoneNumberAPIResolver = ValidatePhoneNumberAPIResolver = __decorate([
|
|
46
|
+
(0, graphql_1.Resolver)(),
|
|
47
|
+
__metadata("design:paramtypes", [service_1.PhoneNumberValidationService])
|
|
48
|
+
], ValidatePhoneNumberAPIResolver);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const APIExtension: import("graphql").DocumentNode;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.APIExtension = void 0;
|
|
4
|
+
const graphql_tag_1 = require("graphql-tag");
|
|
5
|
+
exports.APIExtension = (0, graphql_tag_1.gql) `
|
|
6
|
+
type PhoneNumberValidationSuccess {
|
|
7
|
+
success: Boolean
|
|
8
|
+
}
|
|
9
|
+
type PhoneNumberValidationError {
|
|
10
|
+
message: String
|
|
11
|
+
}
|
|
12
|
+
union PhoneNumberValidationResult =
|
|
13
|
+
| PhoneNumberValidationSuccess
|
|
14
|
+
| PhoneNumberValidationError
|
|
15
|
+
extend type Query {
|
|
16
|
+
validateCurrentOrderPhoneNumber: PhoneNumberValidationResult!
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.PhoneNumberValidationPlugin = void 0;
|
|
10
|
+
const core_1 = require("@deenruv/core");
|
|
11
|
+
const symbol_js_1 = require("./symbol.js");
|
|
12
|
+
const service_js_1 = require("./service.js");
|
|
13
|
+
const extension_js_1 = require("./extension.js");
|
|
14
|
+
const api_js_1 = require("./api.js");
|
|
15
|
+
const phone_number_validation_process_js_1 = require("./phone-number-validation-process.js");
|
|
16
|
+
let PhoneNumberValidationPlugin = class PhoneNumberValidationPlugin {
|
|
17
|
+
static init(opts = {}) {
|
|
18
|
+
this.options = opts;
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
exports.PhoneNumberValidationPlugin = PhoneNumberValidationPlugin;
|
|
23
|
+
PhoneNumberValidationPlugin.options = {};
|
|
24
|
+
exports.PhoneNumberValidationPlugin = PhoneNumberValidationPlugin = __decorate([
|
|
25
|
+
(0, core_1.DeenruvPlugin)({
|
|
26
|
+
compatibility: "^0.0.20",
|
|
27
|
+
imports: [core_1.PluginCommonModule],
|
|
28
|
+
shopApiExtensions: {
|
|
29
|
+
schema: extension_js_1.APIExtension,
|
|
30
|
+
resolvers: [api_js_1.ValidatePhoneNumberAPIResolver],
|
|
31
|
+
},
|
|
32
|
+
providers: [
|
|
33
|
+
{
|
|
34
|
+
provide: symbol_js_1.PHONE_NUMBER_VALIDATION_OPTIONS,
|
|
35
|
+
useFactory: () => PhoneNumberValidationPlugin.options,
|
|
36
|
+
},
|
|
37
|
+
service_js_1.PhoneNumberValidationService,
|
|
38
|
+
],
|
|
39
|
+
configuration: (config) => {
|
|
40
|
+
const options = PhoneNumberValidationPlugin.options;
|
|
41
|
+
if (!options.disableTransitionValidation) {
|
|
42
|
+
config.orderOptions.process.push((0, phone_number_validation_process_js_1.phoneNumberValidationProcess)(options));
|
|
43
|
+
}
|
|
44
|
+
return config;
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
], PhoneNumberValidationPlugin);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.phoneNumberValidationProcess = void 0;
|
|
4
|
+
const service_js_1 = require("./service.js");
|
|
5
|
+
let phoneNumberValidationService;
|
|
6
|
+
const phoneNumberValidationProcess = ({ stateCheck, }) => ({
|
|
7
|
+
init(injector) {
|
|
8
|
+
phoneNumberValidationService = injector.get(service_js_1.PhoneNumberValidationService);
|
|
9
|
+
},
|
|
10
|
+
async onTransitionStart(_, toState, data) {
|
|
11
|
+
if (toState === (stateCheck || "ArrangingPayment")) {
|
|
12
|
+
return await phoneNumberValidationService.validatePhoneNumberForOrder(data.ctx, data.order);
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
exports.phoneNumberValidationProcess = phoneNumberValidationProcess;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ActiveOrderService, Order, RequestContext } from "@deenruv/core";
|
|
2
|
+
import { PhoneNumberValidationOptions } from "./types.js";
|
|
3
|
+
export declare class PhoneNumberValidationService {
|
|
4
|
+
private options;
|
|
5
|
+
private activeOrderService;
|
|
6
|
+
constructor(options: PhoneNumberValidationOptions, activeOrderService: ActiveOrderService);
|
|
7
|
+
private getAllowedCountryCodes;
|
|
8
|
+
private getCountryCode;
|
|
9
|
+
validatePhoneNumberForOrder(ctx: RequestContext, order: Order): Promise<string | void>;
|
|
10
|
+
validatePhoneNumberForCurrentOrder(ctx: RequestContext): Promise<string | void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const core_1 = require("@deenruv/core");
|
|
5
|
+
const testing_1 = require("@deenruv/testing");
|
|
6
|
+
const index_js_1 = require("./index.js");
|
|
7
|
+
const service_js_1 = require("./service.js");
|
|
8
|
+
const utils_js_1 = require("./utils.js");
|
|
9
|
+
(0, testing_1.registerInitializer)("postgres", new testing_1.PostgresInitializer());
|
|
10
|
+
let service;
|
|
11
|
+
(0, vitest_1.describe)("test phone validation plugin", () => {
|
|
12
|
+
const config = Object.assign(Object.assign({}, testing_1.testConfig), { dbConnectionOptions: {
|
|
13
|
+
type: "postgres",
|
|
14
|
+
}, plugins: [
|
|
15
|
+
index_js_1.PhoneNumberValidationPlugin.init({
|
|
16
|
+
requirePhoneNumber: true,
|
|
17
|
+
defaultCountryCode: "PL",
|
|
18
|
+
allowedCountryCodes: ["PL"],
|
|
19
|
+
}),
|
|
20
|
+
] });
|
|
21
|
+
const { server } = (0, testing_1.createTestEnvironment)(config);
|
|
22
|
+
(0, vitest_1.beforeAll)(async () => {
|
|
23
|
+
await server.init({
|
|
24
|
+
initialData: {
|
|
25
|
+
defaultLanguage: core_1.LanguageCode.en,
|
|
26
|
+
defaultZone: "Europe",
|
|
27
|
+
countries: [{ name: "Poland", code: "PL", zone: "Europe" }],
|
|
28
|
+
taxRates: [],
|
|
29
|
+
shippingMethods: [],
|
|
30
|
+
paymentMethods: [],
|
|
31
|
+
collections: [],
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
service = server.app.get(service_js_1.PhoneNumberValidationService);
|
|
35
|
+
}, 30000);
|
|
36
|
+
(0, vitest_1.it)("should define PhoneNumberValidationService", () => {
|
|
37
|
+
(0, vitest_1.expect)(service).toBeDefined();
|
|
38
|
+
});
|
|
39
|
+
const ctx = new core_1.RequestContext({
|
|
40
|
+
apiType: "shop",
|
|
41
|
+
channel: new core_1.Channel({}),
|
|
42
|
+
languageCode: core_1.LanguageCode.en,
|
|
43
|
+
currencyCode: core_1.CurrencyCode.USD,
|
|
44
|
+
authorizedAsOwnerOnly: false,
|
|
45
|
+
isAuthorized: true,
|
|
46
|
+
});
|
|
47
|
+
(0, vitest_1.it)("should validate phone number for order with valid Polish number", async () => {
|
|
48
|
+
const POLISH_ORDER = (0, utils_js_1.getTestOrder)("+48 123 456 789");
|
|
49
|
+
const result = await service.validatePhoneNumberForOrder(ctx, POLISH_ORDER);
|
|
50
|
+
(0, vitest_1.expect)(result).toBeUndefined();
|
|
51
|
+
});
|
|
52
|
+
(0, vitest_1.it)("should return error for order with not allowed country code (e.g. Netherlands)", async () => {
|
|
53
|
+
const NETHERLANDS_ORDER = (0, utils_js_1.getTestOrder)("+31 123 456 789");
|
|
54
|
+
const result = await service.validatePhoneNumberForOrder(ctx, NETHERLANDS_ORDER);
|
|
55
|
+
(0, vitest_1.expect)(result).toBe("phone number country NL is not allowed");
|
|
56
|
+
});
|
|
57
|
+
(0, vitest_1.it)("should return error for order with missing phone number", async () => {
|
|
58
|
+
const ORDER_WITHOUT_PHONE = (0, utils_js_1.getTestOrder)("");
|
|
59
|
+
const result = await service.validatePhoneNumberForOrder(ctx, ORDER_WITHOUT_PHONE);
|
|
60
|
+
(0, vitest_1.expect)(result).toBe("missing required phone number");
|
|
61
|
+
});
|
|
62
|
+
(0, vitest_1.it)("should return error for order with invalid phone number format", async () => {
|
|
63
|
+
const INVALID_ORDER = (0, utils_js_1.getTestOrder)("+48 12345");
|
|
64
|
+
const result = await service.validatePhoneNumberForOrder(ctx, INVALID_ORDER);
|
|
65
|
+
(0, vitest_1.expect)(result).toBe("+48 12345 is not a valid phone number for country PL");
|
|
66
|
+
});
|
|
67
|
+
(0, vitest_1.it)("should return error for order with invalid phone number for country", async () => {
|
|
68
|
+
const INVALID_POLISH_ORDER = (0, utils_js_1.getTestOrder)("+48 123 456 7890");
|
|
69
|
+
const result = await service.validatePhoneNumberForOrder(ctx, INVALID_POLISH_ORDER);
|
|
70
|
+
(0, vitest_1.expect)(result).toBe("+48 123 456 7890 is not a valid phone number for country PL");
|
|
71
|
+
});
|
|
72
|
+
(0, vitest_1.afterAll)(async () => {
|
|
73
|
+
await server.destroy();
|
|
74
|
+
}, 30000);
|
|
75
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
15
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
|
+
};
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.PhoneNumberValidationService = void 0;
|
|
19
|
+
const common_1 = require("@nestjs/common");
|
|
20
|
+
const core_1 = require("@deenruv/core");
|
|
21
|
+
const libphonenumber_js_1 = require("libphonenumber-js");
|
|
22
|
+
const libphonenumber_js_2 = __importDefault(require("libphonenumber-js"));
|
|
23
|
+
const symbol_js_1 = require("./symbol.js");
|
|
24
|
+
let PhoneNumberValidationService = class PhoneNumberValidationService {
|
|
25
|
+
constructor(options, activeOrderService) {
|
|
26
|
+
this.options = options;
|
|
27
|
+
this.activeOrderService = activeOrderService;
|
|
28
|
+
}
|
|
29
|
+
async getAllowedCountryCodes(ctx) {
|
|
30
|
+
const { allowedCountryCodes } = this.options;
|
|
31
|
+
if (typeof allowedCountryCodes === "function") {
|
|
32
|
+
return await allowedCountryCodes(ctx);
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(allowedCountryCodes)) {
|
|
35
|
+
return allowedCountryCodes;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async getCountryCode(ctx, order) {
|
|
39
|
+
if (order.shippingAddress.countryCode)
|
|
40
|
+
return order.shippingAddress.countryCode;
|
|
41
|
+
const { defaultCountryCode } = this.options;
|
|
42
|
+
if (typeof defaultCountryCode === "string")
|
|
43
|
+
return defaultCountryCode;
|
|
44
|
+
if (defaultCountryCode)
|
|
45
|
+
return await defaultCountryCode(ctx, order);
|
|
46
|
+
}
|
|
47
|
+
async validatePhoneNumberForOrder(ctx, order) {
|
|
48
|
+
const { requirePhoneNumber } = this.options;
|
|
49
|
+
const { phoneNumber } = order.shippingAddress;
|
|
50
|
+
if (!phoneNumber) {
|
|
51
|
+
return requirePhoneNumber ? "missing required phone number" : undefined;
|
|
52
|
+
}
|
|
53
|
+
const allowedCountryCodes = await this.getAllowedCountryCodes(ctx);
|
|
54
|
+
const phone = (0, libphonenumber_js_2.default)(phoneNumber);
|
|
55
|
+
if (!phone)
|
|
56
|
+
return "could not parse phone number";
|
|
57
|
+
if (phone.country &&
|
|
58
|
+
allowedCountryCodes &&
|
|
59
|
+
!allowedCountryCodes.includes(phone.country)) {
|
|
60
|
+
return `phone number country ${phone.country} is not allowed`;
|
|
61
|
+
}
|
|
62
|
+
// Try to validate phone number before country hint.
|
|
63
|
+
if ((0, libphonenumber_js_1.isValidPhoneNumber)(phoneNumber)) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const countryCode = await this.getCountryCode(ctx, order);
|
|
67
|
+
if (!countryCode) {
|
|
68
|
+
return "could not validate phone due to unknown country";
|
|
69
|
+
}
|
|
70
|
+
if (!(0, libphonenumber_js_1.isValidPhoneNumber)(phoneNumber, countryCode)) {
|
|
71
|
+
return `${phoneNumber} is not a valid phone number for country ${countryCode}`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async validatePhoneNumberForCurrentOrder(ctx) {
|
|
75
|
+
const order = await this.activeOrderService.getActiveOrder(ctx, undefined);
|
|
76
|
+
if (!order) {
|
|
77
|
+
throw new Error("no active order");
|
|
78
|
+
}
|
|
79
|
+
return this.validatePhoneNumberForOrder(ctx, order);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
exports.PhoneNumberValidationService = PhoneNumberValidationService;
|
|
83
|
+
exports.PhoneNumberValidationService = PhoneNumberValidationService = __decorate([
|
|
84
|
+
(0, common_1.Injectable)(),
|
|
85
|
+
__param(0, (0, common_1.Inject)(symbol_js_1.PHONE_NUMBER_VALIDATION_OPTIONS)),
|
|
86
|
+
__metadata("design:paramtypes", [Object, core_1.ActiveOrderService])
|
|
87
|
+
], PhoneNumberValidationService);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PHONE_NUMBER_VALIDATION_OPTIONS: unique symbol;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Order, OrderState, RequestContext } from "@deenruv/core";
|
|
2
|
+
import { CountryCode } from "libphonenumber-js";
|
|
3
|
+
export type PhoneNumberValidationOptions = {
|
|
4
|
+
disableTransitionValidation?: boolean;
|
|
5
|
+
stateCheck?: OrderState;
|
|
6
|
+
requirePhoneNumber?: boolean;
|
|
7
|
+
defaultCountryCode?: string | ((ctx: RequestContext, order: Order) => Promise<string> | string);
|
|
8
|
+
allowedCountryCodes?: CountryCode[] | ((ctx: RequestContext) => Promise<CountryCode[]> | CountryCode[]);
|
|
9
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getTestOrder = void 0;
|
|
4
|
+
const core_1 = require("@deenruv/core");
|
|
5
|
+
const getTestOrder = (phoneNumber) => {
|
|
6
|
+
return new core_1.Order({
|
|
7
|
+
id: "6",
|
|
8
|
+
currencyCode: core_1.CurrencyCode.USD,
|
|
9
|
+
createdAt: "2018-10-31T11:18:29.261Z",
|
|
10
|
+
updatedAt: "2018-10-31T15:24:17.000Z",
|
|
11
|
+
orderPlacedAt: "2018-10-31T13:54:17.000Z",
|
|
12
|
+
code: "T3EPGJKTVZPBD6Z9",
|
|
13
|
+
state: "ArrangingPayment",
|
|
14
|
+
active: true,
|
|
15
|
+
customer: new core_1.Customer({
|
|
16
|
+
id: "3",
|
|
17
|
+
firstName: "Test",
|
|
18
|
+
lastName: "Customer",
|
|
19
|
+
emailAddress: "test@test.com",
|
|
20
|
+
}),
|
|
21
|
+
lines: [
|
|
22
|
+
new core_1.OrderLine({
|
|
23
|
+
id: "5",
|
|
24
|
+
featuredAsset: {
|
|
25
|
+
preview: "/mailbox/placeholder-image",
|
|
26
|
+
},
|
|
27
|
+
productVariant: new core_1.ProductVariant({
|
|
28
|
+
id: "2",
|
|
29
|
+
name: "Curvy Monitor 24 inch",
|
|
30
|
+
sku: "C24F390",
|
|
31
|
+
}),
|
|
32
|
+
quantity: 1,
|
|
33
|
+
listPrice: 14374,
|
|
34
|
+
listPriceIncludesTax: true,
|
|
35
|
+
adjustments: [
|
|
36
|
+
{
|
|
37
|
+
adjustmentSource: "Promotion:1",
|
|
38
|
+
type: core_1.AdjustmentType.PROMOTION,
|
|
39
|
+
amount: -1000,
|
|
40
|
+
description: "$10 off computer equipment",
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
taxLines: [],
|
|
44
|
+
}),
|
|
45
|
+
new core_1.OrderLine({
|
|
46
|
+
id: "6",
|
|
47
|
+
featuredAsset: {
|
|
48
|
+
preview: "/mailbox/placeholder-image",
|
|
49
|
+
},
|
|
50
|
+
productVariant: new core_1.ProductVariant({
|
|
51
|
+
id: "4",
|
|
52
|
+
name: "Hard Drive 1TB",
|
|
53
|
+
sku: "IHD455T1",
|
|
54
|
+
}),
|
|
55
|
+
quantity: 1,
|
|
56
|
+
listPrice: 3799,
|
|
57
|
+
listPriceIncludesTax: true,
|
|
58
|
+
adjustments: [],
|
|
59
|
+
taxLines: [],
|
|
60
|
+
}),
|
|
61
|
+
],
|
|
62
|
+
subTotal: 15144,
|
|
63
|
+
subTotalWithTax: 18173,
|
|
64
|
+
shipping: 1000,
|
|
65
|
+
shippingLines: [
|
|
66
|
+
new core_1.ShippingLine({
|
|
67
|
+
listPrice: 1000,
|
|
68
|
+
listPriceIncludesTax: true,
|
|
69
|
+
taxLines: [{ taxRate: 20, description: "shipping tax" }],
|
|
70
|
+
shippingMethod: {
|
|
71
|
+
code: "express-flat-rate",
|
|
72
|
+
name: "Express Shipping",
|
|
73
|
+
description: "Express Shipping",
|
|
74
|
+
id: "2",
|
|
75
|
+
},
|
|
76
|
+
}),
|
|
77
|
+
],
|
|
78
|
+
surcharges: [],
|
|
79
|
+
shippingAddress: {
|
|
80
|
+
fullName: "Test Customer",
|
|
81
|
+
company: "",
|
|
82
|
+
streetLine1: "6000 Pagac Land",
|
|
83
|
+
streetLine2: "",
|
|
84
|
+
city: "Port Kirsten",
|
|
85
|
+
province: "Avon",
|
|
86
|
+
postalCode: "ZU32 9CP",
|
|
87
|
+
country: "Cabo Verde",
|
|
88
|
+
phoneNumber,
|
|
89
|
+
},
|
|
90
|
+
payments: [],
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
exports.getTestOrder = getTestOrder;
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deenruv/phone-number-validation",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"homepage": "https://deenruv.com/",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist/**/*"
|
|
8
|
+
],
|
|
9
|
+
"main": "./dist/plugin-server/index.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./dist/plugin-server/index.js",
|
|
12
|
+
"./plugin-server": "./dist/plugin-server/index.js"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@nestjs/graphql": "^12.2.0",
|
|
16
|
+
"libphonenumber-js": "^1.12.6",
|
|
17
|
+
"@deenruv/common": "^1.0.0",
|
|
18
|
+
"@deenruv/react-ui-devkit": "^1.0.0"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"@deenruv/core": "^0.1.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"rimraf": "^5.0.10",
|
|
25
|
+
"typescript": "5.3.3",
|
|
26
|
+
"@deenruv/core": "^1.0.0"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "rimraf dist && tsc --build",
|
|
30
|
+
"watch": "tsc --build --watch",
|
|
31
|
+
"lint": "eslint ./src/**/*.ts",
|
|
32
|
+
"lint:fix": "eslint --fix ./src/**/*.ts"
|
|
33
|
+
}
|
|
34
|
+
}
|