Skip to content

Run the Integration Loop

The point of the MCP server is that an agent can not only write an ElasticPay integration but prove it works, end to end, with no human in the middle. The proof hinges on test clocks: instead of waiting a month to see a payment plan renew, the agent advances simulated time and watches the renewal, the payment, and the webhook events appear in seconds.

Once connected, ask your agent to run this loop. Every step is a single tool call from the tool reference. The same sandbox connection can also stage a bulk migration for the merchant to approve in the dashboard — see Migrate customers with an agent and then Migrate payment plans with an agent.

Each step is one tool call. The payloads below are ready to paste — replace the …_from_step_N placeholders with the ids returned by earlier steps, and adjust the dates (start_date must be today or later, Australian Eastern Time; the advance target must be after it).

  1. Create a test clock, frozen at a known time. Note the clk_… id in the response.

    create_test_clock
    {
    "frozen_time": "2026-10-01T00:00:00Z",
    "name": "renewal-walkthrough"
    }
  2. Create a customer on the clock. Customers join a clock at creation; this is what makes their plans follow simulated time.

    create_customer
    {
    "name": "Ada Lovelace",
    "email": "ada@example.com",
    "test_clock": "clk_from_step_1"
    }
  3. Save a payment method with a fixture token. No widget, no real card: the fixture carries a deterministic outcome.

    create_payment_method
    {
    "fixture_token": "tok_sandbox_visa_succeed",
    "customer_id": "cus_from_step_2",
    "card_name": "Ada Lovelace"
    }
  4. Take a one-off payment: create an intent, then confirm it with the saved method. Fetch it back with get_payment_intent and expect succeeded.

    create_payment_intent
    {
    "amount": 2500,
    "currency": "AUD",
    "customer_id": "cus_from_step_2",
    "payment_method": "pm_from_step_3"
    }
    confirm_payment_intent
    {
    "payment_intent_id": "pi_from_step_4",
    "payment_method": "pm_from_step_3"
    }
  5. Create and activate a recurring plan funded by the saved method, starting on the clock’s next simulated day.

    create_payment_plan
    {
    "customer_id": "cus_from_step_2",
    "name": "Monthly subscription",
    "frequency_type": "monthly",
    "recurring_amount_cents": 4900,
    "start_date": "2026-10-02",
    "until_further_notice": true,
    "payment_instrument_id": "pm_from_step_3"
    }
    activate_payment_plan
    { "payment_plan_id": "pp_from_step_5" }
  6. Advance the clock past the start date. Advancing is asynchronous: poll get_test_clock until status returns to ready.

    advance_test_clock
    {
    "test_clock_id": "clk_from_step_1",
    "frozen_time": "2026-10-03T00:00:00Z"
    }
    get_test_clock
    { "test_clock_id": "clk_from_step_1" }
  7. Observe the renewal — the plan has now generated a payment intent under simulated time.

    list_plan_payment_intents
    {
    "payment_plan_id": "pp_from_step_5",
    "limit": 10
    }
  8. Confirm via events — what a webhook subscriber would have received, newest first, each with delivery status. Webhooks are the source of truth — an agent that checks the event feed is verifying the same signal production code should trust.

    list_webhook_events
    {
    "event_type": "payment_intent.succeeded",
    "limit": 20
    }

Swap the fixture in step 3 and rerun: the charge in step 4 declines instead of succeeding, which is how an agent rehearses failure handling without contriving anything.

Two things about a decline are worth knowing before you write the handling:

A decline is an outcome, not an error. confirm_payment_intent answers normally, with the failure in the body:

confirm_payment_intent (declined)
{
"status": "failed",
"last_payment_error": {
"code": "insufficient_funds",
"message": "Sandbox simulated decline: insufficient_funds"
}
}

A declined one-off payment is not retried. Reading the intent back still says failed, with last_payment_error kept. failed is permanent: to try again, create a new payment intent.

A plan’s renewal also stays failed when it declines, as the renewal in step 7 does, but the plan’s failure behaviour may retry it on a new payment intent, when the card scheme allows one. Test dunning walks it.

create_payment_method (declining)
{
"fixture_token": "tok_sandbox_visa_decline_insufficient_funds",
"customer_id": "cus_from_step_2",
"card_name": "Ada Lovelace"
}

The fixture catalogue covers declines, 3D Secure, and timeouts.

The same loop works with a bank account. On a test clock the sandbox bank sends, settles and returns debits on the clock’s time, so an agent can watch a plan collect its total and then collect a returned debit again.

  1. Save the bank fixture for the customer from step 2. It saves a sandbox bank account with an accepted direct debit mandate.

    create_payment_method
    {
    "fixture_token": "tok_sandbox_au_becs_debit",
    "customer_id": "cus_from_step_2"
    }
  2. Create and activate a plan on it, as in step 5, with a total: for example three weekly payments of 5000, number_of_payments: 3.

  3. Advance the clock past each payment date in turn, then a few days more. A debit is sent on its date and settles two business days later on the clock.

  4. Check that the last debit has settled. The plan alone doesn’t say: remaining_amount_cents of 0 with no next_date only means nothing more will be scheduled. List the plan’s payment intents and wait for the last one to be succeeded.

    list_plan_payment_intents
    { "payment_plan_id": "pp_from_step_2", "limit": 10 }
  5. Return it late, as a bank would days after settlement.

    simulate_bank_late_return
    { "payment_intent_id": "pi_last_debit", "code": "late_return" }
  6. Watch the plan collect it again. It stays active, so its failure behaviour runs. Under NEW_AT_END a new 5000 payment appears on the plan’s next payment date, remaining_amount_cents shows 5000 until it is sent, and advancing the clock past that date collects it.

simulate_bank_dishonour fails a debit that has been sent but not settled in the same way. Bank debit outcomes lists every code, and which ones stop the bank account instead.

  • Confirmation needs the payment method. confirm_payment_intent requires payment_method even when the intent was created with one.
  • What you charge is not always what you refund. Where a surcharge applies, confirm adds it on top: an intent created for 2500 settles at a total_amount of, say, 2620. (Card payments on Australian accounts are never surcharged.) Refunding 2500 leaves the surcharge outstanding and the intent partially_refunded. Omit amount on refund_payment_intent to clear it in full, or refund against total_amount rather than the amount you asked for.
  • Advancing is not instant. After advance_test_clock, poll get_test_clock until ready before asserting anything happened.
  • Plan start dates are Australian. start_date must be today or later in Australian Eastern Time; “tomorrow” is always safe.
  • Clock limit. Sandbox accounts allow 3 active clocks — delete finished ones with delete_test_clock.