import { HttpService, Injectable } from '@nestjs/common'; import { map } from 'rxjs/operators'; import { User } from '../user/user.entity'; import { BTTokenDTO, ExchangingDataDTO, MakePaymentDTO, PaymentInfoDTO, RegisterBTClientDTO } from './payment.dto'; import { v4 as uuidv4 } from 'uuid'; import sha256 from 'crypto-js/sha256'; import Base64 from 'crypto-js/enc-base64'; import qs from 'querystring'; import { PaymentRepository } from './payment.repository'; import { PaymentCredentials } from './payment-credentials.entity'; import { plainToClass } from 'class-transformer'; /* eslint-disable camelcase */ @Injectable() export class BTPaymentService { constructor(private httpService: HttpService, private paymentRepository: PaymentRepository) {} async registerBToAuthClient(currentUser: User): Promise { const payload = { redirect_uris: [process.env.BT_REDIRECT_URI], company_name: 'TPP Corp.', client_name: currentUser.name, company_url: 'https://google.com', contact_person: currentUser.name, email_address: currentUser.email, phone_number: currentUser.phone, }; const options = { headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, }; return await this.httpService .post(`${process.env.BT_PAYMENT_ENDPOINT}/oauth/register/TppOauthBT`, payload, options) .pipe(map((response) => response.data)) .toPromise(); } async create(user: User, register: RegisterBTClientDTO): Promise { const credentials = await this.paymentRepository.findOne({ where: { user, bank: 'BT' } }); if (!credentials) { const newCredentials = new PaymentCredentials(); newCredentials.user = user; newCredentials.bank = 'BT'; newCredentials.clientId = register.client_id; newCredentials.clientSecret = register.client_secret; return await this.paymentRepository.save(newCredentials); } const newCredentials = plainToClass(PaymentCredentials, { id: credentials.id, clientId: register.client_id, clientSecret: register.client_secret, }); return await this.paymentRepository.save(newCredentials); } async makePayment(paymentInfo: PaymentInfoDTO): Promise { const payload = { instructedAmount: { currency: paymentInfo.currency, amount: `${paymentInfo.amount}`, }, // TODO: handle the below details. creditorAccount: { iban: 'RO98BTRLEURCRT0ABCDEFGHI', }, creditorName: 'Creditor Name', debtorId: 'J123456', endToEndIdentification: 'TPP Reference', remittanceInformationUnstructured: 'Merchant reference', }; const options = { headers: { Accept: 'application/json', 'Content-Type': 'application/json', // TODO: Location and IP address should be handled. 'PSU-Geo-Location': 'Romania', 'PSU-IP-Address': '22.33.44.55', 'X-Request-ID': uuidv4(), }, }; return await this.httpService .post(`${process.env.BT_PAYMENT_ENDPOINT}/bt-psd2/v2/payments/ron-payment`, payload, options) .pipe(map((response) => response.data)) .toPromise(); } async buildAuthUri(register: RegisterBTClientDTO, payment: MakePaymentDTO, codeVerifier: string): Promise { /* https://apistorebt.ro/mga/sps/oauth/oauth20/authorize? ?response_type=code &client_id= &redirect_uri= &scope=PIS: &state= &code_challenge= &code_challenge_method=S256 */ const params = { response_type: 'code', client_id: register.client_id, redirect_uri: process.env.BT_REDIRECT_URI, scope: `PIS:${payment.paymentId}`, state: 'statetest', // TODO: State code should be generated and verified after BT redirects back. code_challenge: await this.computeCodeChallange(codeVerifier), code_challenge_method: 'S256', }; const authUri = Object.keys(params) .map((key) => key + '=' + params[key]) .join('&'); return authUri; } async getToken(user: User, exchangingData: ExchangingDataDTO): Promise { const credentials = await this.paymentRepository.findOne({ where: { user, bank: 'BT' } }); const payload = qs.stringify({ code: exchangingData.code, grant_type: 'authorization_code', redirect_uri: process.env.BT_REDIRECT_URI, client_id: credentials.clientId, client_secret: credentials.clientSecret, code_verifier: process.env.BT_CODE_VERIFIER, }); const options = { headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded', }, }; return await this.httpService .post(`${process.env.BT_PAYMENT_ENDPOINT}/oauth/token`, payload, options) .pipe(map((response) => response.data)) .toPromise(); } async updateToken(user: User, tokenData: BTTokenDTO): Promise { const credentials = await this.paymentRepository.findOne({ where: { user, bank: 'BT' } }); credentials.accessToken = tokenData.access_token; credentials.refreshToken = tokenData.refresh_token; const expiresAt = new Date(); expiresAt.setSeconds(parseInt(expiresAt.getSeconds() + tokenData.expires_in)); credentials.expiresAt = expiresAt; return await this.paymentRepository.save(credentials); } async computeCodeChallange(codeVerifier: string): Promise { return await sha256(codeVerifier).toString(Base64).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); } }