Jelajahi Sumber

Merge branch 'master' into decouple-website

Michael Bromley 4 tahun lalu
induk
melakukan
727319fd6a

+ 8 - 0
CHANGELOG.md

@@ -1,3 +1,11 @@
+## 1.0.0-rc.0 (2021-05-05)
+
+
+#### Fixes
+
+* **core** Fix transition to PaymentSettled with multiple payments ([c60fad7](https://github.com/vendure-ecommerce/vendure/commit/c60fad7)), closes [#847](https://github.com/vendure-ecommerce/vendure/issues/847)
+* **core** Handle different input types in validateRequiredFields() (#861) ([2ca6bfd](https://github.com/vendure-ecommerce/vendure/commit/2ca6bfd)), closes [#861](https://github.com/vendure-ecommerce/vendure/issues/861) [#855](https://github.com/vendure-ecommerce/vendure/issues/855)
+
 ## 1.0.0-beta.11 (2021-04-28)
 
 

+ 5 - 3
docs/content/developer-guide/authentication.md

@@ -9,7 +9,9 @@ Authentication is the process of determining the identity of a user. Common ways
 
 By default, Vendure uses a username/email address and password to authenticate users, but also supports a wide range of authentication methods via configurable AuthenticationStrategies.
 
-While developing a storefront, mobile app or server that implements authentication of Vendure further reading on [Managing Sessions]({{< relref "managing-sessions" >}}) is highly recommended.
+{{< alert "primary" >}}
+See the [Managing Sessions guide]({{< relref "managing-sessions" >}}) for how to manage authenticated session in your storefront/client applications.
+{{ /alert >}}
 
 ## Adding support for external authentication
 
@@ -33,9 +35,9 @@ export const config: VendureConfig = {
 
 In the above example, we define the strategies available for authenticating in the Shop API and the Admin API. The `NativeAuthenticationStrategy` is the only one actually provided by Vendure out-of-the-box, and this is the default username/email + password strategy.
 
-The other strategies would be custom-built (or provided by future npm packages) but creating classes that implement the [`AuthenticationStrategy` interface]({{< relref "authentication-strategy" >}}).
+The other strategies would be custom-built (or provided by future npm packages) by creating classes that implement the [`AuthenticationStrategy` interface]({{< relref "authentication-strategy" >}}).
 
-Let's take a look at a couple of examples of what a customer AuthenticationStrategy implementation would look like.
+Let's take a look at a couple of examples of what a custom AuthenticationStrategy implementation would look like.
 
 ## Example: Google authentication
 

+ 1 - 0
docs/content/developer-guide/deployment.md

@@ -32,6 +32,7 @@ For a production Vendure server, there are a few security-related points to cons
     // ...
   }
   ```
+* Consider using [helmet](https://github.com/helmetjs/helmet) as middleware (add to the `apiOptions.middleware` array) to handle security-related headers. 
 
 ## Health/Readiness Checks
 

+ 9 - 6
docs/content/plugins/extending-the-admin-ui/adding-navigation-items/_index.md

@@ -82,12 +82,15 @@ import { SharedModule, addActionBarItem } from '@vendure/admin-ui/core';
   imports: [SharedModule],
   providers: [
     addActionBarItem({
-       id: 'product-reviews',
-       label: 'Product reviews',
-       locationId: 'product-detail',
-       buttonStyle: 'outline',
-       routerLink: ['./reviews'],
-       requiresPermission: 'SuperAdmin'
+      id: 'product-reviews',
+      label: 'Product reviews',
+      locationId: 'product-detail',
+      buttonStyle: 'outline',
+      routerLink: route => {
+          const id = route.snapshot.params.id;
+          return ['./extensions/reviews', id];
+      },
+      requiresPermission: 'SuperAdmin'
     }),
   ],
 })

+ 1 - 1
lerna.json

@@ -2,7 +2,7 @@
   "packages": [
     "packages/*"
   ],
-  "version": "1.0.0-beta.11",
+  "version": "1.0.0-rc.0",
   "npmClient": "yarn",
   "useWorkspaces": true,
   "command": {

+ 2 - 2
packages/admin-ui-plugin/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/admin-ui-plugin",
-  "version": "1.0.0-beta.11",
+  "version": "1.0.0-rc.0",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
   "files": [
@@ -20,7 +20,7 @@
     "@types/express": "^4.17.8",
     "@types/fs-extra": "^9.0.1",
     "@vendure/common": "^1.0.0-beta.11",
-    "@vendure/core": "^1.0.0-beta.11",
+    "@vendure/core": "^1.0.0-rc.0",
     "express": "^4.17.1",
     "rimraf": "^3.0.2",
     "typescript": "4.1.5"

+ 2 - 2
packages/asset-server-plugin/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/asset-server-plugin",
-  "version": "1.0.0-beta.11",
+  "version": "1.0.0-rc.0",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
   "files": [
@@ -23,7 +23,7 @@
     "@types/node-fetch": "^2.5.8",
     "@types/sharp": "^0.27.1",
     "@vendure/common": "^1.0.0-beta.11",
-    "@vendure/core": "^1.0.0-beta.11",
+    "@vendure/core": "^1.0.0-rc.0",
     "aws-sdk": "^2.856.0",
     "express": "^4.17.1",
     "node-fetch": "^2.6.1",

+ 5 - 5
packages/core/e2e/configurable-operation.e2e-spec.ts

@@ -77,8 +77,8 @@ describe('Configurable operations', () => {
                     checker: {
                         code: testShippingEligibilityChecker.code,
                         arguments: [
-                            { name: 'optional', value: 'null' },
-                            { name: 'required', value: '"foo"' },
+                            { name: 'optional', value: '' },
+                            { name: 'required', value: 'foo' },
                         ],
                     },
                     translations: [],
@@ -88,11 +88,11 @@ describe('Configurable operations', () => {
             expect(updateShippingMethod.checker.args).toEqual([
                 {
                     name: 'optional',
-                    value: 'null',
+                    value: '',
                 },
                 {
                     name: 'required',
-                    value: '"foo"',
+                    value: 'foo',
                 },
             ]);
         });
@@ -109,7 +109,7 @@ describe('Configurable operations', () => {
                                 code: testShippingEligibilityChecker.code,
                                 arguments: [
                                     { name: 'optional', value: 'null' },
-                                    { name: 'required', value: 'null' },
+                                    { name: 'required', value: '' },
                                 ],
                             },
                             translations: [],

+ 1 - 1
packages/core/e2e/fixtures/test-payment-methods.ts

@@ -59,7 +59,7 @@ export const partialPaymentMethod = new PaymentMethodHandler({
     createPayment: (ctx, order, amount, args, metadata) => {
         return {
             amount: metadata.amount,
-            state: 'Settled',
+            state: metadata.authorizeOnly ? 'Authorized' : 'Settled',
             transactionId: '12345',
             metadata: { public: metadata },
         };

+ 96 - 2
packages/core/e2e/order.e2e-spec.ts

@@ -1567,8 +1567,10 @@ describe('Orders resolver', () => {
             );
             refundGuard.assertErrorResult(refundOrder);
 
-            expect(refundOrder.message).toBe('Cannot refund an OrderItem which has already been refunded');
-            expect(refundOrder.errorCode).toBe(ErrorCode.ALREADY_REFUNDED_ERROR);
+            expect(refundOrder.message).toBe(
+                'The specified quantity is greater than the available OrderItems',
+            );
+            expect(refundOrder.errorCode).toBe(ErrorCode.QUANTITY_TOO_GREAT_ERROR);
         });
 
         it('manually settle a Refund', async () => {
@@ -1911,6 +1913,55 @@ describe('Orders resolver', () => {
                 orderTotalWithTax - PARTIAL_PAYMENT_AMOUNT,
             );
         });
+
+        // https://github.com/vendure-ecommerce/vendure/issues/847
+        it('manual call to settlePayment works with multiple payments', async () => {
+            const result = await createTestOrder(
+                adminClient,
+                shopClient,
+                customers[1].emailAddress,
+                password,
+            );
+            await proceedToArrangingPayment(shopClient);
+            await shopClient.query<AddPaymentToOrder.Mutation, AddPaymentToOrder.Variables>(ADD_PAYMENT, {
+                input: {
+                    method: partialPaymentMethod.code,
+                    metadata: {
+                        amount: PARTIAL_PAYMENT_AMOUNT,
+                        authorizeOnly: true,
+                    },
+                },
+            });
+            const { addPaymentToOrder: order } = await shopClient.query<
+                AddPaymentToOrder.Mutation,
+                AddPaymentToOrder.Variables
+            >(ADD_PAYMENT, {
+                input: {
+                    method: singleStageRefundablePaymentMethod.code,
+                    metadata: {},
+                },
+            });
+            orderGuard.assertSuccess(order);
+
+            expect(order.state).toBe('PaymentAuthorized');
+
+            const { settlePayment } = await adminClient.query<
+                SettlePayment.Mutation,
+                SettlePayment.Variables
+            >(SETTLE_PAYMENT, {
+                id: order.payments![0].id,
+            });
+
+            paymentGuard.assertSuccess(settlePayment);
+
+            expect(settlePayment.state).toBe('Settled');
+
+            const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
+                id: order.id,
+            });
+
+            expect(order2?.state).toBe('PaymentSettled');
+        });
     });
 
     describe('issues', () => {
@@ -2022,6 +2073,49 @@ describe('Orders resolver', () => {
                 shippingMethod: pick(shippingMethod, ['id', 'name', 'code', 'description']),
             });
         });
+
+        // https://github.com/vendure-ecommerce/vendure/issues/868
+        it('allows multiple refunds of same OrderLine', async () => {
+            await shopClient.asUserWithCredentials(customers[0].emailAddress, password);
+            const { addItemToOrder } = await shopClient.query<
+                AddItemToOrder.Mutation,
+                AddItemToOrder.Variables
+            >(ADD_ITEM_TO_ORDER, {
+                productVariantId: 'T_1',
+                quantity: 2,
+            });
+            await proceedToArrangingPayment(shopClient);
+            const order = await addPaymentToOrder(shopClient, singleStageRefundablePaymentMethod);
+            orderGuard.assertSuccess(order);
+
+            const { refundOrder: refund1 } = await adminClient.query<
+                RefundOrder.Mutation,
+                RefundOrder.Variables
+            >(REFUND_ORDER, {
+                input: {
+                    lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
+                    shipping: 0,
+                    adjustment: 0,
+                    reason: 'foo',
+                    paymentId: order.payments![0].id,
+                },
+            });
+            refundGuard.assertSuccess(refund1);
+
+            const { refundOrder: refund2 } = await adminClient.query<
+                RefundOrder.Mutation,
+                RefundOrder.Variables
+            >(REFUND_ORDER, {
+                input: {
+                    lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
+                    shipping: 0,
+                    adjustment: 0,
+                    reason: 'foo',
+                    paymentId: order.payments![0].id,
+                },
+            });
+            refundGuard.assertSuccess(refund2);
+        });
     });
 });
 

+ 1 - 1
packages/core/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/core",
-  "version": "1.0.0-beta.11",
+  "version": "1.0.0-rc.0",
   "description": "A modern, headless ecommerce framework",
   "repository": {
     "type": "git",

+ 26 - 1
packages/core/src/api/common/request-context.spec.ts

@@ -57,6 +57,30 @@ describe('RequestContext', () => {
             const result = RequestContext.deserialize(serializedCtx);
             expect(result.session).toEqual(original.session);
         });
+
+        // https://github.com/vendure-ecommerce/vendure/issues/864
+        it('serialize request context with circular refs', () => {
+            const cyclic: any = {};
+            const cyclic1: any = {
+                prop: cyclic,
+            };
+            cyclic.prop = cyclic1;
+
+            const requestContext = createRequestContext({
+                simple: 'foo',
+                arr: [1, 2, 3],
+                cycle: cyclic,
+                cycleArr: [cyclic, cyclic],
+            });
+
+            const serialized = requestContext.serialize();
+            expect(serialized._req).toEqual({
+                simple: 'foo',
+                arr: [1, 2, 3],
+                cycle: {},
+                cycleArr: [{}, {}],
+            });
+        });
     });
 
     describe('copy', () => {
@@ -106,7 +130,7 @@ describe('RequestContext', () => {
         });
     });
 
-    function createRequestContext() {
+    function createRequestContext(req?: any) {
         let session: CachedSession;
         let channel: Channel;
         let activeOrder: Order;
@@ -148,6 +172,7 @@ describe('RequestContext', () => {
             languageCode: LanguageCode.en,
             channel,
             session,
+            req: req ?? {},
             isAuthorized: true,
             authorizedAsOwnerOnly: false,
         });

+ 18 - 2
packages/core/src/api/common/request-context.ts

@@ -246,8 +246,24 @@ export class RequestContext {
                     // avoid Express "deprecated: req.host" warning
                     continue;
                 }
-                const val = (target as any)[key];
-                if (!isObject(val) && typeof val !== 'function') {
+                let val: any;
+                try {
+                    val = (target as any)[key];
+                } catch (e) {
+                    val = String(e);
+                }
+
+                if (Array.isArray(val)) {
+                    depth++;
+                    result[key] = val.map(v => {
+                        if (!isObject(v) && typeof val !== 'function') {
+                            return v;
+                        } else {
+                            return copySimpleFieldsToDepth(v, maxDepth, depth);
+                        }
+                    });
+                    depth--;
+                } else if (!isObject(val) && typeof val !== 'function') {
                     result[key] = val;
                 } else if (depth < maxDepth) {
                     depth++;

+ 11 - 7
packages/core/src/service/helpers/config-arg/config-arg.service.ts

@@ -92,15 +92,19 @@ export class ConfigArgService {
         for (const [name, argDef] of Object.entries(def.args)) {
             if (argDef.required) {
                 const inputArg = input.arguments.find(a => a.name === name);
-                let val: unknown;
-                if (inputArg) {
-                    try {
-                        val = JSON.parse(inputArg?.value);
-                    } catch (e) {
-                        // ignore
+
+                let valid = false;
+                try {
+                    if (['string', 'ID', 'datetime'].includes(argDef.type)) {
+                        valid = !!inputArg && inputArg.value !== '' && inputArg.value != null;
+                    } else {
+                        valid = !!inputArg && JSON.parse(inputArg.value) != null;
                     }
+                } catch (e) {
+                    // ignore
                 }
-                if (val == null) {
+
+                if (!valid) {
                     throw new UserInputError('error.configurable-argument-is-required', {
                         name,
                     });

+ 7 - 13
packages/core/src/service/services/order.service.ts

@@ -820,7 +820,7 @@ export class OrderService {
         if (orderTotalIsCovered(order, 'Settled')) {
             return this.transitionToState(ctx, orderId, 'PaymentSettled');
         }
-        if (orderTotalIsCovered(order, 'Authorized')) {
+        if (orderTotalIsCovered(order, ['Authorized', 'Settled'])) {
             return this.transitionToState(ctx, orderId, 'PaymentAuthorized');
         }
         return order;
@@ -867,16 +867,10 @@ export class OrderService {
             if (payment.state !== 'Settled') {
                 return new SettlePaymentError(payment.errorMessage || '');
             }
-            const orderTotalSettled = payment.amount === payment.order.totalWithTax;
-            if (
-                orderTotalSettled &&
-                this.orderStateMachine.canTransition(payment.order.state, 'PaymentSettled')
-            ) {
-                const orderTransitionResult = await this.transitionToState(
-                    ctx,
-                    payment.order.id,
-                    'PaymentSettled',
-                );
+            const order = await this.findOne(ctx, payment.order.id);
+            if (order) {
+                order.payments = await this.getOrderPayments(ctx, order.id);
+                const orderTransitionResult = await this.transitionOrderIfTotalIsCovered(ctx, order);
                 if (isGraphQlErrorResult(orderTransitionResult)) {
                     return orderTransitionResult;
                 }
@@ -1084,7 +1078,7 @@ export class OrderService {
         ) {
             return new NothingToRefundError();
         }
-        const ordersAndItems = await this.getOrdersAndItemsFromLines(ctx, input.lines, i => !i.cancelled);
+        const ordersAndItems = await this.getOrdersAndItemsFromLines(ctx, input.lines, i => !i.refund);
         if (!ordersAndItems) {
             return new QuantityTooGreatError();
         }
@@ -1412,7 +1406,7 @@ export class OrderService {
         const lines = await this.connection.getRepository(ctx, OrderLine).findByIds(
             orderLinesInput.map(l => l.orderLineId),
             {
-                relations: ['order', 'items', 'items.fulfillments', 'order.channels'],
+                relations: ['order', 'items', 'items.fulfillments', 'order.channels', 'items.refund'],
                 order: { id: 'ASC' },
             },
         );

+ 2 - 2
packages/create/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/create",
-  "version": "1.0.0-beta.11",
+  "version": "1.0.0-rc.0",
   "license": "MIT",
   "bin": {
     "create": "./index.js"
@@ -26,7 +26,7 @@
     "@types/handlebars": "^4.1.0",
     "@types/listr": "^0.14.2",
     "@types/semver": "^6.2.2",
-    "@vendure/core": "^1.0.0-beta.11",
+    "@vendure/core": "^1.0.0-rc.0",
     "rimraf": "^3.0.2",
     "ts-node": "^9.0.0",
     "typescript": "4.1.5"

+ 8 - 8
packages/dev-server/package.json

@@ -1,6 +1,6 @@
 {
   "name": "dev-server",
-  "version": "1.0.0-beta.11",
+  "version": "1.0.0-rc.0",
   "main": "index.js",
   "license": "MIT",
   "private": true,
@@ -14,18 +14,18 @@
     "load-test:100k": "node -r ts-node/register load-testing/run-load-test.ts 100000"
   },
   "dependencies": {
-    "@vendure/admin-ui-plugin": "^1.0.0-beta.11",
-    "@vendure/asset-server-plugin": "^1.0.0-beta.11",
+    "@vendure/admin-ui-plugin": "^1.0.0-rc.0",
+    "@vendure/asset-server-plugin": "^1.0.0-rc.0",
     "@vendure/common": "^1.0.0-beta.11",
-    "@vendure/core": "^1.0.0-beta.11",
-    "@vendure/elasticsearch-plugin": "^1.0.0-beta.11",
-    "@vendure/email-plugin": "^1.0.0-beta.11",
+    "@vendure/core": "^1.0.0-rc.0",
+    "@vendure/elasticsearch-plugin": "^1.0.0-rc.0",
+    "@vendure/email-plugin": "^1.0.0-rc.0",
     "typescript": "4.1.5"
   },
   "devDependencies": {
     "@types/csv-stringify": "^3.1.0",
-    "@vendure/testing": "^1.0.0-beta.11",
-    "@vendure/ui-devkit": "^1.0.0-beta.11",
+    "@vendure/testing": "^1.0.0-rc.0",
+    "@vendure/ui-devkit": "^1.0.0-rc.0",
     "concurrently": "^5.0.0",
     "csv-stringify": "^5.3.3"
   }

+ 2 - 2
packages/elasticsearch-plugin/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/elasticsearch-plugin",
-  "version": "1.0.0-beta.11",
+  "version": "1.0.0-rc.0",
   "license": "MIT",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -23,7 +23,7 @@
   },
   "devDependencies": {
     "@vendure/common": "^1.0.0-beta.11",
-    "@vendure/core": "^1.0.0-beta.11",
+    "@vendure/core": "^1.0.0-rc.0",
     "rimraf": "^3.0.2",
     "typescript": "4.1.5"
   }

+ 2 - 2
packages/email-plugin/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/email-plugin",
-  "version": "1.0.0-beta.11",
+  "version": "1.0.0-rc.0",
   "license": "MIT",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -34,7 +34,7 @@
     "@types/handlebars": "^4.1.0",
     "@types/mjml": "^4.0.4",
     "@vendure/common": "^1.0.0-beta.11",
-    "@vendure/core": "^1.0.0-beta.11",
+    "@vendure/core": "^1.0.0-rc.0",
     "rimraf": "^3.0.2",
     "typescript": "4.1.5"
   }

+ 2 - 2
packages/job-queue-plugin/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/job-queue-plugin",
-  "version": "1.0.0-beta.11",
+  "version": "1.0.0-rc.0",
   "license": "MIT",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -22,7 +22,7 @@
     "@google-cloud/pubsub": "^2.8.0",
     "@types/redis": "^2.8.28",
     "@vendure/common": "^1.0.0-beta.11",
-    "@vendure/core": "^1.0.0-beta.11",
+    "@vendure/core": "^1.0.0-rc.0",
     "redis": "^3.0.2",
     "rimraf": "^3.0.2",
     "typescript": "4.1.5"

+ 2 - 2
packages/testing/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/testing",
-  "version": "1.0.0-beta.11",
+  "version": "1.0.0-rc.0",
   "description": "End-to-end testing tools for Vendure projects",
   "keywords": [
     "vendure",
@@ -44,7 +44,7 @@
   "devDependencies": {
     "@types/mysql": "^2.15.15",
     "@types/pg": "^7.14.5",
-    "@vendure/core": "^1.0.0-beta.11",
+    "@vendure/core": "^1.0.0-rc.0",
     "mysql": "^2.18.1",
     "pg": "^8.4.0",
     "rimraf": "^3.0.0",

+ 2 - 2
packages/ui-devkit/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/ui-devkit",
-  "version": "1.0.0-beta.11",
+  "version": "1.0.0-rc.0",
   "description": "A library for authoring Vendure Admin UI extensions",
   "keywords": [
     "vendure",
@@ -51,7 +51,7 @@
     "@rollup/plugin-node-resolve": "^11.2.0",
     "@types/fs-extra": "^9.0.8",
     "@types/glob": "^7.1.3",
-    "@vendure/core": "^1.0.0-beta.11",
+    "@vendure/core": "^1.0.0-rc.0",
     "rimraf": "^3.0.2",
     "rollup": "^2.40.0",
     "rollup-plugin-terser": "^7.0.2",