flamingo.me/flamingo-commerce/v3@v3.11.0/cart/interfaces/controller/forms/personaldataform.go (about) 1 package forms 2 3 import ( 4 "context" 5 "errors" 6 "net/url" 7 "strconv" 8 "time" 9 10 "flamingo.me/flamingo/v3/framework/config" 11 "flamingo.me/flamingo/v3/framework/flamingo" 12 "flamingo.me/flamingo/v3/framework/web" 13 "flamingo.me/form/application" 14 "flamingo.me/form/domain" 15 "github.com/go-playground/form/v4" 16 17 cartApplication "flamingo.me/flamingo-commerce/v3/cart/application" 18 "flamingo.me/flamingo-commerce/v3/cart/domain/cart" 19 customerApplication "flamingo.me/flamingo-commerce/v3/customer/application" 20 ) 21 22 type ( 23 // PersonalDataForm - interface for the form DTO 24 PersonalDataForm interface { 25 MapPerson() *cart.Person 26 MapAdditionalData() *cart.AdditionalData 27 } 28 29 // DefaultPersonalDataFormProvider for creating form instances 30 DefaultPersonalDataFormProvider func() *DefaultPersonalDataForm 31 32 // DefaultPersonalDataForm - the standard form dto for Person data (that implements PersonalDataForm) 33 DefaultPersonalDataForm struct { 34 DateOfBirth string `form:"dateOfBirth"` 35 PassportCountry string `form:"passportCountry"` 36 PassportNumber string `form:"passportNumber"` 37 Address AddressForm `form:"address" validate:"-"` 38 additionalFormData map[string]string 39 additionalFormFieldsCfg config.Slice 40 } 41 42 // DefaultPersonalDataFormService implements Form(Data)Provider interface of form package 43 DefaultPersonalDataFormService struct { 44 applicationCartReceiverService *cartApplication.CartReceiverService 45 defaultPersonalDataFormProvider DefaultPersonalDataFormProvider 46 customerApplicationService *customerApplication.Service 47 additionalFormFieldsCfg config.Slice 48 dateOfBirthRequired bool 49 minAge int 50 passportCountryRequired bool 51 passportNumberRequired bool 52 } 53 54 // PersonalDataFormController - the (mini) MVC for handling Personal Data (Purchaser) 55 PersonalDataFormController struct { 56 responder *web.Responder 57 applicationCartService *cartApplication.CartService 58 applicationCartReceiverService *cartApplication.CartReceiverService 59 60 logger flamingo.Logger 61 62 formHandlerFactory application.FormHandlerFactory 63 } 64 ) 65 66 // Inject - dependencies 67 func (p *DefaultPersonalDataFormService) Inject( 68 applicationCartReceiverService *cartApplication.CartReceiverService, 69 defaultPersonalDataFormProvider DefaultPersonalDataFormProvider, 70 customerApplicationService *customerApplication.Service, 71 cfg *struct { 72 AdditionalFormValues config.Slice `inject:"config:commerce.cart.personalDataForm.additionalFormFields,optional"` 73 DateOfBirthRequired bool `inject:"config:commerce.cart.personalDataForm.dateOfBirthRequired,optional"` 74 MinAge float64 `inject:"config:commerce.cart.personalDataForm.minAge,optional"` 75 PassportCountryRequired bool `inject:"config:commerce.cart.personalDataForm.passportCountryRequired,optional"` 76 PassportNumberRequired bool `inject:"config:commerce.cart.personalDataForm.passportNumberRequired,optional"` 77 }, 78 ) *DefaultPersonalDataFormService { 79 p.applicationCartReceiverService = applicationCartReceiverService 80 p.defaultPersonalDataFormProvider = defaultPersonalDataFormProvider 81 p.customerApplicationService = customerApplicationService 82 if cfg != nil { 83 p.additionalFormFieldsCfg = cfg.AdditionalFormValues 84 p.dateOfBirthRequired = cfg.DateOfBirthRequired 85 p.minAge = int(cfg.MinAge) 86 p.passportCountryRequired = cfg.PassportCountryRequired 87 p.passportNumberRequired = cfg.PassportNumberRequired 88 } 89 90 return p 91 } 92 93 // GetFormData from data provider 94 func (p *DefaultPersonalDataFormService) GetFormData(ctx context.Context, req *web.Request) (interface{}, error) { 95 formData := p.defaultPersonalDataFormProvider() 96 97 cart, err := p.applicationCartReceiverService.ViewCart(ctx, req.Session()) 98 if err != nil { 99 return *formData, nil 100 } 101 102 customer, err := p.customerApplicationService.GetForIdentity(ctx, req) 103 if err == nil { 104 personalData := customer.GetPersonalData() 105 formData.DateOfBirth = personalData.Birthday.Format("2006-01-02") 106 } 107 108 if cart.Purchaser != nil { 109 formData.DateOfBirth = cart.Purchaser.PersonalDetails.DateOfBirth 110 formData.PassportCountry = cart.Purchaser.PersonalDetails.PassportCountry 111 formData.PassportNumber = cart.Purchaser.PersonalDetails.PassportNumber 112 if cart.Purchaser.Address != nil { 113 formData.Address.LoadFromCartAddress(*cart.Purchaser.Address) 114 } 115 } 116 117 if p.additionalFormFieldsCfg != nil { 118 for _, key := range p.additionalFormFieldsCfg { 119 formData.additionalFormData[key.(string)] = cart.AdditionalData.CustomAttributes[key.(string)] 120 } 121 } 122 123 return *formData, nil 124 } 125 126 // Decode fills the form data from the web request 127 func (p *DefaultPersonalDataFormService) Decode(_ context.Context, _ *web.Request, values url.Values, _ interface{}) (interface{}, error) { 128 decoder := form.NewDecoder() 129 personalDataForm := p.defaultPersonalDataFormProvider() 130 err := decoder.Decode(personalDataForm, values) 131 if err != nil { 132 return nil, err 133 } 134 135 if p.additionalFormFieldsCfg != nil { 136 for _, key := range p.additionalFormFieldsCfg { 137 personalDataForm.additionalFormData[key.(string)] = values.Get(key.(string)) 138 } 139 } 140 141 return personalDataForm, nil 142 } 143 144 // Validate form data 145 func (p *DefaultPersonalDataFormService) Validate(ctx context.Context, req *web.Request, validatorProvider domain.ValidatorProvider, formData interface{}) (*domain.ValidationInfo, error) { 146 personalDataForm, ok := formData.(*DefaultPersonalDataForm) 147 if !ok { 148 return nil, errors.New("no DefaultPersonalDataForm given") 149 } 150 validationInfo := domain.ValidationInfo{} 151 validationInfo = validatorProvider.Validate(ctx, req, personalDataForm) 152 153 if p.dateOfBirthRequired && personalDataForm.DateOfBirth == "" { 154 validationInfo.AddFieldError("personalData.dateOfBirth", "formerror_dateOfBirth_required", "date of birth is required") 155 } 156 157 if personalDataForm.DateOfBirth != "" && !validateDOB(personalDataForm.DateOfBirth) { 158 validationInfo.AddFieldError("personalData.dateOfBirth", "formerror_dateOfBirth_formaterror", "date of birth has wrong format required: yyyy-mm-dd") 159 } 160 161 if p.minAge > 0 { 162 if !validateMinimumAge(personalDataForm.DateOfBirth, p.minAge) { 163 validationInfo.AddFieldError("personalData.dateOfBirth", "formerror_dateOfBirth_tooyoung", "you need to be at least "+strconv.Itoa(p.minAge)) 164 } 165 } 166 167 if p.passportCountryRequired && personalDataForm.PassportCountry == "" { 168 validationInfo.AddFieldError("personalData.passportCountry", "formerror_passportCountry_required", "passport infos are required") 169 } 170 171 if p.passportNumberRequired && personalDataForm.PassportNumber == "" { 172 validationInfo.AddFieldError("personalData.passportNumber", "formerror_passportNumber_required", "passport infos are required") 173 } 174 175 return &validationInfo, nil 176 } 177 178 func validateDOB(value string) bool { 179 date, err := time.Parse("2006-01-02", value) 180 if err != nil { 181 return false 182 } 183 184 return date.Before(time.Now().Add(24 * time.Hour)) 185 } 186 187 func validateMinimumAge(value string, minimumAge int) bool { 188 date, err := time.Parse("2006-01-02", value) 189 if err != nil { 190 return false 191 } 192 193 now := time.Now() 194 required := time.Date( 195 now.Year()-minimumAge, 196 now.Month(), 197 now.Day(), 198 0, 199 0, 200 0, 201 0, 202 date.Location(), 203 ) 204 205 return date.Add(-time.Minute).Before(required) 206 } 207 208 // Inject dependencies 209 func (c *PersonalDataFormController) Inject( 210 responder *web.Responder, 211 applicationCartService *cartApplication.CartService, 212 applicationCartReceiverService *cartApplication.CartReceiverService, 213 logger flamingo.Logger, 214 formHandlerFactory application.FormHandlerFactory, 215 ) *PersonalDataFormController { 216 c.responder = responder 217 c.applicationCartReceiverService = applicationCartReceiverService 218 c.applicationCartService = applicationCartService 219 220 c.formHandlerFactory = formHandlerFactory 221 c.logger = logger 222 223 return c 224 } 225 226 // GetUnsubmittedForm returns a Unsubmitted form - using the registered FormService 227 func (c *PersonalDataFormController) GetUnsubmittedForm(ctx context.Context, r *web.Request) (*domain.Form, error) { 228 formHandler, err := c.getFormHandler() 229 if err != nil { 230 return nil, err 231 } 232 return formHandler.HandleUnsubmittedForm(ctx, r) 233 } 234 235 // HandleFormAction handles post of personal data and updates cart 236 func (c *PersonalDataFormController) HandleFormAction(ctx context.Context, r *web.Request) (form *domain.Form, actionSuccessFull bool, err error) { 237 session := web.SessionFromContext(ctx) 238 formHandler, err := c.getFormHandler() 239 if err != nil { 240 return nil, false, err 241 } 242 // ## Handle the submitted form (validation etc) 243 form, err = formHandler.HandleSubmittedForm(ctx, r) 244 if err != nil { 245 return nil, false, err 246 } 247 personalDataForm, ok := form.Data.(PersonalDataForm) 248 if !ok { 249 return form, false, errors.New("cannot convert to PersonalDataForm ") 250 } 251 if !form.IsValidAndSubmitted() { 252 return form, false, nil 253 } 254 255 // UpdatePurchaser (and add additional data) 256 err = c.applicationCartService.UpdatePurchaser(ctx, session, personalDataForm.MapPerson(), personalDataForm.MapAdditionalData()) 257 if err != nil { 258 c.logger.WithContext(ctx).Error("PersonalDataFormController UpdatePurchaser Error %v", err) 259 return form, false, err 260 } 261 return form, true, nil 262 } 263 264 func (c *PersonalDataFormController) getFormHandler() (domain.FormHandler, error) { 265 builder := c.formHandlerFactory.GetFormHandlerBuilder() 266 err := builder.SetNamedFormService("commerce.cart.personaldataFormService") 267 if err != nil { 268 return nil, err 269 } 270 return builder.Build(), nil 271 } 272 273 // Inject dependencies 274 func (p *DefaultPersonalDataForm) Inject( 275 cfg *struct { 276 AdditionalFormValues config.Slice `inject:"config:commerce.cart.personalDataForm.additionalFormFields,optional"` 277 }, 278 ) *DefaultPersonalDataForm { 279 if cfg != nil { 280 p.additionalFormFieldsCfg = cfg.AdditionalFormValues 281 } 282 283 p.additionalFormData = make(map[string]string) 284 285 return p 286 } 287 288 // MapPerson maps the checkout form data to the cart.Person domain struct 289 func (p *DefaultPersonalDataForm) MapPerson() *cart.Person { 290 person := cart.Person{ 291 PersonalDetails: cart.PersonalDetails{ 292 PassportNumber: p.PassportNumber, 293 PassportCountry: p.PassportCountry, 294 DateOfBirth: p.DateOfBirth, 295 }, 296 } 297 return &person 298 } 299 300 // AdditionalData returns the additional form data 301 func (p DefaultPersonalDataForm) AdditionalData(key string) string { 302 return p.additionalFormData[key] 303 } 304 305 // MapAdditionalData - mapper 306 func (p DefaultPersonalDataForm) MapAdditionalData() *cart.AdditionalData { 307 return &cart.AdditionalData{ 308 CustomAttributes: p.additionalFormData, 309 } 310 }