Prechádzať zdrojové kódy

Merge branch 'master' into next

Michael Bromley 5 rokov pred
rodič
commit
c29033beca
48 zmenil súbory, kde vykonal 586 pridanie a 181 odobranie
  1. 67 0
      CHANGELOG.md
  2. BIN
      docs/content/docs/developer-guide/customizing-the-order-process/custom_order_ui.jpg
  3. 123 0
      docs/content/docs/developer-guide/customizing-the-order-process/index.md
  4. 78 0
      docs/content/docs/developer-guide/updating-vendure.md
  5. 4 0
      docs/content/docs/storefront/order-workflow/_index.md
  6. 1 1
      lerna.json
  7. 3 3
      packages/admin-ui-plugin/package.json
  8. 2 2
      packages/admin-ui/package.json
  9. 1 1
      packages/admin-ui/src/lib/core/src/common/version.ts
  10. 2 6
      packages/admin-ui/src/lib/order/src/components/order-process-graph/order-process-graph.component.ts
  11. 3 4
      packages/admin-ui/src/lib/order/src/components/order-process-graph/order-process-node.component.ts
  12. 4 0
      packages/admin-ui/src/lib/order/src/components/order-process-graph/types.ts
  13. 3 3
      packages/asset-server-plugin/package.json
  14. 1 1
      packages/common/package.json
  15. 7 3
      packages/core/e2e/fixtures/test-payment-methods.ts
  16. 2 2
      packages/core/e2e/order-process.e2e-spec.ts
  17. 9 1
      packages/core/e2e/order.e2e-spec.ts
  18. 2 2
      packages/core/package.json
  19. 1 1
      packages/core/src/api/resolvers/shop/shop-order.resolver.ts
  20. 2 1
      packages/core/src/common/finite-state-machine/finite-state-machine.spec.ts
  21. 4 37
      packages/core/src/common/finite-state-machine/finite-state-machine.ts
  22. 1 1
      packages/core/src/common/finite-state-machine/merge-transition-definitions.spec.ts
  23. 1 1
      packages/core/src/common/finite-state-machine/merge-transition-definitions.ts
  24. 118 0
      packages/core/src/common/finite-state-machine/types.ts
  25. 1 1
      packages/core/src/common/finite-state-machine/validate-transition-definition.spec.ts
  26. 1 1
      packages/core/src/common/finite-state-machine/validate-transition-definition.ts
  27. 1 0
      packages/core/src/common/index.ts
  28. 2 1
      packages/core/src/config/custom-field/custom-field-types.ts
  29. 19 5
      packages/core/src/config/order/custom-order-process.ts
  30. 10 25
      packages/core/src/config/payment-method/payment-method-handler.ts
  31. 2 2
      packages/core/src/config/vendure-config.ts
  32. 3 1
      packages/core/src/event-bus/events/attempted-login-event.ts
  33. 7 9
      packages/core/src/service/helpers/order-state-machine/order-state-machine.ts
  34. 1 1
      packages/core/src/service/helpers/order-state-machine/order-state.ts
  35. 46 27
      packages/core/src/service/helpers/payment-state-machine/payment-state-machine.ts
  36. 1 1
      packages/core/src/service/helpers/payment-state-machine/payment-state.ts
  37. 3 2
      packages/core/src/service/helpers/refund-state-machine/refund-state-machine.ts
  38. 1 1
      packages/core/src/service/helpers/refund-state-machine/refund-state.ts
  39. 10 1
      packages/core/src/service/services/auth.service.ts
  40. 2 2
      packages/core/src/service/services/order.service.ts
  41. 11 5
      packages/core/src/service/services/session.service.ts
  42. 3 3
      packages/create/package.json
  43. 1 1
      packages/create/src/create-vendure-app.ts
  44. 9 9
      packages/dev-server/package.json
  45. 3 3
      packages/elasticsearch-plugin/package.json
  46. 3 3
      packages/email-plugin/package.json
  47. 3 3
      packages/testing/package.json
  48. 4 4
      packages/ui-devkit/package.json

+ 67 - 0
CHANGELOG.md

