new
This commit is contained in:
parent
ba4e173c9f
commit
e93f945df3
File diff suppressed because one or more lines are too long
@ -34,6 +34,25 @@ const config = {
|
||||
clientId: process.env.MS_CLIENT_ID || '',
|
||||
clientSecret: process.env.MS_CLIENT_SECRET || '',
|
||||
},
|
||||
amadeus: {
|
||||
clientId: process.env.AMADEUS_CLIENT_ID || 'YnlNGEWG0bMODXXFh59aWczdKy2Abr3l',
|
||||
clientSecret: process.env.AMADEUS_CLIENT_SECRET || 'YaSiX7mGOu3dhDz6',
|
||||
baseURL: 'https://test.api.amadeus.com',
|
||||
},
|
||||
|
||||
tap: {
|
||||
secretKey: process.env.TAP_SECRET_KEY || 'sk_test_XKokBfNWv6FIYuTMg5LPjhJ',
|
||||
publishableKey: process.env.TAP_PUBLISHABLE_KEY || 'pk_test_EtHFV4BuPQokJT6jiROls87Y',
|
||||
merchantId: process.env.TAP_MERCHANT_ID || '26374580',
|
||||
baseURL: 'https://api.tap.company',
|
||||
successUrl: process.env.TAP_SUCCESS_URL || 'https://flyaru.com/payment-success',
|
||||
failureUrl: process.env.TAP_FAILURE_URL || 'https://flyaru.com/payment-failed',
|
||||
webhookUrl: process.env.TAP_WEBHOOK_URL || 'https://flyaru.com/api/webhook',
|
||||
force3DSecure: true,
|
||||
capture: 'capture',
|
||||
refundWindowDays: 30,
|
||||
},
|
||||
|
||||
uploadDir: os.tmpdir(),
|
||||
email: {
|
||||
from: 'FlyAru-Powered by Arwa Travel Group <app@flatlogic.app>',
|
||||
|
||||
@ -36,6 +36,8 @@ const permissionsRoutes = require('./routes/permissions');
|
||||
const agenciesRoutes = require('./routes/agencies');
|
||||
|
||||
const cartRoutes = require('./routes/cart');
|
||||
const amadeusRoutes = require('./routes/amadeus');
|
||||
|
||||
|
||||
const getBaseUrl = (url) => {
|
||||
if (!url) return '';
|
||||
@ -150,6 +152,15 @@ app.use(
|
||||
cartRoutes,
|
||||
);
|
||||
|
||||
// Amadeus flight search routes
|
||||
app.use(
|
||||
'/api/amadeus',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
amadeusRoutes
|
||||
);
|
||||
|
||||
|
||||
|
||||
app.use(
|
||||
'/api/openai',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
|
||||
30
backend/src/routes/amadeus.js
Normal file
30
backend/src/routes/amadeus.js
Normal file
@ -0,0 +1,30 @@
|
||||
const express = require('express');
|
||||
const wrapAsync = require('../helpers').wrapAsync;
|
||||
const passport = require('passport');
|
||||
const AmadeusService = require('../services/amadeusService');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* @route GET /api/amadeus/flights
|
||||
* @desc Search flights via Amadeus
|
||||
* Query params: originLocationCode, destinationLocationCode, departureDate, adults, currency, etc.
|
||||
*/
|
||||
router.get(
|
||||
'/flights',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
wrapAsync(async (req, res) => {
|
||||
const params = {
|
||||
originLocationCode: req.query.originLocationCode,
|
||||
destinationLocationCode: req.query.destinationLocationCode,
|
||||
departureDate: req.query.departureDate,
|
||||
adults: req.query.adults || 1,
|
||||
max: req.query.max || 10,
|
||||
currency: req.query.currency || 'SAR'
|
||||
};
|
||||
const result = await AmadeusService.searchFlights(params);
|
||||
res.json(result);
|
||||
})
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
@ -4,6 +4,12 @@ const CartService = require('../services/cart');
|
||||
const CartDBApi = require('../db/api/cart');
|
||||
const wrapAsync = require('../helpers').wrapAsync;
|
||||
|
||||
const tapService = require('../services/tapService');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const db = require('../db/models');
|
||||
const passport = require('passport');
|
||||
|
||||
|
||||
const config = require('../config');
|
||||
|
||||
const router = express.Router();
|
||||
@ -434,6 +440,45 @@ router.get(
|
||||
wrapAsync(async (req, res) => {
|
||||
const payload = await CartDBApi.findBy({ id: req.params.id });
|
||||
|
||||
// Checkout endpoint
|
||||
router.post(
|
||||
'/checkout',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
wrapAsync(async (req, res) => {
|
||||
const currentUser = req.currentUser;
|
||||
const carts = await CartDBApi.findAll({ user: currentUser.id }, null, { currentUser });
|
||||
if (!carts.rows.length) {
|
||||
return res.status(400).json({ message: 'Cart is empty.' });
|
||||
}
|
||||
const cart = carts.rows[0];
|
||||
const items = await db.cartitem.findAll({ where: { cartId: cart.id } });
|
||||
const amount = items.reduce((sum, item) => sum + item.quantity * item.unitprice, 0);
|
||||
const chargeData = {
|
||||
amount,
|
||||
currency: config.tap.currency || 'SAR',
|
||||
threeDSecure: config.tap.force3DSecure,
|
||||
capture: config.tap.capture,
|
||||
description: `Charge for cart ${cart.id}`,
|
||||
reference: {
|
||||
track: uuidv4(),
|
||||
payment: uuidv4(),
|
||||
order: cart.id,
|
||||
},
|
||||
post: { url: config.tap.webhookUrl },
|
||||
redirect: { url: config.tap.successUrl },
|
||||
customer: {
|
||||
id: currentUser.id,
|
||||
email: currentUser.email,
|
||||
first_name: currentUser.firstName,
|
||||
last_name: currentUser.lastName,
|
||||
},
|
||||
};
|
||||
const charge = await tapService.createCharge(chargeData);
|
||||
res.json({ charge });
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
51
backend/src/services/amadeusService.js
Normal file
51
backend/src/services/amadeusService.js
Normal file
@ -0,0 +1,51 @@
|
||||
const axios = require('axios');
|
||||
const qs = require('qs');
|
||||
const config = require('../config');
|
||||
|
||||
let accessToken = null;
|
||||
let tokenExpiresAt = 0;
|
||||
|
||||
/**
|
||||
* Obtain or refresh Amadeus OAuth token
|
||||
*/
|
||||
async function getAccessToken() {
|
||||
const now = Date.now();
|
||||
if (accessToken && now < tokenExpiresAt) {
|
||||
return accessToken;
|
||||
}
|
||||
const tokenUrl = `${config.amadeus.baseURL}/v1/security/oauth2/token`;
|
||||
const data = qs.stringify({
|
||||
grant_type: 'client_credentials',
|
||||
client_id: config.amadeus.clientId,
|
||||
client_secret: config.amadeus.clientSecret,
|
||||
});
|
||||
const resp = await axios.post(tokenUrl, data, {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
});
|
||||
accessToken = resp.data.access_token;
|
||||
// expires_in is seconds
|
||||
tokenExpiresAt = now + resp.data.expires_in * 1000 - 60000; // refresh 1 min early
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search flights via Amadeus
|
||||
* @param {Object} params originLocationCode, destinationLocationCode, departureDate, adults, currency
|
||||
*/
|
||||
async function searchFlights(params) {
|
||||
const token = await getAccessToken();
|
||||
const url = `${config.amadeus.baseURL}/v2/shopping/flight-offers`;
|
||||
const resp = await axios.get(url, {
|
||||
params: params,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
searchFlights,
|
||||
};
|
||||
22
backend/src/services/tapService.js
Normal file
22
backend/src/services/tapService.js
Normal file
@ -0,0 +1,22 @@
|
||||
const axios = require('axios');
|
||||
const config = require('../config');
|
||||
|
||||
// Initialize Tap API client
|
||||
const tapClient = axios.create({
|
||||
baseURL: config.tap.baseURL,
|
||||
headers: {
|
||||
Authorization: `Bearer ${config.tap.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Create a charge via Tap Payments
|
||||
* @param {Object} data - payload for /v2/charges
|
||||
*/
|
||||
async function createCharge(data) {
|
||||
const resp = await tapClient.post('/v2/charges', data);
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
module.exports = { createCharge };
|
||||
1
frontend/json/runtimeError.json
Normal file
1
frontend/json/runtimeError.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
||||
Loading…
x
Reference in New Issue
Block a user