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

Klarna CKO #5157

Closed
wants to merge 10 commits into from
Closed
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
44 changes: 32 additions & 12 deletions packages/checkout-com/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,14 @@ const { cart } = useCart();
const { setBillingDetails } = useCheckout();
const { isAuthenticated, user } = useUser();
const {
initForm,
loadAvailableMethods,
availableMethods,
submitDisabled,
storedPaymentInstruments,
loadStoredPaymentInstruments,
error
initForm,
loadAvailableMethods,
availableMethods,
submitDisabled,
storedPaymentInstruments,
loadStoredPaymentInstruments,
submitKlarnaForm,
error
} = useCko();
```

Expand Down Expand Up @@ -121,7 +122,11 @@ onMounted(async () => {
await loadAvailableMethods(cart.value.id, user.value && user.value.email);
})
```
5. Execute `initForm`. It mounts different payment handlers depends on arguments (check details below). If you are calling it after load component - **use `onMounted` to make sure DOM Element where it should be mounted already exists**. Card's Frames will be mounted in DOM element with class `card-frame`. Caution: PayPal does not need any SDK, we just redirect to their's website like in 3DS redirection process for credit cards. So if you are interested only in this payment method you could omit this step.
5. Execute `initForm`. It mounts different payment handlers depends on arguments (check details below). If you are calling it after load component - **use `onMounted` to make sure DOM Element where it should be mounted already exists**.

- Card's Frames will be mounted in DOM element with class `card-frame`.
- PayPal does not need any SDK, we just redirect to their's website like in 3DS redirection process for credit cards. So if you are interested only in this payment method you could omit this step.
- Klarna by default will be mounted in container with id `klarna_container`

```ts
interface PaymentMethods {
Expand All @@ -131,8 +136,8 @@ interface PaymentMethods {
}

interface PaymentMethodsConfig {
card?: Omit<Configuration, 'publicKey'>;
klarna?: any;
card?: CardConfiguration;
klarna?: KlarnaConfiguration;
paypal?: any;
}

Expand Down Expand Up @@ -161,7 +166,7 @@ Unfortunately, Checkout.com is not sharing any component for Saved Cards. After
`setPaymentInstrument` will set transaction token in your sessionStorage for a moment to make it work even after the refresh. Then it will set `selectedPaymentMethod` to `CkoPaymentType.SAVED_CARD`.

6. When `submitDisabled` changes to false - it means provided Card's data is proper and you could allow your user go forward. Card's token will be stored in sessionStorage for a moment.
7. Call `submitCardForm` function on card form submit (only for Credit Card method - not necessary for Stored Payment Method). It requires mounted `Frames` instance as it uses `Frames.submitCard()` under the hood.
7. Call `submitCardForm` function on card form submit (only for **Credit Card** method - not necessary for Stored Payment Method). It requires mounted `Frames` instance as it uses `Frames.submitCard()` under the hood. If you are using **Klarna** please call `submitKlarnaForm` (it returns promise) to authorize payment.
8. Then you need to make Payment
`error` - contains error message from the response if you do not use 3ds or we have some server related issues. If the user just removed stored token from sessionStorage it will have `There is no payment token` inside.
`makePayment` - it proceeds with the payment and removes card token afterward. Returns Promise<Payment> if succeed, or Promise<null> if failed.
Expand Down Expand Up @@ -265,7 +270,7 @@ enum CkoPaymentType {
NOT_SELECTED = 0,
CREDIT_CARD = 1,
SAVED_CARD,
KLARNA, // Not supported yet
KLARNA,
PAYPAL
}
```
Expand Down Expand Up @@ -363,6 +368,21 @@ interface CustomLocalization {
}
```

## Customizing Klarna
In `nuxt.config.js` and `initForm` method call you can configure Klarna component.
E.g:
```js
['@vue-storefront/checkout-com/nuxt', {
// ...
klarna: {
containerSelector: '#my-klarna-div',
mounted (response) {
console.log('Hello, I have just mounted klarna component!: ', response)
}
}
}]
```

## Fetching available payment methods
At first, you have to save billing address in your backend to do that. You can do it just after `setBillingDetails` call from `Creadit card component` step. Then you can easily use `loadAvailableMethods` method. It requires reference as the first argument - which is cartId. E.g:
```js
Expand Down
28 changes: 26 additions & 2 deletions packages/checkout-com/__tests__/configuration.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defaultConfig, setup, getPublicKey, getApiUrl, getFramesStyles, getFramesLocalization, getCkoProxyUrl, getTransactionTokenKey, getSaveInstrumentKey, getCurrentChannel, setChannel } from '../src/configuration';
import { defaultConfig, setup, getKlarnaOnMounted, getKlarnaContainerSelector, getPublicKey, getApiUrl, getFramesStyles, getFramesLocalization, getCkoProxyUrl, getTransactionTokenKey, getSaveInstrumentKey, getCurrentChannel, setChannel } from '../src/configuration';

