@nordsym/apiclaw 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/add-api.yml +123 -0
- package/BRIEFING.md +30 -0
- package/CONCEPT.md +494 -0
- package/README.md +272 -0
- package/backend/convex/README.md +90 -0
- package/backend/convex/_generated/api.d.ts +55 -0
- package/backend/convex/_generated/api.js +23 -0
- package/backend/convex/_generated/dataModel.d.ts +60 -0
- package/backend/convex/_generated/server.d.ts +143 -0
- package/backend/convex/_generated/server.js +93 -0
- package/backend/convex/apiKeys.ts +75 -0
- package/backend/convex/purchases.ts +74 -0
- package/backend/convex/schema.ts +45 -0
- package/backend/convex/transactions.ts +57 -0
- package/backend/convex/tsconfig.json +25 -0
- package/backend/convex/users.ts +94 -0
- package/backend/package-lock.json +521 -0
- package/backend/package.json +15 -0
- package/dist/credits.d.ts +54 -0
- package/dist/credits.d.ts.map +1 -0
- package/dist/credits.js +209 -0
- package/dist/credits.js.map +1 -0
- package/dist/discovery.d.ts +37 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +109 -0
- package/dist/discovery.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +355 -0
- package/dist/index.js.map +1 -0
- package/dist/registry/apis.json +20894 -0
- package/dist/registry/parse_apis.py +146 -0
- package/dist/revenuecat.d.ts +61 -0
- package/dist/revenuecat.d.ts.map +1 -0
- package/dist/revenuecat.js +166 -0
- package/dist/revenuecat.js.map +1 -0
- package/dist/test.d.ts +6 -0
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +81 -0
- package/dist/test.js.map +1 -0
- package/dist/types.d.ts +96 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/webhooks/revenuecat.d.ts +48 -0
- package/dist/webhooks/revenuecat.d.ts.map +1 -0
- package/dist/webhooks/revenuecat.js +119 -0
- package/dist/webhooks/revenuecat.js.map +1 -0
- package/docs/revenuecat-setup.md +89 -0
- package/landing/next-env.d.ts +5 -0
- package/landing/next.config.mjs +6 -0
- package/landing/package-lock.json +1666 -0
- package/landing/package.json +27 -0
- package/landing/postcss.config.js +6 -0
- package/landing/src/app/api/keys/route.ts +71 -0
- package/landing/src/app/api/log/route.ts +37 -0
- package/landing/src/app/api/stats/route.ts +37 -0
- package/landing/src/app/globals.css +261 -0
- package/landing/src/app/layout.tsx +37 -0
- package/landing/src/app/page.tsx +753 -0
- package/landing/src/app/page.tsx.bak +567 -0
- package/landing/src/components/AddKeyModal.tsx +159 -0
- package/landing/tailwind.config.ts +34 -0
- package/landing/tsconfig.json +20 -0
- package/newsletter-template.html +71 -0
- package/outreach/OUTREACH-SYSTEM.md +211 -0
- package/outreach/email-template.html +179 -0
- package/outreach/targets.md +133 -0
- package/package.json +39 -0
- package/src/credits.ts +261 -0
- package/src/discovery.ts +147 -0
- package/src/index.ts +396 -0
- package/src/registry/apis.json +20894 -0
- package/src/registry/parse_apis.py +146 -0
- package/src/revenuecat.ts +239 -0
- package/src/test.ts +97 -0
- package/src/types.ts +110 -0
- package/src/webhooks/revenuecat.ts +187 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// RevenueCat webhook handler for APIClaw
|
|
2
|
+
// Handles subscription lifecycle events and syncs to Convex
|
|
3
|
+
|
|
4
|
+
import { Hono } from 'hono';
|
|
5
|
+
|
|
6
|
+
const CONVEX_URL = process.env.CONVEX_URL || 'https://agile-crane-840.convex.cloud';
|
|
7
|
+
|
|
8
|
+
// RevenueCat webhook event types
|
|
9
|
+
type WebhookEventType =
|
|
10
|
+
| 'INITIAL_PURCHASE'
|
|
11
|
+
| 'RENEWAL'
|
|
12
|
+
| 'CANCELLATION'
|
|
13
|
+
| 'UNCANCELLATION'
|
|
14
|
+
| 'NON_RENEWING_PURCHASE'
|
|
15
|
+
| 'SUBSCRIPTION_PAUSED'
|
|
16
|
+
| 'SUBSCRIPTION_EXTENDED'
|
|
17
|
+
| 'EXPIRATION'
|
|
18
|
+
| 'BILLING_ISSUE'
|
|
19
|
+
| 'PRODUCT_CHANGE'
|
|
20
|
+
| 'TRANSFER';
|
|
21
|
+
|
|
22
|
+
interface RevenueCatWebhookEvent {
|
|
23
|
+
api_version: string;
|
|
24
|
+
event: {
|
|
25
|
+
type: WebhookEventType;
|
|
26
|
+
id: string;
|
|
27
|
+
app_id: string;
|
|
28
|
+
app_user_id: string;
|
|
29
|
+
original_app_user_id: string;
|
|
30
|
+
aliases: string[];
|
|
31
|
+
product_id: string;
|
|
32
|
+
entitlement_ids: string[];
|
|
33
|
+
period_type: string;
|
|
34
|
+
purchased_at_ms: number;
|
|
35
|
+
expiration_at_ms: number | null;
|
|
36
|
+
environment: 'SANDBOX' | 'PRODUCTION';
|
|
37
|
+
store: string;
|
|
38
|
+
is_trial_conversion?: boolean;
|
|
39
|
+
cancel_reason?: string;
|
|
40
|
+
price_in_purchased_currency?: number;
|
|
41
|
+
currency?: string;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface UserSubscriptionUpdate {
|
|
46
|
+
userId: string;
|
|
47
|
+
isPro: boolean;
|
|
48
|
+
subscriptionStatus: 'active' | 'cancelled' | 'expired' | 'paused' | 'billing_issue';
|
|
49
|
+
productId: string | null;
|
|
50
|
+
expiresAt: number | null;
|
|
51
|
+
updatedAt: number;
|
|
52
|
+
eventType: WebhookEventType;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Update user subscription status in Convex
|
|
57
|
+
*/
|
|
58
|
+
async function updateConvexUser(update: UserSubscriptionUpdate): Promise<boolean> {
|
|
59
|
+
try {
|
|
60
|
+
const response = await fetch(`${CONVEX_URL}/api/mutation`, {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: {
|
|
63
|
+
'Content-Type': 'application/json',
|
|
64
|
+
},
|
|
65
|
+
body: JSON.stringify({
|
|
66
|
+
path: 'users:updateSubscription',
|
|
67
|
+
args: update,
|
|
68
|
+
}),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
console.error('Convex update failed:', await response.text());
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return true;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error('Error updating Convex:', error);
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Process webhook event and determine subscription state
|
|
85
|
+
*/
|
|
86
|
+
function processEvent(event: RevenueCatWebhookEvent['event']): UserSubscriptionUpdate {
|
|
87
|
+
const now = Date.now();
|
|
88
|
+
const expiresAt = event.expiration_at_ms;
|
|
89
|
+
const isExpired = expiresAt ? expiresAt < now : false;
|
|
90
|
+
|
|
91
|
+
let subscriptionStatus: UserSubscriptionUpdate['subscriptionStatus'] = 'active';
|
|
92
|
+
let isPro = true;
|
|
93
|
+
|
|
94
|
+
switch (event.type) {
|
|
95
|
+
case 'INITIAL_PURCHASE':
|
|
96
|
+
case 'RENEWAL':
|
|
97
|
+
case 'UNCANCELLATION':
|
|
98
|
+
case 'SUBSCRIPTION_EXTENDED':
|
|
99
|
+
subscriptionStatus = 'active';
|
|
100
|
+
isPro = true;
|
|
101
|
+
break;
|
|
102
|
+
|
|
103
|
+
case 'CANCELLATION':
|
|
104
|
+
// Still active until expiration
|
|
105
|
+
subscriptionStatus = 'cancelled';
|
|
106
|
+
isPro = !isExpired;
|
|
107
|
+
break;
|
|
108
|
+
|
|
109
|
+
case 'EXPIRATION':
|
|
110
|
+
subscriptionStatus = 'expired';
|
|
111
|
+
isPro = false;
|
|
112
|
+
break;
|
|
113
|
+
|
|
114
|
+
case 'SUBSCRIPTION_PAUSED':
|
|
115
|
+
subscriptionStatus = 'paused';
|
|
116
|
+
isPro = false;
|
|
117
|
+
break;
|
|
118
|
+
|
|
119
|
+
case 'BILLING_ISSUE':
|
|
120
|
+
subscriptionStatus = 'billing_issue';
|
|
121
|
+
// Keep Pro until grace period ends
|
|
122
|
+
isPro = !isExpired;
|
|
123
|
+
break;
|
|
124
|
+
|
|
125
|
+
case 'PRODUCT_CHANGE':
|
|
126
|
+
case 'TRANSFER':
|
|
127
|
+
subscriptionStatus = 'active';
|
|
128
|
+
isPro = event.entitlement_ids.includes('pro');
|
|
129
|
+
break;
|
|
130
|
+
|
|
131
|
+
default:
|
|
132
|
+
isPro = event.entitlement_ids.includes('pro') && !isExpired;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
userId: event.app_user_id,
|
|
137
|
+
isPro,
|
|
138
|
+
subscriptionStatus,
|
|
139
|
+
productId: event.product_id,
|
|
140
|
+
expiresAt: expiresAt,
|
|
141
|
+
updatedAt: now,
|
|
142
|
+
eventType: event.type,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Create Hono router for webhook handling
|
|
148
|
+
*/
|
|
149
|
+
export function createWebhookRouter(): Hono {
|
|
150
|
+
const app = new Hono();
|
|
151
|
+
|
|
152
|
+
// Health check
|
|
153
|
+
app.get('/webhooks/revenuecat/health', (c) => {
|
|
154
|
+
return c.json({ status: 'ok', service: 'revenuecat-webhook' });
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// RevenueCat webhook endpoint
|
|
158
|
+
app.post('/webhooks/revenuecat', async (c) => {
|
|
159
|
+
try {
|
|
160
|
+
const body = await c.req.json<RevenueCatWebhookEvent>();
|
|
161
|
+
|
|
162
|
+
console.log(`[RevenueCat] Received ${body.event.type} for user ${body.event.app_user_id}`);
|
|
163
|
+
|
|
164
|
+
// Process the event
|
|
165
|
+
const update = processEvent(body.event);
|
|
166
|
+
|
|
167
|
+
// Update Convex
|
|
168
|
+
const success = await updateConvexUser(update);
|
|
169
|
+
|
|
170
|
+
if (success) {
|
|
171
|
+
console.log(`[RevenueCat] Updated user ${update.userId}: isPro=${update.isPro}`);
|
|
172
|
+
return c.json({ success: true });
|
|
173
|
+
} else {
|
|
174
|
+
return c.json({ success: false, error: 'Failed to update database' }, 500);
|
|
175
|
+
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.error('[RevenueCat] Webhook error:', error);
|
|
178
|
+
return c.json({ success: false, error: 'Internal error' }, 500);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return app;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Export for testing
|
|
186
|
+
export { processEvent, updateConvexUser };
|
|
187
|
+
export type { RevenueCatWebhookEvent, UserSubscriptionUpdate };
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"declarationMap": true,
|
|
16
|
+
"sourceMap": true
|
|
17
|
+
},
|
|
18
|
+
"include": ["src/**/*"],
|
|
19
|
+
"exclude": ["node_modules", "dist"]
|
|
20
|
+
}
|