Payment Flow Guide¶
This guide walks through a complete ATH Móvil payment from creation to completion.
Payment Lifecycle¶
ATH Móvil payments follow a state-based lifecycle with the following states:
Payment States¶
| State | Description | Next Actions |
|---|---|---|
| OPEN | Payment created, waiting for customer | Wait for confirmation or cancel |
| CONFIRM | Customer confirmed on ATH Móvil app | Authorize to complete |
| COMPLETED | Payment authorized and finalized | Can refund if needed |
| CANCEL | Payment cancelled or timed out | Cannot recover, create new payment |
Payment Flow¶
The payment process follows these steps:
- Create payment - Your app calls
create_payment()and receives anecommerce_id - Push notification - ATH Móvil sends notification to customer's phone
- Customer approval - Customer opens ATH Móvil app and approves payment
- Poll for confirmation - Your app polls for status change to
CONFIRM - Authorize payment - Your app calls
authorize_payment()to complete the transaction - Completion - Payment status changes to
COMPLETEDand you receive areference_number
Step-by-Step Implementation¶
Step 1: Create the Payment¶
Initialize the client and create a payment:
from athm import ATHMovilClient
import os
client = ATHMovilClient(public_token=os.getenv("ATHM_PUBLIC_TOKEN"))
# Create payment
payment = client.create_payment(
total="5.00",
phone_number="7875551234", # Customer's phone
metadata1="Order #12345", # Optional: your reference
items=[{
"name": "Product Name",
"description": "Product description",
"quantity": "1",
"price": "5.00",
}]
)
print(f"Payment created: {payment.data.ecommerce_id}")
print("Customer will receive a push notification")
What happens:
- Payment is created with
OPENstatus - Customer receives push notification on their phone
- You get an
ecommerce_idto track this payment
Step 2: Wait for Customer Confirmation¶
The customer needs to open their ATH Móvil app and approve the payment:
try:
client.wait_for_confirmation(payment.data.ecommerce_id, timeout=300)
print("Customer confirmed!")
except TimeoutError:
print("Timeout - cancelling payment")
client.cancel_payment(payment.data.ecommerce_id)
except TransactionError:
print("Payment was cancelled")
What happens:
- Customer receives push notification on their phone
- Your app polls ATH Móvil API every 2 seconds
- Customer opens app and approves or rejects
- Status changes to
CONFIRMwhen approved
Step 3: Authorize the Payment¶
Once confirmed, authorize within 10 minutes to finalize:
result = client.authorize_payment(payment.data.ecommerce_id)
print(f"Payment completed!")
print(f"Reference number: {result.data.reference_number}")
print(f"Total: ${result.data.total}")
print(f"Status: {result.data.ecommerce_status}") # "COMPLETED"
What happens:
- Payment is finalized and funds are transferred
- You receive a
reference_numberfor your records - Status changes to
COMPLETED - Customer receives confirmation
Step 4: Error Handling¶
Always handle errors gracefully:
from athm import (
ATHMovilClient,
AuthenticationError,
ValidationError,
TransactionError,
TimeoutError
)
try:
# Create payment
payment = client.create_payment(
total="5.00",
phone_number="7875551234",
items=[{
"name": "Product Name",
"description": "Product Description",
"quantity": "1",
"price": "5.00",
}],
)
# Wait for confirmation
client.wait_for_confirmation(payment.data.ecommerce_id)
# Authorize
result = client.authorize_payment(payment.data.ecommerce_id)
print(f"Success! Reference: {result.data.reference_number}")
except ValidationError as e:
print(f"Invalid data: {e}")
except AuthenticationError as e:
print(f"Authentication failed: {e}")
except TimeoutError as e:
print(f"Payment timed out: {e}")
client.cancel_payment(payment.data.ecommerce_id)
except TransactionError as e:
print(f"Transaction error: {e}")
finally:
client.close()
Complete Example¶
Here's a full production-ready payment flow:
from athm import ATHMovilClient, TransactionError, TimeoutError
import os
def process_payment(amount: str, phone_number: str, order_id: str) -> str | None:
"""Process an ATH Móvil payment. Returns reference_number on success."""
client = ATHMovilClient(public_token=os.getenv("ATHM_PUBLIC_TOKEN"))
try:
# 1. Create payment
payment = client.create_payment(
total=amount,
phone_number=phone_number,
metadata1=f"Order {order_id}",
items=[{
"name": "Order Item",
"description": f"Order {order_id}",
"quantity": "1",
"price": amount,
}],
)
print(f"Payment created: {payment.data.ecommerce_id}")
print(f"Waiting for customer {phone_number} to confirm...")
# 2. Wait for confirmation
client.wait_for_confirmation(payment.data.ecommerce_id, timeout=300)
# 3. Authorize payment
result = client.authorize_payment(payment.data.ecommerce_id)
print(f"Payment completed!")
print(f" Reference: {result.data.reference_number}")
print(f" Amount: ${result.data.total}")
return result.data.reference_number
except TimeoutError:
print("Customer didn't confirm in time, cancelling...")
client.cancel_payment(payment.data.ecommerce_id)
return None
except TransactionError as e:
print(f"Transaction failed: {e}")
return None
except Exception as e:
print(f"Unexpected error: {e}")
return None
finally:
client.close()
# Usage
if __name__ == "__main__":
ref = process_payment(
amount="5.00",
phone_number="7875551234",
order_id="ORD-12345"
)
if ref:
print(f"Save this reference: {ref}")
else:
print("Payment failed")
Update Phone Number¶
If the customer provides a different phone number:
# After payment creation, before confirmation
client.update_phone_number(
ecommerce_id=payment.data.ecommerce_id,
phone_number="7875559999"
)
# Wait for confirmation on the new number
client.wait_for_confirmation(payment.data.ecommerce_id)
Webhooks¶
Webhooks provide real-time notifications when transactions occur. Instead of polling for status changes, ATH Movil will POST to your endpoint.
Subscribing to Webhooks¶
Register your HTTPS endpoint to receive webhook notifications:
from athm import ATHMovilClient
import os
# Private token is required for webhook subscriptions
client = ATHMovilClient(
public_token=os.getenv("ATHM_PUBLIC_TOKEN"),
private_token=os.getenv("ATHM_PRIVATE_TOKEN"),
)
# Subscribe to webhook events
client.subscribe_webhook(
listener_url="https://yoursite.com/webhooks/athm",
payment_received=True,
refund_sent=True,
ecommerce_completed=True,
ecommerce_cancelled=True,
ecommerce_expired=True,
)
Requirements:
- Listener URL must use HTTPS (no self-signed certificates)
- Private token is required for subscription
Handling Webhook Events¶
When ATH Movil sends a webhook to your endpoint, use parse_webhook() to validate and normalize the payload:
from athm import parse_webhook, WebhookEventType, WebhookStatus, ValidationError
# In your web framework (FastAPI, Flask, Django, etc.)
@app.post("/webhooks/athm")
async def handle_athm_webhook(request: Request):
try:
payload = await request.json()
event = parse_webhook(payload)
match event.transaction_type:
case WebhookEventType.PAYMENT:
print(f"Payment received: ${event.total} from {event.name}")
print(f"Reference: {event.reference_number}")
case WebhookEventType.REFUND:
print(f"Refund sent: ${event.total}")
case WebhookEventType.ECOMMERCE:
if event.status == WebhookStatus.COMPLETED:
print(f"eCommerce order {event.ecommerce_id} completed")
elif event.status == WebhookStatus.CANCELLED:
print(f"eCommerce order {event.ecommerce_id} cancelled")
elif event.status == WebhookStatus.EXPIRED:
print(f"eCommerce order {event.ecommerce_id} expired")
case WebhookEventType.DONATION:
print(f"Donation received: ${event.total}")
return {"status": "ok"}
except ValidationError as e:
print(f"Invalid webhook payload: {e}")
return {"status": "error"}, 400
Webhook Event Types¶
| Event Type | Description |
|---|---|
SIMULATED |
Test/simulated payment (sandbox) |
PAYMENT |
Standard payment received |
DONATION |
Donation received |
REFUND |
Refund sent to customer |
ECOMMERCE |
eCommerce transaction (check status for completed/cancelled/expired) |
Webhook Payload Normalization¶
The parse_webhook() function automatically normalizes inconsistencies in the ATH Movil webhook API:
- Field names:
dailyTransactionIDvsdailyTransactionId->daily_transaction_id - Data types: String decimals (
"100.00") and numbers (100.00) ->Decimal - Status values:
CANCEL->cancelled,COMPLETED->completed - Transaction types:
ECOMMERCE->ecommerce
This means you always get consistent, typed data regardless of which event type you receive.
Next Steps¶
- API Reference - Detailed method documentation
- Error Handling - All error codes and recovery