Skip to content

Commit

Permalink
feat: support for Cybersource credit card payment (#464)
Browse files Browse the repository at this point in the history
  • Loading branch information
harshrsys authored Jan 29, 2021
1 parent ae000ee commit e953874
Show file tree
Hide file tree
Showing 7 changed files with 475 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { PaymentConcardisCreditcardCvcDetailComponent } from './payment-concardi
import { PaymentConcardisCreditcardComponent } from './payment-concardis-creditcard/payment-concardis-creditcard.component';
import { PaymentConcardisDirectdebitComponent } from './payment-concardis-directdebit/payment-concardis-directdebit.component';
import { PaymentConcardisComponent } from './payment-concardis/payment-concardis.component';
import { PaymentCybersourceCreditcardComponent } from './payment-cybersource-creditcard/payment-cybersource-creditcard.component';

@NgModule({
imports: [SharedModule],
Expand All @@ -18,6 +19,7 @@ import { PaymentConcardisComponent } from './payment-concardis/payment-concardis
PaymentConcardisCreditcardComponent,
PaymentConcardisCreditcardCvcDetailComponent,
PaymentConcardisDirectdebitComponent,
PaymentCybersourceCreditcardComponent,
],
})
export class CheckoutPaymentPageModule {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,15 @@ <h3>{{ 'checkout.payment.method.select.heading' | translate }}</h3>
></ish-payment-concardis-directdebit>
</ng-template>

<ng-template [ngSwitchCase]="'CyberSource_CreditCard'">
<ish-payment-cybersource-creditcard
[paymentMethod]="paymentMethod"
[activated]="formIsOpen(i)"
(submit)="createNewPaymentInstrument($event)"
(cancel)="cancelNewPaymentInstrument()"
></ish-payment-cybersource-creditcard>
</ng-template>

<ng-template ngSwitchDefault>
<formly-form
[form]="parameterForm"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { CheckboxComponent } from 'ish-shared/forms/components/checkbox/checkbox
import { PaymentConcardisCreditcardCvcDetailComponent } from '../payment-concardis-creditcard-cvc-detail/payment-concardis-creditcard-cvc-detail.component';
import { PaymentConcardisCreditcardComponent } from '../payment-concardis-creditcard/payment-concardis-creditcard.component';
import { PaymentConcardisDirectdebitComponent } from '../payment-concardis-directdebit/payment-concardis-directdebit.component';
import { PaymentCybersourceCreditcardComponent } from '../payment-cybersource-creditcard/payment-cybersource-creditcard.component';

import { CheckoutPaymentComponent } from './checkout-payment.component';

Expand Down Expand Up @@ -51,6 +52,7 @@ describe('Checkout Payment Component', () => {
MockComponent(PaymentConcardisCreditcardComponent),
MockComponent(PaymentConcardisCreditcardCvcDetailComponent),
MockComponent(PaymentConcardisDirectdebitComponent),
MockComponent(PaymentCybersourceCreditcardComponent),
MockDirective(ServerHtmlDirective),
MockPipe(PricePipe),
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<!-- error messages -->
<p *ngIf="errorMessage.general.message" class="text-danger col-sm-offset-4">
{{ errorMessage.general.message | translate }}
</p>

<div [formGroup]="cyberSourceCreditCardForm">
<!-- Containers in which we will load microform -->
<div class="row form-group" [ngClass]="{ 'has-error': errorMessage?.number.message }">
<label class="col-form-label col-md-4" for="number-container"
>{{ 'checkout.credit_card.number.label' | translate }}<span class="required">*</span></label
>
<div class="col-sm-6">
<div id="number-container" class="iframe-container"></div>
<small *ngIf="errorMessage.number?.message" class="form-text">{{
errorMessage.number.message | translate
}}</small>
</div>
</div>

<!-- Expiry date mm-yyyy -->
<div
class="row form-group has-feedback"
[ishShowFormFeedback]="[
cyberSourceCreditCardForm.controls.expirationMonth,
cyberSourceCreditCardForm.controls.expirationYear
]"
>
<label for="month" class="col-form-label col-md-4"
>{{ 'checkout.credit_card.expiration_date.label' | translate }}<span class="required">*</span></label
>
<div class="col-sm-6">
<div class="clearfix row">
<div class="col-6">
<select class="form-control" id="month" formControlName="expirationMonth" data-testing-id="expirationMonth">
<option value="">{{ 'account.date.month' | translate }}</option>
<option *ngFor="let option of monthOptions" [value]="option.value">{{ option.label | translate }}</option>
</select>
<ish-form-control-feedback
[messages]="{ required: 'account.date.month.error.required' }"
[control]="cyberSourceCreditCardForm.controls.expirationMonth"
></ish-form-control-feedback>
</div>
<div class="col-6">
<select class="form-control" id="year" formControlName="expirationYear" data-testing-id="expirationYear">
<option value="">{{ 'account.date.year' | translate }}</option>
<option *ngFor="let option of yearOptions" [value]="option.value">{{ option.label | translate }}</option>
</select>
<ish-form-control-feedback
[messages]="{ required: 'account.date.year.error.required' }"
[control]="cyberSourceCreditCardForm.controls.expirationYear"
></ish-form-control-feedback>
</div>
</div>
</div>
</div>

<!-- security code CVC -->
<div class="row form-group" [ngClass]="{ 'has-error': errorMessage.securityCode?.message }">
<label class="col-form-label col-md-4" for="securityCode-container"
>{{ 'checkout.credit_card.cvc.label' | translate }}<span class="required">*</span></label
>
<div class="col-sm-6">
<div id="securityCode-container" class="iframe-container"></div>
<small *ngIf="errorMessage.securityCode?.message" class="form-text">{{
errorMessage.securityCode.message | translate
}}</small>
</div>
<div class="pull-left">
<ng-template #CVCHelp>
<span [innerHTML]="'checkout.credit_card.cvc.popover.content' | translate"></span>
</ng-template>
<a [ngbPopover]="CVCHelp" [popoverTitle]="'checkout.credit_card.cvc.popover.title' | translate" placement="auto"
><fa-icon [icon]="['fas', 'question-circle']"></fa-icon>
</a>
</div>
</div>
</div>

<div class="offset-md-4 col-md-8">
<div class="form-group">
<input
type="button"
(click)="submitNewPaymentInstrument()"
class="btn btn-primary"
value="{{ 'checkout.account.submit.button.label' | translate }}"
/>
<button type="button" class="btn btn-secondary" (click)="cancelNewPaymentInstrument()">
{{ 'checkout.cancel.button.label' | translate }}
</button>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.flex-microform {
box-sizing: border-box;
display: block;
width: 100%;
height: 34px;
padding: 6px 12px;
margin: 0;
font-family: 'robotoregular', 'Helvetica', 'Arial', sans-serif;
font-size: 14px;
font-style: normal;
font-weight: 400;
font-variant: normal;
font-size-adjust: none;
font-stretch: 100%;
line-height: 20px;
color: #555;
background-color: #fff;
background-image: none;
border: 1px solid #ccc;
border-radius: 2px;
box-shadow: rgba(0, 0, 0, 0.075) 0 1px 1px 0 inset;
transition: border-color, box-shadow, 0s, 0s, ease-in-out, ease-in-out, 0.15s, 0.15s;
}

.flex-microform-valid.flex-microform-focused {
border-color: #20b5b5;
box-shadow: 0 0 8px 0 rgba(102, 175, 233, 0.6);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core';
import b64u from 'b64u';
import { MockComponent, MockDirective } from 'ng-mocks';
import { anything, spy, verify } from 'ts-mockito';

import { PaymentMethod } from 'ish-core/models/payment-method/payment-method.model';
import { FormControlFeedbackComponent } from 'ish-shared/forms/components/form-control-feedback/form-control-feedback.component';
import { ShowFormFeedbackDirective } from 'ish-shared/forms/directives/show-form-feedback.directive';

import { PaymentCybersourceCreditcardComponent } from './payment-cybersource-creditcard.component';

describe('Payment Cybersource Creditcard Component', () => {
let component: PaymentCybersourceCreditcardComponent;
let fixture: ComponentFixture<PaymentCybersourceCreditcardComponent>;
let element: HTMLElement;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
MockComponent(FaIconComponent),
MockComponent(FormControlFeedbackComponent),
MockComponent(NgbPopover),
MockDirective(ShowFormFeedbackDirective),
PaymentCybersourceCreditcardComponent,
],
imports: [ReactiveFormsModule, TranslateModule.forRoot()],
}).compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(PaymentCybersourceCreditcardComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;

component.paymentMethod = {
id: 'CyberSource_CreditCard',
saveAllowed: false,
} as PaymentMethod;
});

it('should be created', () => {
expect(component).toBeTruthy();
expect(element).toBeTruthy();
expect(() => fixture.detectChanges()).not.toThrow();
});

it('should emit cancel event when cancelNewPaymentInstrument is triggered', () => {
fixture.detectChanges();

const emitter = spy(component.cancel);

component.cancelNewPaymentInstrument();
verify(emitter.emit()).once();
});

it('should emit submit event if submit call back returns with no error and parameter form is valid', () => {
fixture.detectChanges();
const emitter = spy(component.submit);

const payloadjson = {
data: { number: '4111 1111 1111 1111', type: '001', expirationMonth: '11', expirationYear: '2022' },
iss: 'test',
exp: '123423',
iat: 'test',
jti: 'test',
};
const payload = b64u.encode(JSON.stringify(payloadjson));

component.cyberSourceCreditCardForm.controls.expirationMonth.setValue('11');
component.cyberSourceCreditCardForm.controls.expirationYear.setValue('2022');
component.expirationMonthVal = '11';
component.expirationYearVal = '2022';
component.submitCallback(undefined, 'header.' + `${payload}` + '.signature');

verify(emitter.emit(anything())).once();
});

it('should not emit submit event if submit call back returns with no error and parameter form is invalid', () => {
fixture.detectChanges();
const emitter = spy(component.submit);

const payloadjson = {
data: { number: '4111 1111 1111 1111', type: '001', expirationMonth: '11', expirationYear: '2022' },
iss: 'test',
exp: '123423',
iat: 'test',
jti: 'test',
};
const payload = b64u.encode(JSON.stringify(payloadjson));

component.expirationMonthVal = '11';
component.expirationYearVal = '2022';
component.submitCallback(undefined, 'header.' + `${payload}` + '.signature');

verify(emitter.emit(anything())).never();
});

it('should show an error if submit call back returns with an error', () => {
fixture.detectChanges();
component.submitCallback({ details: [{ location: 'number', message: 'error in card number' }] }, undefined);

expect(component.errorMessage.number.message).toEqual('checkout.credit_card.number.error.invalid');
});
});
Loading

0 comments on commit e953874

Please sign in to comment.