-
Notifications
You must be signed in to change notification settings - Fork 134
/
Copy pathserver.js
156 lines (136 loc) · 4.92 KB
/
server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// micro provides http helpers
const { createError, json, send } = require('micro');
// microrouter provides http server routing
const { router, get, post } = require('microrouter');
// serve-handler serves static assets
const staticHandler = require('serve-handler');
// async-retry will retry failed API requests
const retry = require('async-retry');
// logger gives us insight into what's happening
const logger = require('./server/logger');
// schema validates incoming requests
const {
validatePaymentPayload,
validateCreateCardPayload,
} = require('./server/schema');
// square provides the API client and error types
const { ApiError, client: square } = require('./server/square');
async function createPayment(req, res) {
const payload = await json(req);
logger.debug(JSON.stringify(payload));
// We validate the payload for specific fields. You may disable this feature
// if you would prefer to handle payload validation on your own.
if (!validatePaymentPayload(payload)) {
throw createError(400, 'Bad Request');
}
await retry(async (bail, attempt) => {
try {
logger.debug('Creating payment', { attempt });
const payment = {
idempotencyKey: payload.idempotencyKey,
locationId: payload.locationId,
sourceId: payload.sourceId,
// While it's tempting to pass this data from the client
// Doing so allows bad actor to modify these values
// Instead, leverage Orders to create an order on the server
// and pass the Order ID to createPayment rather than raw amounts
// See Orders documentation: https://developer.squareup.com/docs/orders-api/what-it-does
amountMoney: {
// the expected amount is in cents, meaning this is $1.00.
amount: '100',
// If you are a non-US account, you must change the currency to match the country in which
// you are accepting the payment.
currency: 'USD',
},
};
if (payload.customerId) {
payment.customerId = payload.customerId;
}
// VerificationDetails is part of Secure Card Authentication.
// This part of the payload is highly recommended (and required for some countries)
// for 'unauthenticated' payment methods like Cards.
if (payload.verificationToken) {
payment.verificationToken = payload.verificationToken;
}
const { result, statusCode } =
await square.paymentsApi.createPayment(payment);
logger.info('Payment succeeded!', { result, statusCode });
send(res, statusCode, {
success: true,
payment: {
id: result.payment.id,
status: result.payment.status,
receiptUrl: result.payment.receiptUrl,
orderId: result.payment.orderId,
},
});
} catch (ex) {
if (ex instanceof ApiError) {
// likely an error in the request. don't retry
logger.error(ex.errors);
bail(ex);
} else {
// IDEA: send to error reporting service
logger.error(`Error creating payment on attempt ${attempt}: ${ex}`);
throw ex; // to attempt retry
}
}
});
}
async function storeCard(req, res) {
const payload = await json(req);
if (!validateCreateCardPayload(payload)) {
throw createError(400, 'Bad Request');
}
await retry(async (bail, attempt) => {
try {
logger.debug('Storing card', { attempt });
const cardReq = {
idempotencyKey: payload.idempotencyKey,
sourceId: payload.sourceId,
card: {
customerId: payload.customerId,
},
};
if (payload.verificationToken) {
cardReq.verificationToken = payload.verificationToken;
}
const { result, statusCode } = await square.cardsApi.createCard(cardReq);
logger.info('Store Card succeeded!', { result, statusCode });
// cast 64-bit values to string
// to prevent JSON serialization error during send method
result.card.expMonth = result.card.expMonth.toString();
result.card.expYear = result.card.expYear.toString();
result.card.version = result.card.version.toString();
send(res, statusCode, {
success: true,
card: result.card,
});
} catch (ex) {
if (ex instanceof ApiError) {
// likely an error in the request. don't retry
logger.error(ex.errors);
bail(ex);
} else {
// IDEA: send to error reporting service
logger.error(
`Error creating card-on-file on attempt ${attempt}: ${ex}`,
);
throw ex; // to attempt retry
}
}
});
}
// serve static files like index.html and favicon.ico from public/ directory
async function serveStatic(req, res) {
logger.debug('Handling request', req.path);
await staticHandler(req, res, {
public: 'public',
});
}
// export routes to be served by micro
module.exports = router(
post('/payment', createPayment),
post('/card', storeCard),
get('/*', serveStatic),
);