feat(pally): support customer-pays-fees scenario with BalanceAmount validation
This commit is contained in:
@@ -51,8 +51,8 @@ async def pally_callback(
|
|||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"Pally webhook received - InvId: %s, OutSum: %s, Commission: %s, TrsId: %s, Status: %s, "
|
"Pally webhook received - InvId: %s, OutSum: %s, Commission: %s, TrsId: %s, Status: %s, "
|
||||||
"CurrencyIn: %s, custom: %s, SignatureValue: %s",
|
"CurrencyIn: %s, custom: %s, BalanceAmount: %s, SignatureValue: %s",
|
||||||
InvId, OutSum, Commission, TrsId, Status, CurrencyIn, custom, SignatureValue
|
InvId, OutSum, Commission, TrsId, Status, CurrencyIn, custom, BalanceAmount, SignatureValue
|
||||||
)
|
)
|
||||||
|
|
||||||
# Enhanced logging for failed payments
|
# Enhanced logging for failed payments
|
||||||
@@ -105,9 +105,37 @@ async def pally_callback(
|
|||||||
return "OK"
|
return "OK"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Handle fee scenarios: use BalanceAmount if available (net amount after fees),
|
||||||
|
# otherwise use OutSum (gross amount paid by customer)
|
||||||
|
if BalanceAmount is not None:
|
||||||
|
# Customer pays fees - BalanceAmount is the net amount credited to merchant
|
||||||
|
credited_amount = int(float(BalanceAmount))
|
||||||
|
gross_amount = int(float(OutSum))
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"Customer-pays-fees payment: bill_id=%s, expected=%s, gross_paid=%s, net_credited=%s",
|
||||||
|
bill.id, bill.amount, gross_amount, credited_amount
|
||||||
|
)
|
||||||
|
|
||||||
|
# Validate that the net credited amount matches our bill amount
|
||||||
|
if bill.amount != credited_amount:
|
||||||
|
logger.error(
|
||||||
|
"Net amount mismatch for bill %s (TrsId: %s) - Expected: %s, Net credited: %s, Gross paid: %s",
|
||||||
|
bill.id, TrsId, bill.amount, credited_amount, gross_amount
|
||||||
|
)
|
||||||
|
return "OK"
|
||||||
|
|
||||||
|
amount = credited_amount # Credit the net amount (without fees)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Standard payment - OutSum should match bill amount exactly
|
||||||
amount = int(float(OutSum))
|
amount = int(float(OutSum))
|
||||||
|
|
||||||
# Validate payment amount
|
logger.info(
|
||||||
|
"Standard payment: bill_id=%s, expected=%s, received=%s",
|
||||||
|
bill.id, bill.amount, amount
|
||||||
|
)
|
||||||
|
|
||||||
if bill.amount != amount:
|
if bill.amount != amount:
|
||||||
logger.error(
|
logger.error(
|
||||||
"Amount mismatch for bill %s (TrsId: %s) - Expected: %s, Received: %s",
|
"Amount mismatch for bill %s (TrsId: %s) - Expected: %s, Received: %s",
|
||||||
|
|||||||
@@ -42,6 +42,11 @@ def main():
|
|||||||
choices=["SUCCESS", "FAIL", "UNDERPAID", "OVERPAID"],
|
choices=["SUCCESS", "FAIL", "UNDERPAID", "OVERPAID"],
|
||||||
help="Статус платежа (по умолчанию SUCCESS)",
|
help="Статус платежа (по умолчанию SUCCESS)",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--customer-pays-fees",
|
||||||
|
action="store_true",
|
||||||
|
help="Симулировать сценарий 'клиент платит комиссию'"
|
||||||
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
pally_token = os.environ["PALLY_TOKEN"]
|
pally_token = os.environ["PALLY_TOKEN"]
|
||||||
@@ -52,18 +57,33 @@ def main():
|
|||||||
trs_id = f"LXZv3R{args.bill_id}" # TrsId - уникальный идентификатор счета в Pally
|
trs_id = f"LXZv3R{args.bill_id}" # TrsId - уникальный идентификатор счета в Pally
|
||||||
currency = "RUB"
|
currency = "RUB"
|
||||||
custom = None # custom field не используется в production (остается None)
|
custom = None # custom field не используется в production (остается None)
|
||||||
commission = "0.00" # Комиссия
|
|
||||||
|
# Симуляция комиссии
|
||||||
|
if args.customer_pays_fees:
|
||||||
|
# Симулируем комиссию ~10% (как в вашем продакшене: 50 -> 55.4)
|
||||||
|
commission_rate = 0.108 # ~10.8%
|
||||||
|
gross_amount = args.amount * (1 + commission_rate)
|
||||||
|
commission = gross_amount - args.amount
|
||||||
|
|
||||||
|
print(f"🔄 Симулируется сценарий 'клиент платит комиссию':")
|
||||||
|
print(f" Сумма заказа: {args.amount} RUB")
|
||||||
|
print(f" Комиссия: {commission:.2f} RUB")
|
||||||
|
print(f" Итого к оплате: {gross_amount:.2f} RUB")
|
||||||
|
print(f" К зачислению: {args.amount} RUB")
|
||||||
|
else:
|
||||||
|
gross_amount = args.amount
|
||||||
|
commission = 0.00
|
||||||
|
|
||||||
# Сигнатура рассчитывается с использованием InvId (который содержит bill ID)
|
# Сигнатура рассчитывается с использованием InvId (который содержит bill ID)
|
||||||
signature = calculate_signature(
|
signature = calculate_signature(
|
||||||
str(args.amount), inv_id, pally_token
|
f"{gross_amount:.2f}", inv_id, pally_token
|
||||||
)
|
)
|
||||||
|
|
||||||
# Используем правильные имена полей согласно документации pally.info
|
# Используем правильные имена полей согласно документации pally.info
|
||||||
payload = {
|
payload = {
|
||||||
"InvId": inv_id, # Содержит ID счета из БД
|
"InvId": inv_id, # Содержит ID счета из БД
|
||||||
"OutSum": str(args.amount),
|
"OutSum": f"{gross_amount:.2f}", # Сумма, которую заплатил клиент (с комиссией)
|
||||||
"Commission": commission,
|
"Commission": f"{commission:.2f}",
|
||||||
"TrsId": trs_id,
|
"TrsId": trs_id,
|
||||||
"Status": args.status,
|
"Status": args.status,
|
||||||
"CurrencyIn": currency,
|
"CurrencyIn": currency,
|
||||||
@@ -71,6 +91,11 @@ def main():
|
|||||||
"SignatureValue": signature,
|
"SignatureValue": signature,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Добавляем BalanceAmount только если клиент платит комиссию
|
||||||
|
if args.customer_pays_fees:
|
||||||
|
payload["BalanceAmount"] = str(args.amount) # Сумма к зачислению (без комиссии)
|
||||||
|
payload["BalanceCurrency"] = currency
|
||||||
|
|
||||||
print(f"{args.host}{ENDPOINT}")
|
print(f"{args.host}{ENDPOINT}")
|
||||||
print(f"Отправляю вебхук: {payload}\n")
|
print(f"Отправляю вебхук: {payload}\n")
|
||||||
response = httpx.post(f"{args.host}{ENDPOINT}", data=payload)
|
response = httpx.post(f"{args.host}{ENDPOINT}", data=payload)
|
||||||
|
|||||||
Reference in New Issue
Block a user