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

Updates hideCvv flag for edit payment methods and new payment methods. #1109

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,38 @@ import paymentMethodDisplay from 'common/components/paymentMethods/paymentMethod
import paymentMethodFormModal from 'common/components/paymentMethods/paymentMethodForm/paymentMethodForm.modal.component'
import coverFees from 'common/components/paymentMethods/coverFees/coverFees.component'

import * as cruPayments from '@cruglobal/cru-payments/dist/cru-payments'
import orderService from 'common/services/api/order.service'
import cartService from 'common/services/api/cart.service'
import { validPaymentMethod } from 'common/services/paymentHelpers/validPaymentMethods'
import giveModalWindowTemplate from 'common/templates/giveModalWindow.tpl.html'
import { SignInEvent } from 'common/services/session/session.service'

import creditCardCvv from '../../../../common/directives/creditCardCvv.directive'
import template from './existingPaymentMethods.tpl.html'

const componentName = 'checkoutExistingPaymentMethods'

class ExistingPaymentMethodsController {
/* @ngInject */
constructor ($log, $scope, orderService, cartService, $uibModal) {
constructor ($log, $scope, orderService, cartService, $uibModal, $window) {
this.$log = $log
this.$scope = $scope
this.orderService = orderService
this.cartService = cartService
this.$uibModal = $uibModal
this.paymentFormResolve = {}
this.validPaymentMethod = validPaymentMethod
this.sessionStorage = $window.sessionStorage

this.$scope.$on(SignInEvent, () => {
this.$onInit()
})
}

$onInit () {
this.enableContinue({ $event: false })
this.loadPaymentMethods()
this.waitForFormInitialization()
}

$onChanges (changes) {
Expand All @@ -52,6 +56,27 @@ class ExistingPaymentMethodsController {
}
}

waitForFormInitialization () {
const unregister = this.$scope.$watch('$ctrl.creditCardPaymentForm.securityCode', () => {
if (this.creditCardPaymentForm && this.creditCardPaymentForm.securityCode) {
unregister()
this.addCvvValidators()
this.switchPayment()
}
})
}

addCvvValidators () {
this.$scope.$watch('$ctrl.creditCardPaymentForm.securityCode.$viewValue', (number) => {
if (this.selectedPaymentMethod?.['card-type'] && this.creditCardPaymentForm.securityCode) {
this.creditCardPaymentForm.securityCode.$validators.minLength = cruPayments.creditCard.cvv.validate.minLength
this.creditCardPaymentForm.securityCode.$validators.maxLength = cruPayments.creditCard.cvv.validate.maxLength
this.enableContinue({ $event: cruPayments.creditCard.cvv.validate.minLength(number) && cruPayments.creditCard.cvv.validate.maxLength(number) })
this.selectedPaymentMethod.cvv = number
}
})
}

loadPaymentMethods () {
this.orderService.getExistingPaymentMethods()
.subscribe((data) => {
Expand Down Expand Up @@ -80,6 +105,7 @@ class ExistingPaymentMethodsController {
// Select the first payment method
this.selectedPaymentMethod = paymentMethods[0]
}
this.shouldRecoverCvv = true
this.switchPayment()
}

Expand Down Expand Up @@ -130,6 +156,13 @@ class ExistingPaymentMethodsController {

switchPayment () {
this.onPaymentChange({ selectedPaymentMethod: this.selectedPaymentMethod })
if (this.selectedPaymentMethod?.['card-type'] && this.creditCardPaymentForm?.securityCode) {
// Set cvv from session storage
const storage = this.shouldRecoverCvv ? JSON.parse(this.sessionStorage.getItem('cvv')) : ''
this.creditCardPaymentForm.securityCode.$setViewValue(storage)
this.creditCardPaymentForm.securityCode.$render()
this.shouldRecoverCvv = false
}
if (this.selectedPaymentMethod?.['bank-name']) {
// This is an EFT payment method so we need to remove any fee coverage
this.orderService.storeCoverFeeDecision(false)
Expand All @@ -144,7 +177,8 @@ export default angular
paymentMethodFormModal.name,
coverFees.name,
orderService.name,
cartService.name
cartService.name,
creditCardCvv.name
])
.component(componentName, {
controller: ExistingPaymentMethodsController,
Expand All @@ -159,6 +193,7 @@ export default angular
brandedCheckoutItem: '<',
onPaymentFormStateChange: '&',
onPaymentChange: '&',
onLoad: '&'
onLoad: '&',
enableContinue: '&'
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Observable } from 'rxjs/Observable'
import 'rxjs/add/observable/of'
import 'rxjs/add/observable/throw'
import 'rxjs/add/operator/toPromise'
import * as cruPayments from '@cruglobal/cru-payments/dist/cru-payments'

import { SignInEvent } from 'common/services/session/session.service'

Expand All @@ -15,24 +16,43 @@ describe('checkout', () => {
beforeEach(angular.mock.module(module.name))
const self = {}

beforeEach(inject(($componentController, $timeout) => {
beforeEach(inject(($componentController, $timeout, $window) => {
self.$timeout = $timeout

self.controller = $componentController(module.name, {}, {
onLoad: jest.fn(),
onPaymentChange: jest.fn(),
enableContinue: jest.fn(),
onPaymentFormStateChange: jest.fn(),
cartData: { items: [] }
cartData: { items: [] },
creditCardPaymentForm: {
securityCode: {
$valid: true,
$validators: {
minLength: (value) => cruPayments.creditCard.cvv.validate.minLength(value),
maxLength: cruPayments.creditCard.cvv.validate.maxLength
},
$setViewValue: jest.fn(),
$render: jest.fn(),
}
},
selectedPaymentMethod: {
cvv: '',
'card-type': 'Visa'
}
})
self.$window = $window
self.$window.sessionStorage.clear()
}))


describe('$onInit', () => {
it('should call loadPaymentMethods', () => {
jest.spyOn(self.controller, 'loadPaymentMethods').mockImplementation(() => {})
jest.spyOn(self.controller, 'waitForFormInitialization').mockImplementation(() => {})
self.controller.$onInit()

expect(self.controller.loadPaymentMethods).toHaveBeenCalled()
expect(self.controller.waitForFormInitialization).toHaveBeenCalled()
})

it('should be called on sign in', () => {
Expand Down Expand Up @@ -329,6 +349,80 @@ describe('checkout', () => {
expect(self.controller.onPaymentChange).toHaveBeenCalledWith({ selectedPaymentMethod: undefined })
expect(self.controller.orderService.storeCoverFeeDecision).not.toHaveBeenCalled()
})

it('should reset securityCode viewValue', () => {
self.controller.switchPayment()

expect(self.controller.creditCardPaymentForm.securityCode.$setViewValue).toHaveBeenCalledWith('')
expect(self.controller.creditCardPaymentForm.securityCode.$render).toHaveBeenCalled()
})

it('should add securityCode viewValue from sessionStorage', () => {
self.$window.sessionStorage.setItem(
'cvv',
'456'
)
self.controller.shouldRecoverCvv = true
self.controller.switchPayment()

expect(self.controller.creditCardPaymentForm.securityCode.$setViewValue).toHaveBeenCalledWith(456)
expect(self.controller.creditCardPaymentForm.securityCode.$render).toHaveBeenCalled()
})
})

describe('addCvvValidators', () => {
it('should add a watch on the security code value', () => {
self.controller.creditCardPaymentForm = {
$valid: true,
$dirty: false,
securityCode: {
$viewValue: '123',
$validators: {}
}
}
self.controller.addCvvValidators()
wrandall22 marked this conversation as resolved.
Show resolved Hide resolved
expect(self.controller.$scope.$$watchers.length).toEqual(1)
expect(self.controller.$scope.$$watchers[0].exp).toEqual('$ctrl.creditCardPaymentForm.securityCode.$viewValue')
})

it('should add validator functions to creditCardPaymentForm.securityCode', () => {
jest.spyOn(self.controller, 'addCvvValidators')
self.controller.selectedPaymentMethod.self = {
type: 'cru.creditcards.named-credit-card',
uri: 'selected uri'
}
self.controller.waitForFormInitialization()
self.controller.$scope.$digest()

expect(self.controller.addCvvValidators).toHaveBeenCalled()
expect(Object.keys(self.controller.creditCardPaymentForm.securityCode.$validators).length).toEqual(2)
expect(typeof self.controller.creditCardPaymentForm.securityCode.$validators.minLength).toBe('function')
expect(typeof self.controller.creditCardPaymentForm.securityCode.$validators.maxLength).toBe('function')
})

it('should call enableContinue when cvv is valid', () => {
self.controller.creditCardPaymentForm.securityCode.$viewValue = '123'
self.controller.addCvvValidators()
self.controller.$scope.$apply()

expect(self.controller.enableContinue).toHaveBeenCalledWith({ $event: true })
})

it('should call enableContinue when cvv is too long', () => {
self.controller.creditCardPaymentForm.securityCode.$viewValue = '12345'
self.controller.addCvvValidators()
self.controller.$scope.$apply()

expect(self.controller.enableContinue).toHaveBeenCalledWith({ $event: false })
})

it('should call enableContinue when cvv is too short', () => {
self.controller.creditCardPaymentForm.securityCode.$viewValue = '1'
self.controller.addCvvValidators()
self.controller.$scope.$apply()

expect(self.controller.enableContinue).toHaveBeenCalledWith({ $event: false })
})
})
})
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
<div class="panel panel-default tab-toggle mb0">
<div class="panel panel-default tab-toggle mb0 existing-payment-method">
<div class="panel-title panel-heading">
<span translate>Your Payment Methods</span>
<i class="fas fa-lock u-floatRight mt--"></i>
</div>

<div class="panel-body">
<div class="radio radio-method" ng-repeat="paymentMethod in $ctrl.paymentMethods" ng-init="expired = !$ctrl.validPaymentMethod(paymentMethod)">
<label>
<input type="radio" name="paymentMethod" ng-model="$ctrl.selectedPaymentMethod" ng-value="paymentMethod" ng-disabled="expired" required ng-change="$ctrl.switchPayment()">
<payment-method-display payment-method="paymentMethod" expired="expired"></payment-method-display>
<button class="btn btn-xs btn-link" ng-click="$ctrl.openPaymentMethodFormModal(paymentMethod)" ng-if="paymentMethod['card-type']" translate>edit</button>
</label>
</div>
<form novalidate name="$ctrl.creditCardPaymentForm">
<div class="radio radio-method saved-payment-methods" ng-repeat="paymentMethod in $ctrl.paymentMethods" ng-init="expired = !$ctrl.validPaymentMethod(paymentMethod)">
<div class="row">
<label>
<input type="radio" name="paymentMethod" ng-model="$ctrl.selectedPaymentMethod" ng-value="paymentMethod" ng-disabled="expired" required ng-change="$ctrl.switchPayment()">
<payment-method-display payment-method="paymentMethod" expired="expired"></payment-method-display>
<button class="btn btn-xs btn-link" ng-click="$ctrl.openPaymentMethodFormModal(paymentMethod)" ng-if="paymentMethod['card-type']" translate>edit</button>
</label>
<span ng-if="$ctrl.selectedPaymentMethod['card-type'] && $ctrl.selectedPaymentMethod === paymentMethod">
<credit-card-cvv ></credit-card-cvv>
</span>
</div>
</div>
</form>

<div class="panel panel-default tab-toggle mb0 mt"
ng-if="(($ctrl.cartData && $ctrl.cartData.items) || $ctrl.brandedCheckoutItem) && $ctrl.selectedPaymentMethod && $ctrl.selectedPaymentMethod['card-type']">
Expand Down
12 changes: 11 additions & 1 deletion src/app/checkout/step-2/step-2.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class Step2Controller {

onPaymentFormStateChange ($event) {
this.paymentFormState = $event.state

if ($event.state === 'loading' && $event.payload) {
const paymentType = $event.payload.creditCard ? $event.payload.creditCard['card-type'] : $event.payload.bankAccount ? $event.payload.bankAccount['account-type'] : 'Unknown'
const request = $event.update
Expand Down Expand Up @@ -109,14 +110,19 @@ class Step2Controller {
this.changeStep({ newStep: 'review' })
this.onStateChange({ state: 'submitted' })
this.paymentFormState = 'success'
} else if ($event.state === 'submitted') {
this.orderService.storeCardSecurityCode(this.selectedPaymentMethod.cvv, this.selectedPaymentMethod.self.uri)
wrandall22 marked this conversation as resolved.
Show resolved Hide resolved
} else if ($event.state === 'unsubmitted') {
this.onStateChange({ state: 'unsubmitted' })
} else if ($event.state === 'error') {
this.onStateChange({ state: 'errorSubmitting' })
}
}

getContinueDisabled () {
isContinueDisabled () {
if (this.selectedPaymentMethod?.['card-type'] && !this.isCvvValid) {
wrandall22 marked this conversation as resolved.
Show resolved Hide resolved
return true
}
if (this.loadingPaymentMethods) {
return true
}
Expand All @@ -129,6 +135,10 @@ class Step2Controller {
}
return false
}

enableContinue (isCvvValid) {
this.isCvvValid = isCvvValid
wrandall22 marked this conversation as resolved.
Show resolved Hide resolved
}
}

export default angular
Expand Down
Loading
Loading