Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EP-2518 Remove ConfirmationStep #1119

Open
wants to merge 22 commits into
base: EP-2517-branded-checkout-improvements
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9f2ceef
Show recaptcha submit button if v3
caleballdrin Nov 20, 2024
7125463
Add .call to Recaptcha success/failure
caleballdrin Nov 20, 2024
170dcf3
Add submit functions and chain them in submitOrderInternal()
caleballdrin Nov 20, 2024
2d059f4
Change next() navigation to thank you page if using v3
caleballdrin Nov 20, 2024
9d95c84
Add fullscreen fixed loading message
caleballdrin Nov 20, 2024
aa1ced2
Add error messages
caleballdrin Nov 20, 2024
af56314
Fix code formatting
caleballdrin Nov 21, 2024
43e9cac
Remove brandedAnalyticsFactory.purchase and ctrl.selfReference
caleballdrin Nov 21, 2024
3a7a60a
Change ng-if to check if useV3 !== 'true'
caleballdrin Nov 21, 2024
64b70dc
Add radio station back in
caleballdrin Nov 21, 2024
446bfbb
Remove .pipe()
caleballdrin Nov 21, 2024
8e101e9
Stop using componentInstance
caleballdrin Nov 22, 2024
374cce9
Add $apply() instead of $digest()
caleballdrin Nov 22, 2024
fb042a2
Import isString and remove mergeMap import
caleballdrin Nov 22, 2024
af339ac
Remove retrieveRadioStationName()
caleballdrin Nov 22, 2024
05028d2
Fix componentInstance issue
caleballdrin Nov 22, 2024
41214a1
Move giftAddedEvent and cartUpdatedEvent to new file to avoid Circula…
caleballdrin Nov 26, 2024
b629164
move submitOrder to a service
caleballdrin Nov 26, 2024
d71a122
Move tests to the service
caleballdrin Nov 26, 2024
b756b17
Refactor code and remove console logs
caleballdrin Nov 28, 2024
dc73810
Fix code spacing
caleballdrin Nov 28, 2024
fc2fe5d
Add tests
caleballdrin Nov 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/app/branded/branded-checkout.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,13 @@ class BrandedCheckoutController {
next () {
switch (this.checkoutStep) {
case 'giftContactPayment':
this.checkoutStep = 'review'
this.fireAnalyticsEvents('review')
// If it is a single step form, the next step should be 'thankYou'
if (this.useV3 === 'true') {
this.checkoutStep = 'thankYou'
} else {
this.checkoutStep = 'review'
this.fireAnalyticsEvents('review')
}
break
case 'review':
this.checkoutStep = 'thankYou'
Expand Down Expand Up @@ -182,6 +187,6 @@ export default angular
onOrderFailed: '&',
language: '@',
showCoverFees: '@',
useV3: '@',
useV3: '@'
}
})
119 changes: 116 additions & 3 deletions src/app/branded/step-1/branded-checkout-step-1.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,27 @@ import checkoutStep2 from 'app/checkout/step-2/step-2.component'

import cartService from 'common/services/api/cart.service'
import orderService from 'common/services/api/order.service'
import analyticsFactory from '../../analytics/analytics.factory'
import brandedAnalyticsFactory from '../../branded/analytics/branded-analytics.factory'

import { FEE_DERIVATIVE } from 'common/components/paymentMethods/coverFees/coverFees.component'

import template from './branded-checkout-step-1.tpl.html'
import { Observable } from 'rxjs'
import { tap, catchError } from 'rxjs/operators'

const componentName = 'brandedCheckoutStep1'

