|
|
import { BotContext } from '../types/botTypes'; |
|
|
import axios from 'axios'; |
|
|
import { createLogger } from '../../utils/logger'; |
|
|
|
|
|
const logger = createLogger('PayPalService'); |
|
|
|
|
|
|
|
|
interface PayPalLink { |
|
|
href: string; |
|
|
rel: string; |
|
|
method: string; |
|
|
} |
|
|
|
|
|
interface PayPalOrder { |
|
|
id: string; |
|
|
status: string; |
|
|
links: PayPalLink[]; |
|
|
} |
|
|
|
|
|
interface PayPalTokenResponse { |
|
|
access_token: string; |
|
|
token_type: string; |
|
|
expires_in: number; |
|
|
} |
|
|
|
|
|
export class PayPalService { |
|
|
private static instance: PayPalService; |
|
|
private readonly clientId: string; |
|
|
private readonly clientSecret: string; |
|
|
private readonly isProduction: boolean; |
|
|
|
|
|
private constructor(ctx: BotContext) { |
|
|
this.clientId = ctx.botData?.paypal_client_id || ''; |
|
|
this.clientSecret = ctx.botData?.paypal_client_secret || ''; |
|
|
this.isProduction = ctx.botData?.settings?.paypal_environment === 'production'; |
|
|
} |
|
|
|
|
|
public static getInstance(ctx: BotContext): PayPalService { |
|
|
if (!PayPalService.instance) { |
|
|
PayPalService.instance = new PayPalService(ctx); |
|
|
} |
|
|
return PayPalService.instance; |
|
|
} |
|
|
|
|
|
async createPaymentLink(userId: string, amount: number): Promise<string> { |
|
|
try { |
|
|
const accessToken = await this.getAccessToken(); |
|
|
const order = await this.createPayPalOrder(amount, userId); |
|
|
|
|
|
|
|
|
const approvalLink = order.links.find(link => link.rel === 'approve'); |
|
|
if (!approvalLink) { |
|
|
throw new Error('No approval URL found in PayPal order'); |
|
|
} |
|
|
|
|
|
return approvalLink.href; |
|
|
} catch (error: any) { |
|
|
logger.error(`Error creating PayPal payment link: ${error.message}`); |
|
|
throw new Error(`Failed to create payment link: ${error.message}`); |
|
|
} |
|
|
} |
|
|
|
|
|
private async createPayPalOrder(amount: number, paymentId: string): Promise<PayPalOrder> { |
|
|
try { |
|
|
const accessToken = await this.getAccessToken(); |
|
|
const baseUrl = this.isProduction |
|
|
? 'https://api-m.paypal.com' |
|
|
: 'https://api-m.sandbox.paypal.com'; |
|
|
|
|
|
const response = await axios.post( |
|
|
`${baseUrl}/v2/checkout/orders`, |
|
|
{ |
|
|
intent: 'CAPTURE', |
|
|
purchase_units: [{ |
|
|
amount: { |
|
|
currency_code: 'USD', |
|
|
value: amount.toString() |
|
|
}, |
|
|
custom_id: paymentId |
|
|
}] |
|
|
}, |
|
|
{ |
|
|
headers: { |
|
|
'Authorization': `Bearer ${accessToken}`, |
|
|
'Content-Type': 'application/json' |
|
|
} |
|
|
} |
|
|
); |
|
|
|
|
|
return response.data; |
|
|
} catch (error: any) { |
|
|
logger.error(`Error creating PayPal order: ${error.message}`); |
|
|
throw new Error(`Failed to create PayPal order: ${error.message}`); |
|
|
} |
|
|
} |
|
|
|
|
|
private async getAccessToken(): Promise<string> { |
|
|
try { |
|
|
const baseUrl = this.isProduction |
|
|
? 'https://api-m.paypal.com' |
|
|
: 'https://api-m.sandbox.paypal.com'; |
|
|
|
|
|
const response = await axios.post( |
|
|
`${baseUrl}/v1/oauth2/token`, |
|
|
'grant_type=client_credentials', |
|
|
{ |
|
|
headers: { |
|
|
'Authorization': `Basic ${Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64')}`, |
|
|
'Content-Type': 'application/x-www-form-urlencoded' |
|
|
} |
|
|
} |
|
|
); |
|
|
|
|
|
const data = response.data as PayPalTokenResponse; |
|
|
return data.access_token; |
|
|
} catch (error: any) { |
|
|
logger.error(`Error getting PayPal access token: ${error.message}`); |
|
|
throw new Error(`Failed to get PayPal access token: ${error.message}`); |
|
|
} |
|
|
} |
|
|
} |