d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
222 lines
9.5 KiB
TypeScript
222 lines
9.5 KiB
TypeScript
import type { SubBlockConfig } from '@/blocks/types'
|
|
import type { TriggerOutput } from '@/triggers/types'
|
|
|
|
/**
|
|
* Shared trigger dropdown options for all RevenueCat triggers.
|
|
* All RevenueCat events POST to the same webhook endpoint; the active trigger is
|
|
* differentiated by `event.type` at match time.
|
|
*/
|
|
export const revenueCatTriggerOptions = [
|
|
{ label: 'Initial Purchase', id: 'revenuecat_initial_purchase' },
|
|
{ label: 'Renewal', id: 'revenuecat_renewal' },
|
|
{ label: 'Cancellation', id: 'revenuecat_cancellation' },
|
|
{ label: 'Expiration', id: 'revenuecat_expiration' },
|
|
{ label: 'Non-Renewing Purchase', id: 'revenuecat_non_renewing_purchase' },
|
|
{ label: 'Product Change', id: 'revenuecat_product_change' },
|
|
]
|
|
|
|
/**
|
|
* Maps each RevenueCat trigger ID to the exact uppercase `event.type` string
|
|
* RevenueCat sends in the webhook payload.
|
|
*/
|
|
export const REVENUECAT_TRIGGER_TO_EVENT_TYPE: Record<string, string> = {
|
|
revenuecat_initial_purchase: 'INITIAL_PURCHASE',
|
|
revenuecat_renewal: 'RENEWAL',
|
|
revenuecat_cancellation: 'CANCELLATION',
|
|
revenuecat_expiration: 'EXPIRATION',
|
|
revenuecat_non_renewing_purchase: 'NON_RENEWING_PURCHASE',
|
|
revenuecat_product_change: 'PRODUCT_CHANGE',
|
|
}
|
|
|
|
/**
|
|
* Maps each RevenueCat trigger ID to the lowercase `WebhookEventType` enum value
|
|
* the REST API v2 expects when creating a webhook integration.
|
|
*
|
|
* @see https://www.revenuecat.com/docs/api-v2 (Integration > Create a webhook integration)
|
|
*/
|
|
export const REVENUECAT_TRIGGER_TO_API_EVENT_TYPE: Record<string, string> = {
|
|
revenuecat_initial_purchase: 'initial_purchase',
|
|
revenuecat_renewal: 'renewal',
|
|
revenuecat_cancellation: 'cancellation',
|
|
revenuecat_expiration: 'expiration',
|
|
revenuecat_non_renewing_purchase: 'non_renewing_purchase',
|
|
revenuecat_product_change: 'product_change',
|
|
}
|
|
|
|
/**
|
|
* Generate setup instructions for a specific RevenueCat event type.
|
|
*
|
|
* Sim creates and tears down the webhook integration automatically through the
|
|
* RevenueCat REST API v2, so the user only supplies a Secret API key and the
|
|
* project ID — no manual dashboard webhook configuration is required.
|
|
*/
|
|
export function revenueCatSetupInstructions(eventLabel: string): string {
|
|
const instructions = [
|
|
'In the RevenueCat dashboard, go to <strong>Project settings > API keys</strong> and create a <strong>v2 Secret API key</strong> with the <code>project_configuration:integrations:read_write</code> permission.',
|
|
'Paste the Secret API key into the <strong>API Key</strong> field below.',
|
|
'Enter your <strong>Project ID</strong> (the <code>proj...</code> identifier shown in your project settings or the dashboard URL) in the <strong>Project ID</strong> field below.',
|
|
'Optionally restrict the trigger to a single <strong>Environment</strong> (Production or Sandbox). Leave it on <strong>All</strong> to receive both.',
|
|
`Sim creates the webhook integration in RevenueCat automatically when you deploy and removes it when you undeploy. This trigger fires on <strong>${eventLabel}</strong> events.`,
|
|
'Sim generates the Authorization header secret for you and verifies every incoming request against it — you do not need to set it manually.',
|
|
'Click "Save" above to deploy and activate your trigger.',
|
|
]
|
|
return instructions
|
|
.map(
|
|
(instruction, index) =>
|
|
`<div class="mb-3"><strong>${index + 1}.</strong> ${instruction}</div>`
|
|
)
|
|
.join('')
|
|
}
|
|
|
|
/**
|
|
* Extra trigger fields shared by all RevenueCat triggers.
|
|
*
|
|
* `apiKey` (a RevenueCat v2 Secret API key) and `projectId` let Sim create and
|
|
* delete the webhook integration through the REST API v2 during deploy/undeploy.
|
|
* `environment` optionally scopes the integration to production or sandbox.
|
|
* The Authorization header secret is generated by Sim during registration and
|
|
* stored in `providerConfig.authHeaderSecret`; the provider handler verifies
|
|
* every incoming `Authorization` header against it.
|
|
*/
|
|
export function buildRevenueCatExtraFields(triggerId: string): SubBlockConfig[] {
|
|
const condition = { field: 'selectedTriggerId', value: triggerId }
|
|
return [
|
|
{
|
|
id: 'apiKey',
|
|
title: 'API Key',
|
|
type: 'short-input',
|
|
password: true,
|
|
paramVisibility: 'user-only',
|
|
placeholder: 'RevenueCat v2 Secret API key (sk_...)',
|
|
description:
|
|
'Secret API key with the project_configuration:integrations:read_write permission. Sim uses it to create and remove the webhook in RevenueCat.',
|
|
required: true,
|
|
mode: 'trigger',
|
|
condition,
|
|
},
|
|
{
|
|
id: 'projectId',
|
|
title: 'Project ID',
|
|
type: 'short-input',
|
|
placeholder: 'proj1ab2c3d4',
|
|
description: 'RevenueCat project identifier the webhook integration is created in.',
|
|
required: true,
|
|
mode: 'trigger',
|
|
condition,
|
|
},
|
|
{
|
|
id: 'environment',
|
|
title: 'Environment',
|
|
type: 'dropdown',
|
|
options: [
|
|
{ label: 'All', id: 'all' },
|
|
{ label: 'Production', id: 'production' },
|
|
{ label: 'Sandbox', id: 'sandbox' },
|
|
],
|
|
value: () => 'all',
|
|
description: 'Restrict events to a single environment, or receive all of them.',
|
|
mode: 'trigger',
|
|
condition,
|
|
},
|
|
]
|
|
}
|
|
|
|
/**
|
|
* Build the output schema for RevenueCat events.
|
|
*
|
|
* Fields are flattened from the `event` object of the webhook payload
|
|
* (RevenueCat wraps the event under `event` and the wrapper carries `api_version`).
|
|
* Event-specific fields (`new_product_id`, `cancel_reason`, `expiration_reason`) are
|
|
* present for all triggers and are `null` when not applicable to the received event type.
|
|
*
|
|
* @see https://www.revenuecat.com/docs/integrations/webhooks/event-types-and-fields
|
|
*/
|
|
export function buildRevenueCatOutputs(): Record<string, TriggerOutput> {
|
|
return {
|
|
type: { type: 'string', description: 'Event type (e.g., INITIAL_PURCHASE, RENEWAL)' },
|
|
id: { type: 'string', description: 'Unique event identifier' },
|
|
app_id: { type: 'string', description: 'RevenueCat app public identifier' },
|
|
event_timestamp_ms: {
|
|
type: 'number',
|
|
description: 'Timestamp (ms since epoch) when the event was generated',
|
|
},
|
|
app_user_id: { type: 'string', description: 'Current App User ID' },
|
|
original_app_user_id: { type: 'string', description: 'First App User ID ever used' },
|
|
aliases: { type: 'json', description: 'All App User IDs ever used by the subscriber' },
|
|
product_id: { type: 'string', description: 'Product identifier' },
|
|
new_product_id: {
|
|
type: 'string',
|
|
description: 'Product identifier changed to (PRODUCT_CHANGE events only)',
|
|
},
|
|
period_type: {
|
|
type: 'string',
|
|
description: 'Period type (TRIAL, INTRO, NORMAL, PROMOTIONAL, or PREPAID)',
|
|
},
|
|
purchased_at_ms: { type: 'number', description: 'Purchase timestamp (ms since epoch)' },
|
|
expiration_at_ms: {
|
|
type: 'number',
|
|
description: 'Expiration timestamp (ms since epoch), nullable',
|
|
},
|
|
environment: { type: 'string', description: 'Environment (SANDBOX or PRODUCTION)' },
|
|
entitlement_id: { type: 'string', description: 'Deprecated single entitlement identifier' },
|
|
entitlement_ids: { type: 'json', description: 'Associated entitlement identifiers' },
|
|
presented_offering_id: {
|
|
type: 'string',
|
|
description: 'Identifier of the offering presented to the user',
|
|
},
|
|
transaction_id: { type: 'string', description: 'Store transaction ID' },
|
|
original_transaction_id: {
|
|
type: 'string',
|
|
description: 'Original subscription transaction ID',
|
|
},
|
|
is_family_share: { type: 'boolean', description: 'Whether the purchase was family shared' },
|
|
country_code: { type: 'string', description: 'ISO country code of the subscriber' },
|
|
currency: { type: 'string', description: 'ISO 4217 currency code' },
|
|
price: { type: 'number', description: 'Price in USD' },
|
|
price_in_purchased_currency: {
|
|
type: 'number',
|
|
description: 'Price in the currency the purchase was made in',
|
|
},
|
|
store: { type: 'string', description: 'Store the purchase was made on (e.g., APP_STORE)' },
|
|
takehome_percentage: {
|
|
type: 'number',
|
|
description: 'Estimated percentage of the price taken home after store commission',
|
|
},
|
|
tax_percentage: { type: 'number', description: 'Estimated percentage taken as tax' },
|
|
commission_percentage: {
|
|
type: 'number',
|
|
description: 'Estimated percentage taken by the store as commission',
|
|
},
|
|
offer_code: { type: 'string', description: 'Offer code applied to the purchase, if any' },
|
|
subscriber_attributes: {
|
|
type: 'json',
|
|
description: 'Subscriber attributes at the time of the event',
|
|
},
|
|
experiments: { type: 'json', description: 'Experiments the subscriber was enrolled in' },
|
|
cancel_reason: {
|
|
type: 'string',
|
|
description: 'Reason for cancellation (CANCELLATION events only)',
|
|
},
|
|
expiration_reason: {
|
|
type: 'string',
|
|
description: 'Reason for expiration (EXPIRATION events only)',
|
|
},
|
|
api_version: { type: 'string', description: 'RevenueCat webhook API version' },
|
|
event: { type: 'json', description: 'Full RevenueCat event object' },
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check whether an incoming RevenueCat event matches the configured trigger.
|
|
* Returns true for unknown trigger IDs so unexpected configs fall through rather than drop.
|
|
*/
|
|
export function isRevenueCatEventMatch(triggerId: string, body: Record<string, unknown>): boolean {
|
|
const expectedType = REVENUECAT_TRIGGER_TO_EVENT_TYPE[triggerId]
|
|
if (!expectedType) {
|
|
return true
|
|
}
|
|
const event = body.event as Record<string, unknown> | undefined
|
|
const actualType = event?.type as string | undefined
|
|
return actualType === expectedType
|
|
}
|