Skip to content

Commit a38ba6e

Browse files
committed
Add endpoint to apply coupon
1 parent 5a6b968 commit a38ba6e

File tree

3 files changed

+66
-40
lines changed

3 files changed

+66
-40
lines changed

README.md

Lines changed: 47 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
A standalone API for JS and other clients to use with Schema. Extend basic endpoints with your own custom functionality, for example, validating user input special or scoping product queries for client usage.
44

55
```
6-
Schema.io <===> NodeJS API (This Package) <===> Your App (React/Angular/Ember/Rails/Etc)
6+
Schema.io <==> NodeJS API (This Package) <==> Your App (React/Angular/Ember/Rails/Etc)
77
```
88

99
## Getting Started
@@ -15,10 +15,10 @@ Schema.io <===> NodeJS API (This Package) <===> Your App (React/Angular/Ember/Ra
1515

1616
### Example .env file
1717

18-
```
18+
```bash
1919
NODE_ENV=development
2020
PORT=3001
21-
FORCE_SSL=true // if you want to redirect all requests to https
21+
FORCE_SSL=true # redirect all requests to https
2222
SCHEMA_CLIENT_ID=my-client-id
2323
SCHEMA_CLIENT_KEY=my-client-key
2424
```
@@ -41,43 +41,41 @@ It's not strictly required to pass the session header, but certain endpoints wil
4141

4242
#### Get current account (session required)
4343

44-
```
44+
```json
4545
GET /v1/account
4646
```
4747

4848
Sensitive fields may be removed from the response. See `api/v1/account.js` for details.
4949

5050
#### Update current account
5151

52-
```
52+
```json
5353
PUT /v1/account
5454
{
5555
"first_name": "Example",
5656
"last_name": "Customer",
57-
"email": "[email protected]",
58-
...
57+
"email": "[email protected]"
5958
}
6059
```
6160

6261
Only specific fields may be updated. See `api/v1/account.js` for details.
6362

6463
#### Create account for the current user
6564

66-
```
65+
```json
6766
POST /v1/account
6867
{
6968
"first_name": "Example",
7069
"last_name": "Customer",
71-
"email": "[email protected]",
72-
...
70+
"email": "[email protected]"
7371
}
7472
```
7573

7674
Only specific fields may be created. See `api/v1/account.js` for details.
7775

7876
#### Login to account
7977

80-
```
78+
```json
8179
POST /v1/account/login
8280
{
8381
"email": "[email protected]",
@@ -89,15 +87,15 @@ If email and password are correct, an account record will be returned. Otherwise
8987

9088
#### Logout of current account
9189

92-
```
90+
```json
9391
POST /v1/account/logout
9492
```
9593

9694
This will remove the previously logged in `account_id` from the session.
9795

9896
#### Send password recovery email
9997

100-
```
98+
```json
10199
POST /v1/account/recover
102100
{
103101
"email": "[email protected]",
@@ -109,7 +107,7 @@ This will send an email to the account if one found, that contains the `reset_ur
109107

110108
#### Reset password from recovery email
111109

112-
```
110+
```json
113111
POST /v1/account/recover
114112
{
115113
"reset_key": "iud287ebuf9uwf92fdi2uhef872h",
@@ -123,15 +121,15 @@ This will reset an account's password if the `reset_key` is found. If successful
123121

124122
#### Get current cart details (session required)
125123

126-
```
124+
```json
127125
GET /v1/cart
128126
```
129127

130128
Sensitive fields may be removed from the response. See `api/v1/cart.js` for details.
131129

132130
#### Update current cart details
133131

134-
```
132+
```json
135133
PUT /v1/cart
136134
{
137135
"shipping": {
@@ -177,15 +175,15 @@ If the cart does not exist for the current session, it will be automatically cre
177175

178176
#### Create cart for the current user
179177

180-
```
178+
```json
181179
POST /v1/cart
182180
```
183181

184182
This will create a cart for the current session, if one does not already exist. Note that it's not usually necessary to make this request, since other requests like `/v1/cart/add-item` will do the same automatically on demand.
185183

186184
#### Add item to cart
187185

188-
```
186+
```json
189187
POST /v1/cart/add-item
190188
{
191189
"product_id": "...",
@@ -205,7 +203,7 @@ If the same product and options already exist in the cart, then its quantity wil
205203

206204
#### Remove item from cart
207205

208-
```
206+
```json
209207
POST /v1/cart/remove-item
210208
{
211209
"item_id": "..."
@@ -214,9 +212,22 @@ POST /v1/cart/remove-item
214212

215213
This will remove an item from the cart matching `item_id`. You can get the item ID value from any of the previous calls to the cart endpoint.
216214

217-
#### Get shipping prices
215+
#### Apply a coupon
218216

217+
```json
218+
POST /v1/cart/apply-coupon
219+
{
220+
"code": "SHIPFREE"
221+
}
219222
```
223+
224+
This will apply a valid coupon code to the cart and affect all relevant prices. If the coupon code is not found or is not valid, the server will respond with status `400` and an error message.
225+
226+
To remove an applied coupon, make the same request with `"code": null`.
227+
228+
#### Get shipping prices
229+
230+
```json
220231
GET /v1/cart/shipment-rating
221232
```
222233

@@ -226,7 +237,7 @@ The typical flow is to update the cart with shipping information first, then mak
226237

227238
### Convert cart to order
228239

229-
```
240+
```json
230241
POST /v1/cart/checkout
231242
```
232243

@@ -238,21 +249,21 @@ If successful, an order record will be returned. Otherwise an error.
238249

239250
#### Get category by slug or ID
240251

241-
```
252+
```json
242253
GET /v1/categories/:slug
243254
```
244255

245256
Sensitive fields may be removed from the response. See `api/v1/categories.js` for details.
246257

247258
#### Get sub-categories by slug or ID
248259

249-
```
260+
```json
250261
GET /v1/categories/:slug/children
251262
```
252263

253264
#### Get products in a category
254265

255-
```
266+
```json
256267
GET /v1/categories/:slug/products
257268
```
258269

@@ -264,15 +275,15 @@ Note: If you want to get products in a single category only and ignore sub-categ
264275

265276
#### Get a product
266277

267-
```
278+
```json
268279
GET /v1/products/:id
269280
```
270281

271282
Sensitive fields may be removed from the response. See `api/v1/products.js` for details.
272283

273284
#### Get products in a category
274285

275-
```
286+
```json
276287
GET /v1/products?category=:slug
277288
```
278289

@@ -284,7 +295,7 @@ Note: If you want to get products including all sub-categories, then see `/v1/ca
284295

285296
#### Subscribe contact to email list(s)
286297

287-
```
298+
```json
288299
POST /v1/contacts/subscribe
289300
{
290301
"first_name": "Example",
@@ -298,7 +309,7 @@ All fields are optional except `email`. You may use `email_optin_lists` for trac
298309

299310
#### Unsubscribe contact from email list(s)
300311

301-
```
312+
```json
302313
POST /v1/contacts/unsubscribe
303314
{
304315
"email": "[email protected]",
@@ -313,15 +324,15 @@ This will flip the `email_optin` field to false on the contact record. If `email
313324

314325
#### Get a page
315326

316-
```
327+
```json
317328
GET /v1/pages/:id
318329
```
319330

320331
Pages are used to store content such as About Us, Privacy Policy, etcetera.
321332

322333
#### Get page articles
323334

324-
```
335+
```json
325336
GET /v1/pages/:id/articles
326337
```
327338

@@ -330,23 +341,23 @@ Articles are useful for different things depending on the page itself. For examp
330341

331342
#### Get a page article
332343

333-
```
344+
```json
334345
GET /v1/pages/:id/articles/:article_id
335346
```
336347

337348
## /v1/sessions
338349

339350
#### Get current session details
340351

341-
```
352+
```json
342353
GET /v1/session
343354
```
344355

345356
This will return current session data, including `account_id` and `cart_id`, along with any other arbitrary fields stored by your client.
346357

347358
#### Update current session details
348359

349-
```
360+
```json
350361
PUT /v1/session
351362
{
352363
"arbitrary_field": "example"
@@ -357,21 +368,19 @@ Update the current session with any fields that might be useful to your client.
357368

358369
## Testing
359370

360-
To run all tests:
361-
362-
```
371+
```bash
363372
npm test
364373
```
365374

366-
As a best practice, you should write new tests for new or modified endpoints.
375+
As a best practice, you should write tests for all new or modified endpoints.
367376

368377
#### Schema client testing
369378

370379
The test setup includes a Schema client that should be used to stub itself with expected requests and results. This allows you to easily test your own API code, without calling out to the Schema.io API itself. It makes test faster and more reliable.
371380

372381
Here's an example using this test client:
373382

374-
```
383+
```javascript
375384
const schema = Test.schemaClient();
376385
const api = Test.apiClient(schema);
377386

api/router.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ module.exports = () => {
4545

4646
// Graceful error handler
4747
function handleRouterError(err, res) {
48+
const message = err.toString().replace('Error: ', '');
4849
res.status(err.code || 500).json({
49-
error: err.code ? err.toString() : 'Internal Server Error'
50+
error: err.code ? message : 'Internal Server Error'
5051
});
5152
if (!err.code) {
5253
console.log(err);

api/v1/cart.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ cart.init = (env, router, schema) => {
1313
router.post('/cart', requireSession, cart.create.bind(this, schema));
1414
router.post('/cart/add-item', requireSession, cart.addItem.bind(this, schema));
1515
router.post('/cart/remove-item', requireSession, cart.removeItem.bind(this, schema));
16+
router.post('/cart/apply-coupon', requireSession, cart.applyCoupon.bind(this, schema));
1617
router.get('/cart/shipment-rating', requireSession, cart.shipmentRating.bind(this, schema));
1718
router.post('/cart/checkout', requireSession, cart.checkout.bind(this, schema));
1819
};
@@ -100,12 +101,26 @@ cart.removeItem = (schema, req) => {
100101
}
101102
return schema.delete('/carts/{cart_id}/items/{item_id}', {
102103
cart_id: req.session.cart_id,
103-
item_id: req.body.item_id
104+
item_id: req.body.item_id,
104105
}).then(() => {
105106
return cart.get(schema, req);
106107
});
107108
};
108109

110+
// Apply a coupon code
111+
cart.applyCoupon = (schema, req) => {
112+
return schema.put('/carts/{cart_id}', {
113+
cart_id: req.session.cart_id,
114+
coupon_code: req.body.code || req.body.coupon_code || null,
115+
}).then(result => {
116+
if (result.errors && result.errors.coupon_code) {
117+
throw util.error(400, 'Coupon code is not valid');
118+
}
119+
return cart.get(schema, req);
120+
});
121+
};
122+
123+
// Ensure shipping fields are sane
109124
cart.sanitizeShipping = (shipping) => {
110125
shipping = util.filterData(shipping, [
111126
'name',
@@ -122,6 +137,7 @@ cart.sanitizeShipping = (shipping) => {
122137
return shipping;
123138
},
124139

140+
// Ensure billing fields are sane
125141
cart.sanitizeBilling = (billing) => {
126142
billing = util.filterData(billing, [
127143
'name',

0 commit comments

Comments
 (0)