class BrandedCheckoutStep1Controller {
/* @ngInject */
constructor ($log, $filter, brandedAnalyticsFactory, cartService, orderService) {
constructor ($scope, $log, $filter, $window, analyticsFactory, brandedAnalyticsFactory, cartService, orderService) {
this.$scope = $scope
this.$log = $log
this.$filter = $filter
this.analyticsFactory = analyticsFactory
this.brandedAnalyticsFactory = brandedAnalyticsFactory
this.cartService = cartService
this.orderService = orderService
this.$window = $window
}

$onInit () {
Expand Down Expand Up @@ -154,12 +159,116 @@ class BrandedCheckoutStep1Controller {
checkSuccessfulSubmission () {
if (every(this.submission, 'completed')) {
if (every(this.submission, { error: false })) {
this.next()
if (this.useV3 === 'true') {
this.submitOrderInternal()
} else {
this.next()
}
} else {
this.submitted = false
}
}
}

loadCart () {
this.errorLoadingCart = false

// Return the observable instead of subscribing here
caleballdrin marked this conversation as resolved.
Show resolved Hide resolved
return this.cartService.get().pipe(
caleballdrin marked this conversation as resolved.
Show resolved Hide resolved
tap(data => {
// Setting cart data and analytics
this.cartData = data
this.brandedAnalyticsFactory.saveCoverFees(this.orderService.retrieveCoverFeeDecision())
this.brandedAnalyticsFactory.saveItem(this.cartData.items[0])
this.brandedAnalyticsFactory.addPaymentInfo()
this.brandedAnalyticsFactory.reviewOrder()
console.log('done loading cart')
caleballdrin marked this conversation as resolved.
Show resolved Hide resolved
}),
catchError(error => {
// Handle errors by setting flag and logging the error
this.errorLoadingCart = true
this.$log.error('Error loading cart data for branded checkout step 2', error)
return Observable.throw(error) // Rethrow the error so the observable chain can handle it
})
)
}

loadCurrentPayment () {
this.loadingCurrentPayment = true

return this.orderService.getCurrentPayment().pipe(
caleballdrin marked this conversation as resolved.
Show resolved Hide resolved
tap(data => {
if (!data) {
this.$log.error('Error loading current payment info: current payment doesn\'t seem to exist')
} else if (data['account-type']) {
this.bankAccountPaymentDetails = data
} else if (data['card-type']) {
this.creditCardPaymentDetails = data
} else {
this.$log.error('Error loading current payment info: current payment type is unknown')
}
this.loadingCurrentPayment = false
}),
catchError(error => {
this.loadingCurrentPayment = false
this.$log.error('Error loading current payment info', error)
return Observable.throw(error) // Propagate error
})
)
}

checkErrors () {
// Then check for errors on the API
return this.orderService.checkErrors().do(
(data) => {
this.needinfoErrors = data
console.log('checking errors')
})
.catch(error => {
this.$log.error('Error loading checkErrors', error)
return Observable.throw(error)
caleballdrin marked this conversation as resolved.
Show resolved Hide resolved
})
}

submitOrderInternal () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to dig deeper, but I'm hoping I can find a way to not have to recreate this logic here by including step-3 on this step-1 page.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure it's possible, but there will be more branching in step-3. The tricky part will be changing step-3 to accept a new prop that lets us save the 3 forms and wait for them to finish saving before doing the submission.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or we could possibly do the handling of the 3 forms in an intermediary, invisible-to-the-user step?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess, step-3 itself has review data in the template, so we probably don't want to use it directly; however, it might make sense to refactor the common logic out to a service and use it in both places (step-3 and branded-step-1)

this.loadingAndSubmitting = true
// Start by loading the cart
this.loadCart()
.mergeMap(() => {
// After loadCart completes, call loadCurrentPayment
console.log('loadCurrentPayment')
return this.loadCurrentPayment()
})
.mergeMap(() => {
// After loadCurrentPayment completes, call checkErrors
console.log('checkErrors')
return this.checkErrors()
})
.mergeMap(() => {
return this.orderService.submitOrder(this)
})
.subscribe(() => {
this.next()
this.loadingAndSubmitting = false
})
}

handleRecaptchaFailure () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may not be needed when we move to enterprise. At this point I've removed it, but may need to do something with it after QA.

this.analyticsFactory.checkoutFieldError('submitOrder', 'failed')
this.submittingOrder = false
this.loadingAndSubmitting = false
this.onSubmittingOrder({ value: false })

this.loadCart()

this.onSubmitted()
this.submissionError = 'generic error'
this.$window.scrollTo(0, 0)
}

canSubmitOrder () {
return !this.submittingOrder
}
}

export default angular
Expand All @@ -169,6 +278,7 @@ export default angular
checkoutStep2.name,
cartService.name,
orderService.name,
analyticsFactory.name,
brandedAnalyticsFactory.name
])
.component(componentName, {
Expand All @@ -189,6 +299,9 @@ export default angular
onPaymentFailed: '&',
radioStationApiUrl: '<',
radioStationRadius: '<',
onSubmittingOrder: '&',
onSubmitted: '&',
useV3: '<',
loadingAndSubmitting: '<'
}
})
64 changes: 62 additions & 2 deletions src/app/branded/step-1/branded-checkout-step-1.tpl.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,47 @@
<div class="alert alert-danger" role="alert" ng-if="$ctrl.needinfoErrors || $ctrl.submissionError">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we were going to put these error messages in a separate component.

