-
-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
24 changed files
with
666 additions
and
125 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
const routes = { | ||
createPayPalOrder: '/api/create-paypal-order', | ||
checkPayPalOrder: '/api/check-paypal-order/:orderId/:paypalOrderId', | ||
} | ||
|
||
export default routes |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import { Request, Response } from 'express' | ||
import * as paypal from '../paypal' | ||
import i18n from '../lang/i18n' | ||
import * as logger from '../common/logger' | ||
import * as wexcommerceTypes from ':wexcommerce-types' | ||
import * as env from '../config/env.config' | ||
import Order from '../models/Order' | ||
import OrderItem from '../models/OrderItem' | ||
import Product from '../models/Product' | ||
import User from '../models/User' | ||
import Setting from '../models/Setting' | ||
import PaymentType from '../models/PaymentType' | ||
import DeliveryType from '../models/DeliveryType' | ||
import * as orderController from './orderController' | ||
|
||
export const createPayPalOrder = async (req: Request, res: Response) => { | ||
try { | ||
const { orderId, amount, currency, name }: wexcommerceTypes.CreatePayPalOrderPayload = req.body | ||
console.log({ orderId, amount, currency, name }) | ||
|
||
const paypalOrderId = await paypal.createOrder(orderId, amount, currency, name) | ||
console.log('paypalOrderId', paypalOrderId) | ||
|
||
return res.json(paypalOrderId) | ||
} catch (err) { | ||
logger.error(`[paypal.createPayPalOrder] ${i18n.t('ERROR')}`, err) | ||
return res.status(400).send(i18n.t('ERROR') + err) | ||
} | ||
} | ||
|
||
/** | ||
* Check Paypal order and update booking if the payment succeeded. | ||
* | ||
* @async | ||
* @param {Request} req | ||
* @param {Response} res | ||
* @returns {unknown} | ||
*/ | ||
export const checkPayPalOrder = async (req: Request, res: Response) => { | ||
try { | ||
const { orderId, paypalOrderId } = req.params | ||
|
||
// | ||
// 1. Retrieve Checkout Sesssion and Booking | ||
// | ||
const order = await Order | ||
.findOne({ _id: orderId, expireAt: { $ne: null } }) | ||
.populate<{ orderItems: env.OrderItem[] }>({ | ||
path: 'orderItems', | ||
populate: { | ||
path: 'product', | ||
model: 'Product', | ||
}, | ||
}) | ||
|
||
if (!order) { | ||
const msg = `Order ${orderId} not found` | ||
logger.info(`[paypal.checkCheckoutSession] ${msg}`) | ||
return res.status(204).send(msg) | ||
} | ||
|
||
let paypalOrder | ||
try { | ||
paypalOrder = await paypal.getOrder(paypalOrderId) | ||
} catch (err) { | ||
logger.error(`[paypal.checkPaypalOrder] retrieve paypal order error: ${orderId}`, err) | ||
} | ||
|
||
if (!paypalOrder) { | ||
const msg = `Order ${paypalOrder} not found` | ||
logger.info(`[paypal.checkPaypalOrder] ${msg}`) | ||
return res.status(204).send(msg) | ||
} | ||
|
||
// | ||
// 2. Update Booking if the payment succeeded | ||
// (Set BookingStatus to Paid and remove expireAt TTL index) | ||
// | ||
if (paypalOrder.status === 'APPROVED') { | ||
for (const oi of order.orderItems) { | ||
const orderItem = await OrderItem.findById(oi.id) | ||
orderItem!.expireAt = undefined | ||
await orderItem!.save() | ||
} | ||
order.expireAt = undefined | ||
order.status = wexcommerceTypes.OrderStatus.Paid | ||
await order.save() | ||
|
||
// Update product quantity | ||
for (const orderItem of order.orderItems) { | ||
const product = await Product.findById(orderItem.product._id) | ||
if (!product) { | ||
throw new Error(`Product ${orderItem.product._id} not found`) | ||
} | ||
product.quantity -= orderItem.quantity | ||
if (product.quantity <= 0) { | ||
product.soldOut = true | ||
product.quantity = 0 | ||
} | ||
await product.save() | ||
} | ||
|
||
// Send confirmation email | ||
const user = await User.findById(order.user) | ||
if (!user) { | ||
logger.info(`User ${order.user} not found`) | ||
return res.sendStatus(204) | ||
} | ||
|
||
user.expireAt = undefined | ||
await user.save() | ||
|
||
const settings = await Setting.findOne({}) | ||
if (!settings) { | ||
throw new Error('Settings not found') | ||
} | ||
const paymentType = (await PaymentType.findById(order.paymentType))!.name | ||
const deliveryType = (await DeliveryType.findById(order.deliveryType))!.name | ||
await orderController.confirm(user, order, order.orderItems, settings, paymentType, deliveryType) | ||
|
||
// Notify admin | ||
// const admin = !!env.ADMIN_EMAIL && await User.findOne({ email: env.ADMIN_EMAIL, type: wexcommerceTypes.UserType.Admin }) | ||
// if (admin) { | ||
// await orderController.notify(env.ADMIN_EMAIL, order, user, settings) | ||
// } | ||
await orderController.notify(env.ADMIN_EMAIL, order, user, settings) | ||
|
||
return res.sendStatus(200) | ||
} | ||
|
||
// | ||
// 3. Delete Booking if the payment didn't succeed | ||
// | ||
await order.deleteOne() | ||
return res.status(400).send(paypalOrder.status) | ||
} catch (err) { | ||
logger.error(`[paypal.checkPaypalOrder] ${i18n.t('ERROR')}`, err) | ||
return res.status(400).send(i18n.t('ERROR') + err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import axios from 'axios' | ||
import * as env from './config/env.config' | ||
import * as helper from './common/helper' | ||
|
||
export const getToken = async () => { | ||
const res = await axios.post( | ||
'https://api-m.sandbox.paypal.com/v1/oauth2/token', | ||
{ grant_type: 'client_credentials' }, | ||
{ | ||
auth: { | ||
username: env.PAYPAL_CLIENT_ID, | ||
password: env.PAYPAL_CLIENT_SECRET, | ||
}, | ||
headers: { | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
}, | ||
}, | ||
) | ||
|
||
return res.data.access_token | ||
} | ||
|
||
export const createOrder = async (orderId: string, amount: number, currency: string, name: string) => { | ||
const price = helper.formatPayPalPrice(amount) | ||
const token = await getToken() | ||
const res = await axios.post( | ||
'https://api-m.sandbox.paypal.com/v2/checkout/orders', | ||
{ | ||
intent: 'CAPTURE', | ||
payment_source: { | ||
paypal: { | ||
experience_context: { | ||
payment_method_preference: 'IMMEDIATE_PAYMENT_REQUIRED', | ||
landing_page: 'LOGIN', | ||
shipping_preference: 'GET_FROM_FILE', | ||
user_action: 'PAY_NOW', | ||
// return_url: `${helper.trimEnd(env.FRONTEND_HOST, '/')}/checkout-session/${bookingId}`, | ||
// cancel_url: env.FRONTEND_HOST, | ||
}, | ||
}, | ||
}, | ||
purchase_units: [ | ||
{ | ||
invoice_id: orderId, | ||
amount: { | ||
currency_code: currency, | ||
value: price, | ||
breakdown: { | ||
item_total: { | ||
currency_code: currency, | ||
value: price, | ||
}, | ||
}, | ||
}, | ||
items: [ | ||
{ | ||
name, | ||
description: name, | ||
unit_amount: { | ||
currency_code: currency, | ||
value: price, | ||
}, | ||
quantity: '1', | ||
// category: 'cars', | ||
// sku: 'sku01', | ||
// image_url: 'https://example.com/static/images/items/1/tshirt_green.jpg', | ||
// url: 'https://example.com/url-to-the-item-being-purchased-1', | ||
// upc: { | ||
// type: 'UPC-A', | ||
// code: '123456789012', | ||
// }, | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
{ | ||
headers: { | ||
'Content-Type': 'application/json', | ||
Authorization: `Bearer ${token}`, | ||
}, | ||
}, | ||
) | ||
|
||
return res.data.id | ||
} | ||
|
||
export const getOrder = async (orderId: string) => { | ||
const token = await getToken() | ||
const res = await axios.get( | ||
`https://api-m.sandbox.paypal.com/v2/checkout/orders/${orderId}`, | ||
{ | ||
headers: { | ||
Authorization: `Bearer ${token}`, | ||
}, | ||
}, | ||
) | ||
|
||
return res.data | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import express from 'express' | ||
import routeNames from '../config/paypalRoutes.config' | ||
import * as paypalController from '../controllers/paypalController' | ||
|
||
const routes = express.Router() | ||
|
||
routes.route(routeNames.createPayPalOrder).post(paypalController.createPayPalOrder) | ||
routes.route(routeNames.checkPayPalOrder).post(paypalController.checkPayPalOrder) | ||
|
||
export default routes |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.