portaly-payment
Portaly Vibe Payment Integration
Use this skill to help a human user finish a Portaly Vibe API integration quickly. Keep answers operational: prefer step lists, API request and response bullets, and copy-ready examples over long architecture explanations.
Portaly Vibe Payment Environments
Portaly Vibe Payment supports two modes per API key: live and test.
API Host & Payment site
Both the API host and the payment site (where buyers are redirected for checkout) live on the same unified domain for both modes:
https://portaly.ai(default)
The host is overridable via the PORTALY_API_HOST environment variable. When generating code that calls the Portaly API, prefer this pattern over hardcoding the URL:
const PORTALY_API_HOST = process.env.PORTALY_API_HOST || 'https://portaly.ai'
See PROVIDER.md at the repo root for the backend compatibility contract.
Mode behavior
| Aspect | Live mode | Test mode |
|---|---|---|
| API key prefix | pcs_live_ |
pcs_test_ |
| Payment provider | TapPay production | TapPay sandbox |
| Order storage | orders collection |
sandboxOrders collection |
| Callback payload | mode: "live" or absent |
mode: "test" |
- Mode is set at API key creation time and cannot be changed after creation.
- A single merchant (
profileId) can have both a live key and a test key active at the same time. - All API endpoints accept both live and test keys. The mode is derived from the key, not from a request parameter.
- Test mode is intended for integration testing. Real charges are not made in test mode when using TapPay sandbox credentials.
- Plans and merchant config are shared across modes. They belong to the merchant (
profileId), not to the API key mode. A plan created with a live key is visible and usable with a test key, and vice versa. Do not create duplicate plans when switching between live and test keys — query existing plans first withGET /api/creator-subscription/plansand reuse them.
Quick Start
- Before starting, AI agent should ask the human user to claim or create a Portaly Vibe Payment API key/CallbackSecret in the Portaly Vibe Dashboard at
https://portaly.ai/dashboardand store the issued secret material safely. - Ask the human user whether they want a live or test key. Recommend starting with a test key for integration development.
- Confirm what the human user is trying to build.
Prepare for payment integration tasks such as:
- create merchant config
- create subscription plans
- upload merchant or plan images (Agent should ask human user to provide image assets if needed)
- After setup, integrate the checkout session creation and callback handling into current system:
- create checkout session before buyer initiates payment
- redirect buyer to Portaly vibe checkout
- verify and consume the callback from Portaly after checkout completion
- if the integration needs subscription lifecycle management, also wire cancel and resume APIs for recurring plans
- if the integration needs subscriber self-service (letting subscribers manage their own subscriptions), wire the portal session API
- Start with
references/api-contract.md. Use it for endpoint lists, auth, request bodies, response bodies, and callback headers. - Load
references/checkout-and-renewal.mdonly when needed. Use it only as supplemental reference when the human user asks about post-checkout charging, renewal, payout, invoice, or bridge-order behavior. - Return implementation-ready output. Prefer numbered steps, API endpoint lists, request and response bullets, and Node.js or TypeScript examples.
Output Style
- Write for an AI agent that is helping a human user complete integration work.
- Lead with the next concrete steps the human should take.
- Be explicit when an API can be called directly by the Agent with the Portaly Vibe Payment API key.
- Prefer using the setup APIs directly for merchant config, plan creation, plan updates, image uploads, and checkout session creation when the user has already provided valid credentials and required inputs.
- Use lists for:
- setup steps
- API endpoints
- required headers
- request fields
- response fields
- callback verification steps
- Prefer concise code samples in JavaScript or TypeScript when the user does not ask for another stack.
- Keep Portaly-owned behavior and third-party-owned behavior clearly separated.
Workflow
1. Apply for the API key
-
Require a Portaly Vibe Payment API key and CallbackSecret for this integration.
-
Instruct the human user to apply for or create the Portaly Vibe Payment API key in the Portaly Vibe Dashboard at
https://portaly.ai/dashboard. -
Ask whether the user wants a live key (
pcs_live_…) or a test key (pcs_test_…). Recommend starting with a test key for development and switching to live for production. -
Be explicit that this step is performed by a human operator in Portaly Vibe Dashboard, not by the third-party integration code.
-
Tell the human user to store the issued secret material safely, or store it on the user's behalf only in an appropriate secret manager or secure environment store.
-
Explain that the API key is used for bearer authentication in API calls and the
callbackSecretis used for verifying the authenticity of callbacks from Portaly If user asking. -
Never ask the user to paste the API key or
callbackSecretinto chat. Chat transcripts can be logged, cached, or echoed back by the model in summaries, diffs, or tool call arguments. Treat secrets as values the agent never needs to see in plaintext. -
Instead, instruct the human user to place the secrets into
.envthemselves (via their editor or shell), using this template:PORTALY_API_KEY=pcs_live_xxx # or pcs_test_xxx for test mode PORTALY_CALLBACK_SECRET=xxx -
The agent reads these at runtime via
process.env.PORTALY_API_KEY(Node) oros.environ["PORTALY_API_KEY"](Python) — it never needs the literal secret value in-context. -
If the project uses a secret manager (1Password CLI, Doppler, AWS/GCP Secrets Manager, Vault, etc.), prefer that over
.env. -
Before proceeding, verify that
.gitignoreincludes.env. If.gitignoredoes not exist or does not include.env, create or update it immediately. Never allow credentials to be committed to version control. -
If the user does paste a secret into chat by mistake, advise them to rotate the key in the Portaly Vibe Dashboard before using it — assume the pasted value is compromised.
2. Configure merchant settings
- Agent should perform these setup actions directly by API call with the Portaly Vibe Payment API key.
- Use the Config APIs when the human user needs to set merchant branding before any product goes live.
- AI Agent should ask the human user to provide a
merchantLogoimage asset, use the config image upload API to upload image to Portaly. The merchant logo is optional — if the user does not have one ready, skip this step and proceed with plan creation. - Use
PUT /api/creator-subscription/configandPOST /api/creator-subscription/config/imagesto set up merchant branding with the Portaly Vibe Payment API key. - Vibe MCP shortcut: If the agent is connected to Vibe MCP (i.e.
vibe_update_brandis available), use it to setmerchantName,appBaseUrl, andbrandDescriptioninstead of the REST call — noPORTALY_API_KEYneeded. Only pass fields that are currently blank or need updating; omit the rest.
3. Create a valid subscription plan
- Agent should perform plan creation, plan updates, and plan image uploads directly by API call with the Portaly Vibe Payment API key.
- Before creating a new plan, always query existing plans with
GET /api/creator-subscription/plansusing the current API key. Plans are shared across live and test modes; if a suitable plan already exists, reuse it instead of creating a duplicate. - Require at least one active plan in Portaly before creating a checkout session.
- Use the Plan APIs to create or update the product basics that the human user wants to list on Portaly.
- Confirm the plan name, description, amount, currency, billing period (
monthly,yearly, orone-time), pricing type (fixedordynamic), and status match the intended product. - For dynamic pricing plans: set
pricingTypetodynamicandbillingPeriodtoone-time. The amount is not set on the plan; instead, the caller passesamountwhen creating each checkout session. - If the third party has its own product catalog, persist the Portaly
planIdtogether with the merchant's internal product or entitlement identifier. - AI Agent should ask the human user to provide a plan image, use the plan image upload API to upload the image to Portaly.
- Treat the
checkoutUrlreturned by Portaly as authoritative. Do not reconstruct it from guessed domains. - After creating or updating a plan, check the response
nameanddescriptionfor garbled text (mojibake). If corrupted, fix shell encoding and usePUT /api/creator-subscription/plans/{planId}to correct it. See the Windows encoding note in Guardrails.
3.5 Create discount codes (optional)
- Use the Discount Code APIs after at least one plan exists.
- A code carries an array of rules; each rule can target a different set of plans with its own discount and duration. Example: code
EARLY2026with two rules — 50% off for 3 cycles (= 3 months) on the monthly plan, and 20% off for 1 cycle (= 1 year) on the yearly plan. - Per rule, confirm with the human user:
- Discount type:
fixed(TWD off) /percent(% off) /free(100% off). - Duration:
repeating N cycles(default 1) orforever(typically withfixed). One cycle equals one billing period — a month for a monthly plan, a year for a yearly plan. - appliesTo:
all(fallback for any plan not covered by a specific rule) orspecificplanIds (e.g. yearly plan only). At most oneallrule per code; planIds may not appear in more than one rule.
- Discount type:
- Code-level params:
- Custom code: 3-40 chars,
[A-Z0-9_-]. Stored and displayed in UPPERCASE; lookup is case-insensitive on input. Unique per profile. Immutable post-create. - Redemption window:
redeemFrom/redeemBy. - Caps:
maxRedemptions(total) /maxRedemptionsPerCustomer(per email).
- Custom code: 3-40 chars,
- Codes are shared across live and test modes (same as plans).
- Codes also serve as ref codes — see the
portaly-userskill for how to recordsignupRefCodeat user registration. When a buyer with a recordedsignupRefCodelater checks out and verifies their email, Portaly auto-applies the matching rule, provided the code is still within itsredeemBywindow. - See
references/discount-code-examples.mdfor example prompts and the parameter cheatsheet. - Money-moving guard: live-mode discount creation requires explicit user confirmation (same rule as live-mode plan creation).
4. Create the checkout session
- Create a checkout session before the buyer initiates payment.
- Call
POST /api/creator-subscription/checkout-sessionswithAuthorization: Bearer {api_key}. - Send
planIdand optionalsuccessRedirectUrl,cancelRedirectUrl,callbackUrl,merchantOrderNumber, and string-keyedmetadata. - Optional
discountCode: when provided, Portaly validates and applies the discount up-front. Invalid codes return400 INVALID_DISCOUNT_CODE. When omitted, Portaly attempts to auto-apply a discount via the buyer'ssignupRefCodeafter their email is verified inside hosted checkout (no extra call needed from the merchant). - Persist
sessionId,checkoutToken,checkoutUrl, andexpiresAton the third-party side. - The session response includes
appliedDiscountwhen a discount was applied at session creation;session.amountis always the post-discount amount the buyer will be charged. - Redirect the buyer to
checkoutUrl.
5. Let Portaly run hosted checkout
- Treat Portaly hosted checkout as a black box from the third-party perspective.
- Do not ask the third party to collect card tokens or implement Portaly-owned payment steps.
6. Consume the result
- The primary external confirmation is the signed callback to
callbackUrl. - Callback is only dispatched when checkout status is
completed. Non-completed outcomes (failed, canceled, expired) do not trigger a callback. - For non-completed outcomes, poll
GET /api/creator-subscription/checkout-sessions/{sessionId}as a fallback. - Use manual
POST /api/creator-subscription/checkout-sessions/{sessionId}/completeonly as an exception flow when the user is building a non-hosted or recovery flow. - Current implementation contract:
subscriptionId === checkoutSessionId === sessionId. - When a recurring checkout succeeds, human user's system may use the callback's
sessionIddirectly as thesubscriptionIdfor later cancel or resume API calls. - Make it explicit to the human user that this is the current Portaly implementation contract and should be persisted on their side after checkout completion.
7. Verify and persist
- Verify
x-portaly-signaturewith the API key'scallbackSecret. - Use the exact timestamp from
x-portaly-timestamp. - Reject callbacks where
x-portaly-timestampis older than 5 minutes to prevent replay attacks. Note:x-portaly-timestampis an ISO datetime string, not Unix seconds. - Serialize the callback payload with stable key ordering before HMAC.
- Reference implementations live in
scripts/sign_callback.pyandscripts/sign_callback.mjs. - After verification, persist
sessionId,subscriptionIdif present,merchantOrderNumber,paymentReference,paymentMethod,status, and the raw callback body for auditing. - If the callback payload does not include
subscriptionId, persistsessionIdas the recurring subscription identifier because the current implementation usessessionIdassubscriptionId. - Use
sessionIdas an idempotency key — if a callback with the samesessionIdhas already been processed, skip duplicate handling to avoid double fulfillment. callbackUrlmust use HTTPS. Serving over plain HTTP exposes thecallbackSecretsignature and payload in transit.- Heads up — Portaly may auto-send a welcome/upgrade email when the callback fires. A successful (
status: completed) callback triggers Portaly'swelcome_paidtemplate by default. Symmetrically, a cancel call triggerssubscription_canceled. If the vibe coder already sends their own purchase-confirmation or cancellation email, disable the matching template before going live withPUT /api/creator-email/templates/welcome_paid(orsubscription_canceled) carrying{ "enabled": false }. See theportaly-emailskill for the full list of email types and disable workflow.
8. Manage recurring subscriptions
- Only recurring plans with
billingPeriod = monthly | yearlysupport cancel or resume. - Cancellation means stopping the next recurring charge. It is not a refund. In your system, the rights or content associated should remain active until the end of the current paid period, which is indicated by
cancelEffectiveAtin the subscription record. - Portaly currently supports merchant-system initiated subscription lifecycle actions through API key authenticated endpoints.
- Use the same Portaly Vibe Payment API key for these calls.
Recurring management APIs:
GET /api/creator-subscription/subscriptions— list all subscriptions with pagination and filteringGET /api/creator-subscription/subscriptions/{subscriptionId}POST /api/creator-subscription/subscriptions/{subscriptionId}/cancelPOST /api/creator-subscription/subscriptions/{subscriptionId}/resume
Order query API:
GET /api/creator-subscription/orders— list payment/order records with pagination
Vibe MCP shortcuts (preferred when available):
vibe_list_orders— list orders without aPORTALY_API_KEY; uses the MCP Bearer token and the connection's configured API mode. Available once theportaly-paymentskill is installed and the MCP session is restarted.vibe_list_members— list subscription members by the same token. Available once theportaly-memberskill is installed.
Recurring management rules:
- These APIs only accept
Authorization: Bearer {api_key} - Do not use Firebase auth for merchant-system integrations
billingPeriod = one-timedoes not support cancel or resumecancelmarks the subscription ascancelAtPeriodEnd = trueresumeonly works before the subscription has become fullycanceled
Cancel request body:
{
"reason": "customer_requested",
"reasonNote": "optional note"
}
Resume request body:
{}
What to persist for recurring lifecycle:
subscriptionIdsessionIdplanIdbillingPeriodstatuscancelAtPeriodEndcancelEffectiveAt
8.5. Wire invitation email CTA (optional)
If the merchant plans to use Portaly's invitation-email flow to recruit followers (waitlist signups, campaigns), the CTA in those emails redirects through https://portaly.ai/r/{code} to a waitlist landing page. By default the page is hosted by Portaly; the vibe coder can also host it themselves on their own domain by setting appBaseUrl on the merchant config.
This is a separate concern from payment integration — for the full setup, install and follow the portaly-email skill: npx skills add portaly-ai/portaly-skills --skill portaly-email.
9. Enable subscriber self-service portal (optional)
- Use this when the merchant wants subscribers to manage their own subscriptions directly.
- The merchant backend creates a portal session via
POST /api/creator-subscription/portal-sessionsonhttps://portaly.ai, then redirects the subscriber to the returnedportalUrl. - This is a server-to-server call — the API key must never be exposed to the client.
- The subscriber lands on Portaly's hosted portal page, already authenticated via the session token. No additional login is required.
- In the portal, subscribers can view subscriptions, cancel, resume, and view payment history.
- Portal sessions expire after 30 minutes.
- The merchant must provide a
returnUrlso the subscriber can navigate back after managing their subscriptions. - See
Portal Session (Subscriber Self-Service)inreferences/api-contract.mdfor full endpoint details and code examples.
Preferred Response Shape
When answering with this skill, prefer this order:
- Goal summary
- Human setup steps
- API list
- Request fields
- Response fields
- Callback handling steps
- Example code
- Troubleshooting notes
Guardrails
- Default to test mode for development. If the loaded key starts with
pcs_live_, confirm with the human user that live mode is intended before making any API call. Never silently run against production billing. - Money-moving actions require explicit user confirmation. Before calling any of the following, state the exact action, target (
subscriptionId/sessionId), and mode (live/test), then wait for the user's "yes":POST /subscriptions/{id}/cancelPOST /subscriptions/{id}/resumePOST /checkout-sessions/{id}/complete(manual completion)- Any plan creation/update in live mode
- Do not batch or loop these actions across multiple subscriptions without per-action confirmation.
- Prefer the hosted checkout flow whenever possible. It already handles email verification, payment-method persistence, callback dispatch, subscription creation, payment creation, invoice task creation, and order bridge writes.
- Distinguish clearly between:
- setup APIs that the Agent can call directly with the Portaly Vibe Payment API key
- Do not invent provider behavior. TapPay and 91APP differ materially.
- Do not assume callback delivery means success without checking the
statusand verified signature. - Do not derive subscription state from redirect success pages alone. Redirects are UX only; callback or status query is the source of truth.
- Treat
references/checkout-and-renewal.mdas non-API background material. Load it only if the task explicitly touches recurring billing, payout, invoice follow-up, or bridge-order behavior. - Windows encoding: On Windows, run
chcp 65001(cmd) or$OutputEncoding = [System.Text.Encoding]::UTF8(PowerShell) before API calls containing non-ASCII text. If a plan'snameordescriptioncomes back garbled, fix encoding andPUTthe correct values. - Rate limiting: All creator-subscription API endpoints (except
POST /checkout-sessions) are rate limited. Read endpoints allow 120 requests/min, write endpoints allow 20 requests/min. If a429response is received, use theRetry-Afterheader to schedule retries. When paginating through large result sets, be mindful of the rate limit budget.
Deliverables
When using this skill, aim to return one or more of:
- a minimal step-by-step integration plan for the human user
- a flat list of relevant APIs
- request and response field breakdowns
- callback verification code in the user's stack
- sample
curl,fetch, or TypeScript snippets - a troubleshooting list keyed by session status
Resources
references/api-contract.mdUse for bearer auth, endpoint contract, callback headers, payload fields, and third-party implementation shape.references/checkout-and-renewal.mdUse only as optional background for the high-level checkout lifecycle and renewal behavior.references/discount-code-examples.mdExample prompts, parameter cheatsheet, and ref-code usage for the Discount Code APIs.scripts/sign_callback.pyUse when you need a deterministic example of Portaly callback signing and verification.scripts/sign_callback.mjsPrefer this for Node.js, JavaScript, TypeScript, Express, or Next.js integrations.
More from portaly-ai/portaly-skills
portaly-user
Help users sync and manage their application users in Portaly Vibe, including initial migration, incremental sync, and dashboard viewing. Trigger when the user mentions Portaly user sync, user management, user synchronization, member sync, or wants to push user data to Portaly.
40portaly-analytics
Help Portaly creators install Google Analytics 4 (GA4) on their websites, set up Portaly event tracking, and connect their GA4 account to Portaly for viewing analytics dashboards. Trigger when the user mentions Google Analytics, GA4, tracking, analytics, website traffic, event tracking, or wants to connect analytics to Portaly.
36portaly-sentry
Run a security and reliability health check on a Portaly Vibe payment integration before deployment. Trigger when the user mentions Portaly health check, payment security audit, pre-deploy check, sentry scan, callback verification audit, integration safety check, or wants to verify their Portaly payment integration is safe to go live.
35portaly-email
Help Portaly creators run follower-email campaigns end-to-end — create a draft, save and iterate on subject + HTML body, send it via Vibe MCP, read post-send analytics — and wire up where the invitation email's CTA redirects (Portaly-hosted waitlist, self-hosted /waitlist/[slug], or directly into the creator's existing register flow). Trigger when the user mentions invitation emails, follower outreach campaigns, sending an email blast to followers, drafting an email campaign, waitlist signup landing page, app base URL, embedding a waitlist CTA, skipping the waitlist when a member system already exists, or asks how the registration email link works / where it lands.
31