Skip to content

Commit

Permalink
feat(core): Allow payment handler to reject settlement
Browse files Browse the repository at this point in the history
Relates to #117
  • Loading branch information
michaelbromley committed Jun 24, 2019
1 parent f2b9a12 commit 4cbae46
Show file tree
Hide file tree
Showing 5 changed files with 309 additions and 44 deletions.
207 changes: 200 additions & 7 deletions packages/core/e2e/order.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,29 @@
import gql from 'graphql-tag';
import path from 'path';

import { ID } from '../../common/lib/shared-types';
import { PaymentMethodHandler } from '../src/config/payment-method/payment-method-handler';

import { TEST_SETUP_TIMEOUT_MS } from './config/test-config';
import { ORDER_FRAGMENT, ORDER_WITH_LINES_FRAGMENT } from './graphql/fragments';
import { GetCustomerList, GetOrder, GetOrderList } from './graphql/generated-e2e-admin-types';
import { AddItemToOrder } from './graphql/generated-e2e-shop-types';
import { GetCustomerList, GetOrder, GetOrderList, OrderFragment, SettlePayment } from './graphql/generated-e2e-admin-types';
import {
AddItemToOrder,
AddPaymentToOrder,
GetShippingMethods,
SetShippingAddress,
SetShippingMethod,
TransitionToState,
} from './graphql/generated-e2e-shop-types';
import { GET_CUSTOMER_LIST } from './graphql/shared-definitions';
import { ADD_ITEM_TO_ORDER } from './graphql/shop-definitions';
import {
ADD_ITEM_TO_ORDER,
ADD_PAYMENT,
GET_ELIGIBLE_SHIPPING_METHODS,
SET_SHIPPING_ADDRESS,
SET_SHIPPING_METHOD,
TRANSITION_TO_STATE,
} from './graphql/shop-definitions';
import { TestAdminClient, TestShopClient } from './test-client';
import { TestServer } from './test-server';

