Skip to content

Commit 38cbefd

Browse files
authored
fix(revenuecat): align tools and block with REST v1 API spec (#4488)
* fix(revenuecat): align tools and block with REST v1 API spec - Validated all 10 tools and the block against context7 REST v1 docs - Unwrap {value:{subscriber}} envelope across post-receipts, attributes, entitlements, and Google subscription endpoints - Trim entitlement output to documented fields (expires_date, grace_period_expires_date, product_identifier, purchase_date) - Add subscriber output fields: last_seen, original_application_version, other_purchases, subscriber_attributes - create_purchase: productId optional (Google-only required); add introductoryPrice, attributes, updated_at_ms; surface customer + subscriber; X-Platform required; presentedOfferingIdentifier and paymentMode - update_subscriber_attributes: read response and surface subscriber; note required updated_at_ms - defer_google_subscription: enforce XOR(extendByDays, expiryTimeMs) and 1-365 range; expiryTimeMs as one-of alternative - grant_entitlement: duration optional, added endTimeMs (one-of) - refund_google_subscription: corrected endpoint to /transactions/{storeTransactionId}/refund - delete_customer: read 'deleted' field (was 'was_deleted') - list_offerings: corrected X-Platform values - get_customer: count active subscriptions by expiry/refund - Added shared throwIfRevenueCatError helper for {code, message} envelope - Moved type coercions from tools.config.tool to tools.config.params to preserve dynamic refs * docs * fix(revenuecat): address PR review feedback - create_purchase: wrap JSON.parse(attributes) with try/catch and clear error - update_subscriber_attributes: drop subscriber output (endpoint returns empty body) and guard JSON.parse on attributes - grant_entitlement: throw when both duration and endTimeMs are provided, matching defer_google_subscription behavior * fix(revenuecat): tighten docs after sub-segment validation - delete_customer: drop dead was_deleted fallback (docs specify 'deleted') - grant_entitlement: mark duration + startTimeMs as deprecated, clarify startTimeMs only affects expiration calc (not grant time) - list_offerings: replace vague platform description with documented X-Platform enum (ios, android, amazon, stripe, roku, paddle) * docs * fix(revenuecat): clear duration default when endTimeMs is provided The duration dropdown defaults to 'monthly' so any user filling in the advanced endTimeMs field would otherwise hit the XOR guard. Clear duration in the params mapper so endTimeMs takes precedence. * fix(revenuecat): clear extendByDays default when expiryTimeMs is provided Mirror the duration/endTimeMs fix from 8204e89: when expiryTimeMs is populated, clear extendByDays in the params mapper so the empty-string form value does not trip the XOR guard. Also harden the tool-level XOR checks in defer_google_subscription and grant_entitlement to treat empty strings as undefined for direct (non-block) callers. * fix(revenuecat): guard NaN in time-ms mappers and require integer days - Block params mapper: only clear duration/extendByDays when the parsed endTimeMs/expiryTimeMs is finite, so invalid input does not silently discard the user's valid companion default - defer_google_subscription: validate extendByDays as integer (was Number.isFinite), matching the error message * fix(revenuecat): fall back to Date.now() when request_date is malformed A malformed request_date would parse to NaN, making every entitlement and subscription compare false and silently zero active counts. Fall back to Date.now() when the parsed value is not finite. * fix(revenuecat): unwrap value envelope in list_offerings response * improvement(revenuecat): include updated_at_ms in attributes placeholder and wand prompt
1 parent d081ab2 commit 38cbefd

14 files changed

Lines changed: 656 additions & 286 deletions

apps/docs/content/docs/en/tools/revenuecat.mdx

Lines changed: 70 additions & 69 deletions
Large diffs are not rendered by default.

apps/sim/app/(landing)/integrations/data/integrations.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10748,7 +10748,7 @@
1074810748
},
1074910749
{
1075010750
"name": "Refund Google Subscription",
10751-
"description": "Refund and optionally revoke a Google Play subscription (Google Play only)"
10751+
"description": "Refund a specific store transaction by its store transaction identifier and revoke access (subscription or non-subscription, last 365 days)"
1075210752
},
1075310753
{
1075410754
"name": "Revoke Google Subscription",
@@ -14173,7 +14173,7 @@
1417314173
"description": "Hire a pre-hire into an employee position. Converts an applicant into an active employee record with position, start date, and manager assignment."
1417414174
},
1417514175
{
14176-
"name": "Update Worker",
14176+
"name": "Update Personal Information",
1417714177
"description": "Update fields on an existing worker record in Workday."
1417814178
},
1417914179
{

apps/sim/blocks/blocks/revenuecat.ts

Lines changed: 187 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export const RevenueCatBlock: BlockConfig<RevenueCatResponse> = {
7272
{ label: 'Daily', id: 'daily' },
7373
{ label: '3 Days', id: 'three_day' },
7474
{ label: 'Weekly', id: 'weekly' },
75+
{ label: '2 Weeks', id: 'two_week' },
7576
{ label: 'Monthly', id: 'monthly' },
7677
{ label: '2 Months', id: 'two_month' },
7778
{ label: '3 Months', id: 'three_month' },
@@ -85,6 +86,28 @@ export const RevenueCatBlock: BlockConfig<RevenueCatResponse> = {
8586
value: 'grant_entitlement',
8687
},
8788
},
89+
{
90+
id: 'endTimeMs',
91+
title: 'End Time (ms)',
92+
type: 'short-input',
93+
placeholder: 'Optional absolute end time in ms since epoch',
94+
condition: {
95+
field: 'operation',
96+
value: 'grant_entitlement',
97+
},
98+
mode: 'advanced',
99+
wandConfig: {
100+
enabled: true,
101+
prompt: `Generate a Unix epoch timestamp in milliseconds based on the user's description.
102+
The timestamp should represent the absolute end time for the entitlement.
103+
Examples:
104+
- "in 7 days" -> current time plus 604800000 milliseconds
105+
- "next month" -> current time plus 2592000000 milliseconds
106+
- "end of 2026" -> 1798761600000
107+
108+
Return ONLY the numeric timestamp, no text.`,
109+
},
110+
},
88111
{
89112
id: 'startTimeMs',
90113
title: 'Start Time (ms)',
@@ -124,9 +147,10 @@ Return ONLY the numeric timestamp, no text.`,
124147
},
125148
{
126149
id: 'productId',
127-
title: 'Product ID',
150+
title: 'Product ID / Store Transaction ID',
128151
type: 'short-input',
129-
placeholder: 'Product identifier',
152+
placeholder:
153+
'Product ID, or store transaction ID for refunds (e.g., GPA.3309-9122-6177-45730)',
130154
condition: {
131155
field: 'operation',
132156
value: [
@@ -139,7 +163,6 @@ Return ONLY the numeric timestamp, no text.`,
139163
required: {
140164
field: 'operation',
141165
value: [
142-
'create_purchase',
143166
'defer_google_subscription',
144167
'refund_google_subscription',
145168
'revoke_google_subscription',
@@ -168,6 +191,61 @@ Return ONLY the numeric timestamp, no text.`,
168191
},
169192
mode: 'advanced',
170193
},
194+
{
195+
id: 'presentedOfferingIdentifier',
196+
title: 'Presented Offering ID',
197+
type: 'short-input',
198+
placeholder: 'Offering identifier shown to the user',
199+
condition: {
200+
field: 'operation',
201+
value: 'create_purchase',
202+
},
203+
mode: 'advanced',
204+
},
205+
{
206+
id: 'paymentMode',
207+
title: 'Payment Mode',
208+
type: 'dropdown',
209+
options: [
210+
{ label: 'Pay As You Go', id: 'pay_as_you_go' },
211+
{ label: 'Pay Up Front', id: 'pay_up_front' },
212+
{ label: 'Free Trial', id: 'free_trial' },
213+
],
214+
condition: {
215+
field: 'operation',
216+
value: 'create_purchase',
217+
},
218+
mode: 'advanced',
219+
},
220+
{
221+
id: 'introductoryPrice',
222+
title: 'Introductory Price',
223+
type: 'short-input',
224+
placeholder: 'e.g., 0.99',
225+
condition: {
226+
field: 'operation',
227+
value: 'create_purchase',
228+
},
229+
mode: 'advanced',
230+
},
231+
{
232+
id: 'updatedAtMs',
233+
title: 'Updated At (ms)',
234+
type: 'short-input',
235+
placeholder: 'Unix epoch ms used to resolve attribute conflicts',
236+
condition: {
237+
field: 'operation',
238+
value: 'create_purchase',
239+
},
240+
mode: 'advanced',
241+
wandConfig: {
242+
enabled: true,
243+
prompt: `Generate a Unix epoch timestamp in milliseconds based on the user's description.
244+
Used by RevenueCat to resolve attribute conflicts on a posted purchase.
245+
246+
Return ONLY the numeric timestamp, no text.`,
247+
},
248+
},
171249
{
172250
id: 'isRestore',
173251
title: 'Is Restore',
@@ -192,22 +270,28 @@ Return ONLY the numeric timestamp, no text.`,
192270
{ label: 'Android', id: 'android' },
193271
{ label: 'Amazon', id: 'amazon' },
194272
{ label: 'macOS', id: 'macos' },
273+
{ label: 'UIKit for Mac', id: 'uikitformac' },
195274
{ label: 'Stripe', id: 'stripe' },
275+
{ label: 'Roku', id: 'roku' },
276+
{ label: 'Paddle', id: 'paddle' },
196277
],
197278
condition: {
198279
field: 'operation',
199280
value: 'create_purchase',
200281
},
201-
mode: 'advanced',
282+
required: {
283+
field: 'operation',
284+
value: 'create_purchase',
285+
},
202286
},
203287
{
204288
id: 'attributes',
205289
title: 'Attributes',
206290
type: 'long-input',
207-
placeholder: '{"$email": {"value": "user@example.com"}}',
291+
placeholder: '{"$email": {"value": "user@example.com", "updated_at_ms": 1709195668093}}',
208292
condition: {
209293
field: 'operation',
210-
value: 'update_subscriber_attributes',
294+
value: ['update_subscriber_attributes', 'create_purchase'],
211295
},
212296
required: {
213297
field: 'operation',
@@ -216,17 +300,17 @@ Return ONLY the numeric timestamp, no text.`,
216300
wandConfig: {
217301
enabled: true,
218302
prompt: `Generate a JSON object of RevenueCat subscriber attributes based on the user's description.
219-
Each attribute key maps to an object with a "value" field.
303+
Each attribute key maps to an object with a "value" field (string) and an "updated_at_ms" field (Unix epoch ms; required by the API for conflict resolution — use the current timestamp unless the user specifies otherwise).
220304
Reserved attribute keys start with "$": $email, $displayName, $phoneNumber, $mediaSource, $campaign, $adGroup, $ad, $keyword, $creative, $iterableUserId, $iterableCampaignId, $iterableTemplateId, $onesignalId, $airshipChannelId, $cleverTapId, $firebaseAppInstanceId.
221305
Custom attributes use plain keys without "$".
222306
223307
Examples:
224308
- "set email to john@example.com and name to John" ->
225-
{"$email": {"value": "john@example.com"}, "$displayName": {"value": "John"}}
309+
{"$email": {"value": "john@example.com", "updated_at_ms": 1709195668093}, "$displayName": {"value": "John", "updated_at_ms": 1709195668093}}
226310
- "set plan to premium and team to acme" ->
227-
{"plan": {"value": "premium"}, "team": {"value": "acme"}}
311+
{"plan": {"value": "premium", "updated_at_ms": 1709195668093}, "team": {"value": "acme", "updated_at_ms": 1709195668093}}
228312
229-
Return ONLY valid JSON.`,
313+
Return ONLY valid JSON - no explanations, no extra text.`,
230314
},
231315
},
232316
{
@@ -238,10 +322,24 @@ Return ONLY valid JSON.`,
238322
field: 'operation',
239323
value: 'defer_google_subscription',
240324
},
241-
required: {
325+
},
326+
{
327+
id: 'expiryTimeMs',
328+
title: 'Expiry Time (ms)',
329+
type: 'short-input',
330+
placeholder: 'Absolute new expiry time in ms since epoch',
331+
condition: {
242332
field: 'operation',
243333
value: 'defer_google_subscription',
244334
},
335+
mode: 'advanced',
336+
wandConfig: {
337+
enabled: true,
338+
prompt: `Generate a Unix epoch timestamp in milliseconds based on the user's description.
339+
The timestamp should represent the new absolute expiry time of the subscription.
340+
341+
Return ONLY the numeric timestamp, no text.`,
342+
},
245343
},
246344
{
247345
id: 'platform',
@@ -251,13 +349,15 @@ Return ONLY valid JSON.`,
251349
{ label: 'iOS', id: 'ios' },
252350
{ label: 'Android', id: 'android' },
253351
{ label: 'Amazon', id: 'amazon' },
254-
{ label: 'macOS', id: 'macos' },
255352
{ label: 'Stripe', id: 'stripe' },
353+
{ label: 'Roku', id: 'roku' },
354+
{ label: 'Paddle', id: 'paddle' },
256355
],
257356
condition: {
258357
field: 'operation',
259358
value: 'list_offerings',
260359
},
360+
mode: 'advanced',
261361
},
262362
],
263363
tools: {
@@ -274,23 +374,50 @@ Return ONLY valid JSON.`,
274374
'revenuecat_revoke_google_subscription',
275375
],
276376
config: {
277-
tool: (params) => {
377+
tool: (params) => `revenuecat_${params.operation}`,
378+
params: (params) => {
379+
const next: Record<string, unknown> = { ...params }
278380
if (params.purchasePlatform && params.operation === 'create_purchase') {
279-
params.platform = params.purchasePlatform
381+
next.platform = params.purchasePlatform
382+
}
383+
next.purchasePlatform = undefined
384+
if (params.productId && params.operation === 'refund_google_subscription') {
385+
next.storeTransactionId = params.productId
386+
next.productId = undefined
280387
}
281-
if (params.isRestore !== undefined) {
282-
params.isRestore = params.isRestore === 'true'
388+
if (params.isRestore !== undefined && params.isRestore !== '') {
389+
next.isRestore = params.isRestore === true || params.isRestore === 'true'
283390
}
284391
if (params.price !== undefined && params.price !== '') {
285-
params.price = Number(params.price)
392+
next.price = Number(params.price)
286393
}
287394
if (params.extendByDays !== undefined && params.extendByDays !== '') {
288-
params.extendByDays = Number(params.extendByDays)
395+
next.extendByDays = Number(params.extendByDays)
289396
}
290397
if (params.startTimeMs !== undefined && params.startTimeMs !== '') {
291-
params.startTimeMs = Number(params.startTimeMs)
398+
next.startTimeMs = Number(params.startTimeMs)
399+
}
400+
if (params.endTimeMs !== undefined && params.endTimeMs !== '') {
401+
const endTimeMs = Number(params.endTimeMs)
402+
if (Number.isFinite(endTimeMs)) {
403+
next.endTimeMs = endTimeMs
404+
next.duration = undefined
405+
}
406+
}
407+
if (params.expiryTimeMs !== undefined && params.expiryTimeMs !== '') {
408+
const expiryTimeMs = Number(params.expiryTimeMs)
409+
if (Number.isFinite(expiryTimeMs)) {
410+
next.expiryTimeMs = expiryTimeMs
411+
next.extendByDays = undefined
412+
}
413+
}
414+
if (params.introductoryPrice !== undefined && params.introductoryPrice !== '') {
415+
next.introductoryPrice = Number(params.introductoryPrice)
292416
}
293-
return `revenuecat_${params.operation}`
417+
if (params.updatedAtMs !== undefined && params.updatedAtMs !== '') {
418+
next.updatedAtMs = Number(params.updatedAtMs)
419+
}
420+
return next
294421
},
295422
},
296423
},
@@ -302,28 +429,61 @@ Return ONLY valid JSON.`,
302429
duration: { type: 'string', description: 'Promotional entitlement duration' },
303430
startTimeMs: { type: 'number', description: 'Custom start time in ms since epoch' },
304431
fetchToken: { type: 'string', description: 'Store receipt or purchase token' },
305-
productId: { type: 'string', description: 'Product identifier' },
432+
productId: {
433+
type: 'string',
434+
description: 'Product identifier (or store transaction ID for refunds)',
435+
},
306436
price: { type: 'number', description: 'Product price' },
307437
currency: { type: 'string', description: 'ISO 4217 currency code' },
308438
isRestore: { type: 'boolean', description: 'Whether this is a restore purchase' },
309-
purchasePlatform: { type: 'string', description: 'Platform for the purchase' },
310-
attributes: { type: 'string', description: 'JSON object of subscriber attributes' },
439+
presentedOfferingIdentifier: {
440+
type: 'string',
441+
description: 'Identifier of the offering presented to the user',
442+
},
443+
paymentMode: {
444+
type: 'string',
445+
description: 'Payment mode (pay_as_you_go, pay_up_front, free_trial)',
446+
},
447+
attributes: {
448+
type: 'string',
449+
description:
450+
'JSON object of subscriber attributes (used by update_subscriber_attributes and create_purchase)',
451+
},
452+
introductoryPrice: { type: 'number', description: 'Introductory price for the purchase' },
453+
updatedAtMs: {
454+
type: 'number',
455+
description: 'Unix epoch ms used by RevenueCat to resolve attribute conflicts',
456+
},
311457
extendByDays: { type: 'number', description: 'Number of days to extend (1-365)' },
312-
platform: { type: 'string', description: 'Platform filter for offerings' },
458+
expiryTimeMs: { type: 'number', description: 'Absolute new expiry time in ms since epoch' },
459+
endTimeMs: {
460+
type: 'number',
461+
description: 'Absolute end time for entitlement in ms since epoch',
462+
},
463+
platform: { type: 'string', description: 'Platform (X-Platform header)' },
313464
},
314465
outputs: {
315466
subscriber: {
316467
type: 'json',
317-
description: 'Subscriber object with subscriptions and entitlements',
468+
description:
469+
'Subscriber object (first_seen, original_app_user_id, original_purchase_date, management_url, subscriptions, entitlements, non_subscriptions)',
318470
},
319471
offerings: {
320472
type: 'json',
321-
description: 'Array of offerings with packages',
473+
description: 'Array of offerings, each with identifier, description, and packages[]',
322474
},
323475
current_offering_id: { type: 'string', description: 'Current offering identifier' },
324-
metadata: { type: 'json', description: 'Operation metadata' },
476+
metadata: {
477+
type: 'json',
478+
description:
479+
'Operation metadata. For get_customer: app_user_id, first_seen, active_entitlements, active_subscriptions. For list_offerings: count, current_offering_id.',
480+
},
325481
deleted: { type: 'boolean', description: 'Whether the subscriber was deleted' },
326482
app_user_id: { type: 'string', description: 'The app user ID' },
327483
updated: { type: 'boolean', description: 'Whether the attributes were updated' },
484+
customer: {
485+
type: 'json',
486+
description: 'Customer object returned by create_purchase (when present in the response)',
487+
},
328488
},
329489
}

0 commit comments

Comments
 (0)