From f86174bc1170b84beef16e03fd9148d82dbe8114 Mon Sep 17 00:00:00 2001 From: kilian Date: Sat, 4 Oct 2025 17:03:36 +0200 Subject: [PATCH] overhaul of paypal handler WIP --- backend/src/payments/payment_handler.py | 126 ++++++++++++++++++------ backend/src/structs/item_store.py | 4 + 2 files changed, 99 insertions(+), 31 deletions(-) create mode 100644 backend/src/structs/item_store.py diff --git a/backend/src/payments/payment_handler.py b/backend/src/payments/payment_handler.py index c682c79..653cf8b 100644 --- a/backend/src/payments/payment_handler.py +++ b/backend/src/payments/payment_handler.py @@ -1,19 +1,23 @@ from typing import Literal -import paypalrestsdk +import logging +import json + from pydantic import BaseModel from fastapi import HTTPException -import logging - import requests + from ..configuration.environment import Environment # Model for payment request body -class PaymentRequest(BaseModel): +class OrderDetails(): + user_id: str - credit_amount: Literal[10, 50, 100] - currency: Literal["USD", "EUR", "CHF"] - description: str = "Purchase of credits" + number_of_credits: Literal[10, 50, 100] + unit_price: float + amount: int + currency: Literal['USD', 'EUR', 'CHF'] + # Payment handler class for managing PayPal payments @@ -24,52 +28,110 @@ class PaypalHandler: password = Environment.paypal_key_sandbox - def __init__( self, - transaction_details: PaymentRequest, + order_details: OrderDetails, sandbox_mode: bool = False ): - self.details = transaction_details + # Initialize the logger self.logger = logging.getLogger(__name__) + + # Payment request parameters + self.order_details = order_details self.sandbox = sandbox_mode # Only support purchase of credit 'bundles': 10, 50 or 100 credits worth of trip generation - def fetch_price(self) -> float: - """ - Fetches the price of credits in the specified currency. - """ - result = self.supabase.table("prices").select("credit_amount").eq("currency", self.details.currency).single().execute() - if result.data: - return result.data.get("price") - else: - self.logger.error(f"Unsupported currency: {self.details.currency}") - return None + # def fetch_price(self) -> float: + # ''' + # Fetches the price of credits in the specified currency. + # ''' + # result = self.supabase.table('prices').select('credit_amount').eq('currency', self.details.currency).single().execute() + # if result.data: + # return result.data.get('price') + # else: + # self.logger.error(f'Unsupported currency: {self.details.currency}') + # return None def validate(self): if self.sandbox : - validation_url = "https://api-m.sandbox.paypal.com/v1/oauth2/token" + validation_url = 'https://api-m.sandbox.paypal.com/v1/oauth2/token' else : - validation_url = "https://api-m.paypal.com/v1/oauth2/token" + validation_url = 'https://api-m.paypal.com/v1/oauth2/token' # payload for the validation request validation_data = {'grant_type': 'client_credentials'} - # pass the request - validation_response = requests.post( - url=validation_url, - data=validation_data, - auth=(self.username, self.password) - ) + try: + # pass the request + validation_response = requests.post( + url=validation_url, + data=validation_data, + auth=(self.username, self.password) + ) - # TODO: continue here + except Exception as exc: + self.logger.error(f'Error while requesting access token: {exc}') + return None + + if validation_response.status_code == 201 : + access_token = json.loads(validation_response.text)['access_token'] + self.logger.info('Validation step successful. Returning access token.') + return access_token + + self.logger.error(f'Error {validation_response.status_code} while requesting access token: {validation_response.text}') + return None - pass + def order( + self, + access_token: int + ): - def order(self): + if self.sandbox : + order_url = 'https://api-m.sandbox.paypal.com/v2/checkout/orders' + else : + order_url = 'https://api-m.paypal.com/v2/checkout/orders' + + # payload for the order equest + order_data = { + 'intent': 'CAPTURE', + 'purchase_units': [ + { + 'items': [ + { + 'name': f'{self.order_details.number_of_credits} Credits Pack', + 'description': f'Credits for {self.order_details.number_of_credits} trip generations on AnyWay.', + 'quantity': self.order_details.amount, + 'unit_amount': { + 'currency_code': self.order_details.currency, + 'value': self.order_details.unit_price + } + + } + ], + 'amount': { + 'currency_code': self.order_details.currency, + 'value': self.order_details.amount*self.order_details.unit_price, + # 'breakdown': { + # 'item_total': { + # 'currency_code': 'CHF', + # 'value': '5.00' + # } + # } + ## what is that for ? + } + } + ], + # TODO: add these to anydev website somehow + 'application_context': { + 'return_url': 'https://anydev.info', + 'cancel_url': 'https://anydev.info' + } + } + + # TODO continue here pass @@ -77,6 +139,8 @@ class PaypalHandler: def capture(self): + + pass diff --git a/backend/src/structs/item_store.py b/backend/src/structs/item_store.py new file mode 100644 index 0000000..1a230f6 --- /dev/null +++ b/backend/src/structs/item_store.py @@ -0,0 +1,4 @@ +"""This module contains the descriptions of items to be purchased in the AnyWay store.""" + + +