flamingo.me/flamingo-commerce/v3@v3.11.0/checkout/interfaces/controller/forms/checkoutform.go (about)

     1  package forms
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"net/url"
     7  	"strings"
     8  
     9  	"flamingo.me/flamingo/v3/framework/flamingo"
    10  	"flamingo.me/flamingo/v3/framework/web"
    11  	"flamingo.me/form/application"
    12  	"flamingo.me/form/domain"
    13  
    14  	cartApplication "flamingo.me/flamingo-commerce/v3/cart/application"
    15  	cartInterfaceForms "flamingo.me/flamingo-commerce/v3/cart/interfaces/controller/forms"
    16  )
    17  
    18  type (
    19  	// CheckoutFormComposite - a complete form (composite) for collecting all checkout data
    20  	CheckoutFormComposite struct {
    21  		// BillingAddressForm - the processed Form object for the BillingAddressForm
    22  		// incoming form values are expected with namespace "billingAddress"
    23  		BillingAddressForm *domain.Form
    24  		// DeliveryForms - the processed Form object for the DeliveryForms
    25  		// incoming form values are expected with namespace "deliveries.###DELIVERYCODE###"
    26  		DeliveryForms map[string]*domain.Form
    27  		// SimplePaymentForm - the processed Form object for the SimplePaymentForm
    28  		// incoming form values are expected with namespace "payment"
    29  		SimplePaymentForm *domain.Form
    30  		// PersonalDataForm - the processed Form object for personal data
    31  		PersonalDataForm *domain.Form
    32  	}
    33  
    34  	// checkoutFormBuilder - private builder for a form with CheckoutForm Data
    35  	checkoutFormBuilder struct {
    36  		checkoutForm *CheckoutFormComposite
    37  	}
    38  
    39  	// CheckoutFormController - the (mini) MVC for the complete checkout form
    40  	CheckoutFormController struct {
    41  		responder                      *web.Responder
    42  		applicationCartService         *cartApplication.CartService
    43  		applicationCartReceiverService *cartApplication.CartReceiverService
    44  		logger                         flamingo.Logger
    45  		formHandlerFactory             application.FormHandlerFactory
    46  		billingAddressFormController   *cartInterfaceForms.BillingAddressFormController
    47  		deliveryFormController         *cartInterfaceForms.DeliveryFormController
    48  		simplePaymentFormController    *cartInterfaceForms.SimplePaymentFormController
    49  		personalDataFormController     *cartInterfaceForms.PersonalDataFormController
    50  		useDeliveryForms               bool
    51  		usePersonalDataForm            bool
    52  	}
    53  )
    54  
    55  // Inject dependencies
    56  func (c *CheckoutFormController) Inject(responder *web.Responder,
    57  	applicationCartService *cartApplication.CartService,
    58  	applicationCartReceiverService *cartApplication.CartReceiverService,
    59  	logger flamingo.Logger,
    60  	formHandlerFactory application.FormHandlerFactory,
    61  	billingAddressFormController *cartInterfaceForms.BillingAddressFormController,
    62  	deliveryFormController *cartInterfaceForms.DeliveryFormController,
    63  	simplePaymentFormController *cartInterfaceForms.SimplePaymentFormController,
    64  	personalDataFormController *cartInterfaceForms.PersonalDataFormController,
    65  	config *struct {
    66  		UseDeliveryForms    bool `inject:"config:commerce.checkout.useDeliveryForms"`
    67  		UsePersonalDataForm bool `inject:"config:commerce.checkout.usePersonalDataForm"`
    68  	},
    69  ) {
    70  	c.responder = responder
    71  	c.applicationCartReceiverService = applicationCartReceiverService
    72  	c.applicationCartService = applicationCartService
    73  	c.formHandlerFactory = formHandlerFactory
    74  	c.logger = logger
    75  	c.billingAddressFormController = billingAddressFormController
    76  	c.deliveryFormController = deliveryFormController
    77  	c.simplePaymentFormController = simplePaymentFormController
    78  	c.personalDataFormController = personalDataFormController
    79  	if config != nil {
    80  		c.useDeliveryForms = config.UseDeliveryForms
    81  		c.usePersonalDataForm = config.UsePersonalDataForm
    82  	}
    83  }
    84  
    85  // GetUnsubmittedForm returns the form
    86  func (c *CheckoutFormController) GetUnsubmittedForm(ctx context.Context, r *web.Request) (*CheckoutFormComposite, error) {
    87  	checkoutFormBuilder := newCheckoutFormBuilder()
    88  
    89  	// Add the billing form:
    90  	billingForm, err := c.billingAddressFormController.GetUnsubmittedForm(ctx, r)
    91  	if err != nil {
    92  		return checkoutFormBuilder.getForm(), err
    93  	}
    94  	err = checkoutFormBuilder.addBillingForm(billingForm)
    95  	if err != nil {
    96  		return checkoutFormBuilder.getForm(), err
    97  	}
    98  
    99  	if c.useDeliveryForms {
   100  		// Add a Delivery Form for every delivery:
   101  		cart, err := c.applicationCartReceiverService.ViewCart(ctx, r.Session())
   102  		if err != nil {
   103  			return checkoutFormBuilder.getForm(), err
   104  		}
   105  		for _, delivery := range cart.Deliveries {
   106  			if !delivery.HasItems() {
   107  				continue
   108  			}
   109  			r.Params["deliveryCode"] = delivery.DeliveryInfo.Code
   110  			deliveryForm, err := c.deliveryFormController.GetUnsubmittedForm(ctx, r)
   111  			if err != nil {
   112  				return checkoutFormBuilder.getForm(), err
   113  			}
   114  			err = checkoutFormBuilder.addDeliveryForm(delivery.DeliveryInfo.Code, deliveryForm)
   115  			if err != nil {
   116  				return checkoutFormBuilder.getForm(), err
   117  			}
   118  		}
   119  	}
   120  
   121  	if c.usePersonalDataForm {
   122  		// 3. Personal Data
   123  		personalDataForm, err := c.personalDataFormController.GetUnsubmittedForm(ctx, r)
   124  		if err != nil {
   125  			return checkoutFormBuilder.getForm(), err
   126  		}
   127  		err = checkoutFormBuilder.addPersonalDataForm(personalDataForm)
   128  		if err != nil {
   129  			return checkoutFormBuilder.getForm(), err
   130  		}
   131  	}
   132  
   133  	// 4. Add the simplePaymentForm
   134  	simplePaymentForm, err := c.simplePaymentFormController.GetUnsubmittedForm(ctx, r)
   135  	if err != nil {
   136  		return checkoutFormBuilder.getForm(), err
   137  	}
   138  	err = checkoutFormBuilder.addSimplePaymentForm(simplePaymentForm)
   139  	if err != nil {
   140  		return checkoutFormBuilder.getForm(), err
   141  	}
   142  
   143  	return checkoutFormBuilder.getForm(), nil
   144  
   145  }
   146  
   147  // newRequestWithResolvedNamespace creates a new request with only the namespaced form values
   148  func newRequestWithResolvedNamespace(namespace string, r *web.Request) *web.Request {
   149  	newRequest := web.CreateRequest(r.Request(), r.Session())
   150  	newURLValues := make(url.Values)
   151  	for k, values := range newRequest.Request().Form {
   152  		if !strings.HasPrefix(k, namespace+".") {
   153  			continue
   154  		}
   155  		strippedKey := strings.Replace(k, namespace+".", "", 1)
   156  		newURLValues[strippedKey] = values
   157  	}
   158  	newRequest.Request().Form = newURLValues
   159  	return newRequest
   160  }
   161  
   162  // HandleFormAction handles the submitted form request
   163  func (c *CheckoutFormController) HandleFormAction(ctx context.Context, r *web.Request) (*CheckoutFormComposite, bool, error) {
   164  	checkoutFormBuilder := newCheckoutFormBuilder()
   165  	overallSuccess := true
   166  	err := r.Request().ParseForm()
   167  	if err != nil {
   168  		return checkoutFormBuilder.getForm(), false, err
   169  	}
   170  
   171  	cart, err := c.applicationCartReceiverService.ViewCart(ctx, r.Session())
   172  	if err != nil {
   173  		return checkoutFormBuilder.getForm(), false, err
   174  	}
   175  
   176  	// 1, #### Process and add Billing Form Controller result
   177  	billingForm, success, err := c.billingAddressFormController.HandleFormAction(ctx, newRequestWithResolvedNamespace("billingAddress", r))
   178  	overallSuccess = overallSuccess && success
   179  	if err != nil {
   180  		return checkoutFormBuilder.getForm(), false, err
   181  	}
   182  	err = checkoutFormBuilder.addBillingForm(billingForm)
   183  	if err != nil {
   184  		return checkoutFormBuilder.getForm(), false, err
   185  	}
   186  
   187  	if c.useDeliveryForms {
   188  		// 2. #### Process ALL the delivery forms:
   189  		// Add a Delivery Form for every delivery:
   190  		for _, delivery := range cart.Deliveries {
   191  			if !delivery.HasItems() {
   192  				continue
   193  			}
   194  			deliveryFormNamespace := "deliveries." + delivery.DeliveryInfo.Code
   195  			// Add the billing form:
   196  			deliverySubRequest := newRequestWithResolvedNamespace(deliveryFormNamespace, r)
   197  			deliverySubRequest.Params["deliveryCode"] = delivery.DeliveryInfo.Code
   198  			deliveryForm, success, err := c.deliveryFormController.HandleFormAction(ctx, deliverySubRequest)
   199  			overallSuccess = overallSuccess && success
   200  			if err != nil {
   201  				return checkoutFormBuilder.getForm(), false, err
   202  			}
   203  			err = checkoutFormBuilder.addDeliveryForm(delivery.DeliveryInfo.Code, deliveryForm)
   204  			if err != nil {
   205  				return checkoutFormBuilder.getForm(), false, err
   206  			}
   207  		}
   208  	}
   209  
   210  	if c.usePersonalDataForm {
   211  		// 3. ### Add the personalDataForm
   212  		personalDataForm, success, err := c.personalDataFormController.HandleFormAction(ctx, newRequestWithResolvedNamespace("personalData", r))
   213  		overallSuccess = overallSuccess && success
   214  		if err != nil {
   215  			return checkoutFormBuilder.getForm(), false, err
   216  		}
   217  		err = checkoutFormBuilder.addPersonalDataForm(personalDataForm)
   218  		if err != nil {
   219  			return checkoutFormBuilder.getForm(), false, err
   220  		}
   221  	}
   222  
   223  	if !cart.GrandTotal.IsZero() {
   224  		// 4. ### Add the simplePaymentForm if payment is required.
   225  		simplePaymentForm, success, err := c.simplePaymentFormController.HandleFormAction(ctx, newRequestWithResolvedNamespace("payment", r))
   226  		overallSuccess = overallSuccess && success
   227  		if err != nil {
   228  			return checkoutFormBuilder.getForm(), false, err
   229  		}
   230  		err = checkoutFormBuilder.addSimplePaymentForm(simplePaymentForm)
   231  		if err != nil {
   232  			return checkoutFormBuilder.getForm(), false, err
   233  		}
   234  	}
   235  
   236  	return checkoutFormBuilder.getForm(), overallSuccess, nil
   237  
   238  }
   239  
   240  func newCheckoutFormBuilder() *checkoutFormBuilder {
   241  	b := &checkoutFormBuilder{
   242  		checkoutForm: &CheckoutFormComposite{
   243  			DeliveryForms: make(map[string]*domain.Form),
   244  		},
   245  	}
   246  	return b
   247  }
   248  
   249  func (b *checkoutFormBuilder) getForm() *CheckoutFormComposite {
   250  	return b.checkoutForm
   251  }
   252  
   253  func (b *checkoutFormBuilder) addDeliveryForm(deliveryCode string, deliveryForm *domain.Form) error {
   254  	_, ok := deliveryForm.Data.(cartInterfaceForms.DeliveryForm)
   255  	if !ok {
   256  		return errors.New("no deliveryFormData?")
   257  	}
   258  	b.checkoutForm.DeliveryForms[deliveryCode] = deliveryForm
   259  	return nil
   260  }
   261  
   262  func (b *checkoutFormBuilder) addBillingForm(billingForm *domain.Form) error {
   263  	_, ok := billingForm.Data.(cartInterfaceForms.BillingAddressForm)
   264  	if !ok {
   265  		return errors.New("no billingFormData?")
   266  	}
   267  	b.checkoutForm.BillingAddressForm = billingForm
   268  	return nil
   269  }
   270  
   271  func (b *checkoutFormBuilder) addPersonalDataForm(personalDataForm *domain.Form) error {
   272  	b.checkoutForm.PersonalDataForm = personalDataForm
   273  	return nil
   274  }
   275  
   276  func (b *checkoutFormBuilder) addSimplePaymentForm(simplePaymentForm *domain.Form) error {
   277  	_, ok := simplePaymentForm.Data.(cartInterfaceForms.SimplePaymentForm)
   278  	if !ok {
   279  		return errors.New("no SimplePaymentForm?")
   280  	}
   281  	b.checkoutForm.SimplePaymentForm = simplePaymentForm
   282  	return nil
   283  }
   284  
   285  // HasAnyGeneralErrors checks if any from the included forms has general errors
   286  func (c *CheckoutFormComposite) HasAnyGeneralErrors() bool {
   287  	errs := c.GetAllGeneralErrors()
   288  	return len(errs) > 0
   289  }
   290  
   291  // GetAllGeneralErrors from the included forms
   292  func (c *CheckoutFormComposite) GetAllGeneralErrors() []domain.Error {
   293  	var generalErrors []domain.Error
   294  	generalErrors = append(generalErrors, c.BillingAddressForm.GetGeneralErrors()...)
   295  	for _, form := range c.DeliveryForms {
   296  		generalErrors = append(generalErrors, form.GetGeneralErrors()...)
   297  	}
   298  	return generalErrors
   299  }