-
-
Notifications
You must be signed in to change notification settings - Fork 220
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
33 changed files
with
699 additions
and
201 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/:bookingId/:orderId', | ||
} | ||
|
||
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,122 @@ | ||
import { Request, Response } from 'express' | ||
import * as paypal from '../paypal' | ||
import i18n from '../lang/i18n' | ||
import * as logger from '../common/logger' | ||
import * as bookcarsTypes from ':bookcars-types' | ||
import * as env from '../config/env.config' | ||
import Booking from '../models/Booking' | ||
import User from '../models/User' | ||
import Car from '../models/Car' | ||
import * as bookingController from './bookingController' | ||
|
||
export const createPayPalOrder = async (req: Request, res: Response) => { | ||
try { | ||
const { bookingId, amount, currency, name }: bookcarsTypes.CreatePayPalOrderPayload = req.body | ||
|
||
const orderId = await paypal.createOrder(bookingId, amount, currency, name) | ||
|
||
return res.json(orderId) | ||
} 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 { bookingId, orderId } = req.params | ||
|
||
// | ||
// 1. Retrieve Checkout Sesssion and Booking | ||
// | ||
const booking = await Booking.findOne({ _id: bookingId, expireAt: { $ne: null } }) | ||
if (!booking) { | ||
const msg = `Booking with id ${bookingId} not found` | ||
logger.info(`[paypal.checkPaypalOrder] ${msg}`) | ||
return res.status(204).send(msg) | ||
} | ||
|
||
let order | ||
try { | ||
order = await paypal.getOrder(orderId) | ||
} catch (err) { | ||
logger.error(`[paypal.checkPaypalOrder] retrieve paypal order error: ${orderId}`, err) | ||
} | ||
|
||
if (!order) { | ||
const msg = `Order ${order} 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 (order.status === 'APPROVED') { | ||
const supplier = await User.findById(booking.supplier) | ||
if (!supplier) { | ||
throw new Error(`Supplier ${booking.supplier} not found`) | ||
} | ||
|
||
booking.paypalOrderId = orderId | ||
booking.expireAt = undefined | ||
booking.status = booking.isDeposit ? bookcarsTypes.BookingStatus.Deposit : bookcarsTypes.BookingStatus.Paid | ||
await booking.save() | ||
|
||
// Mark car as unavailable | ||
// await Car.updateOne({ _id: booking.car }, { available: false }) | ||
const car = await Car.findById(booking.car) | ||
if (!car) { | ||
throw new Error(`Car ${booking.car} not found`) | ||
} | ||
car.trips += 1 | ||
await car.save() | ||
|
||
// Send confirmation email to customer | ||
const user = await User.findById(booking.driver) | ||
if (!user) { | ||
throw new Error(`Driver ${booking.driver} not found`) | ||
} | ||
|
||
user.expireAt = undefined | ||
await user.save() | ||
|
||
if (!await bookingController.confirm(user, supplier, booking, false)) { | ||
return res.sendStatus(400) | ||
} | ||
|
||
// Notify supplier | ||
i18n.locale = supplier.language | ||
let message = i18n.t('BOOKING_PAID_NOTIFICATION') | ||
await bookingController.notify(user, booking.id, supplier, message) | ||
|
||
// Notify admin | ||
const admin = !!env.ADMIN_EMAIL && await User.findOne({ email: env.ADMIN_EMAIL, type: bookcarsTypes.UserType.Admin }) | ||
if (admin) { | ||
i18n.locale = admin.language | ||
message = i18n.t('BOOKING_PAID_NOTIFICATION') | ||
await bookingController.notify(user, booking.id, admin, message) | ||
} | ||
|
||
return res.sendStatus(200) | ||
} | ||
|
||
// | ||
// 3. Delete Booking if the payment didn't succeed | ||
// | ||
await booking.deleteOne() | ||
return res.status(400).send(order.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 (bookingId: 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: bookingId, | ||
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.