starting to implement paywall logic
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Run testing on the backend code / Build (pull_request) Has been cancelled
				
			
		
			
				
	
				Build and deploy the backend to staging / Deploy to staging (pull_request) Has been cancelled
				
			
		
			
				
	
				Build and deploy the backend to staging / Build and push image (pull_request) Has been cancelled
				
			
		
			
				
	
				Run linting on the backend code / Build (pull_request) Has been cancelled
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Run testing on the backend code / Build (pull_request) Has been cancelled
				
			Build and deploy the backend to staging / Deploy to staging (pull_request) Has been cancelled
				
			Build and deploy the backend to staging / Build and push image (pull_request) Has been cancelled
				
			Run linting on the backend code / Build (pull_request) Has been cancelled
				
			This commit is contained in:
		| @@ -12,6 +12,14 @@ LANDMARK_PARAMETERS_PATH = PARAMETERS_DIR / 'landmark_parameters.yaml' | ||||
| OPTIMIZER_PARAMETERS_PATH = PARAMETERS_DIR / 'optimizer_parameters.yaml' | ||||
|  | ||||
|  | ||||
| PAYPAL_CLIENT_ID = os.getenv("your-paypal-client-id") | ||||
| PAYPAL_SECRET = os.getenv("your-paypal-secret") | ||||
| PAYPAL_API_URL = "https://api-m.sandbox.paypal.com" | ||||
|  | ||||
| SUPABASE_URL = os.getenv("your-supabase-url") | ||||
| SUPABASE_KEY = os.getenv("your-supabase-api-key") | ||||
|  | ||||
|  | ||||
| cache_dir_string = os.getenv('OSM_CACHE_DIR', './cache') | ||||
| OSM_CACHE_DIR = Path(cache_dir_string) | ||||
|  | ||||
|   | ||||
| @@ -16,6 +16,7 @@ from .optimization.optimizer import Optimizer | ||||
| from .optimization.refiner import Refiner | ||||
| from .overpass.overpass import fill_cache | ||||
| from .cache import client as cache_client | ||||
| from .payments.payment_routes import router as payment_router | ||||
|  | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
| @@ -37,6 +38,10 @@ async def lifespan(app: FastAPI): | ||||
| app = FastAPI(lifespan=lifespan) | ||||
|  | ||||
|  | ||||
| # Include the payment routes | ||||
| app.include_router(payment_router, prefix="/payments") | ||||
|  | ||||
|  | ||||
| @app.post("/trip/new") | ||||
| def new_trip(preferences: Preferences, | ||||
|              start: tuple[float, float], | ||||
|   | ||||
							
								
								
									
										49
									
								
								backend/src/payments/payment_routes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								backend/src/payments/payment_routes.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| from os import getenv | ||||
| from fastapi import APIRouter, HTTPException | ||||
| from .paypal import create_paypal_order, capture_paypal_order | ||||
| from supabase import create_client, Client | ||||
|  | ||||
| from ..constants import SUPABASE_URL, SUPABASE_KEY | ||||
|  | ||||
|  | ||||
| # Initialize Supabase | ||||
| supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY) | ||||
|  | ||||
|  | ||||
| # Define router | ||||
| router = APIRouter() | ||||
|  | ||||
|  | ||||
| @router.post("/create-paypal-payment/") | ||||
| def create_payment(amount: float, currency: str = "USD"): | ||||
|     """ | ||||
|     Create a PayPal payment and return approval URL. | ||||
|     """ | ||||
|     try: | ||||
|         payment = create_paypal_order(amount, currency) | ||||
|         return {"approval_url": payment["approval_url"]} | ||||
|     except Exception as e: | ||||
|         raise HTTPException(status_code=500, detail=str(e)) | ||||
|  | ||||
|  | ||||
| @router.post("/capture-paypal-payment/") | ||||
| def capture_payment(order_id: str, user_id: str, country: str): | ||||
|     """ | ||||
|     Capture the PayPal payment and unlock the country in Supabase. | ||||
|     """ | ||||
|     try: | ||||
|         # Capture payment | ||||
|         capture_response = capture_paypal_order(order_id) | ||||
|         if capture_response.get("status") == "COMPLETED": | ||||
|             # Update Supabase to unlock the country for the user | ||||
|             supabase.table("unlocked_countries").insert({ | ||||
|                 "user_id": user_id, | ||||
|                 "country": country, | ||||
|                 "paid": True, | ||||
|             }).execute() | ||||
|  | ||||
|             return {"status": "Payment captured and country unlocked successfully"} | ||||
|         else: | ||||
|             raise HTTPException(status_code=400, detail="Payment not completed") | ||||
|     except Exception as e: | ||||
|         raise HTTPException(status_code=500, detail=str(e)) | ||||
							
								
								
									
										87
									
								
								backend/src/payments/paypal.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								backend/src/payments/paypal.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | ||||