const consoleLogMock = {
error: jest.fn()
Expand Down Expand Up @@ -29,6 +29,8 @@ describe('[checkout-com] configuration', () => {
expect(getFramesLocalization()).toEqual(defaultConfig.card.localization);
expect(getTransactionTokenKey()).toBe(defaultConfig.tokenizedCardKey);
expect(getCurrentChannel()).toBe(config.defaultChannel);
expect(getKlarnaOnMounted()).toBe(defaultConfig.klarna.mounted);
expect(getKlarnaContainerSelector()).toBe(defaultConfig.klarna.containerSelector);

});

Expand All @@ -43,6 +45,12 @@ describe('[checkout-com] configuration', () => {
style: {ab: '12'},
localization: 'en-US'
},
klarna: {
containerSelector: '#test',
mounted: () => {
return 1 + 2;
}
},
tokenizedCardKey: 'temporary-tokenized-value-key',
saveInstrumentKey: 'some-new-value'
}
Expand All @@ -58,6 +66,8 @@ describe('[checkout-com] configuration', () => {
expect(getFramesLocalization()).toEqual(config.channels.en.card.localization);
expect(getTransactionTokenKey()).toBe(config.channels.en.tokenizedCardKey);
expect(getSaveInstrumentKey()).toBe(config.channels.en.saveInstrumentKey);
expect(getKlarnaOnMounted()).toBe(config.channels.en.klarna.mounted);
expect(getKlarnaContainerSelector()).toBe(config.channels.en.klarna.containerSelector);
expect(getCurrentChannel()).toBe(config.defaultChannel);

});
Expand All @@ -79,6 +89,12 @@ describe('[checkout-com] configuration', () => {
style: {ab: '12'},
localization: 'en-US'
},
klarna: {
containerSelector: '#ab',
mounted: () => {
return 1 + 2;
}
},
tokenizedCardKey: 'temporary-tokenized-value-key',
saveInstrumentKey: 'some-new-value'
},
Expand All @@ -89,6 +105,12 @@ describe('[checkout-com] configuration', () => {
style: {asdas: '1552'},
localization: 'it-IT'
},
klarna: {
containerSelector: '#other',
mounted: () => {
return 1 + 2;
}
},
tokenizedCardKey: 'some-token-value-it',
saveInstrumentKey: 'some-new-value-it-ab'
}
Expand All @@ -106,6 +128,8 @@ describe('[checkout-com] configuration', () => {
expect(getFramesLocalization()).toEqual(config.channels.it.card.localization);
expect(getTransactionTokenKey()).toBe(config.channels.it.tokenizedCardKey);
expect(getSaveInstrumentKey()).toBe(config.channels.it.saveInstrumentKey);
expect(getKlarnaOnMounted()).toBe(config.channels.it.klarna.mounted);
expect(getKlarnaContainerSelector()).toBe(config.channels.it.klarna.containerSelector);
expect(getCurrentChannel()).toBe(newChannel);

});
Expand Down Expand Up @@ -133,7 +157,7 @@ describe('[checkout-com] configuration', () => {

});

it('prints error if not existing channel is default', () => {
it('prints error if not existing channel is picked', () => {

const config = {
channels: {
Expand Down
52 changes: 51 additions & 1 deletion packages/checkout-com/__tests__/helpers.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,57 @@
import { getCurrentPaymentMethodPayload, CkoPaymentType } from '../src/helpers';
import {
getCurrentPaymentMethodPayload,
CkoPaymentType,
getTransactionToken,
setTransactionToken,
removeTransactionToken
} from '../src/helpers';

const getItemValue = 'vxbcyoodgfdg';

const sessionStorageMock = {
removeItem: jest.fn(),
getItem: jest.fn(() => getItemValue),
setItem: jest.fn(),
clear: jest.fn(),
key: jest.fn(),
length: 1
};

Object.defineProperty(window, 'sessionStorage', {
value: sessionStorageMock
});

const transactionKey = 'adasdascxvbxcvjhdfgfhdfg';

jest.mock('../src/configuration.ts', () => ({
getTransactionTokenKey: jest.fn(() => transactionKey)
}));

import { getTransactionTokenKey } from '../src/configuration';

describe('[checkout-com] helpers', () => {

it('getTransactionToken', () => {
const value = getTransactionToken();

expect(value).toBe(getItemValue);
expect(getTransactionTokenKey).toHaveBeenCalled();
expect(sessionStorageMock.getItem).toHaveBeenCalledWith(transactionKey);
});

it('setTransactionToken', () => {
const value = 123;
setTransactionToken(value);

expect(sessionStorageMock.setItem).toHaveBeenCalledWith(transactionKey, value);
});

it('removeTransactionToken', () => {
removeTransactionToken();

expect(sessionStorageMock.removeItem).toHaveBeenCalledWith(transactionKey);
});

it('builds payment payload for credit card', () => {

/*eslint-disable */
Expand Down
42 changes: 16 additions & 26 deletions packages/checkout-com/__tests__/useCkoCard.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import useCkoCard from '../src/useCkoCard';
import { createContext, createPayment, getCustomerCards, removeSavedCard } from '../src/payment';
import { getCurrentPaymentMethodPayload, CkoPaymentType } from '../src/helpers';
import { getCurrentPaymentMethodPayload, getTransactionToken, removeTransactionToken, setTransactionToken, CkoPaymentType } from '../src/helpers';
import { getPublicKey, getFramesStyles } from '../src/configuration';
import { ref } from '@vue/composition-api';

Expand All @@ -19,6 +19,9 @@ jest.mock('../src/payment', () => ({
}));
jest.mock('../src/helpers', () => ({
getCurrentPaymentMethodPayload: jest.fn(),
setTransactionToken: jest.fn(),
removeTransactionToken: jest.fn(),
getTransactionToken: jest.fn(),
CkoPaymentType: jest.requireActual('../src/helpers').CkoPaymentType,
PaymentInstrument: jest.requireActual('../src/helpers').PaymentInstrument
}));
Expand All @@ -30,19 +33,6 @@ jest.mock('../src/configuration', () => ({
CardConfiguration: jest.requireActual('../src/configuration').CardConfiguration
}));

const sessionStorageMock = {
removeItem: jest.fn(),
getItem: jest.fn(),
setItem: jest.fn(),
clear: jest.fn(),
key: jest.fn(),
length: 1
};

Object.defineProperty(window, 'sessionStorage', {
value: sessionStorageMock
});

const framesMock = {
submitCard: jest.fn(),
init: jest.fn(),
Expand Down Expand Up @@ -79,7 +69,7 @@ describe('[checkout-com] useCkoCard', () => {

it('does not create context if provided', async () => {

sessionStorageMock.getItem.mockImplementation(() => 'abc');
(getTransactionToken as jest.Mock).mockImplementation(() => 'abc');
/*eslint-disable */
const reference = {
cartId: 15,
Expand All @@ -100,7 +90,7 @@ describe('[checkout-com] useCkoCard', () => {

it('creates context if not provided', async () => {

sessionStorageMock.getItem.mockImplementation(() => 'abc');
(getTransactionToken as jest.Mock).mockImplementation(() => 'abc');
/*eslint-disable */
const payload = {
cartId: 15,
Expand Down Expand Up @@ -131,7 +121,7 @@ describe('[checkout-com] useCkoCard', () => {
error
} = useCkoCard(paymentMethod);

sessionStorageMock.getItem.mockImplementation(() => 'abc');
(getTransactionToken as jest.Mock).mockImplementation(() => 'abc');

(createContext as jest.Mock).mockImplementation(() => Promise.resolve({
data: {
Expand Down Expand Up @@ -162,7 +152,7 @@ describe('[checkout-com] useCkoCard', () => {

it('calls createPayment & returns proper success response', async () => {

sessionStorageMock.getItem.mockImplementation(() => 'abc');
(getTransactionToken as jest.Mock).mockImplementation(() => 'abc');
/*eslint-disable */
const payload = {
cartId: 15,
Expand All @@ -184,7 +174,7 @@ describe('[checkout-com] useCkoCard', () => {

it('uses default values for success and failure url and save_payment_instrument', async () => {
const token = 'abc';
sessionStorageMock.getItem.mockImplementation(() => token);
(getTransactionToken as jest.Mock).mockImplementation(() => token);

/*eslint-disable */
const payload = {
Expand Down Expand Up @@ -217,7 +207,7 @@ describe('[checkout-com] useCkoCard', () => {
it('allows to set success and failure url and save_payment_instrument and reference', async () => {

const token = '123';
sessionStorageMock.getItem.mockImplementation(() => token);
(getTransactionToken as jest.Mock).mockImplementation(() => token);
/*eslint-disable */
const payload = {
cartId: 15,
Expand Down Expand Up @@ -251,7 +241,7 @@ describe('[checkout-com] useCkoCard', () => {

it('throws an error if receives diff. code than 200 and 202 from createPayment request', async () => {

sessionStorageMock.getItem.mockImplementation(() => 'abc');
(getTransactionToken as jest.Mock).mockImplementation(() => 'abc');
/*eslint-disable */
const payload = {
cartId: 15,
Expand Down Expand Up @@ -334,7 +324,7 @@ describe('[checkout-com] useCkoCard', () => {
expect(submitDisabled.value).toBeFalsy();

framesMock.init.mock.calls[0][0].cardTokenized({ token });
expect(sessionStorageMock.setItem).toHaveBeenCalledWith(undefined, token);
expect(setTransactionToken).toHaveBeenCalledWith(token);

framesMock.init.mock.calls[0][0].cardTokenizationFailed(errorMessage);
expect(submitDisabled.value).toBeTruthy();
Expand Down Expand Up @@ -419,7 +409,7 @@ describe('[checkout-com] useCkoCard', () => {
}));
await loadStoredPaymentInstruments(customerId)
const instrument = payment_instruments[1];
sessionStorageMock.getItem.mockImplementation(() => 'targetly-bad-value')
(getTransactionToken as jest.Mock).mockImplementation(() => 'targetly-bad-value')
const paymentInstrumentsWithoutRemoved = payment_instruments.filter(ins => ins.payment_instrument_id != instrument.payment_instrument_id);
/* eslint-enable */

Expand Down Expand Up @@ -451,7 +441,7 @@ describe('[checkout-com] useCkoCard', () => {
(removeSavedCard as jest.Mock).mockImplementation(() => Promise.resolve());
/*eslint-disable */
const instrument = payment_instruments[1];
sessionStorageMock.getItem.mockImplementation(() => instrument.id)
(getTransactionToken as jest.Mock).mockImplementation(() => instrument.id)
const paymentInstrumentsWithoutRemoved = payment_instruments.filter(ins => ins.payment_instrument_id !== instrument.payment_instrument_id);

await removePaymentInstrument(customerId, instrument.payment_instrument_id)
Expand All @@ -463,7 +453,7 @@ describe('[checkout-com] useCkoCard', () => {
/* eslint-enable */
expect(storedPaymentInstruments.value).toEqual(paymentInstrumentsWithoutRemoved);
expect(selectedCardPaymentMethod.value).toBe(CkoPaymentType.CREDIT_CARD);
expect(sessionStorageMock.removeItem).toHaveBeenCalled();
expect(removeTransactionToken).toHaveBeenCalled();
});

it('sets error if remove stored payment instruments fails', async () => {
Expand All @@ -483,7 +473,7 @@ describe('[checkout-com] useCkoCard', () => {
const token = '12345';
setPaymentInstrument(token);

expect(sessionStorageMock.setItem).toHaveBeenCalledWith(undefined, token);
expect(setTransactionToken).toHaveBeenCalledWith(token);
expect(selectedCardPaymentMethod.value).toBe(CkoPaymentType.SAVED_CARD);
});

Expand Down
Loading