Skip to content

Add Take payments on a call guide - #867

Open
Manny-r31 wants to merge 4 commits into
mainfrom
Manny/Take-Payments-On-Call-Guide
Open

Manny-r31 wants to merge 4 commits into
mainfrom
Manny/Take-Payments-On-Call-Guide

Conversation

@Manny-r31

@Manny-r31 Manny-r31 commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #770. Part of #765.

Merge after #833. Both PRs edit the SWML pay and cXML <Pay> reference pages. This PR only adds a one-sentence backlink to each, so I'll rebase once #833 merges.

Summary

  • New task-first capability guide, Take payments on a call (voice/take-payments). It covers:
    • which product to use for taking payments
    • how a payment works, with a themed diagram and a mermaid twin
    • building your own payment connector (stub servers in Python and TypeScript)
    • a first payment walkthrough
    • charging a card and saving a card as a token
    • prompt and card options, and handling the result
    • stopping collection from Relay
    • taking a payment from an AI agent with FunctionResult.pay
    • PCI compliance
  • The surfaces covered are SWML and Relay. REST, the Browser SDK and Call Flow Builder are marked unavailable. The Compatibility API <Pay> is left out of the guide to keep the platform page SWML-native, and the <Pay> reference links to the guide instead.
  • The Dashboard has no payment connector setup to document. The guide treats the connector as an HTTPS endpoint you host and shows how to build one.
  • Cross-links:
    • a voice overview card, and a link from the overview's PCI FAQ
    • the llms.txt "Secure payments" bullet now points to the guide
    • backlinks from the SWML pay reference, the cXML <Pay> reference, Relay call.pay (Python and TypeScript), FunctionResult.pay (Python and TypeScript), the content redaction guide and Record calls
  • No nav change (the voice folder is folder-driven), no redirects, and no changelog entry, since this is documentation work.

Notes for reviewers

  • Relay PayAction.wait() and on_completed never resolve for pay in signalwire-sdk==3.4.1 or @signalwire/sdk@2.0.5. The SDK waits for a state field that calling.call.pay events don't carry. The guide's Relay samples wait for the final calling.call.pay event, the one carrying result. The existing Relay pay and PayAction reference examples call action.wait(), so they wait forever. That needs an upstream SDK fix and a reference update.

  • A declined card still plays the "payment completed" prompt. The closing prompt plays on any HTTP 200 from the connector, so a decline returned as a 200 sounds like a success to the caller, while pay_result is internal-error with payment_error: card-declined. Worth raising with the platform team. The guide branches on pay_result and doesn't rely on the prompt.

  • REST: calling.pay and calling.pay.stop work through POST /api/calling/calls, but they aren't in the API spec, so the guide marks REST as unavailable. Adding them to the spec would be a follow-up.

  • Connector signing and token_id: the guide documents current platform behavior. Connector requests are signed with X-SignalWire-Signature, and the connector's token_id comes back as payment_token. Pay fixes (SWML + cXML) #833 currently says connector requests aren't signed; my review there suggests corrected wording. Both platform changes need changelog entries dated to when they shipped, which I'll add separately once the dates are confirmed.

  • SWML pay reference errors that Pay fixes (SWML + cXML) #833 doesn't cover (not changed in this PR):

    • security_code is typed as a boolean, but the platform requires the strings "true" or "false".
    • prompts[].attempts should be attempt.
    • validation-error is described as "Card details failed validation", but failed card checks end in too-many-failed-attempts.

    Pay fixes (SWML + cXML) #833 already fixes the rest: the missing parameters, the masked card in the connector request example, and payment-completed being sent for declines.

  • Other docs and SDK inconsistencies:

    • Both SWML builders type security_code as a boolean, and the Python builder rejects "false" at runtime. The guide's "Skip the security code" example therefore uses add_verb/addVerb for that one field.
    • The content redaction guide's JSON shows security_code and postal_code as booleans.
    • The TypeScript validateWebhookSignature reference page documents a function that @signalwire/sdk@2.0.5 doesn't export. The TypeScript connector checks the signature with node:crypto.
  • Recording during collection: the guide doesn't claim that recordings pause automatically while card details are entered. It tells readers to pause recording and transcription themselves.

  • Rule drift: .claude/rules/mdx-capability-guides.md says platform.yml lists pages/calling/ pages explicitly. The voice folder is folder-driven now.

