Skip to content
This repository has been archived by the owner on Jul 26, 2024. It is now read-only.

Commit

Permalink
fix bug
Browse files Browse the repository at this point in the history
  • Loading branch information
fayssalmechmeche committed Jul 13, 2024
1 parent 6d8a0f6 commit 8992e63
Show file tree
Hide file tree
Showing 12 changed files with 238 additions and 26 deletions.
5 changes: 5 additions & 0 deletions backend/app/models/sql/Order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export class Order extends Model {
declare status: string;
declare payment_status: string;
declare reference: string;
declare session_id: string;
declare userId: ForeignKey<User['id']>;
}

Expand All @@ -35,6 +36,10 @@ export default (sequelize: Sequelize) => {
type: DataTypes.STRING,
allowNull: false,
},
session_id: {
type: DataTypes.TEXT,
allowNull: false,
},
},
{ sequelize, underscored: true },
);
Expand Down
4 changes: 4 additions & 0 deletions backend/app/repositories/sql/OrderProductRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export class OrderProductRepository {
return OrderProduct.findByPk(id);
}

static async findByOrderId(orderId: number): Promise<OrderProduct[]> {
return OrderProduct.findAll({ where: { orderId } });
}

static async update(orderProduct: OrderProduct): Promise<OrderProduct> {
return orderProduct.save();
}
Expand Down
68 changes: 57 additions & 11 deletions backend/app/routers/CheckoutRouter.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Router } from 'express';
import { NextFunction, Request, Response } from 'express';
import uniqid from 'uniqid';
import dotenv from 'dotenv';
import { CheckoutService } from '../services/CheckoutService';
import { CartService } from '../services/CartService';
import { CartRepository } from '../repositories/mongodb/CartRepository';
import { VariantRepository } from '../repositories/sql/VariantRepository';
import { auth } from '../middlewares/auth';
import { OrderRepository } from '../repositories/sql/OrderRepository';
import { OrderProductRepository } from '../repositories/sql/OrderProductRepository';

dotenv.config();

