Skip to content

Commit

Permalink
fix(medusa/payment-paytr): Wrong gitignore configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
adrien2p committed Mar 16, 2022
1 parent fc3deba commit 8e07991
Show file tree
Hide file tree
Showing 10 changed files with 372 additions and 10 deletions.
11 changes: 8 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
.idea
/lib
/dist
node_modules
.DS_store
**/.DS_Store

**/.env
**/.env.test
dist
coverage
coverage

/tsconfig.tsbuildinfo
/package-lock.json
/yarn.json

.env.*
10 changes: 5 additions & 5 deletions packages/medusa/payment-paytr/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ node_modules
dist
coverage

api/
services/
/api/
/services/
utils.js
types.js
index.js

./tsconfig.tsbuildinfo
./package-lock.json
./yarn.json
/tsconfig.tsbuildinfo
/package-lock.json
/yarn.json

.env.*
4 changes: 2 additions & 2 deletions packages/medusa/payment-paytr/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
"clean": "./node_modules/.bin/rimraf services api/ utils.* types.* index.*",
"build": "npm run clean && tsc -p tsconfig.json",
"watch": "tsc --watch",
"test": "NODE_ENV=test ./node_modules/.bin/jest -i --collect-coverage && npm run coverage:badges",
"test:local": "NODE_ENV=test ./node_modules/.bin/jest --maxWorkers=75% --collect-coverage && npm run coverage:badges"
"test": "NODE_ENV=test ./node_modules/.bin/jest -i",
"test:local": "NODE_ENV=test ./node_modules/.bin/jest --maxWorkers=75%"
},
"devDependencies": {
"@medusajs/medusa": "1.2.1",
Expand Down
10 changes: 10 additions & 0 deletions packages/medusa/payment-paytr/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Router } from 'express';
import routes from './routes';

export default (): Router => {
const app = Router();

routes(app);

return app;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { NextFunction, Request, Response } from 'express';

export default (
fn: (req: Request, res: Response) => Promise<void>
): ((req: Request, res: Response, next: NextFunction) => Promise<void>) => {
return (req: Request, res: Response, next: NextFunction) => fn(req, res).catch(next);
};
3 changes: 3 additions & 0 deletions packages/medusa/payment-paytr/src/api/middleware/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { default as wrap } from './await-middleware';

export default { wrap };
29 changes: 29 additions & 0 deletions packages/medusa/payment-paytr/src/api/routes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Response, Router } from 'express';
import * as bodyParser from 'body-parser';

import middlewares from '../middleware';
import { CustomRequest } from '../../types';
import PayTRProviderService from '../../services/paytr-provider';

const route = Router();

export default (app: Router): Router => {
app.use('/pay-tr', route);

route.post('/callback', bodyParser.json(), bodyParser.urlencoded({ extended: true }), middlewares.wrap(webhook));

return app;
};

async function webhook(req: CustomRequest, res: Response): Promise<void> {
try {
const data = req.body;

const payTRProviderService = req.scope.resolve('pp_paytr') as PayTRProviderService;
await payTRProviderService.handleCallback(data);

res.send('OK');
} catch (err) {
res.status(400).json({ message: err.message });
}
}
27 changes: 27 additions & 0 deletions packages/medusa/payment-paytr/src/api/store/carts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Response, Router } from 'express';
import PayTRProviderService from '../../services/paytr-provider';
import * as bodyParser from 'body-parser';
import middlewares from '../middleware';
import { CustomRequest } from '../../types';

const router = Router();

export default () => {
router.post('/:cart_id/generate-iframe-url', bodyParser.json(), middlewares.wrap(generateToken));
return router;
};

async function generateToken(req: CustomRequest, res: Response): Promise<void> {
const { cart_id } = req.params;

const payTrProvider = req.scope.resolve<PayTRProviderService>('pp_paytr');

await payTrProvider
.generateToken(cart_id)
.then((token: string) => {
return res.json({ url: 'https://www.paytr.com/odeme/guvenli/' + token });
})
.catch((err) => {
return res.status(400).send({ errorMessage: err.message });
});
}
219 changes: 219 additions & 0 deletions packages/medusa/payment-paytr/src/services/paytr-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import OrderService from '@medusajs/medusa/dist/services/order';
import TotalsService from '@medusajs/medusa/dist/services/totals';
import { Cart, Payment, PaymentSession } from '@medusajs/medusa/dist';
import { CustomerService, RegionService } from '@medusajs/medusa/dist/services';
import { PaymentService } from 'medusa-interfaces';
import { PaymentSessionStatus } from '@medusajs/medusa/dist/models/payment-session';
import { MerchantConfig, PaymentData, PaymentSessionData } from '../types';
import CartService from '@medusajs/medusa/dist/services/cart';
import * as nodeBase64 from 'nodejs-base64-converter';
import { EntityManager } from 'typeorm';
import {
buildAddressFromCart,
buildOid,
buildPaymentToken,
buildPaytrToken,
findPendingPaymentSession,
getCartIdFromOid,
request,
} from '../utils';
import { PaymentSessionRepository } from '@medusajs/medusa/dist/repositories/payment-session';

