Skip to content

Commit

Permalink
Merge pull request #1101 from CruGlobal/add-recaptcha-to-checkout
Browse files Browse the repository at this point in the history
URGENT - Add recaptcha to checkout
  • Loading branch information
wrandall22 authored Jul 31, 2024
2 parents fe3e62d + 0739b61 commit d5abc23
Show file tree
Hide file tree
Showing 13 changed files with 1,117 additions and 160 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ dist
.DS_STORE
coverage
*.iml
.vscode

*.log
*.back.*
.sass-cache
stats.json
*.iml

# asdf
.tool-versions
3 changes: 2 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module.exports = {
collectCoverage: true,
collectCoverageFrom: [
'src/**/*.js',
'src/**/*.{ts,tsx}',
'!**/*.fixture.js'
],
restoreMocks: true,
Expand All @@ -20,7 +21,7 @@ module.exports = {
],
testEnvironment: 'jsdom',
transform: {
'^.+\\.js?$': 'babel-jest',
'^.+\\.(js|tsx)?$': 'babel-jest',
'^.+\\.html$': '<rootDir>/jest/htmlTransform.js'
}
}
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"moment": "^2.24.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-google-recaptcha-v3": "^1.10.1",
"react2angular": "^4.0.6",
"rollbar": "^2.7.1",
"rxjs": "^5.2.0",
Expand All @@ -42,6 +43,10 @@
"@babel/preset-react": "^7.16.7",
"@babel/preset-typescript": "^7.16.7",
"@babel/runtime": "^7.5.5",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^12.1.5",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/user-event": "^14.4.3",
"@types/angular": "^1.8.4",
"@types/node": "^17.0.12",
"@types/react": "^17.0.38",
Expand All @@ -64,9 +69,6 @@
"sass-loader": "^10",
"standard": "^16.0.4",
"style-loader": "^1.0.0",
"ts-essentials": "^9.1.2",
"ts-loader": "^9.2.6",
"ts-node": "^10.4.0",
"typescript-eslint": "^0.0.1-alpha.0",
"webpack": "^4.39.2",
"webpack-bundle-analyzer": "^3.4.1",
Expand Down
79 changes: 50 additions & 29 deletions src/app/checkout/step-3/step-3.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import desigSrcDirective from 'common/directives/desigSrc.directive'
import { cartUpdatedEvent } from 'common/components/nav/navCart/navCart.component'
import { SignInEvent } from 'common/services/session/session.service'
import { startDate } from 'common/services/giftHelpers/giftDates.service'
import recaptchaComponent from 'common/components/Recaptcha/RecaptchaWrapper'

import template from './step-3.tpl.html'

Expand All @@ -24,8 +25,9 @@ const componentName = 'checkoutStep3'