Expand Down Expand Up @@ -52,15 +55,19 @@ CheckoutRouter.post(
}
}
try {
const reference = 'sneakpeak' + '-' + uniqid();

const session = await CheckoutService.getCheckoutSession(
cartProducts,
res.locals.user.id,
reference,
);

console.log(session);
const order = await CheckoutService.createOrder(
session.amount_total as number,
session.id as string,
parseInt(res.locals.user.id),
reference,
session.id,
res.locals.user.id,
);

for (const item of cartProducts) {
Expand Down Expand Up @@ -98,12 +105,51 @@ CheckoutRouter.post(
},
);

CheckoutRouter.get('/success', async (req: Request, res: Response) => {
const success_url = req.body.success_url;
res.json(success_url);
});
CheckoutRouter.get(
'/success/:reference',
auth,
async (req: Request, res: Response) => {
const order = await OrderRepository.findByReference(req.params.reference);
if (!order) {
return res.status(404).json({ error: 'Order not found' });
}
if (order.status === 'pending') {
res.redirect('/checkout/cancel/' + req.params.reference);
}
res.json(order);
},
);

CheckoutRouter.get('/cancel', async (req: Request, res: Response) => {
const cancel_url = req.body.cancel_url;
res.json(cancel_url);
});
CheckoutRouter.get(
'/cancel/:reference',
auth,
async (req: Request, res: Response) => {
console.log(req.params.reference);
const order = await OrderRepository.findByReference(req.params.reference);
if (!order) {
return res.status(404).json({ error: 'Order not found' });
}
const orderProducts = await OrderProductRepository.findByOrderId(order.id);
const sessionid = order.session_id;
let linkPaiement = await CheckoutService.getCheckoutSessionById(sessionid);
console.log(linkPaiement);
if (!linkPaiement) {
return res.status(404).json({ error: 'Session not found' });
}

if (linkPaiement.payment_status === 'paid') {
return res.status(400).json({ error: 'Order already paid' });
}

if (linkPaiement.status === 'expired') {
linkPaiement = await CheckoutService.getCheckoutSession(
orderProducts,
res.locals.user.id,
order.reference,
);
order.session_id = linkPaiement.id;
OrderRepository.update(order);
}
res.json(linkPaiement);
},
);
25 changes: 21 additions & 4 deletions backend/app/services/CheckoutService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string, {

export class CheckoutService {
public static async getCheckoutSession(
cartProducts: CartProduct[],
cartProducts: CartProduct[] | OrderProduct[],
userId: string,
reference: string,
): Promise<Stripe.Checkout.Session> {
const products = cartProducts.map((product) => {
return {
Expand All @@ -37,23 +38,39 @@ export class CheckoutService {
metadata: { userId: userId },
line_items: products,
mode: 'payment',
cancel_url: 'http://localhost:5173/checkout/cancel',
success_url: 'http://localhost:5173/checkout/success',
cancel_url: 'http://localhost:5173/checkout/cancel/' + reference,
success_url: 'http://localhost:5173/checkout/success/' + reference,
});
return session;
}

public static async getCheckoutSessionByOrderId(
orderId: number,
): Promise<Stripe.Checkout.Session> {
const order = await OrderRepository.findById(orderId);
if (!order) throw new Error('Order not found');
return await stripe.checkout.sessions.retrieve(order.session_id);
}

public static async getCheckoutSessionById(
sessionId: string,
): Promise<Stripe.Checkout.Session> {
return await stripe.checkout.sessions.retrieve(sessionId);
}
public static async createOrder(
total: number,
reference: string,
session_id: string,
userId: number,
): Promise<Order> {
console.log(total, reference, userId);
const new_order = OrderRepository.build({
total: total / 100,
status: 'pending',
payment_status: 'pending',
reference: 'sneakpeak' + '-' + reference,
reference: reference,
userId: userId,
session_id: session_id,
});

return await OrderRepository.create(new_order);
Expand Down
22 changes: 22 additions & 0 deletions backend/migrations/16-order.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { DataTypes, Sequelize } from 'sequelize';
import { Migration } from '../bin/migrate';

// ATTENTION à bien underscorer manuellement les noms de colonnes
export const up: Migration = async ({
context: sequelize,
}: {
context: Sequelize;
}) => {
await sequelize.getQueryInterface().addColumn('orders', 'session_id', {
type: DataTypes.TEXT('long'),
allowNull: false,
});
};

export const down: Migration = async ({
context: sequelize,
}: {
context: Sequelize;
}) => {
await sequelize.getQueryInterface().removeColumn('orders', 'session_id');
};
8 changes: 7 additions & 1 deletion backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@
"postmark": "^4.0.2",
"sequelize": "^6.37.3",
"slugify": "^1.6.6",
"stripe": "^16.0.0",
"umzug": "^3.8.1",
"stripe": "^16.0.0"
"uniqid": "^5.4.0"
},
"devDependencies": {
"@eslint/js": "^9.2.0",
Expand Down
2 changes: 1 addition & 1 deletion compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ services:
image: stripe/stripe-cli
depends_on:
- backend
command: listen --forward-to http://backend/webhook --api-key $STRIPE_SECRET_KEY
command: listen --forward-to http://backend:3000/webhook --api-key $STRIPE_SECRET_KEY

volumes:
postgres:
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import EmailVerificationView from '../views/EmailVerificationView.vue'
import SearchView from '@/views/SearchView.vue'
import CartView from '@/views/CartView.vue'
import CheckoutView from '@/views/CheckoutView.vue'
import CheckoutSuccessView from '@/views/CheckoutSuccessView.vue'
import CheckoutCancelView from '@/views/CheckoutCancelView.vue'
import ResetPasswordView from '@/views/ResetPasswordView.vue'
import CGUView from '@/views/legal/CGUView.vue'
import { checkAuth } from '@/helpers/auth'
Expand Down Expand Up @@ -37,6 +39,16 @@ const router = createRouter({
name: 'checkout',
component: CheckoutView
},
{
path: '/checkout/success/:reference',
name: 'success',
component: CheckoutSuccessView
},
{
path: '/checkout/cancel/:reference',
name: 'cancel',
component: CheckoutCancelView
},
{
path: '/reset-password',
name: 'reset_password',
Expand Down
51 changes: 51 additions & 0 deletions frontend/src/views/CheckoutCancelView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<script setup lang="ts">
import BasePage from '@/components/BasePage.vue';
import { RouterLink, useRoute } from 'vue-router';
import { ref, onMounted } from 'vue';
const route = useRoute();
const reference = route.params.reference;
const linkPaiement = ref('');
const error = ref('');
const BASE_URL = import.meta.env.VITE_API_URL
onMounted(async () => {
try {
const response = await fetch(`${BASE_URL}/checkout/cancel/${reference}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
console.log(data);
linkPaiement.value = data.url;
} catch (err) {
error.value = err.message;
}
});
</script>

<template>
<BasePage>
<div class="text-center h-full w-full my-auto">
<h1 class="text-2xl font-semibold">Votre paiement a échoué</h1>

<p class="mt-4">Votre paiement a échoué. Veuillez réessayer.</p>



<button type="button" class="bg-black text-white p-3 px-5 my-4">

<a :href="linkPaiement">Retourner à la page de paiement</a>


</button>
</div>
</BasePage>
</template>

<style scoped></style>
39 changes: 39 additions & 0 deletions frontend/src/views/CheckoutSuccessView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<script setup lang="ts">
import BasePage from '@/components/BasePage.vue';
import { RouterLink, useRoute } from 'vue-router';
import { ref, onMounted } from 'vue';
const reference = ref('');
onMounted(async () => {
const route = useRoute();
reference.value = route.params.reference as string;
});
</script>

<template>
<BasePage>
<div class="text-center h-full w-full my-auto">
<h1 class="text-2xl font-semibold">Merci pour votre commande</h1>

<p class="mt-4">Votre commande a été traitée avec succès.</p>



<button type="button" class="bg-black text-white p-3 px-5 my-4">

<RouterLink :to="'/order/' + reference">
Voir les détails de la commande
</RouterLink>



</button>
</div>
</BasePage>
</template>

<style scoped></style>
Loading

0 comments on commit 8992e63

Please sign in to comment.