Verification

  • yarn fern-md-check: all 2831 MDX files are valid. I didn't run yarn fern-check, since no yml or redirects changed.
  • I extracted every code sample and validated it against the pinned SDKs:
    • Python: py_compile and pyflakes clean on all 12 files.
    • TypeScript: tsc --strict clean on all 12 files, and each also passes node --check as .mjs.
    • Every YAML and JSON block parses, and the cURL block passes bash -n.
    • The Python and TypeScript SWML builder output is identical to the hand-written JSON for every document.
  • I ran both stub connectors with signed requests: a charge, a zero-amount token, and a forged signature, which returned 403.
  • I checked SDK method and parameter names against the installed packages.
  • I checked every internal link against its target's slug: and every #anchor by hand. That includes the three guide anchors other pages link to: #build-a-payment-connector, #take-a-payment-from-an-ai-agent and #payments-and-pci-compliance.
  • Please check the diagram in light and dark mode, since no check covers how it renders.

Preview URLs

  • /docs/platform/voice/take-payments
  • /docs/platform/voice
  • /docs/platform/voice/record-calls
  • /docs/platform/ai/content-redaction
  • /docs/swml/reference/calling/pay
  • /docs/compatibility-api/cxml/reference/voice/pay
  • /docs/server-sdks/reference/python/relay/call/pay
  • /docs/server-sdks/reference/typescript/relay/call/pay
  • /docs/server-sdks/reference/python/agents/function-result/pay
  • /docs/server-sdks/reference/typescript/agents/function-result/pay

🤖 Generated with Claude Code

Manny-r31 and others added 2 commits October 27, 2026 14:39
New task-first capability guide at voice/take-payments covering SWML pay,
Relay call.pay, AI agents via FunctionResult.pay, the payment connector
protocol, results, and PCI. Cross-link it from the voice overview, llms.txt,
and the SWML, cXML, Relay, FunctionResult, content redaction, and record
calls pages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

@Manny-r31 Manny-r31 mentioned this pull request Oct 27, 2026
6 of 13 tasks

@hey-august hey-august left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Requesting changes. The guide is in good shape, but four statements contradict the platform, three code samples fail on first run, and two smaller items need a touch. Platform behavior below is verified against source.

Blocking

  • Custom prompts do not replace SignalWire's retry prompts (take-payments.mdx:1192). The sentence "Each entry in prompts replaces SignalWire's prompt for one step" holds only for the first attempt. Prompt selection weights any error-matched prompt above a plain one, so the built-in timeout and invalid-* prompts outrank a custom prompt with no error_type. The page's own example, a plain payment-card-number prompt with max_attempts: 3, plays the stock timeout prompt on retry. State that a prompt needs the matching error_type to replace a retry prompt.
  • The HMAC is computed over the URL with credentials removed (take-payments.mdx:115 and :244). The page says the signature covers the URL "exactly as you passed it". The platform strips any user:password@ before signing and sends those as Basic auth. A reader who protects the connector with basic auth and sets the CONNECTOR_URL variable to the credentialed string gets a 403 on every request. One sentence fixes it.
  • The result table omits validation-error (take-payments.mdx:1665). The table is introduced as the full set but leaves out the validation-error value. The SWML reference this page links does list it. Add the row.
  • The reference defines payment-connector-error differently from the guide (swml/.../pay/index.mdx:753). The reference says the connector returned an error. In practice a reachable connector that answers non-200 ends as internal-error, and payment-connector-error survives only when the connector is unreachable, which is what the guide says at lines 643 and 1669. Align the reference with the guide.
  • The TypeScript connector corrupts multi-byte request bodies (take-payments.mdx:320). The statement rawBody += chunk stringifies each Buffer on its own. A UTF-8 character split across two chunks becomes U+FFFD, and the HMAC check fails intermittently on non-ASCII descriptions. Call req.setEncoding("utf8") before the loop, or collect Buffers and use Buffer.concat.
  • The stop timer leaves a promise rejection unhandled (take-payments.mdx:1931). The call setTimeout(() => void action.stop(), 120_000) discards the promise from stop(). If the platform refuses the stop, Node 15 and later terminates the handler mid-call. Add .catch(() => {}).
  • The call.pay method raises rather than returning an error (take-payments.mdx:652). The troubleshooting text says call.pay returns an error. The Python SDK raises and the TypeScript SDK rejects, and neither Relay sample catches it. An http:// connector URL produces a traceback and a hung call. Reword the sentence, and consider wrapping call.pay in a try block in both samples.

Non-blocking

  • The connector-body table should mark parameters as optional (take-payments.mdx:205). The platform omits the parameters key when none are set, the same as description. The sample body labeled as the Bayview fare body includes parameters, which the first run never sends.
  • The Python sample disables schema validation for the whole document (take-payments.mdx:1576). The sample passes schema_validation=False to SWMLService. The mdx-capability-guides rule says to bypass validation for the one verb only. The TypeScript twin already does that with addVerb.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Take payments on a call

2 participants