flamingo.me/flamingo-commerce/v3@v3.11.0/checkout/application/placeorder/validate_payment_test.go (about) 1 package placeorder_test 2 3 import ( 4 "context" 5 "errors" 6 "net/url" 7 "testing" 8 9 cartDomain "flamingo.me/flamingo-commerce/v3/cart/domain/cart" 10 "flamingo.me/flamingo-commerce/v3/checkout/application/placeorder" 11 "flamingo.me/flamingo-commerce/v3/checkout/domain/placeorder/process" 12 "flamingo.me/flamingo-commerce/v3/checkout/domain/placeorder/states" 13 "flamingo.me/flamingo-commerce/v3/payment/application" 14 "flamingo.me/flamingo-commerce/v3/payment/domain" 15 "flamingo.me/flamingo-commerce/v3/payment/interfaces" 16 "flamingo.me/flamingo-commerce/v3/payment/interfaces/mocks" 17 price "flamingo.me/flamingo-commerce/v3/price/domain" 18 "github.com/google/go-cmp/cmp" 19 "github.com/stretchr/testify/assert" 20 "github.com/stretchr/testify/mock" 21 "github.com/stretchr/testify/require" 22 ) 23 24 func provideProcessFactory(t *testing.T) *process.Factory { 25 t.Helper() 26 factory := &process.Factory{} 27 factory.Inject( 28 func() *process.Process { 29 return &process.Process{} 30 }, 31 &struct { 32 StartState process.State `inject:"startState"` 33 FailedState process.State `inject:"failedState"` 34 }{ 35 StartState: &states.New{}, 36 FailedState: &states.Failed{}, 37 }, 38 ) 39 return factory 40 } 41 42 func provideCartWithPaymentSelection(t *testing.T) cartDomain.Cart { 43 t.Helper() 44 cart := cartDomain.Cart{} 45 paymentSelection, err := cartDomain.NewDefaultPaymentSelection("test", map[string]string{price.ChargeTypeMain: "main"}, cart) 46 require.NoError(t, err) 47 cart.PaymentSelection = paymentSelection 48 return cart 49 } 50 51 func paymentServiceHelper(t *testing.T, gateway interfaces.WebCartPaymentGateway) *application.PaymentService { 52 t.Helper() 53 paymentService := &application.PaymentService{} 54 55 paymentService.Inject(func() map[string]interfaces.WebCartPaymentGateway { 56 return map[string]interfaces.WebCartPaymentGateway{ 57 "test": gateway, 58 } 59 }) 60 return paymentService 61 } 62 63 func TestPaymentValidator(t *testing.T) { 64 type flowStatusResult struct { 65 flowStatus *domain.FlowStatus 66 err error 67 } 68 69 type want struct { 70 runResult process.RunResult 71 state string 72 stateData process.StateData 73 } 74 75 tests := []struct { 76 name string 77 flowStatus flowStatusResult 78 want want 79 }{ 80 { 81 name: "generic payment error during FlowStatus request", 82 flowStatus: flowStatusResult{ 83 err: errors.New("generic_error"), 84 }, 85 want: want{ 86 runResult: process.RunResult{Failed: process.ErrorOccurredReason{Error: "generic_error"}}, 87 state: states.New{}.Name(), 88 }, 89 }, 90 { 91 name: "status: unapproved, action: show iframe", 92 flowStatus: flowStatusResult{ 93 flowStatus: &domain.FlowStatus{ 94 Status: domain.PaymentFlowStatusUnapproved, 95 Action: domain.PaymentFlowActionShowIframe, 96 ActionData: domain.FlowActionData{ 97 URL: &url.URL{Scheme: "https", Host: "iframe-url.com"}, 98 }, 99 }, 100 }, 101 want: want{ 102 runResult: process.RunResult{Failed: nil}, 103 state: states.ShowIframe{}.Name(), 104 stateData: process.StateData(&url.URL{Scheme: "https", Host: "iframe-url.com"}), 105 }, 106 }, 107 { 108 name: "status: unapproved, action: show iframe - URL missing", 109 flowStatus: flowStatusResult{ 110 flowStatus: &domain.FlowStatus{ 111 Status: domain.PaymentFlowStatusUnapproved, 112 Action: domain.PaymentFlowActionShowIframe, 113 }, 114 }, 115 want: want{ 116 runResult: process.RunResult{Failed: process.PaymentErrorOccurredReason{Error: placeorder.ValidatePaymentErrorNoActionURL}}, 117 state: states.New{}.Name(), 118 }, 119 }, 120 { 121 name: "status: unapproved, action: show html", 122 flowStatus: flowStatusResult{ 123 flowStatus: &domain.FlowStatus{ 124 Status: domain.PaymentFlowStatusUnapproved, 125 Action: domain.PaymentFlowActionShowHTML, 126 ActionData: domain.FlowActionData{ 127 DisplayData: "<h2>Payment Form<h2><form><input type=\"hidden\" /></form>", 128 }, 129 }, 130 }, 131 want: want{ 132 runResult: process.RunResult{Failed: nil}, 133 state: states.ShowHTML{}.Name(), 134 stateData: process.StateData("<h2>Payment Form<h2><form><input type=\"hidden\" /></form>"), 135 }, 136 }, 137 { 138 name: "status: unapproved, action: show html - html missing", 139 flowStatus: flowStatusResult{ 140 flowStatus: &domain.FlowStatus{ 141 Status: domain.PaymentFlowStatusUnapproved, 142 Action: domain.PaymentFlowActionShowHTML, 143 }, 144 }, 145 want: want{ 146 runResult: process.RunResult{Failed: process.PaymentErrorOccurredReason{Error: placeorder.ValidatePaymentErrorNoActionDisplayData}}, 147 state: states.New{}.Name(), 148 }, 149 }, 150 { 151 name: "status: unapproved, action: redirect", 152 flowStatus: flowStatusResult{ 153 flowStatus: &domain.FlowStatus{ 154 Status: domain.PaymentFlowStatusUnapproved, 155 Action: domain.PaymentFlowActionRedirect, 156 ActionData: domain.FlowActionData{ 157 URL: &url.URL{Scheme: "https", Host: "redirect-url.com"}, 158 }, 159 }, 160 }, 161 want: want{ 162 runResult: process.RunResult{Failed: nil}, 163 state: states.Redirect{}.Name(), 164 stateData: process.StateData(&url.URL{Scheme: "https", Host: "redirect-url.com"}), 165 }, 166 }, 167 { 168 name: "status: unapproved, action: redirect - URL missing", 169 flowStatus: flowStatusResult{ 170 flowStatus: &domain.FlowStatus{ 171 Status: domain.PaymentFlowStatusUnapproved, 172 Action: domain.PaymentFlowActionRedirect, 173 }, 174 }, 175 want: want{ 176 runResult: process.RunResult{Failed: process.PaymentErrorOccurredReason{Error: placeorder.ValidatePaymentErrorNoActionURL}}, 177 state: states.New{}.Name(), 178 }, 179 }, 180 { 181 name: "status: unapproved, action: trigger_client_sdk", 182 flowStatus: flowStatusResult{ 183 flowStatus: &domain.FlowStatus{ 184 Status: domain.PaymentFlowStatusUnapproved, 185 Action: domain.PaymentFlowActionTriggerClientSDK, 186 ActionData: domain.FlowActionData{ 187 URL: &url.URL{Scheme: "https", Host: "redirect-url.com"}, 188 DisplayData: `{"foo": "bar"}`, 189 }, 190 }, 191 }, 192 want: want{ 193 runResult: process.RunResult{Failed: nil}, 194 state: states.TriggerClientSDK{}.Name(), 195 stateData: process.StateData(states.TriggerClientSDKData{ 196 URL: &url.URL{Scheme: "https", Host: "redirect-url.com"}, 197 Data: `{"foo": "bar"}`, 198 }), 199 }, 200 }, 201 { 202 name: "status: unapproved, action: trigger_client_sdk - URL missing", 203 flowStatus: flowStatusResult{ 204 flowStatus: &domain.FlowStatus{ 205 Status: domain.PaymentFlowStatusUnapproved, 206 Action: domain.PaymentFlowActionTriggerClientSDK, 207 }, 208 }, 209 want: want{ 210 runResult: process.RunResult{Failed: process.PaymentErrorOccurredReason{Error: placeorder.ValidatePaymentErrorNoActionURL}}, 211 state: states.New{}.Name(), 212 }, 213 }, 214 { 215 name: "status: unapproved, action: post redirect", 216 flowStatus: flowStatusResult{ 217 flowStatus: &domain.FlowStatus{ 218 Status: domain.PaymentFlowStatusUnapproved, 219 Action: domain.PaymentFlowActionPostRedirect, 220 ActionData: domain.FlowActionData{ 221 URL: &url.URL{Scheme: "https", Host: "post-redirect-url.com"}, 222 FormParameter: map[string]domain.FormField{ 223 "form-field-0": { 224 Value: []string{"value0", "value1"}, 225 }, 226 "form-field-1": { 227 Value: []string{"value0"}, 228 }, 229 }, 230 }, 231 }, 232 }, 233 want: want{ 234 runResult: process.RunResult{Failed: nil}, 235 state: states.PostRedirect{}.Name(), 236 stateData: process.StateData(states.PostRedirectData{ 237 FormFields: map[string]states.FormField{ 238 "form-field-0": { 239 Value: []string{"value0", "value1"}, 240 }, 241 "form-field-1": { 242 Value: []string{"value0"}, 243 }, 244 }, 245 URL: &url.URL{Scheme: "https", Host: "post-redirect-url.com"}, 246 }), 247 }, 248 }, 249 { 250 name: "status: unapproved, action: show wallet payment", 251 flowStatus: flowStatusResult{ 252 flowStatus: &domain.FlowStatus{ 253 Status: domain.PaymentFlowStatusUnapproved, 254 Action: domain.PaymentFlowActionShowWalletPayment, 255 ActionData: domain.FlowActionData{ 256 WalletDetails: &domain.WalletDetails{ 257 UsedPaymentMethod: "ApplePay", 258 PaymentRequestAPI: domain.PaymentRequestAPI{ 259 Methods: `{ 260 "supportedMethods": "https://apple.com/apple-pay", 261 "data": { 262 "version": 3, 263 "merchantIdentifier": "merchant.com.aoe.om3.kso", 264 "merchantCapabilities": ["supports3DS"], 265 "supportedNetworks": ["asterCard", "visa", "discover", "amex"], 266 "countryCode": "DE" 267 } 268 } 269 `, 270 MerchantValidationURL: &url.URL{Scheme: "https", Host: "validate.example.com"}, 271 }, 272 }, 273 }, 274 }, 275 }, 276 want: want{ 277 runResult: process.RunResult{Failed: nil}, 278 state: states.ShowWalletPayment{}.Name(), 279 stateData: process.StateData(states.ShowWalletPaymentData{ 280 UsedPaymentMethod: "ApplePay", 281 PaymentRequestAPI: domain.PaymentRequestAPI{ 282 Methods: `{ 283 "supportedMethods": "https://apple.com/apple-pay", 284 "data": { 285 "version": 3, 286 "merchantIdentifier": "merchant.com.aoe.om3.kso", 287 "merchantCapabilities": ["supports3DS"], 288 "supportedNetworks": ["asterCard", "visa", "discover", "amex"], 289 "countryCode": "DE" 290 } 291 } 292 `, 293 MerchantValidationURL: &url.URL{Scheme: "https", Host: "validate.example.com"}, 294 }, 295 }), 296 }, 297 }, 298 { 299 name: "status: unapproved, action: post redirect - URL missing", 300 flowStatus: flowStatusResult{ 301 flowStatus: &domain.FlowStatus{ 302 Status: domain.PaymentFlowStatusUnapproved, 303 Action: domain.PaymentFlowActionPostRedirect, 304 }, 305 }, 306 want: want{ 307 runResult: process.RunResult{Failed: process.PaymentErrorOccurredReason{Error: placeorder.ValidatePaymentErrorNoActionURL}}, 308 state: states.New{}.Name(), 309 }, 310 }, 311 { 312 name: "status: unapproved, action: not supported", 313 flowStatus: flowStatusResult{ 314 flowStatus: &domain.FlowStatus{ 315 Status: domain.PaymentFlowStatusUnapproved, 316 Action: "unknown", 317 }, 318 }, 319 want: want{ 320 runResult: process.RunResult{Failed: process.PaymentErrorOccurredReason{Error: "Payment action not supported: \"unknown\""}}, 321 state: states.New{}.Name(), 322 }, 323 }, 324 { 325 name: "status: approved", 326 flowStatus: flowStatusResult{ 327 flowStatus: &domain.FlowStatus{ 328 Status: domain.PaymentFlowStatusApproved, 329 }, 330 }, 331 want: want{ 332 runResult: process.RunResult{}, 333 state: states.CompletePayment{}.Name(), 334 }, 335 }, 336 { 337 name: "status: completed", 338 flowStatus: flowStatusResult{ 339 flowStatus: &domain.FlowStatus{ 340 Status: domain.PaymentFlowStatusCompleted, 341 }, 342 }, 343 want: want{ 344 runResult: process.RunResult{}, 345 state: states.Success{}.Name(), 346 }, 347 }, 348 { 349 name: "status: completed", 350 flowStatus: flowStatusResult{ 351 flowStatus: &domain.FlowStatus{ 352 Status: domain.PaymentFlowStatusCompleted, 353 }, 354 }, 355 want: want{ 356 runResult: process.RunResult{}, 357 state: states.Success{}.Name(), 358 }, 359 }, 360 { 361 name: "status: aborted", 362 flowStatus: flowStatusResult{ 363 flowStatus: &domain.FlowStatus{ 364 Status: domain.PaymentFlowStatusAborted, 365 }, 366 }, 367 want: want{ 368 runResult: process.RunResult{Failed: process.PaymentCanceledByCustomerReason{}}, 369 state: states.New{}.Name(), 370 }, 371 }, 372 { 373 name: "status: cancelled", 374 flowStatus: flowStatusResult{ 375 flowStatus: &domain.FlowStatus{ 376 Status: domain.PaymentFlowStatusCancelled, 377 }, 378 }, 379 want: want{ 380 runResult: process.RunResult{Failed: process.PaymentErrorOccurredReason{}}, 381 state: states.New{}.Name(), 382 }, 383 }, 384 { 385 name: "status: failed", 386 flowStatus: flowStatusResult{ 387 flowStatus: &domain.FlowStatus{ 388 Status: domain.PaymentFlowStatusFailed, 389 }, 390 }, 391 want: want{ 392 runResult: process.RunResult{Failed: process.PaymentErrorOccurredReason{}}, 393 state: states.New{}.Name(), 394 }, 395 }, 396 { 397 name: "status: wait for customer", 398 flowStatus: flowStatusResult{ 399 flowStatus: &domain.FlowStatus{ 400 Status: domain.PaymentFlowWaitingForCustomer, 401 }, 402 }, 403 want: want{ 404 runResult: process.RunResult{}, 405 state: states.WaitForCustomer{}.Name(), 406 }, 407 }, 408 { 409 name: "status: unknown", 410 flowStatus: flowStatusResult{ 411 flowStatus: &domain.FlowStatus{ 412 Status: "unknown", 413 }, 414 }, 415 want: want{ 416 runResult: process.RunResult{Failed: process.PaymentErrorOccurredReason{Error: "Payment status not supported: \"unknown\""}}, 417 state: states.New{}.Name(), 418 }, 419 }, 420 } 421 for _, tt := range tests { 422 t.Run(tt.name, func(t *testing.T) { 423 factory := provideProcessFactory(t) 424 p, _ := factory.New(&url.URL{}, provideCartWithPaymentSelection(t)) 425 gateway := mocks.NewWebCartPaymentGateway(t) 426 gateway.EXPECT().FlowStatus(mock.Anything, mock.Anything, p.Context().UUID).Return(tt.flowStatus.flowStatus, tt.flowStatus.err).Once() 427 428 paymentService := paymentServiceHelper(t, gateway) 429 got := placeorder.PaymentValidator(context.Background(), p, paymentService) 430 if diff := cmp.Diff(got, tt.want.runResult); diff != "" { 431 t.Error("PaymentValidator() = -got +want", diff) 432 } 433 434 assert.Equal(t, p.Context().CurrentStateName, tt.want.state) 435 436 if diff := cmp.Diff(p.Context().CurrentStateData, tt.want.stateData); diff != "" { 437 t.Error("CurrentStateData = -got +want", diff) 438 } 439 }) 440 } 441 }