Expand All @@ -16,13 +33,24 @@ describe('Orders resolver', () => {
const shopClient = new TestShopClient();
const server = new TestServer();
let customers: GetCustomerList.Items[];
let orders: OrderFragment[];
const password = 'test';

beforeAll(async () => {
const token = await server.init({
productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
customerCount: 2,
});
const token = await server.init(
{
productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
customerCount: 2,
},
{
paymentOptions: {
paymentMethodHandlers: [
twoStagePaymentMethod,
failsToSettlePaymentMethod,
],
},
},
);
await adminClient.init();

// Create a couple of orders to be queried
Expand Down Expand Up @@ -54,14 +82,169 @@ describe('Orders resolver', () => {
it('orders', async () => {
const result = await adminClient.query<GetOrderList.Query>(GET_ORDERS_LIST);
expect(result.orders.items.map(o => o.id)).toEqual(['T_1', 'T_2']);
orders = result.orders.items;
});

it('order', async () => {
const result = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, { id: 'T_2' });
expect(result.order!.id).toBe('T_2');
});

describe('payments', () => {

it('settlePayment fails', async () => {
await shopClient.asUserWithCredentials(customers[0].emailAddress, password);
await proceedToArrangingPayment(shopClient);

const { addPaymentToOrder } = await shopClient.query<
AddPaymentToOrder.Mutation,
AddPaymentToOrder.Variables
>(ADD_PAYMENT, {
input: {
method: failsToSettlePaymentMethod.code,
metadata: {
baz: 'quux',
},
},
});
const order = addPaymentToOrder!;

expect(order.state).toBe('PaymentAuthorized');

const payment = order.payments![0];
const { settlePayment } = await adminClient.query<SettlePayment.Mutation, SettlePayment.Variables>(SETTLE_PAYMENT, {
id: payment.id,
});

expect(settlePayment!.id).toBe(payment.id);
expect(settlePayment!.state).toBe('Authorized');

const result = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, { id: order.id });

expect(result.order!.state).toBe('PaymentAuthorized');
});

it('settlePayment succeeds', async () => {
await shopClient.asUserWithCredentials(customers[1].emailAddress, password);
await proceedToArrangingPayment(shopClient);

const { addPaymentToOrder } = await shopClient.query<
AddPaymentToOrder.Mutation,
AddPaymentToOrder.Variables
>(ADD_PAYMENT, {
input: {
method: twoStagePaymentMethod.code,
metadata: {
baz: 'quux',
},
},
});
const order = addPaymentToOrder!;

expect(order.state).toBe('PaymentAuthorized');

const payment = order.payments![0];
const { settlePayment } = await adminClient.query<SettlePayment.Mutation, SettlePayment.Variables>(SETTLE_PAYMENT, {
id: payment.id,
});

expect(settlePayment!.id).toBe(payment.id);
expect(settlePayment!.state).toBe('Settled');
// further metadata is combined into existing object
expect(settlePayment!.metadata).toEqual({
baz: 'quux',
moreData: 42,
});

const result = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, { id: order.id });

expect(result.order!.state).toBe('PaymentSettled');
expect(result.order!.payments![0].state).toBe('Settled');
});
});
});

/**
* A two-stage (authorize, capture) payment method.
*/
const twoStagePaymentMethod = new PaymentMethodHandler({
code: 'authorize-only-payment-method',
description: 'Test Payment Method',
args: {},
createPayment: (order, args, metadata) => {
return {
amount: order.total,
state: 'Authorized',
transactionId: '12345',
metadata,
};
},
settlePayment: () => {
return {
success: true,
metadata: {
moreData: 42,
},
};
},
});

/**
* A payment method where calling `settlePayment` always fails.
*/
const failsToSettlePaymentMethod = new PaymentMethodHandler({
code: 'fails-to-settle-payment-method',
description: 'Test Payment Method',
args: {},
createPayment: (order, args, metadata) => {
return {
amount: order.total,
state: 'Authorized',
transactionId: '12345',
metadata,
};
},
settlePayment: () => {
return {
success: false,
errorMessage: 'Something went horribly wrong',
};
},
});

async function proceedToArrangingPayment(shopClient: TestShopClient): Promise<ID> {
await shopClient.query<SetShippingAddress.Mutation, SetShippingAddress.Variables>(
SET_SHIPPING_ADDRESS,
{
input: {
fullName: 'name',
streetLine1: '12 the street',
city: 'foo',
postalCode: '123456',
countryCode: 'US',
},
},
);

const { eligibleShippingMethods } = await shopClient.query<GetShippingMethods.Query>(
GET_ELIGIBLE_SHIPPING_METHODS,
);

await shopClient.query<SetShippingMethod.Mutation, SetShippingMethod.Variables>(
SET_SHIPPING_METHOD,
{
id: eligibleShippingMethods[1].id,
},
);

const { transitionOrderToState } = await shopClient.query<
TransitionToState.Mutation,
TransitionToState.Variables
>(TRANSITION_TO_STATE, { state: 'ArrangingPayment' });

return transitionOrderToState!.id;
}

export const GET_ORDERS_LIST = gql`
query GetOrderList($options: OrderListOptions) {
orders(options: $options) {
Expand All @@ -82,3 +265,13 @@ export const GET_ORDER = gql`
}
${ORDER_WITH_LINES_FRAGMENT}
`;

export const SETTLE_PAYMENT = gql`
mutation SettlePayment($id: ID!) {
settlePayment(id: $id) {
id
state
metadata
}
}
`;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ConfigArgType } from '@vendure/common/lib/generated-types';

import { PaymentConfig, PaymentMethodHandler } from './payment-method-handler';
import { CreatePaymentResult, PaymentMethodHandler } from './payment-method-handler';

/**
* A dummy API to simulate an SDK provided by a popular payments service.
Expand All @@ -14,6 +14,9 @@ const gripeSDK = {
.substr(3),
});
},
capture: (transactionId: string) => {
return true;
},
},
};

Expand All @@ -25,9 +28,10 @@ export const examplePaymentHandler = new PaymentMethodHandler({
code: 'example-payment-provider',
description: 'Example Payment Provider',
args: {
automaticCapture: ConfigArgType.BOOLEAN,
apiKey: ConfigArgType.STRING,
},
createPayment: async (order, args, metadata): Promise<PaymentConfig> => {
createPayment: async (order, args, metadata): Promise<CreatePaymentResult> => {
try {
const result = await gripeSDK.charges.create({
apiKey: args.apiKey,
Expand All @@ -36,11 +40,9 @@ export const examplePaymentHandler = new PaymentMethodHandler({
});
return {
amount: order.total,
state: 'Settled' as 'Settled',
state: args.automaticCapture ? 'Settled' : 'Authorized',
transactionId: result.id.toString(),
metadata: {
sampleMetadata: 'some arbitrary values',
},
metadata,
};
} catch (err) {
return {
Expand All @@ -52,4 +54,13 @@ export const examplePaymentHandler = new PaymentMethodHandler({
};
}
},
settlePayment: async (order, payment, args) => {
const result = await gripeSDK.charges.capture(payment.transactionId);
return {
success: result,
metadata: {
captureId: '1234567',
},
};
},
});
Loading

0 comments on commit 4cbae46

Please sign in to comment.