| from requests import post | ||||
| from fastapi import HTTPException | ||||
|  | ||||
| from ..constants import PAYPAL_API_URL, PAYPAL_CLIENT_ID, PAYPAL_SECRET | ||||
|  | ||||
|  | ||||
| def get_paypal_access_token(): | ||||
|     """ | ||||
|     Get an access token from PayPal to authenticate API requests. | ||||
|     """ | ||||
|     response = post( | ||||
|         f"{PAYPAL_API_URL}/v1/oauth2/token", | ||||
|         headers={ | ||||
|             "Accept": "application/json", | ||||
|             "Accept-Language": "en_US", | ||||
|         }, | ||||
|         data={"grant_type": "client_credentials"}, | ||||
|         auth=(PAYPAL_CLIENT_ID, PAYPAL_SECRET), | ||||
|     ) | ||||
|  | ||||
|     if response.status_code != 200: | ||||
|         raise HTTPException(status_code=response.status_code, detail="Failed to get PayPal access token") | ||||
|      | ||||
|     return response.json()["access_token"] | ||||
|  | ||||
|  | ||||
| def create_paypal_order(amount: float, currency: str = "USD"): | ||||
|     """ | ||||
|     Create a PayPal payment order. | ||||
|     """ | ||||
|     access_token = get_paypal_access_token() | ||||
|  | ||||
|     order_data = { | ||||
|         "intent": "CAPTURE", | ||||
|         "purchase_units": [ | ||||
|             { | ||||
|                 "amount": { | ||||
|                     "currency_code": currency, | ||||
|                     "value": f"{amount:.2f}", | ||||
|                 } | ||||
|             } | ||||
|         ], | ||||
|         "application_context": { | ||||
|             "return_url": "https://placeholderlink.com/payment-success", | ||||
|             "cancel_url": "https://placeholderlink.com/payment-cancelled", | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     response = post( | ||||
|         f"{PAYPAL_API_URL}/v2/checkout/orders", | ||||
|         headers={ | ||||
|             "Content-Type": "application/json", | ||||
|             "Authorization": f"Bearer {access_token}", | ||||
|         }, | ||||
|         json=order_data, | ||||
|     ) | ||||
|  | ||||
|     if response.status_code != 201: | ||||
|         raise HTTPException(status_code=response.status_code, detail="Failed to create PayPal order") | ||||
|  | ||||
|     order = response.json() | ||||
|     approval_url = next(link['href'] for link in order['links'] if link['rel'] == 'approve') | ||||
|  | ||||
|     return { | ||||
|         "approval_url": approval_url, | ||||
|         "order_id": order["id"], | ||||
|     } | ||||
|  | ||||
|  | ||||
| def capture_paypal_order(order_id: str): | ||||
|     """ | ||||
|     Capture the PayPal payment order after user approval. | ||||
|     """ | ||||
|     access_token = get_paypal_access_token() | ||||
|  | ||||
|     response = post( | ||||
|         f"{PAYPAL_API_URL}/v2/checkout/orders/{order_id}/capture", | ||||
|         headers={ | ||||
|             "Content-Type": "application/json", | ||||
|             "Authorization": f"Bearer {access_token}", | ||||
|         }, | ||||
|     ) | ||||
|  | ||||
|     if response.status_code != 201: | ||||
|         raise HTTPException(status_code=response.status_code, detail="Failed to capture PayPal payment") | ||||
|  | ||||
|     return response.json() | ||||
							
								
								
									
										53
									
								
								backend/src/payments/region_access.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								backend/src/payments/region_access.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| from fastapi import HTTPException, status | ||||
| from supabase import create_client, Client | ||||
|  | ||||
| from ..constants import SUPABASE_URL, SUPABASE_KEY | ||||
|  | ||||
|  | ||||
| # Initialize Supabase client | ||||
| supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY) | ||||
|  | ||||
| def is_loc_in_country(location: tuple[float, float], country: str) -> bool: | ||||
|     """ | ||||
|     TODO: needs to be implemented. | ||||
|     """ | ||||
|     pass | ||||
|  | ||||
|  | ||||
| def check_region_access(user_id: str, start_location: tuple[float, float]) -> None: | ||||
|     """ | ||||
|     Checks if the user has access to the region where `start_location` is located. | ||||
|  | ||||
|     Args: | ||||
|         user_id (str): The ID of the current user. | ||||
|         start_location (tuple): The starting location of the trip (lat, lon). | ||||
|  | ||||
|     Raises: | ||||
|         HTTPException: If the region is locked and the user has not unlocked it. | ||||
|     """ | ||||
|  | ||||
|     # Define the locked countries (regions that require payment) | ||||
|     locked_countries = ["FR", "DE", "ES"]  # Example list of locked countries. Needs to be adapted also. | ||||
|  | ||||
|     # Iterate over locked countries and check if the start location is in a locked region | ||||
|     for country in locked_countries: | ||||
|         if is_loc_in_country(start_location, country): | ||||
|             # Query Supabase to check if the user has unlocked this country | ||||
|             response = supabase \ | ||||
|                 .from_("unlocked_countries") \ | ||||
|                 .select(f"{country}") \ | ||||
|                 .eq("user_id", user_id) \ | ||||
|                 .single() \ | ||||
|                 .execute() | ||||
|  | ||||
|             # Check if the country is unlocked (True) or locked (False or missing) | ||||
|             if response.data and response.data.get(country) is True: | ||||
|                 # Country is unlocked; continue | ||||
|                 continue | ||||
|             else: | ||||
|                 # Raise an exception if the country is not unlocked | ||||
|                 raise HTTPException( | ||||
|                     status_code=status.HTTP_403_FORBIDDEN, | ||||
|                     detail=f"Access to the region {country} is locked. Please unlock it to continue." | ||||
|                 ) | ||||
|  | ||||
		Reference in New Issue
	
	Block a user