| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- import hashlib
- import hmac
- import json
- import logging
- import os
- from typing import NoReturn
- from pathlib import Path
- import httpx
- from fastapi import HTTPException
- from fastapi.encoders import jsonable_encoder
- from pydantic import ValidationError
- from app.schema import CartSession, NowPaymentsInvoice, ShippingCostEntry, ShippingRate
- logger = logging.getLogger(__name__)
- def prepare_stripe_data(cart: CartSession) -> list[str, int]:
- """Returns list of items ready for Stripe to be processed.
- Each item is in the form of {price: <price_id>, quantity: <amount>}
- list_items = [
- {
- # Provide the exact Price ID (for example, pr_1234) of the product you want to sell
- 'price': ,
- 'quantity': 0
- }
- ]
- """
- list_items = []
- for item in cart.items.values():
- item = {
- "price": item.product_id,
- "quantity": item.quantity,
- }
- list_items.append(item)
- return list_items
- def is_country_in_europe(country: str) -> bool | NoReturn:
- """
- Check if given country is part of a list of countries in Europe.
- """
- dir_path = Path(__file__).resolve().parent
- europe_db_filepath = Path(f"{dir_path}/europe.json")
- try:
- with open(europe_db_filepath, encoding='utf-8') as file:
- europe_db = json.load(file)
- if country in europe_db['countries']:
- return True
- return False
- except FileNotFoundError as error:
- logger.error(f"is-country-in-europe => {error}")
-
- def calculate_shipping_cost(country: str, weight: int, currency: str):
- """Returns appropriate shipping price based on given Country and product's Weight."""
- dir_path = Path(__file__).resolve().parent
- shipping_cost_db_filepath = Path(f"{dir_path}/shipping-cost.json")
- try:
- with open(shipping_cost_db_filepath, encoding='utf-8') as f:
- shipping_cost_db = json.load(f)
- area = None
- if country == "Switzerland":
- area = country
-
- elif is_country_in_europe(country):
- area = "Europe"
-
- else:
- area = "International"
-
- shipping_rate = None
- for entry in shipping_cost_db['areas'][area]:
- try:
- entry = ShippingCostEntry(**entry)
- if (
- entry.max is None and weight >= entry.min or
- entry.min <= weight <= entry.max
- ):
- shipping_rate = ShippingRate(price=entry.price, area=area)
-
- except ValidationError as e:
- logger.error("pydantic validation errors =>")
- for e in e.errors():
- logger.error(f"error => {e}")
-
- return shipping_rate
- except FileNotFoundError as error:
- logger.error(f"calculate-shipping-cost => {error}")
-
- def prepare_shipping_rate(country: str, weight: int, currency: str):
- """
- """
- shipping_rate = calculate_shipping_cost(country, weight, currency)
- # Stripe needs shipping price set in cents as an integer
- shipping_rate.price = int(shipping_rate.price * 100)
- return {
- "shipping_rate_data": {
- "type": "fixed_amount",
- "fixed_amount": {"amount": shipping_rate.price, "currency": currency},
- "display_name": shipping_rate.area,
- }
- }
- async def generate_payment_link(data: NowPaymentsInvoice) -> tuple[str, str] | NoReturn:
- """Generate payment link for NOW Payments crypto payment service.
- See <https://documenter.getpostman.com/view/7907941/2s93JusNJt#f5e4e645-dce2-4b06-b2ca-2a29aaa5e845>.
- """
- environment = os.getenv("ENV")
- API_URL = "https://api.nowpayments.io/v1/"
- NOW_API_KEY = os.getenv("NOWPAYMENTS_API_KEY")
- if environment != "production":
- API_URL = "https://api-sandbox.nowpayments.io/v1/"
- NOW_API_KEY = os.getenv("NOWPAYMENTS_SANDBOX_API_KEY")
- ENDPOINT = "invoice"
- headers = {"x-api-key": NOW_API_KEY}
- async with httpx.AsyncClient(headers=headers) as client:
- resp = await client.post(f"{API_URL}{ENDPOINT}", json=jsonable_encoder(data))
- try:
- resp.raise_for_status()
- if resp.status_code == 200:
- data = resp.json()
- return data["invoice_url"], data["id"]
- except httpx.HTTPStatusError as exc:
- logger.error(f"Error {exc.response.status_code}: {exc.response.text}")
- raise HTTPException(
- status_code=400, detail="NOWPayments link cannot be generated."
- )
- def ipn_signature_check(
- np_secret_key: str, np_x_signature: str, payload: dict[str]
- ) -> bool:
- """Signature verification function as part of the NOW Paymnents's
- Instant Payment Notifications (IPN) webhook workflow.
- See <https://documenter.getpostman.com/view/7907941/2s93JusNJt>.
- """
- sorted_msg = json.dumps(payload, separators=(",", ":"), sort_keys=True)
- digest = hmac.new(
- str(np_secret_key).encode(), f"{sorted_msg}".encode(), hashlib.sha512
- )
- signature = digest.hexdigest()
- if signature == np_x_signature:
- return True
- else:
- return False
|