renew_domain
Renew a domain for one additional year. Provide the order ID from a completed purchase to create a Stripe checkout session for payment.
Instructions
Renew a domain for 1 additional year.
Creates a Stripe checkout session for the renewal payment.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| order_id | Yes | The order ID of a completed domain purchase (e.g. "ord_abc123"). |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- instadomain/mcp_server.py:159-173 (handler)MCP tool handler: calls the backend /renew/{order_id} endpoint via HTTP POST
@mcp.tool() async def renew_domain(order_id: str) -> dict: """Renew a domain for 1 additional year. Creates a Stripe checkout session for the renewal payment. Args: order_id: The order ID of a completed domain purchase (e.g. "ord_abc123"). """ async with httpx.AsyncClient(base_url=BACKEND_URL, timeout=15) as client: resp = await client.post(f"/renew/{order_id}") if resp.status_code in {400, 404}: return resp.json() resp.raise_for_status() return resp.json() - instadomain/mcp_server.py:159-160 (registration)Registration of the renew_domain tool via @mcp.tool() decorator
@mcp.tool() async def renew_domain(order_id: str) -> dict: - instadomain/routes_manage.py:60-109 (handler)Backend API handler: validates order, gets price, creates Stripe checkout session, and returns renewal info
@router.post("/renew/{order_id}") async def renew_domain(order_id: str, request: Request): """Create a Stripe checkout session to renew a domain for 1 year.""" pool = request.app.state.pool order = await get_order(pool, order_id) if order is None: raise HTTPException(status_code=404, detail="Order not found") if order["status"] != "complete": raise HTTPException( status_code=400, detail=( f"Order must be in 'complete' status to renew " f"(current: {order['status']})" ), ) domain = f"{order['domain']}.{order['tld']}" opensrs = request.app.state.opensrs try: wholesale_cents = await _get_price(domain, opensrs) except Exception as exc: raise HTTPException(status_code=502, detail=f"Price lookup failed: {exc}") tld = order["tld"] retail_cents = calculate_retail_cents(wholesale_cents, tld) try: checkout = await asyncio.to_thread( create_checkout_session, f"{domain} (renewal)", retail_cents, order_id ) except Exception as exc: raise HTTPException(status_code=502, detail=f"Payment setup failed: {exc}") async with pool.acquire() as conn: await conn.execute( "UPDATE orders SET renewal_stripe_session_id = $1, " "updated_at = now() WHERE id = $2", checkout["session_id"], order_id, ) return { "order_id": order_id, "checkout_url": checkout["checkout_url"], "price_cents": retail_cents, "price_display": format_price(retail_cents), "domain": domain, "renewal_years": 1, } - OpenSRS XML API client method that builds a RENEW envelope and calls the OpenSRS API
def renew_domain(self, domain: str, current_expiry_year: int, years: int = 1) -> dict: """Renew a domain for additional years. current_expiry_year is required by OpenSRS to prevent duplicate renewals. Pass the year the domain currently expires (e.g. 2027). Returns dict with order_id, new expiry date, and admin email. """ attrs = { "domain": domain, "period": str(years), "handle": "process", "auto_renew": "0", "currentexpirationyear": str(current_expiry_year), } xml_body = build_envelope("RENEW", "DOMAIN", attrs) response = self._post(xml_body) data = parse_response(response.text) attrs_data = data.get("attributes", {}) return { "order_id": attrs_data.get("id", ""), "expiry": attrs_data.get("registration_expiration_date", ""), "admin_email": attrs_data.get("admin_email", ""), } - instadomain/routes_buy.py:154-226 (handler)Stripe webhook handler that calls OpenSRS renew_domain after payment, updates expiry in DB
async def _handle_renewal_webhook( request: Request, pool, order: dict, session_id: str, payment_intent: str | None, ) -> dict: """Process a Stripe webhook for a domain renewal payment.""" logger.info("Webhook: processing renewal for order %s", order["id"]) if order.get("renewal_stripe_session_id") != session_id: logger.info( "Webhook: renewal session %s does not match stored %s, skipping (duplicate)", session_id, order.get("renewal_stripe_session_id"), ) return { "received": True, "order_id": order["id"], "renewal": True, "duplicate": True, } domain = f"{order['domain']}.{order['tld']}" opensrs = request.app.state.opensrs expires_at = order.get("domain_expires_at") if expires_at is None: logger.error("Renewal failed for %s: domain_expires_at is NULL", domain) if payment_intent: try: await asyncio.to_thread(issue_refund, payment_intent) except Exception as refund_exc: logger.error("Refund also failed for %s: %s", payment_intent, refund_exc) asyncio.create_task(send_renewal_failure_alert( order_id=order["id"], domain=domain, error_msg="domain_expires_at is NULL, could not determine currentexpirationyear", )) return { "received": True, "order_id": order["id"], "renewal": True, "error": "missing expiry", } current_expiry_year = expires_at.year try: renew_result = await asyncio.to_thread( opensrs.renew_domain, domain, current_expiry_year, 1 ) expiry_str = renew_result.get("expiry") if expiry_str: try: expiry_dt = datetime.fromisoformat(expiry_str).replace( tzinfo=timezone.utc ) await update_domain_expiry(pool, order["id"], expiry_dt) except (ValueError, TypeError): logger.warning("Could not parse renewal expiry: %s", expiry_str) async with pool.acquire() as conn: await conn.execute( "UPDATE orders SET renewal_stripe_session_id = NULL, " "updated_at = now() WHERE id = $1", order["id"], ) logger.info("Renewal successful for %s", domain) except Exception as exc: logger.error("Renewal failed for %s: %s", domain, exc) if payment_intent: try: await asyncio.to_thread(issue_refund, payment_intent) except Exception as refund_exc: logger.error("Refund also failed for %s: %s", payment_intent, refund_exc) asyncio.create_task(send_renewal_failure_alert( order_id=order["id"], domain=domain, error_msg=str(exc), )) return {"received": True, "order_id": order["id"], "renewal": True}