Skip to content

Commit 6cf6657

Browse files
committed
feat: add complete order endpoint
1 parent 065c49c commit 6cf6657

File tree

8 files changed

+150
-32
lines changed

8 files changed

+150
-32
lines changed

src/pages/api/checkout.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import { PAYMENT_TYPE } from "../../core/constants";
2-
import { getById } from "../../services/quote";
3-
import initiateLatitudePurchase from "../../services/latitude/initiatePurchase";
2+
import * as quoteService from "../../services/quote";
3+
import * as latitudeService from "../../services/latitude";
44

55
const handlePost = async (req, res) => {
66
const { quoteId, paymentType } = req?.body || {};
7-
const quote = getById(quoteId);
7+
const quote = quoteService.getById(quoteId);
88

99
switch (paymentType) {
1010
case PAYMENT_TYPE.LATITUDE_INTEREST_FREE:
11-
const { result, redirectUrl } = await initiateLatitudePurchase(quote);
12-
res.status(200).json({ result, redirectUrl })
11+
const { result, redirectUrl } = await latitudeService.initiatePurchase(
12+
quote
13+
);
14+
res.status(200).json({ result, redirectUrl });
1315
break;
1416

1517
// Other payment methods here

src/pages/api/latitude/complete.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import * as quoteService from "../../../services/quote";
2+
import * as latitudeService from "../../../services/latitude";
3+
4+
const handlePost = async (req, res) => {
5+
const { merchantReference, transactionReference, gatewayReference } =
6+
req?.body || {};
7+
8+
if (!merchantReference || !transactionReference || !gatewayReference) {
9+
res.status(400).end("Missing required params");
10+
break;
11+
}
12+
13+
// verify whether quote exists in your DB
14+
const quote = quoteService.getById(merchantReference);
15+
16+
if (!quote.id) {
17+
res.status(400).end(`Quote ${merchantReference} not found`);
18+
break;
19+
}
20+
21+
const { result } = await latitudeService.verifyPurchase({
22+
merchantReference,
23+
transactionReference,
24+
gatewayReference,
25+
});
26+
27+
// mark order as paid when result = "completed"
28+
if (result === "completed") {
29+
const { orderId } = quoteService.markAsPaid({
30+
quoteId: merchantReference,
31+
transactionReference,
32+
gatewayReference,
33+
});
34+
35+
res.status(200).json({ orderId });
36+
return;
37+
}
38+
39+
// handle result = "failed" and other exceptions
40+
res.status(400).end(`Could not complete quote ${merchantReference}`);
41+
};
42+
43+
const handleDefault = (req, res) => {
44+
const { method } = req;
45+
res.setHeader("Allow", Object.keys(handlers));
46+
res.status(405).end(`Method ${method} not allowed`);
47+
};
48+
49+
const handlers = {
50+
POST: handlePost,
51+
default: handleDefault,
52+
};
53+
54+
export default (req, res) =>
55+
(handlers[req.method] || handlers.default)(req, res);

src/services/latitude/common.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export const MERCHANT_ID = process.env.LATITUDE_MERCHANT_ID;
2+
export const IS_PRODUCTION = process.env.IS_PRODUCTION;
3+
4+
export const AXIOS_CONFIG = {
5+
// axios auto-generates authorisation header with auth.username and auth.password
6+
auth: {
7+
username: MERCHANT_ID,
8+
password: process.env.LATITUDE_MERCHANT_SECRET,
9+
},
10+
headers: {
11+
"Content-Type": "application/json",
12+
},
13+
};
14+
15+
export const ENDPOINTS = {
16+
PURCHASE: `${API_BASE_URL}/purchase`,
17+
VERIFY_PURCHASE: `${API_BASE_URL}/purchase/verify`,
18+
};

src/services/latitude/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./initiatePurchase";
2+
export * from "./verifyPurchase";

src/services/latitude/initiatePurchase.js

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,24 @@ import axios from "axios";
22

33
import prepareRequest from "./prepareRequest";
44

5-
const API_BASE_URL = process.env.LATITUDE_API_BASE_URL;
6-
const MERCHANT_ID = process.env.LATITUDE_MERCHANT_ID;
7-
const MERCHANT_SECRET = process.env.LATITUDE_MERCHANT_SECRET;
5+
import { AXIOS_CONFIG, ENDPOINTS } from "./common";
6+
7+
const ERROR_RESPONSE = {
8+
result: "failed",
9+
redirectUrl: "/error",
10+
};
811

912
/**
1013
* Initiates request with Latitude Interest free
1114
* @param {object} quote
1215
*/
13-
const initiatePurchase = async (quote) => {
16+
export const initiatePurchase = async (quote) => {
1417
try {
1518
const data = prepareRequest(quote);
16-
17-
const response = await axios.post(`${API_BASE_URL}/purchase`, data, {
18-
// axios auto-generates authorisation header with auth.username and auth.password
19-
auth: {
20-
username: MERCHANT_ID,
21-
password: MERCHANT_SECRET,
22-
},
23-
headers: {
24-
"Content-Type": "application/json",
25-
},
26-
});
19+
const response = await axios.post(ENDPOINTS.PURCHASE, data, AXIOS_CONFIG);
2720

2821
return response?.data;
2922
} catch (err) {
30-
return {
31-
result: "failed",
32-
redirectUrl: "/error",
33-
};
23+
return ERROR_RESPONSE;
3424
}
3525
};
36-
37-
export default initiatePurchase;

src/services/latitude/prepareRequest.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
const MERCHANT_ID = process.env.LATITUDE_MERCHANT_ID;
2-
const IS_PRODUCTION = process.env.IS_PRODUCTION;
1+
import { MERCHANT_ID, IS_PRODUCTION } from "./common";
32

4-
const prepareRequest = (quote) => ({
3+
export default (quote) => ({
54
merchantId: MERCHANT_ID,
65
merchantName: "example-store-javascript",
76
isTest: !Boolean(IS_PRODUCTION),
@@ -52,8 +51,6 @@ const prepareRequest = (quote) => ({
5251
totalShippingAmount: quote.shipping.amount,
5352
totalTaxAmount: quote.tax.amount,
5453
totalDiscountAmount: quote.discount.amount,
55-
platformType: "custom",
54+
platformType: "direct",
5655
platformVersion: "0.0.1",
5756
});
58-
59-
export default prepareRequest;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import axios from "axios";
2+
import { AXIOS_CONFIG, ENDPOINTS } from "./common";
3+
4+
const ERROR_RESPONSE = {
5+
result: "failed",
6+
};
7+
8+
/**
9+
* Verifies purchase request with Latitude Interest free
10+
* @param {string} merchantReference
11+
* @param {string} transactionRefeference
12+
* @param {string} gatewayReference
13+
*/
14+
export const verifyPurchase = async ({
15+
merchantReference = "",
16+
transactionRefeference = "",
17+
gatewayReference = "",
18+
}) => {
19+
try {
20+
const data = {
21+
merchantReference,
22+
transactionRefeference,
23+
gatewayReference,
24+
};
25+
26+
const response = await axios.get(
27+
ENDPOINTS.VERIFY_PURCHASE,
28+
data,
29+
AXIOS_CONFIG
30+
);
31+
32+
return response?.data;
33+
} catch (err) {
34+
return ERROR_RESPONSE;
35+
}
36+
};

src/services/quote/index.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,23 @@ import mockQuote from "../../__mocks__/quote.json";
66
* @param {string} quoteId
77
*/
88
export const getById = (quoteId) => mockQuote;
9+
10+
/**
11+
* Currently returns a mock order id.
12+
* Ideally this function is expected to
13+
* - change status to "Paid" (or similar)
14+
* - store transactionReference gatewayReference in DB for future reference
15+
* @param {string} quoteId
16+
* @param {string} transactionReference
17+
* @param {string} gatewayReference
18+
*/
19+
export const markAsPaid = ({
20+
quoteId,
21+
transactionReference,
22+
gatewayReference,
23+
}) => ({
24+
orderId: 30440,
25+
quoteId,
26+
transactionReference,
27+
gatewayReference,
28+
});

0 commit comments

Comments
 (0)