<p ng-repeat="error in $ctrl.needinfoErrors" ng-switch="error">
<span ng-switch-when="need.email" translate>{{'REVIEW_EMAIL_ERROR'}}</span>
<span ng-switch-when="need.payment.method" translate>{{'REVIEW_PAYMENT_ERROR'}}</span>
<span ng-switch-when="need.billing.address" translate>{{'REVIEW_BILLING_ADDRESS_ERROR'}}</span>
<span ng-switch-default translate>
<span ng-if="$ctrl.submissionErrorStatus > -1" translate>
{{'REVIEW_MISSING_DATA_ERROR'}}
</span>
<span ng-if="$ctrl.submissionErrorStatus === -1" translate>
{{'REVIEW_TIME_OUT_ERROR'}}
</span>
</span>
</p>
<p ng-if="$ctrl.submissionError" ng-switch="$ctrl.submissionError">
<span ng-switch-when="Current payment type is unknown" translate>
{{'REVIEW_SUBMITTING_PAYMENT_ERROR'}}
</span>
<span ng-switch-when="CardExpiredException" translate>
{{'REVIEW_CARD_EXPIRED_ERROR'}}
</span>
<span ng-switch-when="CardErrorException" translate>
{{'REVIEW_INVALID_CARD_ERROR'}}
</span>
<span ng-switch-when="CardDeclinedException" translate>
{{'REVIEW_CARD_DECLINED_ERROR'}}
</span>
<span ng-switch-when="InsufficientFundException" translate>
{{'REVIEW_INSUFFICIENT_FUNDS_ERROR'}}
</span>
<span ng-switch-when="AuthorizedAmountExceededException" translate>
{{'REVIEW_EXCEEDS_BALANCE_ERROR'}}
</span>
<span ng-switch-when="InvalidCVV2Exception" translate>
{{'REVIEW_INVALID_SEC_CODE_ERROR'}}
</span>
<span ng-switch-when="InvalidAddressException" translate>
{{'REVIEW_ADDRESS_MISMATCH_ERROR'}}
</span>
<span ng-switch-default translate>
{{'REVIEW_DEFAULT_ERROR'}}
</span>
</p>
</div>
<div class="panel">
<div class="panel-body loading-overlay-parent">
<loading ng-if="$ctrl.loadingProductConfig">
Expand Down Expand Up @@ -46,10 +90,26 @@ <h3 class="panel-name" translate>{{'PAYMENT'}}</h3>
</div>
<div class="panel">
<div class="panel-body text-right">
<button class="btn btn-primary"
<div class="checkout-cta pull-right" ng-if="$ctrl.useV3 === 'true'">
<recaptcha-wrapper
action="'branded_submit'"
on-success="$ctrl.submit"
on-failure="$ctrl.handleRecaptchaFailure"
component-instance="$ctrl"
button-id="'submitOrderButton'"
button-type="'submit'"
button-classes="'btn btn-primary btn-lg btn-block'"
button-disabled="$ctrl.errorLoadingProductConfig || !$ctrl.canSubmitOrder()"
button-label="'SUBMIT_GIFT'"></recaptcha-wrapper>
</div>

