Esempio n. 1
0
 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));
 }
Esempio n. 2
0
        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[]
                    { }
                },
            };
        }
Esempio n. 3
0
 public static TransitionBuilder <CheckoutState> TransitionTo(this TransitionBuilder <Guard> builder, CheckoutState target)
 {
     return(new TransitionBuilder <CheckoutState>(
                target: target,
                guards: builder.Guards));
 }