File size: 3,581 Bytes
d16c049
 
80d4bc1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d16c049
 
 
 
80d4bc1
 
d16c049
80d4bc1
d16c049
80d4bc1
 
 
 
 
 
d16c049
 
80d4bc1
d16c049
 
 
 
80d4bc1
d16c049
 
 
 
 
80d4bc1
 
 
 
d16c049
 
 
 
 
 
 
 
 
80d4bc1
 
 
 
 
 
 
 
d16c049
 
 
 
 
 
 
 
 
 
 
 
 
80d4bc1
 
 
 
d16c049
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80d4bc1
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import { BotContext } from '../types/botTypes';
import axios from 'axios';
import { createLogger } from '../../utils/logger';

const logger = createLogger('PayPalService');

// PayPal API Response Types
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);
      
      // Find the approval URL from the order links
      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}`);
    }
  }
}