@unchainedshop/core-payment 4.4.0 → 4.6.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/README.md CHANGED
@@ -94,6 +94,37 @@ const providers = await paymentModule.findSupported({
94
94
  | `PAYMENT_PROVIDER_UPDATE` | Provider updated |
95
95
  | `PAYMENT_PROVIDER_REMOVE` | Provider deleted |
96
96
 
97
+ ## Security (PCI DSS)
98
+
99
+ This module is designed for **PCI DSS SAQ-A eligibility**:
100
+
101
+ ### Tokenization
102
+
103
+ - **No card data storage**: Credit card numbers (PAN) and CVV are never stored
104
+ - **Provider tokens only**: Only payment provider-issued tokens are stored
105
+ - **Secure credentials**: Payment credentials contain references, not card data
106
+
107
+ ```typescript
108
+ // PaymentCredentials structure - tokens only, no card data
109
+ type PaymentCredentials = {
110
+ paymentProviderId: string;
111
+ userId: string;
112
+ token?: string; // Provider-issued token (NOT card number)
113
+ isPreferred?: boolean;
114
+ meta: any; // Provider-specific metadata
115
+ };
116
+ ```
117
+
118
+ ### Payment Flow
119
+
120
+ All payment integrations use tokenization patterns:
121
+ 1. Card data collected by payment provider (Stripe, Datatrans, etc.)
122
+ 2. Provider returns secure token
123
+ 3. Unchained stores only the token reference
124
+ 4. Subsequent charges use the token
125
+
126
+ See [SECURITY.md](../../SECURITY.md) for complete PCI DSS compliance documentation.
127
+
97
128
  ## License
98
129
 
99
130
  EUPL-1.2
@@ -9,13 +9,19 @@ export declare const configurePaymentCredentialsModule: (PaymentCredentials: mon
9
9
  credentialsExists: ({ paymentCredentialsId, }: {
10
10
  paymentCredentialsId: string;
11
11
  }) => Promise<boolean>;
12
- findPaymentCredential: ({ paymentCredentialsId, userId, paymentProviderId, }: {
12
+ findPaymentCredential: ({ paymentCredentialsId, userId, paymentProviderId, isPreferred, }: {
13
13
  paymentCredentialsId?: string;
14
14
  userId?: string;
15
15
  paymentProviderId?: string;
16
+ isPreferred?: boolean;
16
17
  }, options?: mongodb.FindOptions) => Promise<PaymentCredentialsType | null>;
17
18
  findPaymentCredentials: (query: mongodb.Filter<PaymentCredentialsType>, options?: mongodb.FindOptions) => Promise<PaymentCredentialsType[]>;
18
- upsertCredentials: ({ userId, paymentProviderId, _id, token, ...meta }: Pick<PaymentCredentialsType, "userId" | "paymentProviderId" | "_id" | "token"> & Record<string, any>) => Promise<PaymentCredentialsType>;
19
+ upsertCredentials: ({ userId, paymentProviderId, _id, token, ...meta }: Partial<PaymentCredentialsType> & {
20
+ userId: PaymentCredentialsType["userId"];
21
+ paymentProviderId: PaymentCredentialsType["paymentProviderId"];
22
+ _id?: PaymentCredentialsType["_id"];
23
+ token?: PaymentCredentialsType["token"];
24
+ } & Record<string, any>) => Promise<PaymentCredentialsType>;
19
25
  removeCredentials: (paymentCredentialsId: string) => Promise<mongodb.WithId<PaymentCredentialsType> | null>;
20
26
  deleteUserPaymentCredentials: (userId: string) => Promise<number>;
21
27
  };
@@ -28,10 +28,10 @@ export const configurePaymentCredentialsModule = (PaymentCredentials) => {
28
28
  const credentialsCount = await PaymentCredentials.countDocuments(generateDbFilterById(paymentCredentialsId), { limit: 1 });
29
29
  return !!credentialsCount;
30
30
  },
31
- findPaymentCredential: async ({ paymentCredentialsId, userId, paymentProviderId, }, options) => {
31
+ findPaymentCredential: async ({ paymentCredentialsId, userId, paymentProviderId, isPreferred, }, options) => {
32
32
  return PaymentCredentials.findOne(paymentCredentialsId
33
- ? generateDbFilterById(paymentCredentialsId)
34
- : { userId, paymentProviderId }, options);
33
+ ? generateDbFilterById(paymentCredentialsId, isPreferred ? { isPreferred: true } : {})
34
+ : { userId, paymentProviderId, ...(isPreferred ? { isPreferred: true } : {}) }, options);
35
35
  },
36
36
  findPaymentCredentials: async (query, options) => {
37
37
  const credentials = await PaymentCredentials.find(query, options).toArray();
@@ -2,13 +2,11 @@ import { type PaymentSettingsOptions } from '../payment-settings.ts';
2
2
  import type { ModuleInput } from '@unchainedshop/mongodb';
3
3
  export declare const configurePaymentModule: ({ db, options: paymentOptions, }: ModuleInput<PaymentSettingsOptions>) => Promise<{
4
4
  paymentProviders: {
5
- count: (query: import("mongodb").Filter<import("../db/PaymentProvidersCollection.ts").PaymentProvider>) => Promise<number>;
5
+ count: (query?: import("./configurePaymentProvidersModule.ts").PaymentProviderQuery) => Promise<number>;
6
6
  findProvider: ({ paymentProviderId, ...query }: import("mongodb").Filter<import("../db/PaymentProvidersCollection.ts").PaymentProvider> & {
7
7
  paymentProviderId: string;
8
8
  }, options?: import("mongodb").FindOptions) => Promise<import("../db/PaymentProvidersCollection.ts").PaymentProvider | null>;
9
- findProviders: (query: import("mongodb").Filter<import("../db/PaymentProvidersCollection.ts").PaymentProvider> & {
10
- includeDeleted?: boolean;
11
- }, options?: import("mongodb").FindOptions) => Promise<import("../db/PaymentProvidersCollection.ts").PaymentProvider[]>;
9
+ findProviders: (query?: import("./configurePaymentProvidersModule.ts").PaymentProviderQuery, options?: import("mongodb").FindOptions) => Promise<import("../db/PaymentProvidersCollection.ts").PaymentProvider[]>;
12
10
  allProviders: () => Promise<import("mongodb").WithId<import("../db/PaymentProvidersCollection.ts").PaymentProvider>[]>;
13
11
  providerExists: ({ paymentProviderId }: {
14
12
  paymentProviderId: string;
@@ -26,13 +24,19 @@ export declare const configurePaymentModule: ({ db, options: paymentOptions, }:
26
24
  credentialsExists: ({ paymentCredentialsId, }: {
27
25
  paymentCredentialsId: string;
28
26
  }) => Promise<boolean>;
29
- findPaymentCredential: ({ paymentCredentialsId, userId, paymentProviderId, }: {
27
+ findPaymentCredential: ({ paymentCredentialsId, userId, paymentProviderId, isPreferred, }: {
30
28
  paymentCredentialsId?: string;
31
29
  userId?: string;
32
30
  paymentProviderId?: string;
31
+ isPreferred?: boolean;
33
32
  }, options?: import("mongodb").FindOptions) => Promise<import("../db/PaymentCredentialsCollection.ts").PaymentCredentials | null>;
34
33
  findPaymentCredentials: (query: import("mongodb").Filter<import("../db/PaymentCredentialsCollection.ts").PaymentCredentials>, options?: import("mongodb").FindOptions) => Promise<import("../db/PaymentCredentialsCollection.ts").PaymentCredentials[]>;
35
- upsertCredentials: ({ userId, paymentProviderId, _id, token, ...meta }: Pick<import("../db/PaymentCredentialsCollection.ts").PaymentCredentials, "userId" | "paymentProviderId" | "_id" | "token"> & Record<string, any>) => Promise<import("../db/PaymentCredentialsCollection.ts").PaymentCredentials>;
34
+ upsertCredentials: ({ userId, paymentProviderId, _id, token, ...meta }: Partial<import("../db/PaymentCredentialsCollection.ts").PaymentCredentials> & {
35
+ userId: import("../db/PaymentCredentialsCollection.ts").PaymentCredentials["userId"];
36
+ paymentProviderId: import("../db/PaymentCredentialsCollection.ts").PaymentCredentials["paymentProviderId"];
37
+ _id?: import("../db/PaymentCredentialsCollection.ts").PaymentCredentials["_id"];
38
+ token?: import("../db/PaymentCredentialsCollection.ts").PaymentCredentials["token"];
39
+ } & Record<string, any>) => Promise<import("../db/PaymentCredentialsCollection.ts").PaymentCredentials>;
36
40
  removeCredentials: (paymentCredentialsId: string) => Promise<import("mongodb").WithId<import("../db/PaymentCredentialsCollection.ts").PaymentCredentials> | null>;
37
41
  deleteUserPaymentCredentials: (userId: string) => Promise<number>;
38
42
  };
@@ -1,40 +1,23 @@
1
1
  import { mongodb } from '@unchainedshop/mongodb';
2
- import type { PaymentProvider } from '../db/PaymentProvidersCollection.ts';
2
+ import type { PaymentProvider, PaymentProviderType } from '../db/PaymentProvidersCollection.ts';
3
3
  export interface PaymentInterface {
4
4
  _id: string;
5
5
  label: string;
6
6
  version: string;
7
7
  }
8
- export declare const buildFindSelector: ({ includeDeleted, ...rest }?: mongodb.Filter<PaymentProvider> & {
8
+ export interface PaymentProviderQuery {
9
+ paymentProviderIds?: string[];
10
+ type?: PaymentProviderType;
9
11
  includeDeleted?: boolean;
10
- }) => {
11
- type?: mongodb.Condition<import("../db/PaymentProvidersCollection.ts").PaymentProviderType> | undefined;
12
- created?: mongodb.Condition<Date> | undefined;
13
- deleted?: Date | mongodb.FilterOperators<Date | null | undefined> | null | undefined;
14
- updated?: mongodb.Condition<Date | undefined>;
15
- adapterKey?: mongodb.Condition<string> | undefined;
16
- configuration?: mongodb.Condition<import("../db/PaymentProvidersCollection.ts").PaymentConfiguration> | undefined;
17
- _id?: mongodb.Condition<string> | undefined;
18
- $and?: mongodb.Filter<mongodb.WithId<PaymentProvider>>[] | undefined;
19
- $nor?: mongodb.Filter<mongodb.WithId<PaymentProvider>>[] | undefined;
20
- $or?: mongodb.Filter<mongodb.WithId<PaymentProvider>>[] | undefined;
21
- $text?: {
22
- $search: string;
23
- $language?: string;
24
- $caseSensitive?: boolean;
25
- $diacriticSensitive?: boolean;
26
- };
27
- $where?: string | ((this: mongodb.WithId<PaymentProvider>) => boolean) | undefined;
28
- $comment?: string | mongodb.BSON.Document;
29
- };
12
+ queryString?: string;
13
+ }
14
+ export declare const buildFindSelector: ({ includeDeleted, queryString, paymentProviderIds, type, }?: PaymentProviderQuery) => mongodb.Filter<PaymentProvider>;
30
15
  export declare const configurePaymentProvidersModule: (PaymentProviders: mongodb.Collection<PaymentProvider>) => {
31
- count: (query: mongodb.Filter<PaymentProvider>) => Promise<number>;
16
+ count: (query?: PaymentProviderQuery) => Promise<number>;
32
17
  findProvider: ({ paymentProviderId, ...query }: mongodb.Filter<PaymentProvider> & {
33
18
  paymentProviderId: string;
34
19
  }, options?: mongodb.FindOptions) => Promise<PaymentProvider | null>;
35
- findProviders: (query: mongodb.Filter<PaymentProvider> & {
36
- includeDeleted?: boolean;
37
- }, options?: mongodb.FindOptions) => Promise<PaymentProvider[]>;
20
+ findProviders: (query?: PaymentProviderQuery, options?: mongodb.FindOptions) => Promise<PaymentProvider[]>;
38
21
  allProviders: () => Promise<mongodb.WithId<PaymentProvider>[]>;
39
22
  providerExists: ({ paymentProviderId }: {
40
23
  paymentProviderId: string;
@@ -1,5 +1,5 @@
1
1
  import { emit, registerEvents } from '@unchainedshop/events';
2
- import { generateDbFilterById, generateDbObjectId, mongodb } from '@unchainedshop/mongodb';
2
+ import { generateDbFilterById, generateDbObjectId, mongodb, escapeRegexString, } from '@unchainedshop/mongodb';
3
3
  import pMemoize from 'p-memoize';
4
4
  import ExpiryMap from 'expiry-map';
5
5
  const PAYMENT_PROVIDER_EVENTS = [
@@ -8,20 +8,31 @@ const PAYMENT_PROVIDER_EVENTS = [
8
8
  'PAYMENT_PROVIDER_REMOVE',
9
9
  ];
10
10
  const allProvidersCache = new ExpiryMap(process.env.NODE_ENV === 'production' ? 60000 : 1);
11
- export const buildFindSelector = ({ includeDeleted = false, ...rest } = {}) => {
12
- return { ...(includeDeleted ? {} : { deleted: null }), ...rest };
11
+ export const buildFindSelector = ({ includeDeleted = false, queryString, paymentProviderIds, type, } = {}) => {
12
+ const selector = includeDeleted ? {} : { deleted: null };
13
+ if (paymentProviderIds) {
14
+ selector._id = { $in: paymentProviderIds };
15
+ }
16
+ if (type) {
17
+ selector.type = type;
18
+ }
19
+ if (queryString) {
20
+ const regex = new RegExp(escapeRegexString(queryString), 'i');
21
+ selector.$or = [{ _id: regex }, { adapterKey: regex }];
22
+ }
23
+ return selector;
13
24
  };
14
25
  export const configurePaymentProvidersModule = (PaymentProviders) => {
15
26
  registerEvents(PAYMENT_PROVIDER_EVENTS);
16
27
  return {
17
- count: async (query) => {
28
+ count: async (query = {}) => {
18
29
  const providerCount = await PaymentProviders.countDocuments(buildFindSelector(query));
19
30
  return providerCount;
20
31
  },
21
32
  findProvider: async ({ paymentProviderId, ...query }, options) => {
22
33
  return PaymentProviders.findOne(paymentProviderId ? generateDbFilterById(paymentProviderId) : query, options);
23
34
  },
24
- findProviders: async (query, options = { sort: { created: 1 } }) => {
35
+ findProviders: async (query = {}, options = { sort: { created: 1 } }) => {
25
36
  const providers = PaymentProviders.find(buildFindSelector(query), options);
26
37
  return providers.toArray();
27
38
  },
@@ -1,4 +1,6 @@
1
1
  export * from './module/configurePaymentModule.ts';
2
+ export * from './module/configurePaymentCredentialsModule.ts';
3
+ export * from './module/configurePaymentProvidersModule.ts';
2
4
  export * from './payment-settings.ts';
3
5
  export * from './db/PaymentCredentialsCollection.ts';
4
6
  export * from './db/PaymentProvidersCollection.ts';
@@ -1,4 +1,6 @@
1
1
  export * from "./module/configurePaymentModule.js";
2
+ export * from "./module/configurePaymentCredentialsModule.js";
3
+ export * from "./module/configurePaymentProvidersModule.js";
2
4
  export * from "./payment-settings.js";
3
5
  export * from "./db/PaymentCredentialsCollection.js";
4
6
  export * from "./db/PaymentProvidersCollection.js";
package/package.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "name": "@unchainedshop/core-payment",
3
- "version": "4.4.0",
3
+ "description": "Payment provider management module for the Unchained Engine",
4
+ "version": "4.6.0",
4
5
  "main": "lib/payment-index.js",
5
6
  "types": "lib/payment-index.d.ts",
6
7
  "type": "module",
8
+ "sideEffects": false,
7
9
  "scripts": {
8
10
  "clean": "tsc -b --clean",
9
11
  "build": "tsc -b",
@@ -33,9 +35,9 @@
33
35
  },
34
36
  "homepage": "https://github.com/unchainedshop/unchained#readme",
35
37
  "dependencies": {
36
- "@unchainedshop/events": "^4.4.0",
37
- "@unchainedshop/logger": "^4.4.0",
38
- "@unchainedshop/utils": "^4.4.0",
38
+ "@unchainedshop/events": "^4.6.0",
39
+ "@unchainedshop/logger": "^4.6.0",
40
+ "@unchainedshop/utils": "^4.6.0",
39
41
  "expiry-map": "^2.0.0",
40
42
  "p-memoize": "^8.0.0"
41
43
  },