@@ -1,3 +1,70 @@
+## 0.14.0 (2020-07-20)
+
+
+#### Fixes
+
+* **admin-ui** Fix error when creating new Customer ([edc56f8](https://github.com/vendure-ecommerce/vendure/commit/edc56f8))
+* **admin-ui** Fix ts error introduced by ShippingMethods custom fields ([8c38ad1](https://github.com/vendure-ecommerce/vendure/commit/8c38ad1))
+* **admin-ui** Save custom fields in the Customer detail view ([3c45b16](https://github.com/vendure-ecommerce/vendure/commit/3c45b16)), closes [#387](https://github.com/vendure-ecommerce/vendure/issues/387)
+* **core** Correct handling of multiple session for same user ([2c42305](https://github.com/vendure-ecommerce/vendure/commit/2c42305))
+* **core** Correctly call PaymentMethodHandler.onStateTransitionStart ([143e62f](https://github.com/vendure-ecommerce/vendure/commit/143e62f))
+* **core** Define cascade behaviour for featured assets ([3f0c79b](https://github.com/vendure-ecommerce/vendure/commit/3f0c79b))
+* **core** Fix bug where session user in cache would get removed ([ebec0f0](https://github.com/vendure-ecommerce/vendure/commit/ebec0f0))
+* **core** Fix error when de-serializing a RequestContext without expiry ([a1e03fd](https://github.com/vendure-ecommerce/vendure/commit/a1e03fd))
+* **core** Prevent countryCode exception when adding payment to order ([49c2ad4](https://github.com/vendure-ecommerce/vendure/commit/49c2ad4))
+
+#### Features
+
+* **admin-ui-plugin** Support `loginUrl` option ([5a95476](https://github.com/vendure-ecommerce/vendure/commit/5a95476))
+* **admin-ui** Add `loginUrl` option to support external login pages ([2745146](https://github.com/vendure-ecommerce/vendure/commit/2745146)), closes [#215](https://github.com/vendure-ecommerce/vendure/issues/215)
+* **admin-ui** Add ability to delete administrator from admin list ([e217ce0](https://github.com/vendure-ecommerce/vendure/commit/e217ce0)), closes [#384](https://github.com/vendure-ecommerce/vendure/issues/384)
+* **admin-ui** Display auth strategy in customer history ([bdfc43d](https://github.com/vendure-ecommerce/vendure/commit/bdfc43d))
+* **admin-ui** Display customer last login time ([0f9dd1c](https://github.com/vendure-ecommerce/vendure/commit/0f9dd1c))
+* **admin-ui** Enable updating of Order custom fields ([5bbd80b](https://github.com/vendure-ecommerce/vendure/commit/5bbd80b)), closes [#404](https://github.com/vendure-ecommerce/vendure/issues/404)
+* **admin-ui** Implement multiple asset deletion ([b2f3f08](https://github.com/vendure-ecommerce/vendure/commit/b2f3f08)), closes [#380](https://github.com/vendure-ecommerce/vendure/issues/380)
+* **admin-ui** Implement order process state chart view ([7283258](https://github.com/vendure-ecommerce/vendure/commit/7283258))
+* **admin-ui** Improve multi-selection in Asset gallery component ([a4e132a](https://github.com/vendure-ecommerce/vendure/commit/a4e132a)), closes [#380](https://github.com/vendure-ecommerce/vendure/issues/380)
+* **admin-ui** Support custom state transitions from Order detail view ([1d2ba31](https://github.com/vendure-ecommerce/vendure/commit/1d2ba31))
+* **core** Add `ProductOption.group` field & resolver ([f20e108](https://github.com/vendure-ecommerce/vendure/commit/f20e108)), closes [#378](https://github.com/vendure-ecommerce/vendure/issues/378)
+* **core** Add `ProductVariant.product` field & resolver ([0334848](https://github.com/vendure-ecommerce/vendure/commit/0334848)), closes [#378](https://github.com/vendure-ecommerce/vendure/issues/378)
+* **core** Add admin helpers to ExternalAuthenticationService ([3456ffb](https://github.com/vendure-ecommerce/vendure/commit/3456ffb))
+* **core** Add custom fields to registerCustomerAccount mutation ([be1f200](https://github.com/vendure-ecommerce/vendure/commit/be1f200)), closes [#388](https://github.com/vendure-ecommerce/vendure/issues/388)
+* **core** Allow all CustomOrderProcess handlers to be async functions ([5d67d06](https://github.com/vendure-ecommerce/vendure/commit/5d67d06))
+* **core** Enable custom fields on ShippingMethod entity (#406) ([fbc36ab](https://github.com/vendure-ecommerce/vendure/commit/fbc36ab)), closes [#406](https://github.com/vendure-ecommerce/vendure/issues/406) [#402](https://github.com/vendure-ecommerce/vendure/issues/402)
+* **core** Export ExternalAuthenticationService ([c3ed2cd](https://github.com/vendure-ecommerce/vendure/commit/c3ed2cd))
+* **core** Expose `nextStates` on Order type in Admin API ([ece0bbe](https://github.com/vendure-ecommerce/vendure/commit/ece0bbe))
+* **core** Expose order state machine config via `serverConfig` type ([0a77438](https://github.com/vendure-ecommerce/vendure/commit/0a77438))
+* **core** Expose User.authenticationMethod in GraphQL APIs ([96f923a](https://github.com/vendure-ecommerce/vendure/commit/96f923a))
+* **core** Implement `authenticate` mutation for Admin API ([357f878](https://github.com/vendure-ecommerce/vendure/commit/357f878))
+* **core** Implement `deleteAdministrator` mutation ([dc82b2c](https://github.com/vendure-ecommerce/vendure/commit/dc82b2c)), closes [#384](https://github.com/vendure-ecommerce/vendure/issues/384)
+* **core** Implement `setOrderCustomFields` in Admin API ([ad89fc9](https://github.com/vendure-ecommerce/vendure/commit/ad89fc9)), closes [#404](https://github.com/vendure-ecommerce/vendure/issues/404)
+* **core** Implement `setOrderCustomFields` in Shop API ([3a12dc5](https://github.com/vendure-ecommerce/vendure/commit/3a12dc5)), closes [#404](https://github.com/vendure-ecommerce/vendure/issues/404)
+* **core** Implement `transitionOrderToState` in Admin API ([3196b52](https://github.com/vendure-ecommerce/vendure/commit/3196b52))
+* **core** Implement configurable session caching ([09a432d](https://github.com/vendure-ecommerce/vendure/commit/09a432d)), closes [#394](https://github.com/vendure-ecommerce/vendure/issues/394)
+* **core** Implement deleteAssets mutation ([6f12014](https://github.com/vendure-ecommerce/vendure/commit/6f12014)), closes [#380](https://github.com/vendure-ecommerce/vendure/issues/380)
+* **core** Improve customization of order process ([0011ea9](https://github.com/vendure-ecommerce/vendure/commit/0011ea9)), closes [#401](https://github.com/vendure-ecommerce/vendure/issues/401)
+* **core** Include auth strategy name in AttemptedLoginEvent ([b83f1fe](https://github.com/vendure-ecommerce/vendure/commit/b83f1fe))
+* **core** Log error variables as well as message ([de25bdb](https://github.com/vendure-ecommerce/vendure/commit/de25bdb))
+* **core** More flexible customer registration flow ([92350e6](https://github.com/vendure-ecommerce/vendure/commit/92350e6)), closes [#392](https://github.com/vendure-ecommerce/vendure/issues/392)
+* **core** More flexible handling of shipping calculations ([d166c08](https://github.com/vendure-ecommerce/vendure/commit/d166c08)), closes [#397](https://github.com/vendure-ecommerce/vendure/issues/397) [#398](https://github.com/vendure-ecommerce/vendure/issues/398)
+* **core** Record lastLogin date on authenticate ([39c743b](https://github.com/vendure-ecommerce/vendure/commit/39c743b))
+* **core** Record strategy used to register in Customer history ([5504044](https://github.com/vendure-ecommerce/vendure/commit/5504044))
+* **core** Rework User/auth implementation to enable 3rd party auth ([f12b96f](https://github.com/vendure-ecommerce/vendure/commit/f12b96f)), closes [#215](https://github.com/vendure-ecommerce/vendure/issues/215)
+* **core** Store authenticationStrategy on an AuthenticatedSession ([e737c56](https://github.com/vendure-ecommerce/vendure/commit/e737c56))
+* **email-plugin** Use new User model in email handlers ([16dd884](https://github.com/vendure-ecommerce/vendure/commit/16dd884))
+
+
+### BREAKING CHANGE
+
+* (email-plugin) The default email handlers have been updated to use the new User model, and as a result the email templates "email-verification", "email-address-change" and "password-reset" should be updated to remove the "user" object, so `{{ user.verificationToken }}` becomes `{{ verificationToken }}` and so on.
+* A new `AuthenticationMethod` entity has been added, with a one-to-many relation to the existing User entities. Several properties that were formerly part of the User entity have now moved to the `AuthenticationMethod` entity. Upgrading with therefore require a careful database migration to ensure that no data is lost. On release, a migration script will be provided for this.
+* Some ON DELETE behaviour was incorrectly defined in the database schema, and has how been fixed. This will require a non-destructive migration.
+* The `AttemptedLoginEvent.identifier` property is now optional, since it will only be sent when using the "native" authentication strategy. Code that listens for this event should now check that the `identifier` property is defined before attempting to use it.
+* The `RequestContext.session` object is no longer a `Session` entity. Instead it is a new type, `SerializedSession` which contains a subset of data pertaining to the current session. For example, if you have custom code which references `ctx.session.activeOrder` you will now get an error, since `activeOrder` does not exist on `SerializedSession`. Instead you would use `SerializedSession.activeOrderId` and then lookup the order in a separate query.
+
+The reason for this change is to enable efficient session caching.
+* The Administrator entity has a new `deletedAt` field, which will require a non-destructive database migration.
+* The way custom Order states are defined has changed. The `VendureConfig.orderOptions.process` property now accepts an **array** of objects implementing the `CustomerOrderProcess` interface. This interface is more-or-less the same as the old `OrderProcessOptions` object, but the use of an array now allows better composition, and since `CustomerOrderProcess` inherits from `InjectableStrategy`, this means providers can now be injected and used in the custom order process logic.
 ## <small>0.13.1 (2020-06-30)</small>
 
 

BIN
docs/content/docs/developer-guide/customizing-the-order-process/custom_order_ui.jpg


+ 123 - 0
docs/content/docs/developer-guide/customizing-the-order-process/index.md

@@ -0,0 +1,123 @@
+---
+title: 'Customizing the Order Process'
+showtoc: true
+---
+
+# Customizing the Order Process
+
+Vendure defines an order process which is based on a [finite state machine]({{< relref "fsm" >}}). This means that the [`Order.state` property]({{< relref "order" >}}#state) will be one of a set of [pre-defined states]({{< relref "order-state" >}}). From the current state, the Order can then transition (change) to another state, and the available next states depend on what the current state is.
+
+So, as an example, all orders being in the `AddingItems` state. This means that the Customer is adding items to his or her shopping cart. From there, the Order can transition to the `ArrangingPayment` state. A diagram of the default states and transitions can be found in the [Order Workflow guide]({{< relref "order-workflow" >}}).
+
+## Defining custom states and transitions
+
+Sometimes you might need to modify the default Order process to better match your business needs. This is done by defining one or more [`CustomOrderProcess`]({{< relref "custom-order-process" >}}) objects and passing them to the [`OrderOptions.process`]({{< relref "order-options" >}}#process) config property.
+
+### Example: Adding a new state
+
+Let's say your company can only sell to customers with a valid EU tax ID. We'll assume that you've already used a [custom field]({{< relref "customizing-models" >}}) to store that code on the Customer entity.
+
+Now you want to add a step _before_ the customer handles payment, where we can collect and verify the tax ID.
+
+So we want to change the default process of:
+
+```text
+AddingItems -> ArrangingPayment
+```
+
+to instead be:
+
+```text
+AddingItems -> ValidatingCustomer -> ArrangingPayment
+```
+
+Here's how we would define the new state:
+
+```TypeScript
+// customer-validation-process.ts
+import { CustomOrderProcess } from '@vendure/core';
+
+export const customerValidationProcess: CustomOrderProcess<'ValidatingCustomer'> = {
+  transitions: {
+    AddingItems: {
+      to: ['ValidatingCustomer'],
+      mergeStrategy: 'replace',
+    },
+    ValidatingCustomer: {
+      to: ['ArrangingPayment', 'AddingItems'],
+    },
+  },
+};
+```
+This object means:
+
+* the `AddingItems` state may _only_ transition to the `ValidatingCustomer` state (`mergeStrategy: 'replace'` tells Vendure to discard any existing transition targets and replace with this one). 
+* the `ValidatingCustomer` may transition to the `ArrangingPayment` state (assuming the tax ID is valid) or back to the `AddingItems` state.
+
+And then add this configuration to our main VendureConfig:
+
+```TypeScript
+// vendure-config.ts
+import { VendureConfig } from '@vendure/core';
+import { customerValidationProcess } from './customer-validation-process';
+
+export const config: VendureConfig = {
+  // ...
+  orderOptions: {
+    process: [customerValidationProcess],
+  },
+};
+```
+
+
+### Example: Intercepting a state transition
+
+Now we have defined out new `ValidatingCustomer` state, but there is as yet nothing to enforce that the tax ID is valid. To add this constraint, we'll use the [`onTransitionStart` state transition hook]({{< relref "state-machine-config" >}}#ontransitionstart).
+
+This allows us to perform our custom logic and potentially prevent the transition from occurring. We will also assume that we have available a provider named `TaxIdService` which contains the logic to validate a tax ID.
+
+```TypeScript
+// customer-validation-process.ts
+
+// We declare this in the outer scope and can then use it 
+// in our onTransitionStart function.
+let taxIdService: TaxIdService;
+
+const customerValidationProcess: CustomOrderProcess<'ValidatingCustomer'> = {
+  transitions: {
+    AddingItems: {
+      to: ['ValidatingCustomer'],
+      mergeStrategy: 'replace',
+    },
+    ValidatingCustomer: {
+      to: ['ArrangingPayment', 'AddingItems'],
+    },
+  },
+
+  // The init method allows us to inject services
+  // and other providers
+  init(injector) {
+    taxIdService = injector.get(TaxIdService);
+  },
+
+  // The logic for enforcing our validation goes here
+  async onTransitionStart(fromState, toState, data) {
+    if (fromState === 'ValidatingCustomer' && toState === 'ArrangingPayment') {
+      const isValid = await taxIdService.verifyTaxId(data.order.customer);
+      if (!isValid) {
+        // Returning a string is interpreted as an error message.
+        // The state transition will fail.
+        return `The tax ID is not valid`;
+      }
+    }
+  },
+};
+
+```
+
+## Controlling custom states in the Admin UI
+
+If you have defined custom order states, the Admin UI will allow you to manually transition an 
+order from one state to another:
+
+{{< figure src="./custom_order_ui.jpg" >}}

+ 78 - 0
docs/content/docs/developer-guide/updating-vendure.md

@@ -0,0 +1,78 @@
+---
+title: "Updating Vendure"
+showtoc: true
+---
+
+# Updating Vendure
+
+This guide provides guidance for updating the Vendure core framework to a newer version.
+
+## How to update
+
+First, check the [changelog](https://github.com/vendure-ecommerce/vendure/blob/master/CHANGELOG.md) for an overview of the changes and any breaking changes in the next version.
+
+In your project's `package.json` file, find all the `@vendure/...` packages and change the version
+to the latest. All the Vendure packages have the same version, and are all released together.
+
+```diff
+{
+  // ...
+  "dependencies": {
+-    "@vendure/common": "0.10.1",
++    "@vendure/common": "0.11.0",
+-    "@vendure/core": "0.10.1",
++    "@vendure/core": "0.11.0",
+     // etc.
+  }
+}
+```
+
+Then run `npm install` or `yarn install` depending on which package manager you prefer.
+
+## Breaking changes
+
+Vendure follows the [SemVer convention](https://semver.org/) for version numbering. While in beta (pre-v1.0.0), this means breaking changes are possible in each release:
+
+> Major version zero is all about rapid development. If you’re changing the API every day you should either still be in version 0.y.z or on a separate development branch working on the next major version.
+ [[source]](https://semver.org/#doesnt-this-discourage-rapid-development-and-fast-iteration)
+
+In practice, we aim to introduce breaking changes in **minor versions** and non-breaking changes in **patch versions**. So v0.10.0 -> v0.11.0 will contain breaking changes, whereas v0.11.0 -> v0.11.1 should not.
+
+### What kinds of breaking changes can be expected?
+
+* Changes to the database schema
+* Changes to the GraphQL schema
+* Updates of major underlying libraries, such as upgrading NestJS to a new major version
+* Changes to certain core services which are often used in plugins, such as the JobQueue or WorkerService providers.
+
+Every release will be accompanied by an entry in the [changelog](https://github.com/vendure-ecommerce/vendure/blob/master/CHANGELOG.md), listing the changes in that release. And breaking changes are clearly listed under a **BREAKING CHANGE** heading.
+
+### Database migrations
+
+Database changes are one of the most common causes for breaking changes. In most cases, the changes are minor (such as the addition of a new column) and non-destructive (i.e. performing the migration has no risk of data loss).
+
+However, some more fundamental changes occasionally require a careful approach to database migration in order to preserve existing data.
+
+The key rule is **never run your production instance with the `synchronize` option set to `true`**. Doing so can cause inadvertent data loss in rare cases.
+
+For any database schema changes, it is advised to:
+
+1. Read the changelog breaking changes entries to see what changes to expect
+2. Create a new database migration as described in the [Migrations guide](http://localhost:1313/docs/developer-guide/migrations/)
+3. Manually check the migration script. In some cases manual action is needed to customize the script in order to correctly migrate your existing data.
+4. Test the migration script against non-production data.
+5. Only when you have verified that the migration works as expected, run it against your production database.
+
+### GraphQL schema changes
+
+If you are using a code-generation tool (such as [graphql-code-generator](https://graphql-code-generator.com/)) for your custom plugins or storefront, it is a good idea to re-generate after upgrading, which will catch any errors caused by changes to the GraphQL schema.
+
+### TypeScript API changes
+
+If you are using Vendure providers (services, JobQueue, EventBus etc.) in your custom plugins, you should look out for breakages caused by changes to those services. Major changes will be listed in the changelog, but occasionally internal changes may also impact your code. 
+
+The best way to check whether this is the case is to build your entire project after upgrading, to see if any new TypeScript compiler errors emerge.
+
+### Admin UI changes
+
+If you are using UI extensions to create your own custom Admin UI using the [`compileUiExtensions`]({{< relref "compile-ui-extensions" >}}) function, then you'll need to delete and re-compile your admin-ui directory after upgrading (this is the directory specified by the [`outputPath`]({{< relref "ui-extension-compiler-options" >}}#outputpath) property).

+ 4 - 0
docs/content/docs/storefront/order-workflow/_index.md

@@ -11,6 +11,10 @@ An Order is a collection of one or more ProductVariants which can be purchased b
 
 Every Order has a `state` property of type [`OrderState`]({{< relref "order-state" >}}). The following diagram shows the default states and how an Order transitions from one to the next.
 
+{{% alert %}}
+Note that this default workflow can be modified to better fit your business processes. See the [Customizing the Order Process guide]({{< relref "customizing-the-order-process" >}}).
+{{< /alert >}}
+
 {{< figure src="./order_state_diagram.png" >}}
 
 ## Structure of an Order

+ 1 - 1
lerna.json

@@ -2,7 +2,7 @@
   "packages": [
     "packages/*"
   ],
-  "version": "0.13.1",
+  "version": "0.14.0",
   "npmClient": "yarn",
   "useWorkspaces": true,
   "command": {

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

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/admin-ui-plugin",
-  "version": "0.13.1",
+  "version": "0.14.0",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
   "files": [
@@ -19,8 +19,8 @@
   "devDependencies": {
     "@types/express": "^4.0.39",
     "@types/fs-extra": "^8.0.1",
-    "@vendure/common": "^0.13.1",
-    "@vendure/core": "^0.13.1",
+    "@vendure/common": "^0.14.0",
+    "@vendure/core": "^0.14.0",
     "express": "^4.16.4",
     "rimraf": "^3.0.0",
     "typescript": "3.8.3"

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

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/admin-ui",
-  "version": "0.13.1",
+  "version": "0.14.0",
   "license": "MIT",
   "scripts": {
     "ng": "ng",
@@ -35,7 +35,7 @@
     "@ng-select/ng-select": "^3.7.2",
     "@ngx-translate/core": "^12.1.2",
     "@ngx-translate/http-loader": "^4.0.0",
-    "@vendure/common": "^0.13.1",
+    "@vendure/common": "^0.14.0",
     "@webcomponents/custom-elements": "^1.2.4",
     "apollo-angular": "^1.8.0",
     "apollo-cache-inmemory": "^1.6.5",

+ 1 - 1
packages/admin-ui/src/lib/core/src/common/version.ts

@@ -1,2 +1,2 @@
 // Auto-generated by the set-version.js script.
-export const ADMIN_UI_VERSION = '0.13.1';
+export const ADMIN_UI_VERSION = '0.14.0';

+ 2 - 6
packages/admin-ui/src/lib/order/src/components/order-process-graph/order-process-graph.component.ts

@@ -12,16 +12,12 @@ import {
     ViewChildren,
 } from '@angular/core';
 import { OrderProcessState } from '@vendure/admin-ui/core';
-import { BehaviorSubject, Observable, Subject } from 'rxjs';
+import { BehaviorSubject, Observable } from 'rxjs';
 import { debounceTime } from 'rxjs/operators';
 
 import { NODE_HEIGHT } from './constants';
 import { OrderProcessNodeComponent } from './order-process-node.component';
-
-export type StateNode = {
-    name: string;
-    to: StateNode[];
-};
+import { StateNode } from './types';
 
 @Component({
     selector: 'vdr-order-process-graph',

+ 3 - 4
packages/admin-ui/src/lib/order/src/components/order-process-graph/order-process-node.component.ts

@@ -4,13 +4,12 @@ import {
     ElementRef,
     Input,
     OnChanges,
-    OnInit,
     SimpleChanges,
 } from '@angular/core';
-import { BehaviorSubject, Observable } from 'rxjs';
+import { BehaviorSubject } from 'rxjs';
 
 import { NODE_HEIGHT } from './constants';
-import { OrderProcessGraphComponent, StateNode } from './order-process-graph.component';
+import { StateNode } from './types';
 
 @Component({
     selector: 'vdr-order-process-node',
@@ -29,7 +28,7 @@ export class OrderProcessNodeComponent implements OnChanges {
     // i18n extractor from extracting a "Cancelled" key
     cancelledState = 'Cancelled';
 
-    constructor(private graph: OrderProcessGraphComponent, private elementRef: ElementRef<HTMLDivElement>) {}
+    constructor(private elementRef: ElementRef<HTMLDivElement>) {}
 
     ngOnChanges(changes: SimpleChanges) {
         this.isCancellable = !!this.node.to.find((s) => s.name === 'Cancelled');

+ 4 - 0
packages/admin-ui/src/lib/order/src/components/order-process-graph/types.ts

@@ -0,0 +1,4 @@
+export type StateNode = {
+    name: string;
+    to: StateNode[];
+};

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

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/asset-server-plugin",
-  "version": "0.13.1",
+  "version": "0.14.0",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
   "files": [
@@ -22,8 +22,8 @@
     "@types/fs-extra": "^8.0.1",
     "@types/node-fetch": "^2.5.4",
     "@types/sharp": "^0.24.0",
-    "@vendure/common": "^0.13.1",
-    "@vendure/core": "^0.13.1",
+    "@vendure/common": "^0.14.0",
+    "@vendure/core": "^0.14.0",
     "aws-sdk": "^2.670.0",
     "express": "^4.16.4",
     "node-fetch": "^2.6.0",

+ 1 - 1
packages/common/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/common",
-  "version": "0.13.1",
+  "version": "0.14.0",
   "main": "index.js",
   "license": "MIT",
   "scripts": {

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

@@ -14,11 +14,12 @@ export const testSuccessfulPaymentMethod = new PaymentMethodHandler({
             metadata,
         };
     },
-    settlePayment: order => ({
+    settlePayment: (order) => ({
         success: true,
     }),
 });
 
+export const onTransitionSpy = jest.fn();
 /**
  * A two-stage (authorize, capture) payment method, with no createRefund method.
  */
@@ -42,6 +43,9 @@ export const twoStagePaymentMethod = new PaymentMethodHandler({
             },
         };
     },
+    onStateTransitionStart: (fromState, toState, data) => {
+        onTransitionSpy(fromState, toState, data);
+    },
 });
 
 /**
@@ -104,7 +108,7 @@ export const testFailingPaymentMethod = new PaymentMethodHandler({
             metadata,
         };
     },
-    settlePayment: order => ({
+    settlePayment: (order) => ({
         success: true,
     }),
 });
@@ -120,7 +124,7 @@ export const testErrorPaymentMethod = new PaymentMethodHandler({
             metadata,
         };
     },
-    settlePayment: order => ({
+    settlePayment: (order) => ({
         success: true,
     }),
 });

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

@@ -66,7 +66,7 @@ describe('Order process', () => {
         onTransitionEnd(fromState, toState, data) {
             transitionEndSpy(fromState, toState, data);
         },
-        onError(fromState, toState, message) {
+        onTransitionError(fromState, toState, message) {
             transitionErrorSpy(fromState, toState, message);
         },
     };
@@ -84,7 +84,7 @@ describe('Order process', () => {
 
     const { server, adminClient, shopClient } = createTestEnvironment(
         mergeConfig(testConfig, {
-            orderOptions: { process: [customOrderProcess, customOrderProcess2] },
+            orderOptions: { process: [customOrderProcess as any, customOrderProcess2 as any] },
             paymentOptions: {
                 paymentMethodHandlers: [testSuccessfulPaymentMethod],
             },

+ 9 - 1
packages/core/e2e/order.e2e-spec.ts

@@ -9,6 +9,7 @@ import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-conf
 
 import {
     failsToSettlePaymentMethod,
+    onTransitionSpy,
     singleStageRefundablePaymentMethod,
     twoStagePaymentMethod,
 } from './fixtures/test-payment-methods';
@@ -149,12 +150,16 @@ describe('Orders resolver', () => {
             expect(result.order!.state).toBe('PaymentAuthorized');
         });
 
-        it('settlePayment succeeds', async () => {
+        it('settlePayment succeeds, onStateTransitionStart called', async () => {
+            onTransitionSpy.mockClear();
             await shopClient.asUserWithCredentials(customers[1].emailAddress, password);
             await proceedToArrangingPayment(shopClient);
             const order = await addPaymentToOrder(shopClient, twoStagePaymentMethod);
 
             expect(order.state).toBe('PaymentAuthorized');
+            expect(onTransitionSpy).toHaveBeenCalledTimes(1);
+            expect(onTransitionSpy.mock.calls[0][0]).toBe('Created');
+            expect(onTransitionSpy.mock.calls[0][1]).toBe('Authorized');
 
             const payment = order.payments![0];
             const { settlePayment } = await adminClient.query<
@@ -171,6 +176,9 @@ describe('Orders resolver', () => {
                 baz: 'quux',
                 moreData: 42,
             });
+            expect(onTransitionSpy).toHaveBeenCalledTimes(2);
+            expect(onTransitionSpy.mock.calls[1][0]).toBe('Authorized');
+            expect(onTransitionSpy.mock.calls[1][1]).toBe('Settled');
 
             const result = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
                 id: order.id,

+ 2 - 2
packages/core/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/core",
-  "version": "0.13.1",
+  "version": "0.14.0",
   "description": "A modern, headless ecommerce framework",
   "repository": {
     "type": "git",
@@ -47,7 +47,7 @@
     "@nestjs/testing": "7.0.5",
     "@nestjs/typeorm": "7.0.0",
     "@types/fs-extra": "^8.0.1",
-    "@vendure/common": "^0.13.1",
+    "@vendure/common": "^0.14.0",
     "apollo-server-express": "2.11.0",
     "bcrypt": "^4.0.1",
     "body-parser": "^1.19.0",

+ 1 - 1
packages/core/src/api/resolvers/shop/shop-order.resolver.ts

@@ -196,7 +196,7 @@ export class ShopOrderResolver {
 
     @Query()
     @Allow(Permission.Owner)
-    async nextOrderStates(@Ctx() ctx: RequestContext): Promise<string[]> {
+    async nextOrderStates(@Ctx() ctx: RequestContext): Promise<ReadonlyArray<string>> {
         if (ctx.authorizedAsOwnerOnly) {
             const sessionOrder = await this.getOrderFromContext(ctx, true);
             return this.orderService.getNextOrderStates(sessionOrder);

+ 2 - 1
packages/core/src/common/finite-state-machine/finite-state-machine.spec.ts

@@ -1,6 +1,7 @@
 import { of } from 'rxjs';
 
-import { FSM, Transitions } from './finite-state-machine';
+import { FSM } from './finite-state-machine';
+import { Transitions } from './types';
 
 describe('Finite State Machine', () => {
     type TestState = 'DoorsClosed' | 'DoorsOpen' | 'Moving';

+ 4 - 37
packages/core/src/common/finite-state-machine/finite-state-machine.ts

@@ -1,44 +1,11 @@
-import { Observable } from 'rxjs';
-
 import { awaitPromiseOrObservable } from '../utils';
 
-/**
- * @description
- * A type which is used to define all valid transitions and transition callbacks
- *
- * @docsCategory StateMachine
- */
-export type Transitions<State extends string, Target extends string = State> = {
-    [S in State]: {
-        to: Target[];
-        mergeStrategy?: 'merge' | 'replace';
-    };
-};
-
-/**
- * @description
- * The config object used to instantiate a new FSM instance.
- *
- * @docsCategory StateMachine
- */
-export type StateMachineConfig<T extends string, Data = undefined> = {
-    transitions: Transitions<T>;
-    /**
-     * Called before a transition takes place. If the function resolves to false or a string, then the transition
-     * will be cancelled. In the case of a string, the string will be forwarded to the onError handler.
-     */
-    onTransitionStart?(
-        fromState: T,
-        toState: T,
-        data: Data,
-    ): boolean | string | void | Promise<boolean | string | void> | Observable<boolean | string | void>;
-    onTransitionEnd?(fromState: T, toState: T, data: Data): void | Promise<void> | Observable<void>;
-    onError?(fromState: T, toState: T, message?: string): void | Promise<void> | Observable<void>;
-};
+import { StateMachineConfig } from './types';
 
 /**
  * @description
- * A simple type-safe finite state machine
+ * A simple type-safe finite state machine. This is used internally to control the Order process, ensuring that
+ * the state of Orders, Payments and Refunds follows a well-defined behaviour.
  *
  * @docsCategory StateMachine
  */
@@ -108,7 +75,7 @@ export class FSM<T extends string, Data = any> {
     /**
      * Returns an array of state to which the machine may transition from the current state.
      */
-    getNextStates(): T[] {
+    getNextStates(): ReadonlyArray<T> {
         return this.config.transitions[this._currentState].to;
     }
 

+ 1 - 1
packages/core/src/common/finite-state-machine/merge-transition-definitions.spec.ts

@@ -1,5 +1,5 @@
-import { Transitions } from './finite-state-machine';
 import { mergeTransitionDefinitions } from './merge-transition-definitions';
+import { Transitions } from './types';
 
 describe('FSM mergeTransitionDefinitions()', () => {
     it('handles no b', () => {

+ 1 - 1
packages/core/src/common/finite-state-machine/merge-transition-definitions.ts

@@ -1,6 +1,6 @@
 import { simpleDeepClone } from '@vendure/common/lib/simple-deep-clone';
 
-import { Transitions } from './finite-state-machine';
+import { Transitions } from './types';
 
 /**
  * Merges two state machine Transitions definitions.

+ 118 - 0
packages/core/src/common/finite-state-machine/types.ts

@@ -0,0 +1,118 @@
+import { Observable } from 'rxjs';
+
+/**
+ * @description
+ * A type which is used to define valid states and transitions for a state machine based
+ * on {@link FSM}.
+ *
+ * @example
+ * ```TypeScript
+ * type LightColor = 'Green' | 'Amber' | 'Red';
+ *
+ * const trafficLightTransitions: Transitions<LightColor> = {
+ *   Green: {
+ *     to: ['Amber'],
+ *   },
+ *   Amber: {
+ *     to: ['Red'],
+ *   },
+ *   Red: {
+ *     to: ['Green'],
+ *   },
+ * };
+ * ```
+ *
+ * The `mergeStrategy` property defines how to handle the merging of states when one set of
+ * transitions is being merged with another (as in the case of defining a {@link CustomerOrderProcess})
+ *
+ * @docsCategory StateMachine
+ */
+export type Transitions<State extends string, Target extends string = State> = {
+    [S in State]: {
+        to: Readonly<Target[]>;
+        mergeStrategy?: 'merge' | 'replace';
+    };
+};
+
+/**
+ * @description
+ * Called before a transition takes place. If the function resolves to `false` or a string, then the transition
+ * will be cancelled. In the case of a string, the string (error message) will be forwarded to the onError handler.
+ *
+ * If this function returns a value resolving to `true` or `void` (no return value), then the transition
+ * will be permitted.
+ *
+ * @docsCategory StateMachine
+ * @docsPage StateMachineConfig
+ */
+export type OnTransitionStartFn<T extends string, Data> = (
+    fromState: T,
+    toState: T,
+    data: Data,
+) => boolean | string | void | Promise<boolean | string | void> | Observable<boolean | string | void>;
+
+/**
+ * @description
+ * Called after a transition has taken place.
+ *
+ * @docsCategory StateMachine
+ * @docsPage StateMachineConfig
+ */
+export type OnTransitionErrorFn<T extends string> = (
+    fromState: T,
+    toState: T,
+    message?: string,
+) => void | Promise<void> | Observable<void>;
+
+/**
+ * @description
+ * Called when a transition is prevented and the `onTransitionStart` handler has returned an
+ * error message.
+ *
+ * @docsCategory StateMachine
+ * @docsPage StateMachineConfig
+ */
+export type OnTransitionEndFn<T extends string, Data> = (
+    fromState: T,
+    toState: T,
+    data: Data,
+) => void | Promise<void> | Observable<void>;
+
+/**
+ * @description
+ * The config object used to instantiate a new {@link FSM} instance.
+ *
+ * @docsCategory StateMachine
+ * @docsPage StateMachineConfig
+ */
+export interface StateMachineConfig<T extends string, Data = undefined> {
+    /**
+     * @description
+     * Defines the available states of the state machine as well as the permitted
+     * transitions from one state to another.
+     */
+    readonly transitions: Transitions<T>;
+
+    /**
+     * @description
+     * Called before a transition takes place. If the function resolves to `false` or a string, then the transition
+     * will be cancelled. In the case of a string, the string (error message) will be forwarded to the onError handler.
+     *
+     * If this function returns a value resolving to `true` or `void` (no return value), then the transition
+     * will be permitted.
+     */
+    onTransitionStart?: OnTransitionStartFn<T, Data>;
+
+    /**
+     * @description
+     * Called after a transition has taken place.
+     */
+    onTransitionEnd?: OnTransitionEndFn<T, Data>;
+
+    /**
+     * @description
+     * Called when a transition is prevented and the `onTransitionStart` handler has returned an
+     * error message.
+     */
+    onError?: OnTransitionErrorFn<T>;
+}

+ 1 - 1
packages/core/src/common/finite-state-machine/validate-transition-definition.spec.ts

@@ -1,6 +1,6 @@
 import { OrderState } from '../../service/helpers/order-state-machine/order-state';
 
-import { Transitions } from './finite-state-machine';
+import { Transitions } from './types';
 import { validateTransitionDefinition } from './validate-transition-definition';
 
 describe('FSM validateTransitionDefinition()', () => {

+ 1 - 1
packages/core/src/common/finite-state-machine/validate-transition-definition.ts

@@ -1,4 +1,4 @@
-import { Transitions } from './finite-state-machine';
+import { Transitions } from './types';
 
 type ValidationResult = { reachable: boolean };
 

+ 1 - 0
packages/core/src/common/index.ts

@@ -1,4 +1,5 @@
 export * from './finite-state-machine/finite-state-machine';
+export * from './finite-state-machine/types';
 export * from './async-queue';
 export * from './error/errors';
 export * from './injector';

+ 2 - 1
packages/core/src/config/custom-field/custom-field-types.ts

@@ -82,7 +82,8 @@ export type CustomFieldConfig =
  * * `validate?: (value: any) => string | LocalizedString[] | void`: A custom validation function.
  *
  * The `LocalizedString` type looks like this:
- * ```
+ *
+ * ```TypeScript
  * type LocalizedString = {
  *   languageCode: LanguageCode;
  *   value: string;

+ 19 - 5
packages/core/src/config/order/custom-order-process.ts

@@ -1,10 +1,24 @@
-import { StateMachineConfig, Transitions } from '../../common/finite-state-machine/finite-state-machine';
+import {
+    OnTransitionEndFn,
+    OnTransitionErrorFn,
+    OnTransitionStartFn,
+    StateMachineConfig,
+    Transitions,
+} from '../../common/finite-state-machine/types';
 import { InjectableStrategy } from '../../common/types/injectable-strategy';
-import { Order } from '../../entity/order/order.entity';
 import { OrderState, OrderTransitionData } from '../../service/helpers/order-state-machine/order-state';
 
-export interface CustomOrderProcess<State extends string>
-    extends InjectableStrategy,
-        Omit<StateMachineConfig<State & OrderState, OrderTransitionData>, 'transitions'> {
+/**
+ * @description
+ * Used to define extensions to or modifications of the default order process.
+ *
+ * For detailed description of the interface members, see the {@link StateMachineConfig} docs.
+ *
+ * @docsCategory orders
+ */
+export interface CustomOrderProcess<State extends string> extends InjectableStrategy {
     transitions?: Transitions<State, State | OrderState> & Partial<Transitions<OrderState | State>>;
+    onTransitionStart?: OnTransitionStartFn<State | OrderState, OrderTransitionData>;
+    onTransitionEnd?: OnTransitionEndFn<State | OrderState, OrderTransitionData>;
+    onTransitionError?: OnTransitionErrorFn<State | OrderState>;
 }

+ 10 - 25
packages/core/src/config/payment-method/payment-method-handler.ts

@@ -9,7 +9,7 @@ import {
     ConfigurableOperationDefOptions,
     LocalizedStringArray,
 } from '../../common/configurable-operation';
-import { StateMachineConfig } from '../../common/finite-state-machine/finite-state-machine';
+import { OnTransitionStartFn, StateMachineConfig } from '../../common/finite-state-machine/types';
 import { Order } from '../../entity/order/order.entity';
 import { Payment, PaymentMetadata } from '../../entity/payment/payment.entity';
 import {
@@ -20,24 +20,9 @@ import { RefundState } from '../../service/helpers/refund-state-machine/refund-s
 
 export type PaymentMethodArgType = ConfigArgSubset<'int' | 'string' | 'boolean'>;
 export type PaymentMethodArgs = ConfigArgs<PaymentMethodArgType>;
-export type OnTransitionStartReturnType = ReturnType<Required<StateMachineConfig<any>>['onTransitionStart']>;
-
-/**
- * @description
- * The signature of the function defined by `onStateTransitionStart` in {@link PaymentMethodConfigOptions}.
- *
- * This function is called before the state of a Payment is transitioned. Its
- * return value used to determine whether the transition can occur.
- *
- * @docsCategory payment
- * @docsPage Payment Method Types
- */
-export type OnTransitionStartFn<T extends PaymentMethodArgs> = (
-    fromState: PaymentState,
-    toState: PaymentState,
-    args: ConfigArgValues<T>,
-    data: PaymentTransitionData,
-) => OnTransitionStartReturnType;
+export type OnPaymentTransitionStartReturnType = ReturnType<
+    Required<StateMachineConfig<any>>['onTransitionStart']
+>;
 
 /**
  * @description
@@ -172,7 +157,7 @@ export interface PaymentMethodConfigOptions<T extends PaymentMethodArgs = Paymen
      * This function, when specified, will be invoked before any transition from one {@link PaymentState} to another.
      * The return value (a sync / async `boolean`) is used to determine whether the transition is permitted.
      */
-    onStateTransitionStart?: OnTransitionStartFn<T>;
+    onStateTransitionStart?: OnTransitionStartFn<PaymentState, PaymentTransitionData>;
 }
 
 /**
@@ -184,6 +169,7 @@ export interface PaymentMethodConfigOptions<T extends PaymentMethodArgs = Paymen
  *
  * @example
  * ```ts
+ * import { PaymentMethodHandler, CreatePaymentResult, SettlePaymentResult, LanguageCode } from '\@vendure/core';
  * // A mock 3rd-party payment SDK
  * import gripeSDK from 'gripe';
  *
@@ -196,7 +182,7 @@ export interface PaymentMethodConfigOptions<T extends PaymentMethodArgs = Paymen
  *     args: {
  *         apiKey: { type: 'string' },
  *     },
- *     createPayment: async (order, args, metadata): Promise<PaymentConfig> => {
+ *     createPayment: async (order, args, metadata): Promise<CreatePaymentResult> => {
  *         try {
  *             const result = await gripeSDK.charges.create({
  *                 apiKey: args.apiKey,
@@ -233,7 +219,7 @@ export class PaymentMethodHandler<
     private readonly createPaymentFn: CreatePaymentFn<T>;
     private readonly settlePaymentFn: SettlePaymentFn<T>;
     private readonly createRefundFn?: CreateRefundFn<T>;
-    private readonly onTransitionStartFn?: OnTransitionStartFn<T>;
+    private readonly onTransitionStartFn?: OnTransitionStartFn<PaymentState, PaymentTransitionData>;
 
     constructor(config: PaymentMethodConfigOptions<T>) {
         super(config);
@@ -297,11 +283,10 @@ export class PaymentMethodHandler<
     onStateTransitionStart(
         fromState: PaymentState,
         toState: PaymentState,
-        args: ConfigArg[],
         data: PaymentTransitionData,
-    ): OnTransitionStartReturnType {
+    ): OnPaymentTransitionStartReturnType {
         if (typeof this.onTransitionStartFn === 'function') {
-            return this.onTransitionStartFn(fromState, toState, argsArrayToHash(args), data);
+            return this.onTransitionStartFn(fromState, toState, data);
         } else {
             return true;
         }

+ 2 - 2
packages/core/src/config/vendure-config.ts

@@ -8,7 +8,7 @@ import { Observable } from 'rxjs';
 import { ConnectionOptions } from 'typeorm';
 
 import { RequestContext } from '../api/common/request-context';
-import { Transitions } from '../common/finite-state-machine/finite-state-machine';
+import { Transitions } from '../common/finite-state-machine/types';
 import { Order } from '../entity/order/order.entity';
 import { OrderState } from '../service/helpers/order-state-machine/order-state';
 
@@ -294,7 +294,7 @@ export interface OrderOptions {
      *
      * @default []
      */
-    process?: Array<CustomOrderProcess<string>>;
+    process?: Array<CustomOrderProcess<any>>;
     /**
      * @description
      * Defines the strategy used to merge a guest Order and an existing Order when

+ 3 - 1
packages/core/src/event-bus/events/attempted-login-event.ts

@@ -5,12 +5,14 @@ import { VendureEvent } from '../vendure-event';
 /**
  * @description
  * This event is fired when an attempt is made to log in via the shop or admin API `login` mutation.
+ * The `strategy` represents the name of the AuthenticationStrategy used in the login attempt.
+ * If the "native" strategy is used, the additional `identifier` property will be available.
  *
  * @docsCategory events
  * @docsPage Event Types
  */
 export class AttemptedLoginEvent extends VendureEvent {
-    constructor(public ctx: RequestContext, public identifier: string) {
+    constructor(public ctx: RequestContext, public strategy: string, public identifier?: string) {
         super();
     }
 }

+ 7 - 9
packages/core/src/service/helpers/order-state-machine/order-state-machine.ts

@@ -1,17 +1,13 @@
 import { Injectable } from '@nestjs/common';
 import { InjectConnection } from '@nestjs/typeorm';
 import { HistoryEntryType } from '@vendure/common/lib/generated-types';
-import { Observable } from 'rxjs';
 import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../../api/common/request-context';
 import { IllegalOperationError } from '../../../common/error/errors';
-import {
-    FSM,
-    StateMachineConfig,
-    Transitions,
-} from '../../../common/finite-state-machine/finite-state-machine';
+import { FSM } from '../../../common/finite-state-machine/finite-state-machine';
 import { mergeTransitionDefinitions } from '../../../common/finite-state-machine/merge-transition-definitions';
+import { StateMachineConfig, Transitions } from '../../../common/finite-state-machine/types';
 import { validateTransitionDefinition } from '../../../common/finite-state-machine/validate-transition-definition';
 import { awaitPromiseOrObservable } from '../../../common/utils';
 import { ConfigService } from '../../../config/config.service';
@@ -52,7 +48,7 @@ export class OrderStateMachine {
         return new FSM(this.config, currentState).canTransitionTo(newState);
     }
 
-    getNextStates(order: Order): OrderState[] {
+    getNextStates(order: Order): ReadonlyArray<OrderState> {
         const fsm = new FSM(this.config, order.state);
         return fsm.getNextStates();
     }
@@ -164,8 +160,10 @@ export class OrderStateMachine {
             },
             onError: async (fromState, toState, message) => {
                 for (const process of customProcesses) {
-                    if (typeof process.onError === 'function') {
-                        await awaitPromiseOrObservable(process.onError(fromState, toState, message));
+                    if (typeof process.onTransitionError === 'function') {
+                        await awaitPromiseOrObservable(
+                            process.onTransitionError(fromState, toState, message),
+                        );
                     }
                 }
                 throw new IllegalOperationError(message || 'error.cannot-transition-order-from-to', {

+ 1 - 1
packages/core/src/service/helpers/order-state-machine/order-state.ts

@@ -1,5 +1,5 @@
 import { RequestContext } from '../../../api/common/request-context';
-import { Transitions } from '../../../common/finite-state-machine/finite-state-machine';
+import { Transitions } from '../../../common/finite-state-machine/types';
 import { Order } from '../../../entity/order/order.entity';
 
 /**

+ 46 - 27
packages/core/src/service/helpers/payment-state-machine/payment-state-machine.ts

@@ -3,44 +3,26 @@ import { HistoryEntryType } from '@vendure/common/lib/generated-types';
 
 import { RequestContext } from '../../../api/common/request-context';
 import { IllegalOperationError } from '../../../common/error/errors';
-import { FSM, StateMachineConfig } from '../../../common/finite-state-machine/finite-state-machine';
+import { FSM } from '../../../common/finite-state-machine/finite-state-machine';
+import { StateMachineConfig } from '../../../common/finite-state-machine/types';
+import { awaitPromiseOrObservable } from '../../../common/utils';
 import { ConfigService } from '../../../config/config.service';
 import { Order } from '../../../entity/order/order.entity';
 import { Payment } from '../../../entity/payment/payment.entity';
 import { HistoryService } from '../../services/history.service';
+import { OrderState, OrderTransitionData } from '../order-state-machine/order-state';
 
 import { PaymentState, paymentStateTransitions, PaymentTransitionData } from './payment-state';
 
 @Injectable()
 export class PaymentStateMachine {
-    private readonly config: StateMachineConfig<PaymentState, PaymentTransitionData> = {
-        transitions: paymentStateTransitions,
-        onTransitionStart: async (fromState, toState, data) => {
-            return true;
-        },
-        onTransitionEnd: async (fromState, toState, data) => {
-            await this.historyService.createHistoryEntryForOrder({
-                ctx: data.ctx,
-                orderId: data.order.id,
-                type: HistoryEntryType.ORDER_PAYMENT_TRANSITION,
-                data: {
-                    paymentId: data.payment.id,
-                    from: fromState,
-                    to: toState,
-                },
-            });
-        },
-        onError: (fromState, toState, message) => {
-            throw new IllegalOperationError(message || 'error.cannot-transition-payment-from-to', {
-                fromState,
-                toState,
-            });
-        },
-    };
+    private readonly config: StateMachineConfig<PaymentState, PaymentTransitionData>;
 
-    constructor(private configService: ConfigService, private historyService: HistoryService) {}
+    constructor(private configService: ConfigService, private historyService: HistoryService) {
+        this.config = this.initConfig();
+    }
 
-    getNextStates(payment: Payment): PaymentState[] {
+    getNextStates(payment: Payment): ReadonlyArray<PaymentState> {
         const fsm = new FSM(this.config, payment.state);
         return fsm.getNextStates();
     }
@@ -50,4 +32,41 @@ export class PaymentStateMachine {
         await fsm.transitionTo(state, { ctx, order, payment });
         payment.state = state;
     }
+
+    private initConfig(): StateMachineConfig<PaymentState, PaymentTransitionData> {
+        const { paymentMethodHandlers } = this.configService.paymentOptions;
+        return {
+            transitions: paymentStateTransitions,
+            onTransitionStart: async (fromState, toState, data) => {
+                for (const handler of paymentMethodHandlers) {
+                    if (data.payment.method === handler.code) {
+                        const result = await awaitPromiseOrObservable(
+                            handler.onStateTransitionStart(fromState, toState, data),
+                        );
+                        if (result !== true) {
+                            return result;
+                        }
+                    }
+                }
+            },
+            onTransitionEnd: async (fromState, toState, data) => {
+                await this.historyService.createHistoryEntryForOrder({
+                    ctx: data.ctx,
+                    orderId: data.order.id,
+                    type: HistoryEntryType.ORDER_PAYMENT_TRANSITION,
+                    data: {
+                        paymentId: data.payment.id,
+                        from: fromState,
+                        to: toState,
+                    },
+                });
+            },
+            onError: (fromState, toState, message) => {
+                throw new IllegalOperationError(message || 'error.cannot-transition-payment-from-to', {
+                    fromState,
+                    toState,
+                });
+            },
+        };
+    }
 }

+ 1 - 1
packages/core/src/service/helpers/payment-state-machine/payment-state.ts

@@ -1,5 +1,5 @@
 import { RequestContext } from '../../../api/common/request-context';
-import { Transitions } from '../../../common/finite-state-machine/finite-state-machine';
+import { Transitions } from '../../../common/finite-state-machine/types';
 import { Order } from '../../../entity/order/order.entity';
 import { Payment } from '../../../entity/payment/payment.entity';
 

+ 3 - 2
packages/core/src/service/helpers/refund-state-machine/refund-state-machine.ts

@@ -3,7 +3,8 @@ import { HistoryEntryType } from '@vendure/common/lib/generated-types';
 
 import { RequestContext } from '../../../api/common/request-context';
 import { IllegalOperationError } from '../../../common/error/errors';
-import { FSM, StateMachineConfig } from '../../../common/finite-state-machine/finite-state-machine';
+import { FSM } from '../../../common/finite-state-machine/finite-state-machine';
+import { StateMachineConfig } from '../../../common/finite-state-machine/types';
 import { ConfigService } from '../../../config/config.service';
 import { Order } from '../../../entity/order/order.entity';
 import { Refund } from '../../../entity/refund/refund.entity';
@@ -41,7 +42,7 @@ export class RefundStateMachine {
 
     constructor(private configService: ConfigService, private historyService: HistoryService) {}
 
-    getNextStates(refund: Refund): RefundState[] {
+    getNextStates(refund: Refund): ReadonlyArray<RefundState> {
         const fsm = new FSM(this.config, refund.state);
         return fsm.getNextStates();
     }

+ 1 - 1
packages/core/src/service/helpers/refund-state-machine/refund-state.ts

@@ -1,5 +1,5 @@
 import { RequestContext } from '../../../api/common/request-context';
-import { Transitions } from '../../../common/finite-state-machine/finite-state-machine';
+import { Transitions } from '../../../common/finite-state-machine/types';
 import { Order } from '../../../entity/order/order.entity';
 import { Payment } from '../../../entity/payment/payment.entity';
 import { Refund } from '../../../entity/refund/refund.entity';

+ 10 - 1
packages/core/src/service/services/auth.service.ts

@@ -9,6 +9,7 @@ import { InternalServerError, NotVerifiedError, UnauthorizedError } from '../../
 import { AuthenticationStrategy } from '../../config/auth/authentication-strategy';
 import {
     NATIVE_AUTH_STRATEGY_NAME,
+    NativeAuthenticationData,
     NativeAuthenticationStrategy,
 } from '../../config/auth/native-authentication-strategy';
 import { ConfigService } from '../../config/config.service';
@@ -42,7 +43,15 @@ export class AuthService {
         authenticationMethod: string,
         authenticationData: any,
     ): Promise<AuthenticatedSession> {
-        this.eventBus.publish(new AttemptedLoginEvent(ctx, authenticationMethod));
+        this.eventBus.publish(
+            new AttemptedLoginEvent(
+                ctx,
+                authenticationMethod,
+                authenticationMethod === NATIVE_AUTH_STRATEGY_NAME
+                    ? (authenticationData as NativeAuthenticationData).username
+                    : undefined,
+            ),
+        );
         const authenticationStrategy = this.getAuthenticationStrategy(apiType, authenticationMethod);
         const user = await authenticationStrategy.authenticate(ctx, authenticationData);
         if (!user) {

+ 2 - 2
packages/core/src/service/services/order.service.ts

@@ -96,7 +96,7 @@ export class OrderService {
         return Object.entries(this.orderStateMachine.config.transitions).map(([name, { to }]) => ({
             name,
             to,
-        }));
+        })) as OrderProcessState[];
     }
 
     findAll(ctx: RequestContext, options?: ListQueryOptions<Order>): Promise<PaginatedList<Order>> {
@@ -379,7 +379,7 @@ export class OrderService {
         return order.promotions || [];
     }
 
-    getNextOrderStates(order: Order): OrderState[] {
+    getNextOrderStates(order: Order): ReadonlyArray<OrderState> {
         return this.orderStateMachine.getNextStates(order);
     }
 

+ 11 - 5
packages/core/src/service/services/session.service.ts

@@ -167,7 +167,9 @@ export class SessionService implements EntitySubscriberInterface {
     }
 
     async setActiveOrder(serializedSession: CachedSession, order: Order): Promise<CachedSession> {
-        const session = await this.connection.getRepository(Session).findOne(serializedSession.id);
+        const session = await this.connection
+            .getRepository(Session)
+            .findOne(serializedSession.id, { relations: ['user', 'user.roles', 'user.roles.channels'] });
         if (session) {
             session.activeOrder = order;
             await this.connection.getRepository(Session).save(session, { reload: false });
@@ -182,10 +184,14 @@ export class SessionService implements EntitySubscriberInterface {
         if (serializedSession.activeOrderId) {
             const session = await this.connection
                 .getRepository(Session)
-                .save({ id: serializedSession.id, activeOrder: null });
-            const updatedSerializedSession = this.serializeSession(session);
-            await this.configService.authOptions.sessionCacheStrategy.set(updatedSerializedSession);
-            return updatedSerializedSession;
+                .findOne(serializedSession.id, { relations: ['user', 'user.roles', 'user.roles.channels'] });
+            if (session) {
+                session.activeOrder = null;
+                await this.connection.getRepository(Session).save(session);
+                const updatedSerializedSession = this.serializeSession(session);
+                await this.configService.authOptions.sessionCacheStrategy.set(updatedSerializedSession);
+                return updatedSerializedSession;
+            }
         }
         return serializedSession;
     }

+ 3 - 3
packages/create/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/create",
-  "version": "0.13.1",
+  "version": "0.14.0",
   "license": "MIT",
   "bin": {
     "create": "./index.js"
@@ -26,13 +26,13 @@
     "@types/handlebars": "^4.1.0",
     "@types/listr": "^0.14.0",
     "@types/semver": "^6.0.0",
-    "@vendure/core": "^0.13.1",
+    "@vendure/core": "^0.14.0",
     "rimraf": "^3.0.0",
     "ts-node": "^8.4.1",
     "typescript": "3.8.3"
   },
   "dependencies": {
-    "@vendure/common": "^0.13.1",
+    "@vendure/common": "^0.14.0",
     "chalk": "^3.0.0",
     "commander": "^5.0.0",
     "cross-spawn": "^7.0.1",

+ 1 - 1
packages/create/src/create-vendure-app.ts

@@ -249,7 +249,7 @@ async function createApp(
                         app = await populate(bootstrapFn, initialDataPath);
                     }
                     // Pause to ensure the worker jobs have time to complete.
-                    await new Promise((resolve) => setTimeout(resolve, isCi ? 10000 : 2000));
+                    await new Promise((resolve) => setTimeout(resolve, isCi ? 20000 : 2000));
                     await app.close();
                 } catch (e) {
                     console.log(e);

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

@@ -1,6 +1,6 @@
 {
   "name": "dev-server",
-  "version": "0.13.1",
+  "version": "0.14.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": "^0.13.1",
-    "@vendure/asset-server-plugin": "^0.13.1",
-    "@vendure/common": "^0.13.1",
-    "@vendure/core": "^0.13.1",
-    "@vendure/elasticsearch-plugin": "^0.13.1",
-    "@vendure/email-plugin": "^0.13.1",
+    "@vendure/admin-ui-plugin": "^0.14.0",
+    "@vendure/asset-server-plugin": "^0.14.0",
+    "@vendure/common": "^0.14.0",
+    "@vendure/core": "^0.14.0",
+    "@vendure/elasticsearch-plugin": "^0.14.0",
+    "@vendure/email-plugin": "^0.14.0",
     "typescript": "3.8.3"
   },
   "devDependencies": {
     "@types/csv-stringify": "^3.1.0",
-    "@vendure/testing": "^0.13.1",
-    "@vendure/ui-devkit": "^0.13.1",
+    "@vendure/testing": "^0.14.0",
+    "@vendure/ui-devkit": "^0.14.0",
     "concurrently": "^5.0.0",
     "csv-stringify": "^5.3.3"
   }

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

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/elasticsearch-plugin",
-  "version": "0.13.1",
+  "version": "0.14.0",
   "license": "MIT",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -22,8 +22,8 @@
     "deepmerge": "^4.0.0"
   },
   "devDependencies": {
-    "@vendure/common": "^0.13.1",
-    "@vendure/core": "^0.13.1",
+    "@vendure/common": "^0.14.0",
+    "@vendure/core": "^0.14.0",
     "rimraf": "^3.0.0",
     "typescript": "3.8.3"
   }

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

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/email-plugin",
-  "version": "0.13.1",
+  "version": "0.14.0",
   "license": "MIT",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -33,8 +33,8 @@
     "@types/handlebars": "^4.1.0",
     "@types/mjml": "^4.0.2",
     "@types/nodemailer": "^6.4.0",
-    "@vendure/common": "^0.13.1",
-    "@vendure/core": "^0.13.1",
+    "@vendure/common": "^0.14.0",
+    "@vendure/core": "^0.14.0",
     "rimraf": "^3.0.0",
     "typescript": "3.8.3"
   }

+ 3 - 3
packages/testing/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/testing",
-  "version": "0.13.1",
+  "version": "0.14.0",
   "description": "End-to-end testing tools for Vendure projects",
   "keywords": [
     "vendure",
@@ -33,7 +33,7 @@
   },
   "dependencies": {
     "@types/node-fetch": "^2.5.4",
-    "@vendure/common": "^0.13.1",
+    "@vendure/common": "^0.14.0",
     "faker": "^4.1.0",
     "form-data": "^3.0.0",
     "graphql": "^14.5.8",
@@ -44,7 +44,7 @@
   "devDependencies": {
     "@types/mysql": "^2.15.8",
     "@types/pg": "^7.14.1",
-    "@vendure/core": "^0.13.1",
+    "@vendure/core": "^0.14.0",
     "mysql": "^2.17.1",
     "pg": "^7.17.1",
     "rimraf": "^3.0.0",

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

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/ui-devkit",
-  "version": "0.13.1",
+  "version": "0.14.0",
   "description": "A library for authoring Vendure Admin UI extensions",
   "keywords": [
     "vendure",
@@ -39,8 +39,8 @@
     "@angular/cli": "^9.0.5",
     "@angular/compiler": "^9.0.6",
     "@angular/compiler-cli": "^9.0.6",
-    "@vendure/admin-ui": "^0.13.1",
-    "@vendure/common": "^0.13.1",
+    "@vendure/admin-ui": "^0.14.0",
+    "@vendure/common": "^0.14.0",
     "chalk": "^3.0.0",
     "chokidar": "^3.3.1",
     "fs-extra": "^9.0.0",
@@ -51,7 +51,7 @@
     "@rollup/plugin-node-resolve": "^7.1.1",
     "@types/fs-extra": "^8.1.0",
     "@types/glob": "^7.1.1",
-    "@vendure/core": "^0.13.1",
+    "@vendure/core": "^0.14.0",
     "rimraf": "^3.0.0",
     "rollup": "^2.2.0",
     "rollup-plugin-terser": "^5.3.0",