public static TransitionBuilder <CheckoutEngine.Trigger> Then(this TransitionBuilder <CheckoutState> builder, params CheckoutEngine.Trigger[] triggers) { return(new TransitionBuilder <CheckoutEngine.Trigger>( target: builder.Target, guards: builder.Guards, triggers: triggers)); }
public CheckoutEngine(Guards guards, IPaymentMethodInfoProvider paymentMethodInfoProvider, TransitionBuilder transitionBuilder) { PaymentMethodInfoProvider = paymentMethodInfoProvider; Guards = guards; TransitionBuilder = transitionBuilder; // This state machine defines the checkout validation workflow. The keys are states // and the values are arrays of transitions out of that state. Transitions describe // the preconditions that must be met before we can move to a new state. // When the checkout engine runs, it looks up the current state (dictionary key) and // gets the transitions for that key (dictionary value). For each transition, it runs // the guards. Each guard checks some state and simply returns true or false. The first // transition for which all guards return true will be taken. If there are any triggers // on that transition, they will be executed, then the current state will be set to // the transition's target state. Now we have a new current state and we'll run this // again. // We keep looping until we end up in a state with no valid transitions (a "terminal // state") or we transition back to the same state we started from (basic infinite // loop protection). In either case, we have a final state that we return to the // caller. StateMachine = new Dictionary <CheckoutState, IEnumerable <Transition> > { { CheckoutState.Start, new Transition[] { TransitionBuilder .Always() .TransitionTo(CheckoutState.ShoppingCart_Validating), } }, { CheckoutState.ShoppingCart_Validating, new Transition[] { TransitionBuilder .If(Guards.ShoppingCartIsEmpty) .TransitionTo(CheckoutState.ShoppingCartIsEmpty), TransitionBuilder .If(Guards.SubtotalDoesNotMeetMinimumAmount) .TransitionTo(CheckoutState.SubtotalDoesNotMeetMinimumAmount), TransitionBuilder .If(Guards.CartItemsLessThanMinimumItemCount) .TransitionTo(CheckoutState.CartItemsLessThanMinimumItemCount), TransitionBuilder .If(Guards.CartItemsGreaterThanMaximumItemCount) .TransitionTo(CheckoutState.CartItemsGreaterThanMaximumItemCount), TransitionBuilder .If(Guards.RecurringScheduleMismatchOnItems) .TransitionTo(CheckoutState.RecurringScheduleMismatchOnItems), TransitionBuilder .Always() .TransitionTo(CheckoutState.Account_Validating), } }, { CheckoutState.Account_Validating, new Transition[] { TransitionBuilder .If(Guards.CustomerAccountRequired) .TransitionTo(CheckoutState.CustomerAccountRequired) .Then( Triggers.UpdateAccount(CheckoutStageStatusExtensions.UpdateAvailable, false), Triggers.UpdateAccount(CheckoutStageStatusExtensions.UpdateFulfilled, false)), TransitionBuilder .Always() .TransitionTo(CheckoutState.PaymentMethod_Cleaning) .Then( Triggers.UpdateAccount(CheckoutStageStatusExtensions.UpdateAvailable, true), Triggers.UpdateAccount(CheckoutStageStatusExtensions.UpdateFulfilled, true)), } }, { CheckoutState.PaymentMethod_Cleaning, new Transition[] { // If a payment method is selected but no details are entered, then deselect the payment method TransitionBuilder .If( Guards.PaymentMethodIsCreditCard, Guards.Not(Guards.PaymentMethodIsOffsite), Guards.CreditCardDetailsMissing) .TransitionTo(CheckoutState.PaymentMethod_Validating) .Then(Triggers.ClearSelectedPaymentMethod), TransitionBuilder .If( Guards.PaymentMethodIsPurchaseOrder, Guards.PurchaseOrderDetailsMissing) .TransitionTo(CheckoutState.PaymentMethod_Validating) .Then(Triggers.ClearSelectedPaymentMethod), TransitionBuilder .Always() .TransitionTo(CheckoutState.PaymentMethod_Validating), } }, { CheckoutState.PaymentMethod_Validating, new Transition[] { TransitionBuilder .If(Guards.PaymentMethodRequired, Guards.Not(Guards.PaymentMethodPresent)) .TransitionTo(CheckoutState.PaymentMethodRequired) .Then(Triggers.UpdatePaymentMethod(CheckoutStageStatusExtensions.UpdateFulfilled, false)), TransitionBuilder .If(Guards.PaymentMethodIsOffsite) .TransitionTo(CheckoutState.PaymentMethod_Valid), TransitionBuilder .If(Guards.PaymentMethodIsMicroPay) .TransitionTo(CheckoutState.PaymentMethod_ValidatingMicropayDetails), TransitionBuilder .If(Guards.PaymentMethodIsPayPalExpress) .TransitionTo(CheckoutState.PaymentMethod_ValidatingPayPalExpressDetails), TransitionBuilder .If(Guards.PaymentMethodIsAmazonPayments) .TransitionTo(CheckoutState.PaymentMethod_ValidatingAmazonPaymentsDetails), TransitionBuilder .If(Guards.PaymentMethodIsCreditCard) .TransitionTo(CheckoutState.PaymentMethod_ValidatingCreditCardDetails), TransitionBuilder .If(Guards.PaymentMethodIsPurchaseOrder) .TransitionTo(CheckoutState.PaymentMethod_ValidatingPurchaseOrderDetails), TransitionBuilder .If(Guards.Always) .TransitionTo(CheckoutState.PaymentMethod_Valid), } }, { CheckoutState.PaymentMethod_ValidatingMicropayDetails, new Transition[] { TransitionBuilder .If(Guards.MicroPayBalanceIsInsufficient) .TransitionTo(CheckoutState.MicroPayBalanceIsInsufficient) .Then(Triggers.UpdatePaymentMethod(CheckoutStageStatusExtensions.UpdateFulfilled, false)), TransitionBuilder .If(Guards.Always) .TransitionTo(CheckoutState.PaymentMethod_Valid), } }, { CheckoutState.PaymentMethod_ValidatingPayPalExpressDetails, new Transition[] { TransitionBuilder .If(Guards.Always) .TransitionTo(CheckoutState.PaymentMethod_Valid), } }, { CheckoutState.PaymentMethod_ValidatingAmazonPaymentsDetails, new Transition[] { TransitionBuilder .If(Guards.AmazonPaymentsDetailsMissing) .TransitionTo(CheckoutState.AmazonPaymentsDetailsRequired) .Then( Triggers.UpdatePaymentMethod(CheckoutStageStatusExtensions.UpdateAvailable, false), Triggers.UpdatePaymentMethod(CheckoutStageStatusExtensions.UpdateFulfilled, false)), TransitionBuilder .If(Guards.Always) .TransitionTo(CheckoutState.PaymentMethod_Valid), } }, { CheckoutState.PaymentMethod_ValidatingCreditCardDetails, new Transition[] { TransitionBuilder .If(Guards.CreditCardDetailsMissing) .TransitionTo(CheckoutState.CreditCardDetailsRequired) .Then( Triggers.UpdatePaymentMethod(CheckoutStageStatusExtensions.UpdateAvailable, false), Triggers.UpdatePaymentMethod(CheckoutStageStatusExtensions.UpdateFulfilled, false)), TransitionBuilder .If(Guards.Always) .TransitionTo(CheckoutState.PaymentMethod_Valid), } }, { CheckoutState.PaymentMethod_ValidatingPurchaseOrderDetails, new Transition[] { TransitionBuilder .If(Guards.PurchaseOrderDetailsMissing) .TransitionTo(CheckoutState.PurchaseOrderDetailsRequired) .Then( Triggers.UpdatePaymentMethod(CheckoutStageStatusExtensions.UpdateAvailable, false), Triggers.UpdatePaymentMethod(CheckoutStageStatusExtensions.UpdateFulfilled, false)), TransitionBuilder .If(Guards.Always) .TransitionTo(CheckoutState.PaymentMethod_Valid), } }, { CheckoutState.PaymentMethod_Valid, new Transition[] { TransitionBuilder .If(Guards.Always) .TransitionTo(CheckoutState.BillingAddress_Validating) .Then(Triggers.UpdatePaymentMethod(CheckoutStageStatusExtensions.UpdateFulfilled, true)), } }, { CheckoutState.BillingAddress_Validating, new Transition[] { TransitionBuilder .If(Guards.BillingAddressDisabled) .TransitionTo(CheckoutState.BillingAddress_Valid) .Then(Triggers.UpdateBillingAddress(CheckoutStageStatusExtensions.UpdateDisabled, true)), TransitionBuilder .If(Guards.BillingAddressRequired, Guards.Not(Guards.BillingAddressPresent)) .TransitionTo(CheckoutState.BillingAddressRequired) .Then(Triggers.UpdateBillingAddress(CheckoutStageStatusExtensions.UpdateFulfilled, false)), TransitionBuilder .If(Guards.Always) .TransitionTo(CheckoutState.BillingAddress_Valid), } }, { CheckoutState.BillingAddress_Valid, new Transition[] { TransitionBuilder .If(Guards.Always) .TransitionTo(CheckoutState.ShippingAddress_Validating) .Then(Triggers.UpdateBillingAddress(CheckoutStageStatusExtensions.UpdateFulfilled, true)), } }, { CheckoutState.ShippingAddress_Validating, new Transition[] { TransitionBuilder .If(Guards.SkipShippingOnCheckout) .TransitionTo(CheckoutState.ShippingAddress_Valid) .Then(Triggers.UpdateShippingAddress(CheckoutStageStatusExtensions.UpdateDisabled, true)), TransitionBuilder .If(Guards.Not(Guards.AllowShipToDifferentThanBillTo)) .TransitionTo(CheckoutState.ShippingAddress_Valid) .Then(Triggers.UpdateShippingAddress(CheckoutStageStatusExtensions.UpdateDisabled, true)), TransitionBuilder .If(Guards.ShippingAddressRequired, Guards.Not(Guards.ShippingAddressPresent)) .TransitionTo(CheckoutState.ShippingAddressRequired) .Then(Triggers.UpdateShippingAddress(CheckoutStageStatusExtensions.UpdateFulfilled, false)), TransitionBuilder .If(Guards.Always) .TransitionTo(CheckoutState.ShippingAddress_Valid), } }, { CheckoutState.ShippingAddress_Valid, new Transition[] { TransitionBuilder .If(Guards.Always) .TransitionTo(CheckoutState.ShippingMethod_Validating) .Then(Triggers.UpdateShippingAddress(CheckoutStageStatusExtensions.UpdateFulfilled, true)), } }, { CheckoutState.ShippingMethod_Validating, new Transition[] { TransitionBuilder .If(Guards.SkipShippingOnCheckout) .TransitionTo(CheckoutState.ShippingMethod_Valid) .Then(Triggers.UpdateShippingMethod(CheckoutStageStatusExtensions.UpdateDisabled, true)), TransitionBuilder .If(Guards.Not(Guards.ShippingMethodRequired)) .TransitionTo(CheckoutState.ShippingMethod_Valid) .Then(Triggers.UpdateShippingMethod(CheckoutStageStatusExtensions.UpdateRequired, false)), TransitionBuilder .If(Guards.ShippingMethodRequired, Guards.Not(Guards.ShippingMethodPresent)) .TransitionTo(CheckoutState.ShippingMethodRequired) .Then(Triggers.UpdateShippingMethod(CheckoutStageStatusExtensions.UpdateFulfilled, false)), TransitionBuilder .If(Guards.ShippingMethodPresent, Guards.Not(Guards.ShippingMethodIsValid)) .TransitionTo(CheckoutState.ShippingMethodRequired) .Then(Triggers.UpdateShippingMethod(CheckoutStageStatusExtensions.UpdateFulfilled, false)), TransitionBuilder .Always() .TransitionTo(CheckoutState.ShippingMethod_Valid), } }, { CheckoutState.ShippingMethod_Valid, new Transition[] { TransitionBuilder .Always() .TransitionTo(CheckoutState.GiftCardSetup_Validating) .Then(Triggers.UpdateShippingMethod(CheckoutStageStatusExtensions.UpdateFulfilled, true)), } }, { CheckoutState.GiftCardSetup_Validating, new Transition[] { TransitionBuilder .If(Guards.CartContainsGiftCard, Guards.Not(Guards.GiftCardSetupComplete)) .TransitionTo(CheckoutState.GiftCardRequiresSetup) .Then(Triggers.UpdateGiftCardSetup(CheckoutStageStatusExtensions.UpdateFulfilled, false)), TransitionBuilder .Always() .TransitionTo(CheckoutState.Checkout_Validating) .Then(Triggers.UpdateGiftCardSetup(CheckoutStageStatusExtensions.UpdateFulfilled, true)), } }, { CheckoutState.Checkout_Validating, new Transition[] { TransitionBuilder .If(Guards.ShippingAddressMustMatchBillingAddress, Guards.ShippingAddressDoesNotMatchBillingAddress) .TransitionTo(CheckoutState.ShippingAddressDoesNotMatchBillingAddress), TransitionBuilder .If(Guards.RequireCustomerOver13, Guards.CustomerIsNotOver13) .TransitionTo(CheckoutState.CustomerIsNotOver13), TransitionBuilder .If(Guards.TermsAndConditionsRequired, Guards.TermsAndConditionsNotAccepted) .TransitionTo(CheckoutState.TermsAndConditionsRequired), TransitionBuilder .Always() .TransitionTo(CheckoutState.Valid), } }, { CheckoutState.Valid, new Transition[] { } }, }; }
public static TransitionBuilder <CheckoutState> TransitionTo(this TransitionBuilder <Guard> builder, CheckoutState target) { return(new TransitionBuilder <CheckoutState>( target: target, guards: builder.Guards)); }