Initial commit

This commit is contained in:
Abdulaziz Axmadaliyev
2026-02-26 16:35:47 +05:00
commit 92165edbe6
2984 changed files with 629155 additions and 0 deletions

0
app/services/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,15 @@
import base64
import json
from app.core.config import settings
def generate_payme_checkout_url(order_id: int, amount: str):
data = {
"m": settings.PAYME_MERCHANT_ID,
"ac.order": str(order_id),
"a": int(float(amount) * 100),
}
encoded = base64.b64encode(json.dumps(data).encode()).decode()
return f"https://checkout.paycom.uz/{encoded}"

View File

@@ -0,0 +1,110 @@
import time
from sqlalchemy.orm import Session
from app.services.transaction_service import (
get_transaction_by_payme_id,
create_transaction,
update_transaction_state,
)
from app.services.shopify_service import mark_order_paid, get_shopify_order
from app.core.constants import (
STATE_CREATED,
STATE_COMPLETED,
STATE_CANCELLED,
ERROR_TRANSACTION_NOT_FOUND,
)
async def check_perform_transaction(params: dict) -> dict:
order_id = params["account"]["order"]
payme_amount = params["amount"] # tiyin
order = await get_shopify_order(order_id)
if not order:
return {"allow": False}
# Draft orders use "total_price", completed orders too — same field name
shopify_amount = int(float(order["total_price"]) * 100)
if shopify_amount != payme_amount:
return {"allow": False}
# Draft: status field is "status" not "financial_status"
if order.get("_is_draft"):
already_paid = order.get("status") == "completed"
else:
already_paid = order.get("financial_status") == "paid"
if already_paid:
return {"allow": False}
return {"allow": True}
async def create_transaction_handler(db: Session, params: dict) -> dict:
payme_id = params["id"]
order_id = params["account"]["order"]
amount = params["amount"]
existing = get_transaction_by_payme_id(db, payme_id)
if existing:
return {
"create_time": int(time.time() * 1000),
"transaction": payme_id,
"state": existing.state,
}
create_transaction(db, order_id, payme_id, amount)
return {
"create_time": int(time.time() * 1000),
"transaction": payme_id,
"state": STATE_CREATED,
}
async def perform_transaction_handler(db: Session, params: dict) -> dict | None:
payme_id = params["id"]
transaction = get_transaction_by_payme_id(db, payme_id)
if not transaction:
return None
if transaction.state == STATE_COMPLETED:
return {
"perform_time": int(time.time() * 1000),
"transaction": payme_id,
"state": STATE_COMPLETED,
}
update_transaction_state(db, transaction, STATE_COMPLETED)
# Fetch order to know if it's a draft
order = await get_shopify_order(transaction.order_id)
is_draft = order.get("_is_draft", False) if order else False
amount_uzs = str(transaction.amount / 100)
await mark_order_paid(transaction.order_id, amount_uzs, is_draft=is_draft)
return {
"perform_time": int(time.time() * 1000),
"transaction": payme_id,
"state": STATE_COMPLETED,
}
async def cancel_transaction_handler(db: Session, params: dict) -> dict | None:
payme_id = params["id"]
transaction = get_transaction_by_payme_id(db, payme_id)
if not transaction:
return None
update_transaction_state(db, transaction, STATE_CANCELLED)
return {
"cancel_time": int(time.time() * 1000),
"transaction": payme_id,
"state": STATE_CANCELLED,
}

View File

@@ -0,0 +1,63 @@
import httpx
from app.core.config import settings
SHOPIFY_API = f"https://{settings.SHOPIFY_STORE_URL}/admin/api/2024-01"
HEADERS = {
"X-Shopify-Access-Token": settings.SHOPIFY_ACCESS_TOKEN,
"Content-Type": "application/json",
}
async def get_shopify_order(order_id: str) -> dict | None:
"""
Tries regular orders first, falls back to draft orders.
Returns the order dict or None.
"""
async with httpx.AsyncClient() as client:
# 1. Try regular order
r = await client.get(f"{SHOPIFY_API}/orders/{order_id}.json", headers=HEADERS)
if r.status_code == 200:
order = r.json().get("order")
if order:
order["_is_draft"] = False
return order
# 2. Try draft order
r = await client.get(f"{SHOPIFY_API}/draft_orders/{order_id}.json", headers=HEADERS)
if r.status_code == 200:
order = r.json().get("draft_order")
if order:
order["_is_draft"] = True
return order
return None
async def mark_order_paid(order_id: str, amount: str, is_draft: bool = False) -> dict:
"""
For draft orders → complete the draft (converts it to a real order, marks paid).
For real orders → post a capture transaction.
"""
async with httpx.AsyncClient() as client:
if is_draft:
# Complete the draft order — this marks it as paid in Shopify
r = await client.post(
f"{SHOPIFY_API}/draft_orders/{order_id}/complete.json",
headers=HEADERS,
params={"payment_pending": False}, # False = mark as paid immediately
)
return r.json()
else:
# Regular order — post a capture transaction
r = await client.post(
f"{SHOPIFY_API}/orders/{order_id}/transactions.json",
headers=HEADERS,
json={
"transaction": {
"kind": "capture",
"status": "success",
"amount": amount,
}
},
)
return r.json()

View File

@@ -0,0 +1,29 @@
from sqlalchemy.orm import Session
from app.models.transaction import Transaction
from app.core.constants import *
def get_transaction_by_payme_id(db: Session, payme_id: str):
return db.query(Transaction).filter(
Transaction.payme_transaction_id == payme_id
).first()
def create_transaction(db: Session, order_id: str, payme_id: str, amount: int):
transaction = Transaction(
order_id=order_id,
payme_transaction_id=payme_id,
amount=amount,
state=STATE_CREATED,
)
db.add(transaction)
db.commit()
db.refresh(transaction)
return transaction
def update_transaction_state(db: Session, transaction: Transaction, state: int):
transaction.state = state
db.commit()
db.refresh(transaction)
return transaction