class Step3Controller {
/* @ngInject */
constructor (orderService, $window, $scope, $log, analyticsFactory, cartService, commonService, profileService) {
constructor (orderService, $window, $scope, $log, analyticsFactory, cartService, commonService, profileService, envService) {
this.orderService = orderService
this.envService = envService
this.$window = $window
this.$scope = $scope
this.$log = $log
Expand All @@ -35,6 +37,8 @@ class Step3Controller {
this.commonService = commonService
this.startDate = startDate
this.sessionStorage = $window.sessionStorage
this.selfReference = this
this.isBranded = this.envService.read('isBrandedCheckout')

this.$scope.$on(SignInEvent, () => {
this.$onInit()
Expand Down Expand Up @@ -122,49 +126,65 @@ class Step3Controller {
}

submitOrder () {
delete this.submissionError
delete this.submissionErrorStatus
this.submitOrderInternal(this)
}

submitOrderInternal (componentInstance) {
delete componentInstance.submissionError
delete componentInstance.submissionErrorStatus
// Prevent multiple submissions
if (this.submittingOrder) return
this.submittingOrder = true
this.onSubmittingOrder({ value: true })
if (componentInstance.submittingOrder) return
componentInstance.submittingOrder = true
componentInstance.onSubmittingOrder({ value: true })

let submitRequest
if (this.bankAccountPaymentDetails) {
submitRequest = this.orderService.submit()
} else if (this.creditCardPaymentDetails) {
const cvv = this.orderService.retrieveCardSecurityCode()
submitRequest = this.orderService.submit(cvv)
if (componentInstance.bankAccountPaymentDetails) {
submitRequest = componentInstance.orderService.submit()
} else if (componentInstance.creditCardPaymentDetails) {
const cvv = componentInstance.orderService.retrieveCardSecurityCode()
submitRequest = componentInstance.orderService.submit(cvv)
} else {
submitRequest = Observable.throw({ data: 'Current payment type is unknown' })
}
submitRequest.subscribe(() => {
this.analyticsFactory.purchase(this.donorDetails, this.cartData, this.orderService.retrieveCoverFeeDecision())
this.submittingOrder = false
this.onSubmittingOrder({ value: false })
this.orderService.clearCardSecurityCodes()
this.orderService.clearCoverFees()
this.onSubmitted()
this.$scope.$emit(cartUpdatedEvent)
this.changeStep({ newStep: 'thankYou' })
componentInstance.analyticsFactory.purchase(componentInstance.donorDetails, componentInstance.cartData, componentInstance.orderService.retrieveCoverFeeDecision())
componentInstance.submittingOrder = false
componentInstance.onSubmittingOrder({ value: false })
componentInstance.orderService.clearCardSecurityCodes()
componentInstance.orderService.clearCoverFees()
componentInstance.onSubmitted()
componentInstance.$scope.$emit(cartUpdatedEvent)
componentInstance.changeStep({ newStep: 'thankYou' })
},
error => {
this.analyticsFactory.checkoutFieldError('submitOrder', 'failed')
this.submittingOrder = false
this.onSubmittingOrder({ value: false })
componentInstance.analyticsFactory.checkoutFieldError('submitOrder', 'failed')
componentInstance.submittingOrder = false
componentInstance.onSubmittingOrder({ value: false })

this.loadCart()
componentInstance.loadCart()

if (error.config && error.config.data && error.config.data['security-code']) {
error.config.data['security-code'] = error.config.data['security-code'].replace(/./g, 'X') // Mask security-code
}
this.$log.error('Error submitting purchase:', error)
this.onSubmitted()
this.submissionErrorStatus = error.status
this.submissionError = isString(error && error.data) ? (error && error.data).replace(/[:].*$/, '') : 'generic error' // Keep prefix before first colon for easier ng-switch matching
this.$window.scrollTo(0, 0)
componentInstance.$log.error('Error submitting purchase:', error)
componentInstance.onSubmitted()
componentInstance.submissionErrorStatus = error.status
componentInstance.submissionError = isString(error && error.data) ? (error && error.data).replace(/[:].*$/, '') : 'generic error' // Keep prefix before first colon for easier ng-switch matching
componentInstance.$window.scrollTo(0, 0)
})
}

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

componentInstance.loadCart()

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

export default angular
Expand All @@ -177,7 +197,8 @@ export default angular
profileService.name,
analyticsFactory.name,
cartService.name,
commonService.name
commonService.name,
recaptchaComponent.name
])
.component(componentName, {
controller: Step3Controller,
Expand Down
16 changes: 16 additions & 0 deletions src/app/checkout/step-3/step-3.component.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -445,5 +445,21 @@ describe('checkout', () => {
})
})
})

describe('handleRecaptchaFailure', () => {
it('should show an error if recaptcha fails', () => {
const componentInstance = self.controller
jest.spyOn(componentInstance.analyticsFactory, 'checkoutFieldError').mockImplementation(() => {})
self.controller.handleRecaptchaFailure(componentInstance)

expect(componentInstance.analyticsFactory.checkoutFieldError).toHaveBeenCalledWith('submitOrder', 'failed')
expect(componentInstance.submittingOrder).toEqual(false)
expect(componentInstance.onSubmittingOrder).toHaveBeenCalledWith({ value: false })
expect(componentInstance.loadCart).toHaveBeenCalled()
expect(componentInstance.onSubmitted).toHaveBeenCalled()
expect(componentInstance.submissionError).toEqual('generic error')
expect(componentInstance.$window.scrollTo).toHaveBeenCalledWith(0, 0)
})
})
})
})
20 changes: 19 additions & 1 deletion src/app/checkout/step-3/step-3.tpl.html
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,25 @@
<display-rate-totals rate-totals="$ctrl.cartData.frequencyTotals"></display-rate-totals>
</div>
<div class="checkout-cta pull-right">
<button id="submitOrderButton" type="submit" class="btn btn-primary btn-lg btn-block" ng-click="$ctrl.submitOrder()" ng-disabled="!$ctrl.canSubmitOrder()" translate>
<recaptcha-wrapper
ng-if="!$ctrl.isBranded"
action="'submit'"
on-success="$ctrl.submitOrderInternal"
on-failure="$ctrl.handleRecaptchaFailure"
component-instance="$ctrl.selfReference"
button-id="'submitOrderButton'"
button-type="'submit'"
button-classes="'btn btn-primary btn-lg btn-block'"
button-disabled="!$ctrl.canSubmitOrder()"
button-label="'SUBMIT_GIFT'"></recaptcha-wrapper>
<button
ng-if="$ctrl.isBranded"
id="submitOrderButton"
type="submit"
class="btn btn-primary btn-lg btn-block"
ng-click="$ctrl.submitOrder()"
ng-disabled="!$ctrl.canSubmitOrder()"
translate>
{{'SUBMIT_GIFT'}}
</button>
</div>
Expand Down
18 changes: 12 additions & 6 deletions src/common/app.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,39 +53,44 @@ export const appConfig = /* @ngInject */ function (envServiceProvider, $compileP
imgDomainDesignation: 'https://localhost.cru.org:9000',
publicCru: 'https://stage.cru.org',
publicGive: 'https://give-stage2.cru.org',
acsUrl: 'https://cru-mkt-stage1.adobe-campaign.com/lp/LP63?_uuid=f1938f90-38ea-41a6-baad-9ac133f6d2ec&service=%404k83N_C5RZnLNvwz7waA2SwyzIuP6ATcN8vJjmT5km0iZPYKUUYk54sthkZjj-hltAuOKDYocuEi5Pxv8BSICoA4uppcvU_STKCzjv9RzLpE4hqj&pkey='
acsUrl: 'https://cru-mkt-stage1.adobe-campaign.com/lp/LP63?_uuid=f1938f90-38ea-41a6-baad-9ac133f6d2ec&service=%404k83N_C5RZnLNvwz7waA2SwyzIuP6ATcN8vJjmT5km0iZPYKUUYk54sthkZjj-hltAuOKDYocuEi5Pxv8BSICoA4uppcvU_STKCzjv9RzLpE4hqj&pkey=',
recaptchaKey: '6LdNz5UlAAAAAPSrzydROuY76yGVIquVQAup69PO'
},
devcloud: {
apiUrl: 'https://give-stage2.cru.org',
imgDomain: '//give-static.cru.org',
imgDomainDesignation: 'https://give-dev-cloud.cru.org',
publicCru: 'https://stage-cloud.cru.org',
publicGive: 'https://give-dev-cloud.cru.org',
acsUrl: 'https://cru-mkt-stage1.adobe-campaign.com/lp/LP63?_uuid=f1938f90-38ea-41a6-baad-9ac133f6d2ec&service=%404k83N_C5RZnLNvwz7waA2SwyzIuP6ATcN8vJjmT5km0iZPYKUUYk54sthkZjj-hltAuOKDYocuEi5Pxv8BSICoA4uppcvU_STKCzjv9RzLpE4hqj&pkey='
acsUrl: 'https://cru-mkt-stage1.adobe-campaign.com/lp/LP63?_uuid=f1938f90-38ea-41a6-baad-9ac133f6d2ec&service=%404k83N_C5RZnLNvwz7waA2SwyzIuP6ATcN8vJjmT5km0iZPYKUUYk54sthkZjj-hltAuOKDYocuEi5Pxv8BSICoA4uppcvU_STKCzjv9RzLpE4hqj&pkey=',
recaptchaKey: '6LdNz5UlAAAAAPSrzydROuY76yGVIquVQAup69PO'
},
stagecloud: {
apiUrl: 'https://give-stage-cloud.cru.org',
imgDomain: '//give-static.cru.org',
imgDomainDesignation: 'https://give-stage-cloud.cru.org',
publicCru: 'https://stage-cloud.cru.org',
publicGive: 'https://give-stage-cloud.cru.org',
acsUrl: 'https://cru-mkt-stage1.adobe-campaign.com/lp/LP63?_uuid=f1938f90-38ea-41a6-baad-9ac133f6d2ec&service=%404k83N_C5RZnLNvwz7waA2SwyzIuP6ATcN8vJjmT5km0iZPYKUUYk54sthkZjj-hltAuOKDYocuEi5Pxv8BSICoA4uppcvU_STKCzjv9RzLpE4hqj&pkey='
acsUrl: 'https://cru-mkt-stage1.adobe-campaign.com/lp/LP63?_uuid=f1938f90-38ea-41a6-baad-9ac133f6d2ec&service=%404k83N_C5RZnLNvwz7waA2SwyzIuP6ATcN8vJjmT5km0iZPYKUUYk54sthkZjj-hltAuOKDYocuEi5Pxv8BSICoA4uppcvU_STKCzjv9RzLpE4hqj&pkey=',
recaptchaKey: '6LdNz5UlAAAAAPSrzydROuY76yGVIquVQAup69PO'
},
prodcloud: {
apiUrl: 'https://give-prod-cloud.cru.org',
imgDomain: '//give-static.cru.org',
imgDomainDesignation: 'https://give-prod-cloud.cru.org',
publicCru: 'https://www.cru.org',
publicGive: 'https://give-prod-cloud.cru.org',
acsUrl: 'https://cru-mkt-prod1-m.adobe-campaign.com/lp/LPEmailPrefCenter?_uuid=8831d67a-0d46-406b-8987-fd07c97c4ca7&service=%400fAlW4GPmxXExp8qlx7HDlAM6FSZUd0yYRlQg6HRsO_kglfi0gs650oHPZX6LrOvg7OHoIWWpobOeGZduxdNU_m5alc&pkey='
acsUrl: 'https://cru-mkt-prod1-m.adobe-campaign.com/lp/LPEmailPrefCenter?_uuid=8831d67a-0d46-406b-8987-fd07c97c4ca7&service=%400fAlW4GPmxXExp8qlx7HDlAM6FSZUd0yYRlQg6HRsO_kglfi0gs650oHPZX6LrOvg7OHoIWWpobOeGZduxdNU_m5alc&pkey=',
recaptchaKey: '6LdNz5UlAAAAAPSrzydROuY76yGVIquVQAup69PO'
},
staging: {
apiUrl: 'https://give-stage2.cru.org',
imgDomain: '//give-static.cru.org',
imgDomainDesignation: 'https://give-stage2.cru.org',
publicCru: 'https://stage.cru.org',
publicGive: 'https://give-stage2.cru.org',
acsUrl: 'https://cru-mkt-stage1.adobe-campaign.com/lp/LP63?_uuid=f1938f90-38ea-41a6-baad-9ac133f6d2ec&service=%404k83N_C5RZnLNvwz7waA2SwyzIuP6ATcN8vJjmT5km0iZPYKUUYk54sthkZjj-hltAuOKDYocuEi5Pxv8BSICoA4uppcvU_STKCzjv9RzLpE4hqj&pkey='
acsUrl: 'https://cru-mkt-stage1.adobe-campaign.com/lp/LP63?_uuid=f1938f90-38ea-41a6-baad-9ac133f6d2ec&service=%404k83N_C5RZnLNvwz7waA2SwyzIuP6ATcN8vJjmT5km0iZPYKUUYk54sthkZjj-hltAuOKDYocuEi5Pxv8BSICoA4uppcvU_STKCzjv9RzLpE4hqj&pkey=',
recaptchaKey: '6LdNz5UlAAAAAPSrzydROuY76yGVIquVQAup69PO'
},
nonprod: {
apiUrl: 'https://give-stage2-next.cru.org',
Expand All @@ -110,7 +115,8 @@ export const appConfig = /* @ngInject */ function (envServiceProvider, $compileP
imgDomainDesignation: 'https://give.cru.org',
publicCru: 'https://www.cru.org',
publicGive: 'https://give.cru.org',
acsUrl: 'https://cru-mkt-prod1-m.adobe-campaign.com/lp/LPEmailPrefCenter?_uuid=8831d67a-0d46-406b-8987-fd07c97c4ca7&service=%400fAlW4GPmxXExp8qlx7HDlAM6FSZUd0yYRlQg6HRsO_kglfi0gs650oHPZX6LrOvg7OHoIWWpobOeGZduxdNU_m5alc&pkey='
acsUrl: 'https://cru-mkt-prod1-m.adobe-campaign.com/lp/LPEmailPrefCenter?_uuid=8831d67a-0d46-406b-8987-fd07c97c4ca7&service=%400fAlW4GPmxXExp8qlx7HDlAM6FSZUd0yYRlQg6HRsO_kglfi0gs650oHPZX6LrOvg7OHoIWWpobOeGZduxdNU_m5alc&pkey=',
recaptchaKey: '6LdNz5UlAAAAAPSrzydROuY76yGVIquVQAup69PO'
},
defaults: {
isCheckout: false,
Expand Down
Loading

0 comments on commit d5abc23

Please sign in to comment.