export default class PayTRProviderService extends PaymentService {
static identifier = 'paytr';

readonly #merchantConfig: MerchantConfig;

readonly #manager: EntityManager;
readonly #paymentSessionRepository: typeof PaymentSessionRepository;
readonly #orderService: OrderService;
readonly #customerService: CustomerService;
readonly #regionService: RegionService;
readonly #totalsService: TotalsService;
readonly #cartService: CartService;

constructor(
{ manager, paymentSessionRepository, cartService, customerService, totalsService, regionService, orderService },
options: MerchantConfig
) {
super();

this.#merchantConfig = options;

this.#manager = manager;
this.#paymentSessionRepository = paymentSessionRepository;
this.#orderService = orderService;
this.#customerService = customerService;
this.#regionService = regionService;
this.#totalsService = totalsService;
this.#cartService = cartService;
}

async generateToken(cartId: string): Promise<string | never> {
const cart = await this.retrieveCart(cartId);
const amount = await this.#totalsService.getTotal(cart);
const { currency_code } = await this.#regionService.retrieve(cart.region_id);
const formattedItems = cart.items.map((item) => [item.title, item.unit_price, item.quantity.toString()]);
const cartToken = nodeBase64.encode(JSON.stringify(formattedItems));
const userIp = cart.context?.ip ?? 'xxx.x.xxx.xxx';
const merchantOid = buildOid(cart.id.split('_').pop());
const payTrToken = await buildPaymentToken({
amount,
orderId: merchantOid,
email: cart.customer?.email,
ip: userIp,
currency_code,
cartToken,
merchantConfig: this.#merchantConfig,
});
const billingAddress = buildAddressFromCart(cart);
/* eslint-disable @typescript-eslint/no-unused-vars */
const { token_endpoint, refund_endpoint, ...config } = this.#merchantConfig;
const data = {
...config,
paytr_token: payTrToken,
no_installment: this.#merchantConfig.no_installment,
max_installment: this.#merchantConfig.max_installment,
payment_amount: amount,
currency: currency_code,
user_name: (cart?.billing_address?.first_name + ' ' + cart?.billing_address?.last_name).trim(),
user_address: billingAddress,
email: cart.customer?.email,
user_phone: cart.billing_address?.phone,
user_ip: userIp,
user_basket: cartToken,
merchant_oid: merchantOid,
lang: cart.customer?.metadata?.lang ?? 'tr',
};

return await request(token_endpoint, data)
.then((res: { token: string }) => {
return res.token;
})
.catch((e) => {
throw new Error(`An error occurred while trying to create the payment.\n${e?.message ?? e}`);
});
}

async createPayment(cart: Cart): Promise<PaymentSessionData> {
const merchantOid = buildOid(cart.id.split('_').pop());
return { merchantOid, isPending: true };
}

async getStatus(data: PaymentData): Promise<PaymentSessionStatus> {
const { status } = data as { status: string | null };

if (!status) {
return PaymentSessionStatus.PENDING;
}

return status === 'success' ? PaymentSessionStatus.AUTHORIZED : PaymentSessionStatus.ERROR;
}

async getPaymentData(sessionData: { data: PaymentSessionData }): Promise<PaymentSessionData> {
return sessionData.data;
}

async authorizePayment(paymentSession: PaymentSession): Promise<{ status: string; data: PaymentSessionData }> {
return { status: 'authorized', data: paymentSession.data };
}

async updatePayment(
sessionData: { data: PaymentSessionData },
updateData: PaymentSessionData
): Promise<PaymentSessionData> {
return {
...sessionData.data,
...updateData,
};
}

async deletePayment(): Promise<void> {
return;
}

async capturePayment(payment: Payment): Promise<PaymentData> {
return payment.data;
}

async refundPayment(payment: Payment, refundAmount: number): Promise<PaymentData> {
const tokenBody =
this.#merchantConfig.merchant_id +
payment.data.merchantOid +
refundAmount +
this.#merchantConfig.merchant_salt;
const token = buildPaytrToken(tokenBody, { merchant_key: this.#merchantConfig.merchant_key });

return await request(this.#merchantConfig.refund_endpoint, {
merchant_id: this.#merchantConfig.merchant_id,
merchant_oid: payment.data.merchantOid,
return_amount: refundAmount,
paytr_token: token,
})
.then(() => {
return payment.data;
})
.catch((e) => {
throw new Error(`An error occurred while trying to refund a payment.\n${e?.message ?? e}`);
});
}

async cancelPayment(payment: Payment): Promise<PaymentData> {
return payment.data;
}

public async handleCallback({
merchant_oid,
status,
total_amount,
hash,
}: {
merchant_oid: string;
status: 'success' | 'error';
total_amount: number;
hash: string;
}): Promise<void | never> {
const tokenBody = merchant_oid + this.#merchantConfig.merchant_salt + status + total_amount;
const token = buildPaytrToken(tokenBody, { merchant_key: this.#merchantConfig.merchant_key });

if (token != hash) {
throw new Error('PAYTR notification failed: bad hash');
}

const cartId = getCartIdFromOid(merchant_oid);
const cart = await this.retrieveCart(cartId);
const pendingPaymentSession = findPendingPaymentSession(cart.payment_sessions, {
merchantOid: merchant_oid,
});
if (!pendingPaymentSession) {
throw new Error('Unable to complete payment session. The payment session was not found.');
}

const paymentSessionRepo = this.#manager.getCustomRepository(this.#paymentSessionRepository);
pendingPaymentSession.data = {
...pendingPaymentSession.data,
isPending: !(status === 'success'),
status,
};
await paymentSessionRepo.save(pendingPaymentSession);
}

async retrieveCart(cartId: string): Promise<Cart> {
return this.#cartService.retrieve(cartId, {
select: ['gift_card_total', 'subtotal', 'tax_total', 'shipping_total', 'discount_total', 'total'],
relations: [
'items',
'discounts',
'discounts.rule',
'discounts.rule.valid_for',
'gift_cards',
'billing_address',
'shipping_address',
'region',
'region.payment_providers',
'payment_sessions',
'customer',
],
});
}
}
Loading

0 comments on commit 8e07991

Please sign in to comment.