forked from adrien2p/medusa-plugins
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(medusa/payment-paytr): Wrong gitignore configuration
- Loading branch information
Showing
10 changed files
with
372 additions
and
10 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
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.* |
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,10 @@ | ||
import { Router } from 'express'; | ||
import routes from './routes'; | ||
|
||
export default (): Router => { | ||
const app = Router(); | ||
|
||
routes(app); | ||
|
||
return app; | ||
}; |
7 changes: 7 additions & 0 deletions
7
packages/medusa/payment-paytr/src/api/middleware/await-middleware.ts
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,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); | ||
}; |
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,3 @@ | ||
import { default as wrap } from './await-middleware'; | ||
|
||
export default { wrap }; |
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,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 }); | ||
} | ||
} |
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,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
219
packages/medusa/payment-paytr/src/services/paytr-provider.ts
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,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', | ||
], | ||
}); | ||
} | ||
} |
Oops, something went wrong.