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 }