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

Sofort CKO #5163

Merged
merged 5 commits into from
Nov 16, 2020
Merged
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
14 changes: 6 additions & 8 deletions packages/checkout-com/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,19 +121,17 @@ 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`. Caution: PayPal and Sofort do 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.

```ts
interface PaymentMethods {
card?: boolean;
klarna?: boolean;
paypal?: boolean;
}

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

const initForm = (initMethods: PaymentMethods = null, config: PaymentMethodsConfig = {}): void
Expand Down Expand Up @@ -192,15 +190,15 @@ if (error.value) {
const order = await placeOrder();
```

12. `payment.data.redirect_url` contains 3DS Auth redirect url for Credit Card if it requires it and it always contain redirect url for the PayPal. You have to support it:
12. `payment.data.redirect_url` contains 3DS Auth redirect url for Credit Card if it requires it and it always contain redirect url for the PayPal and Sofort. You have to support it:
```js
if (payment.data.redirect_url) {
window.location.href = payment.data.redirect_url;
return;
}
```

13. After 3DS Auth/PayPal Auth, user will be redirected to one of these urls. They are being created inside `makePayment` method:
13. After 3DS Auth/PayPal Auth/Sofort Auth, user will be redirected to one of these urls. They are being created inside `makePayment` method:
```js
success_url: `${window.location.origin}/cko/payment-success`,
failure_url: `${window.location.origin}/cko/payment-error`
Expand Down Expand Up @@ -266,7 +264,8 @@ enum CkoPaymentType {
CREDIT_CARD = 1,
SAVED_CARD,
KLARNA, // Not supported yet
PAYPAL
PAYPAL,
SOFORT
}
```

Expand Down Expand Up @@ -311,13 +310,12 @@ const savePaymentInstrument = ref(loadSavePaymentInstrument());
```

## Autoloading SDK
Checkout.com supports 3 payment methods - Credit Card, Klarna & Paypal. By default, module fetches SDK only for Credit Card (Frames). You can customize it with module's config `paymentMethods` attribute. E.g:
Checkout.com supports many payment methods, only a few have own SDKs - Credit Card & Klarna. By default, module fetches SDK only for Credit Card (Frames). You can customize it with module's config `paymentMethods` attribute. E.g:
```js
['@vue-storefront/checkout-com/nuxt', {
// ...
paymentMethods: {
cc: true,
paypal: false,
klarna: true
}
}]
Expand Down
171 changes: 171 additions & 0 deletions packages/checkout-com/__tests__/useCkoSofort.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import useCkoSofort from '../src/useCkoSofort';
import { createContext, createPayment } from '../src/payment';
import { getCurrentPaymentMethodPayload, CkoPaymentType } from '../src/helpers';

const defaultPaymentResponse = {
status: 200
};
jest.mock('../src/payment', () => ({
createContext: jest.fn(() => Promise.resolve({
data: {
id: '12'
}
})),
createPayment: jest.fn(() => Promise.resolve(defaultPaymentResponse))
}));
jest.mock('../src/helpers', () => ({
getCurrentPaymentMethodPayload: jest.fn(),
CkoPaymentType: jest.requireActual('../src/helpers').CkoPaymentType
}));

const {
makePayment,
error
} = useCkoSofort();

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

beforeEach(() => {
jest.clearAllMocks();
});

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

/*eslint-disable */
const reference = {
cartId: 15,
email: 'ab@gmail.com',
contextDataId: 'provided-id',
success_url: null,
failure_url: null
};
/* eslint-enable */

await makePayment(reference);

expect(createContext).not.toHaveBeenCalled();

});

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

/*eslint-disable */
const payload = {
cartId: 15,
email: 'ab@gmail.com',
success_url: null,
failure_url: null
};
/* eslint-enable */

const expectedPayload = {
reference: payload.cartId,
email: payload.email
};

await makePayment(payload);

expect(createContext).toHaveBeenCalledWith(expectedPayload);

});

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

/*eslint-disable */
const payload = {
cartId: 15,
contextDataId: 'abc',
email: 'ab@gmail.com',
success_url: null,
failure_url: null
};
/* eslint-enable */

const response = await makePayment(payload);

expect(createPayment).toHaveBeenCalled();
expect(response).toEqual(defaultPaymentResponse);

});

it('uses default values for success and failure url and reference', async () => {

/*eslint-disable */
const payload = {
cartId: 15,
contextDataId: 'abc',
email: 'ab@gmail.com',
reference: 'zyxxzxz'
};

const exptectedObject = {
context_id: payload.contextDataId,
success_url: `${window.location.origin}/cko/payment-success`,
failure_url: `${window.location.origin}/cko/payment-error`,
reference: 'zyxxzxz'
}
/* eslint-enable */

const response = await makePayment(payload);

expect(getCurrentPaymentMethodPayload).toHaveBeenCalledWith(CkoPaymentType.SOFORT, exptectedObject);
expect(response).toEqual(defaultPaymentResponse);

});

it('allows to set success and failure url', async () => {

/*eslint-disable */
const payload = {
cartId: 15,
contextDataId: 'abc',
email: 'ab@gmail.com',
success_url: 'aa',
failure_url: 'bb'
};

const expectedObject = {
context_id: payload.contextDataId,
success_url: payload.success_url,
failure_url: payload.failure_url,
reference: null
}
/* eslint-enable */

const response = await makePayment(payload);

expect(getCurrentPaymentMethodPayload).toHaveBeenCalledWith(CkoPaymentType.SOFORT, expectedObject);
expect(response).toEqual(defaultPaymentResponse);

});

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

/*eslint-disable */
const payload = {
cartId: 15,
contextDataId: 'abc',
email: 'ab@gmail.com',
success_url: null,
failure_url: null
};

const errorValue = 'Some error';

(createPayment as jest.Mock).mockImplementation(() => Promise.resolve({
status: 400,
data: {
error_type: errorValue
}
}))
/* eslint-enable */

const response = await makePayment(payload);

expect(createPayment).toHaveBeenCalled();
expect(error.value.message).toBe(errorValue);
expect(response).toBe(null);

});

});
1 change: 0 additions & 1 deletion packages/checkout-com/nuxt/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import proxyMiddleware from '@vue-storefront/checkout-com/nuxt/proxyMiddleware';

const defaultPaymentMethods = {
cc: true,
paypal: true,
klarna: false
};

Expand Down
7 changes: 6 additions & 1 deletion packages/checkout-com/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ enum CkoPaymentType {
CREDIT_CARD = 1,
SAVED_CARD,
KLARNA,
PAYPAL
PAYPAL,
SOFORT
}

const buildBasePaymentMethodPayload = ({ context_id, save_payment_instrument, secure3d, success_url, failure_url, cvv, reference }: PaymentPropeties) => ({
Expand Down Expand Up @@ -75,6 +76,10 @@ const buildPaymentPayloadStrategies = {
[CkoPaymentType.PAYPAL]: (properties: PaymentPropeties): PaymentMethodPayload => ({
...buildBasePaymentMethodPayload(properties),
type: 'paypal'
}),
[CkoPaymentType.SOFORT]: (properties: PaymentPropeties): PaymentMethodPayload => ({
...buildBasePaymentMethodPayload(properties),
type: 'sofort'
})
};

Expand Down
9 changes: 9 additions & 0 deletions packages/checkout-com/src/useCko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ref, computed } from '@vue/composition-api';
import { CkoPaymentType } from './helpers';
import useCkoCard from './useCkoCard';
import useCkoPaypal from './useCkoPaypal';
import useCkoSofort from './useCkoSofort';

const error = ref(null);
const availableMethods = ref([]);
Expand Down Expand Up @@ -53,6 +54,11 @@ const useCko = () => {
error: paypalError
} = useCkoPaypal();

const {
makePayment: makeSofortPayment,
error: sofortError
} = useCkoSofort();

const loadAvailableMethods = async (reference, email?) => {
try {
const response = await createContext({ reference, email });
Expand Down Expand Up @@ -117,6 +123,9 @@ const useCko = () => {
} else if (selectedPaymentMethod.value === CkoPaymentType.PAYPAL) {
finalizeTransactionFunction = makePaypalPayment;
localError = paypalError;
} else if (selectedPaymentMethod.value === CkoPaymentType.SOFORT) {
finalizeTransactionFunction = makeSofortPayment;
localError = sofortError;
} else {
error.value = new Error('Not supported payment method');
return;
Expand Down
49 changes: 49 additions & 0 deletions packages/checkout-com/src/useCkoSofort.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/* eslint-disable camelcase, @typescript-eslint/camelcase */

import { createContext, createPayment } from './payment';
import { ref } from '@vue/composition-api';
import { CkoPaymentType, getCurrentPaymentMethodPayload } from './helpers';

const error = ref(null);

const useCkoSofort = () => {
const makePayment = async ({
cartId,
email,
contextDataId = null,
success_url = null,
failure_url = null,
reference = null
}) => {
try {
let context;
if (!contextDataId) {
context = await createContext({ reference: cartId, email });
}

const payment = await createPayment(
getCurrentPaymentMethodPayload(CkoPaymentType.SOFORT, {
reference,
context_id: contextDataId || context.data.id,
success_url: success_url || `${window.location.origin}/cko/payment-success`,
failure_url: failure_url || `${window.location.origin}/cko/payment-error`
})
);

if (![200, 202].includes(payment.status)) {
throw new Error(payment.data.error_type);
}

return payment;
} catch (e) {
error.value = e;
return null;
}
};

return {
error,
makePayment
};
};
export default useCkoSofort;
3 changes: 3 additions & 0 deletions packages/core/docs/checkout-com/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## 0.0.9 (not released)
- Sofort support ([#5158](https://github.com/DivanteLtd/vue-storefront/issues/5158))

## 0.0.8

- Fixed proxied endpoints URLs ([#5117](https://github.com/DivanteLtd/vue-storefront/pull/5117))
Expand Down