Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ NEXT_PUBLIC_FRONTEND_URL=http://localhost:3000
NEXT_PUBLIC_GA_TRACKING_ID=REDACTED
NEXT_PUBLIC_DISCORD_SERVER_URL=https://discord.gg/bnJ3narzF3

LYF_API_VENDOR_ID=deebb957-9025-4894-b52d-24493cdb7278
LYF_API_SECRET_KEY=B52DFC6C7F4AA054CDB08E38B2298C97A4A12039
LYF_CREDIT_CARD_API_URL=https://sandbox-webpos.lyf.eu/fr/plugin/PaymentCb.aspx
LYF_FROM_APPLICATION_API_URL=https://sandbox-webpos.lyf.eu/fr/plugin/Payment.aspx
LYF_API_VENDOR_ID=REDACTED
LYF_API_SECRET_KEY=REDACTED
LYF_API_DOMAIN=https://sandbox-acceptance.lyf.eu

SESSION_SECRET_KEY=generate_on_https://randomkeygen.com

Expand Down
13 changes: 3 additions & 10 deletions app/components/hub/transactions/operations/topUp/TopUp.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,19 @@
import { useState } from 'react';

import { useAuthenticatedSession } from '@blitzjs/auth';
import { useMutation } from '@blitzjs/rpc';

import { TopUpInputType } from 'app/components/forms/validations';
import Snackbar from 'app/core/layouts/Snackbar';
import useSnackbar from 'app/entities/hooks/useSnackbar';
import requestTopUp, { PaymentMethod } from 'app/entities/transactions/mutations/requestTopUp';
import requestTopUp from 'app/entities/transactions/mutations/requestTopUp';

import TopUpForm from './TopUpForm';

export default function TopUp() {
const { open, message, severity, onClose, onShow } = useSnackbar();
const [paymentMethod, setPaymentMethod] = useState<PaymentMethod>('credit');
const [topUp] = useMutation(requestTopUp);

const beforeSubmit = (paymentMethod: PaymentMethod) => () => setPaymentMethod(paymentMethod);

const onSuccess = (data: TopUpInputType) => {
topUp({
amount: data.amount,
method: paymentMethod
amount: data.amount
}).then(
(url) => {
window.location.assign(url as string);
Expand All @@ -33,7 +26,7 @@ export default function TopUp() {

return (
<>
<TopUpForm beforeSubmit={beforeSubmit} onSuccess={onSuccess} />
<TopUpForm onSuccess={onSuccess} />

<Snackbar open={open} message={message} severity={severity} onClose={onClose} />
</>
Expand Down
22 changes: 3 additions & 19 deletions app/components/hub/transactions/operations/topUp/TopUpForm.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';

import Image from 'next/image';

import EnhancedTextField from 'app/components/forms/EnhancedTextfield';
import { FORM_ERROR, Form } from 'app/components/forms/Form';
import { TopUpInput, TopUpInputType } from 'app/components/forms/validations';
import { PaymentMethod } from 'app/entities/transactions/mutations/requestTopUp';

type TopUpFormProps = {
onSuccess: (values: TopUpInputType) => void;
beforeSubmit: (paymentMethod: PaymentMethod) => () => void;
};

export default function TopUpForm(props: TopUpFormProps) {
Expand All @@ -36,21 +32,9 @@ export default function TopUpForm(props: TopUpFormProps) {
>
<EnhancedTextField type="number" name="amount" label="Montant" inputProps={{ min: 5, max: 1000, step: 0.01 }} />

<div className="flex justify-center">
<Button type="submit" onClick={props.beforeSubmit('credit')}>
<Image
src="/static/images/logos/mastercard.svg"
width={100}
height={25}
alt="Mastercard logo"
quality={100}
/>
</Button>

<Button type="submit" onClick={props.beforeSubmit('lyf')}>
<Image src="/static/images/logos/lyf.svg" width={100} height={25} alt="Lyf logo" quality={100} />
</Button>
</div>
<Button type="submit" variant="contained">
Recharger
</Button>

<Typography variant="caption" align="center">
Si vous rencontrez un problème lors de votre rechargement, contactez un membre BDE
Expand Down
47 changes: 0 additions & 47 deletions app/core/utils/topup.ts

This file was deleted.

111 changes: 111 additions & 0 deletions app/core/utils/topup/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { BinaryLike, KeyObject, createHmac, randomBytes } from 'crypto';
import { stringify } from 'safe-stable-stringify';
import zod, { string } from 'zod';

export function makeMerchantReference(card: number, timestamp: number) {
const card_prefix = card > 0 ? 'p' : 'm';

return `r${card_prefix}${Math.abs(card)}t${Math.ceil(timestamp)}`;
}

export function makeShopOrderReference(card: number, amount: number) {
const card_prefix = card > 0 ? 'p' : 'm';

return `o${card_prefix}${Math.abs(card)}a${amount}`;
}

const AuthorizationObjectScheme = zod.object({
nonce: zod.string(),
timestamp: zod.number().int().positive(),
id: string().uuid(),
hash: string()
});

export function makeJson(from): string {
return stringify(from)!;
}

function makeNonce() {
return randomBytes(3).toString('hex');
}

function makeHmac(
secret: BinaryLike | KeyObject,
payload: string,
timestamp: number,
path: string,
nonce: string,
method: string,
algorithm: string
): string {
const raw = [method, path, payload, nonce, timestamp].join('+');

return createHmac(algorithm, secret).update(raw).digest('hex');
}

export function makeAuthorizationHeader(
secret: BinaryLike | KeyObject,
posId: string,
payload: string,
timestamp: number,
path: string,
nonce: string = makeNonce(),
method: string = 'POST',
algorithm: string = 'sha512'
): string {
const hmac = makeHmac(secret, payload, timestamp, path, nonce, method, algorithm);

return `MAC ${hmac}, nonce=${nonce}, timestamp=${timestamp}, id=${posId}, hash=${algorithm}`;
}

export function validateAuthorizationHeader(
handledSecrets: Map<string, BinaryLike | KeyObject>,
authorizationString: string,
payload: string,
path: string,
method: string = 'POST'
): boolean {
const [hmac, ...rawParameters] = authorizationString.split(',');
const { nonce, timestamp, id, hash } = AuthorizationObjectScheme.parse(
new Map(rawParameters.map((v) => v.split('=', 1) as [string, string]))
);

if (handledSecrets.has(id)) {
const secret = handledSecrets[id];

const refHmac = makeHmac(secret, payload, timestamp, path, nonce, method, hash);

return hmac == `MAC ${refHmac}`;
} else {
return false;
}
}

// export type TopUpInfo = {
// userId: string;
// card: number;
// amount: number;
// reference: string;
// orderReference: string;
// creationDate: number;
// };

// export function generateTopUpToken(info: TopUpInfo, secret: BinaryLike | KeyObject) {
// let ret = Buffer.from(JSON.stringify(info)).toString('base64url');

// return `${ret}.${createHmac('sha512', secret).update(ret).digest('hex')}`;
// }

// export function parseTopUpToken(token: string, secret: BinaryLike | KeyObject) {
// let data = token.split('.');

// if (data.length != 2) return null;

// if (data[1] != createHmac('sha512', secret).update(data[0]).digest('hex')) return null;

// try {
// return JSON.parse(Buffer.from(data[0], 'base64url').toString('utf8'));
// } catch {
// return null;
// }
// }
Loading