<button ng-if="$ctrl.useV3 !== 'true'" class="btn btn-primary"
ng-click="$ctrl.submit()"
ng-disabled="$ctrl.errorLoadingProductConfig"
translate>{{'CONTINUE'}}</button>
</div>
</div>
</div>
</div>
<loading type="fixed" ng-if="$ctrl.loadingAndSubmitting">
<translate>{{'SUBMITTING_GIFT'}}</translate>
</loading>
2 changes: 1 addition & 1 deletion src/app/cart/cart.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import productModalService from 'common/services/productModal.service'
import desigSrcDirective from 'common/directives/desigSrc.directive'

import displayRateTotals from 'common/components/displayRateTotals/displayRateTotals.component'
import { cartUpdatedEvent } from 'common/components/nav/navCart/navCart.component'
import { cartUpdatedEvent } from 'common/lib/cartEvents'

import analyticsFactory from 'app/analytics/analytics.factory'

Expand Down
2 changes: 1 addition & 1 deletion src/app/cart/cart.component.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Observable } from 'rxjs/Observable'
import 'rxjs/add/observable/of'
import 'rxjs/add/observable/throw'

import { cartUpdatedEvent } from 'common/components/nav/navCart/navCart.component'
import { cartUpdatedEvent } from 'common/lib/cartEvents'

describe('cart', () => {
beforeEach(angular.mock.module(module.name))
Expand Down
11 changes: 6 additions & 5 deletions src/app/checkout/cart-summary/cart-summary.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,22 @@ export const submitOrderEvent = 'submitOrderEvent'

class CartSummaryController {
/* @ngInject */
constructor (cartService, $scope) {
constructor (cartService, $scope, $rootScope) {
this.$scope = $scope
this.$rootScope = $rootScope
this.cartService = cartService
}

buildCartUrl () {
return this.cartService.buildCartUrl()
}

handleRecaptchaFailure (componentInstance) {
componentInstance.$rootScope.$emit(recaptchaFailedEvent)
handleRecaptchaFailure () {
this.$rootScope.$emit(recaptchaFailedEvent)
}

onSubmit (componentInstance) {
componentInstance.$rootScope.$emit(submitOrderEvent)
onSubmit () {
this.$rootScope.$emit(submitOrderEvent)
}
}

Expand Down
12 changes: 6 additions & 6 deletions src/app/checkout/cart-summary/cart-summary.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,17 @@ describe('checkout', function () {

describe('onSubmit', () => {
it('should emit an event', () => {
jest.spyOn(componentInstance.$rootScope, '$emit').mockImplementation(() => {})
self.controller.onSubmit(componentInstance)
expect(componentInstance.$rootScope.$emit).toHaveBeenCalledWith(submitOrderEvent)
jest.spyOn(self.controller.$rootScope, '$emit').mockImplementation(() => {})
self.controller.onSubmit()
expect(self.controller.$rootScope.$emit).toHaveBeenCalledWith(submitOrderEvent)
})
})

describe('handleRecaptchaFailure', () => {
it('should emit an event', () => {
jest.spyOn(componentInstance.$rootScope, '$emit').mockImplementation(() => {})
self.controller.handleRecaptchaFailure(componentInstance)
expect(componentInstance.$rootScope.$emit).toHaveBeenCalledWith(recaptchaFailedEvent)
jest.spyOn(self.controller.$rootScope, '$emit').mockImplementation(() => {})
self.controller.handleRecaptchaFailure()
expect(self.controller.$rootScope.$emit).toHaveBeenCalledWith(recaptchaFailedEvent)
})
})
})
Expand Down
Loading