wirejs-module-payments-stripe 0.1.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/README.md ADDED
@@ -0,0 +1,10 @@
1
+ Stripe payments module for `wirejs` apps.
2
+
3
+ In your backend / api workspace:
4
+
5
+ ```ts
6
+ import { Payments } from `wirejs-module-payments-stripe`;
7
+
8
+ ```
9
+
10
+ In your front-end, choose an SSR path to
@@ -0,0 +1,185 @@
1
+ import { Endpoint, Resource, Setting } from 'wirejs-resources';
2
+ import Stripe from 'stripe';
3
+ export type CheckoutMode = 'subscription' | 'payment';
4
+ export type Customer = {
5
+ id: string;
6
+ };
7
+ export type SubscriptionInterval = "day" | "week" | "month" | "year";
8
+ export type Product = {
9
+ id: string;
10
+ name: string;
11
+ type: 'one_time' | 'recurring';
12
+ interval?: SubscriptionInterval;
13
+ currency: 'usd';
14
+ /**
15
+ * In cents
16
+ */
17
+ unitAmount: number;
18
+ metadata: Record<string, string>;
19
+ };
20
+ export type Price = {
21
+ product: string | {
22
+ id: string;
23
+ } | undefined;
24
+ recurring: {
25
+ interval: SubscriptionInterval;
26
+ } | null;
27
+ currency: string;
28
+ unit_amount: number | null;
29
+ metadata: Record<string, string>;
30
+ };
31
+ export type OneTimeProduct = Product & {
32
+ type: 'one_time';
33
+ };
34
+ export type SubscriptionProduct = Product & {
35
+ type: 'recurring';
36
+ };
37
+ export type LineItem<T extends CheckoutMode> = {
38
+ product: Product & (T extends 'subscription' ? SubscriptionProduct : OneTimeProduct);
39
+ quantity: number;
40
+ };
41
+ export type OneTimePurchaseLineItem = LineItem<'payment'>;
42
+ export type SubscriptionLineItem = LineItem<'subscription'>;
43
+ export type Transaction = {
44
+ /**
45
+ * Stripe ID.
46
+ */
47
+ id: string;
48
+ customerId: string;
49
+ type: 'payment' | 'refund';
50
+ /**
51
+ * cents, always positive
52
+ */
53
+ amount: number;
54
+ currency: string;
55
+ /**
56
+ * only used for refunds
57
+ */
58
+ relatedPaymentId?: string;
59
+ /**
60
+ * optional for refunds
61
+ */
62
+ reason?: string;
63
+ createdAt: string;
64
+ items?: Array<{
65
+ id: string;
66
+ description: string | null;
67
+ quantity: number | null;
68
+ amount: number;
69
+ productId?: string;
70
+ }>;
71
+ };
72
+ export type SubscriptionLine = {
73
+ /**
74
+ * Stripe Line Item ID
75
+ */
76
+ id: string;
77
+ /**
78
+ * Stripe Subscription ID
79
+ */
80
+ subscriptionId: string;
81
+ customerId: string;
82
+ /**
83
+ * Internal product/plan ID.
84
+ */
85
+ productId: string;
86
+ amount: number;
87
+ currency: 'usd';
88
+ interval: SubscriptionInterval;
89
+ quantity: number;
90
+ status: Stripe.Subscription['status'] | 'deleted';
91
+ currentPeriodStart: string;
92
+ currentPeriodEnd: string;
93
+ canceledAt?: string;
94
+ createdAt: string;
95
+ updatedAt: string;
96
+ metadata?: Record<string, string>;
97
+ };
98
+ export declare class PaymentService extends Resource {
99
+ devMode: Setting<{
100
+ readonly private: false;
101
+ readonly description: "If \"yes\", skips Stripe checkout and immediately records successful payments.";
102
+ readonly init: () => string;
103
+ readonly options: ["yes", "no"];
104
+ }>;
105
+ stripeKey: Setting<{
106
+ readonly private: true;
107
+ readonly description: "The API key issued by Stripe.";
108
+ }>;
109
+ stripeEndpointSecret: Setting<{
110
+ readonly private: true;
111
+ readonly description: "The verification secret issued by Stripe for this endpoint.";
112
+ }>;
113
+ private prices;
114
+ private transactions;
115
+ private subscriptionLines;
116
+ private customerInternalIdToExternalId;
117
+ private customerExternalIdToInternalId;
118
+ webhookEndpoint: Endpoint;
119
+ _stripe: undefined | Promise<Stripe | undefined>;
120
+ _endpointSecretData: undefined | Promise<string>;
121
+ constructor(scope: Resource | string, id: string);
122
+ /**
123
+ * Creates a raw Stripe client.
124
+ *
125
+ * @returns
126
+ */
127
+ getClient(): Promise<Stripe | undefined>;
128
+ getEndpointSecret(): Promise<string>;
129
+ private getPrice;
130
+ private getProductFromPrice;
131
+ private getStripeCustomer;
132
+ private getInternalCustomer;
133
+ private recordInvoicePayment;
134
+ private recordCheckoutPayment;
135
+ private recordCustomerSubscriptionLine;
136
+ private recordCustomerSubscription;
137
+ private recordLocalModeCheckout;
138
+ createCheckoutUrl<T extends OneTimePurchaseLineItem | SubscriptionLineItem>({ customer, lineItems, successUrl, cancelUrl }: {
139
+ customer: Customer;
140
+ lineItems: T[];
141
+ successUrl: URL | string;
142
+ cancelUrl: URL | string;
143
+ }): Promise<string | null>;
144
+ cancelSubscriptionLine(id: string): Promise<void>;
145
+ listPayments(internalCustomerId: string): Promise<{
146
+ id: string;
147
+ customerId: string;
148
+ type: "payment" | "refund";
149
+ amount: number;
150
+ currency: string;
151
+ relatedPaymentId?: string | undefined;
152
+ reason?: string | undefined;
153
+ createdAt: string;
154
+ items?: {
155
+ id: string;
156
+ description: string | null;
157
+ quantity: number | null;
158
+ amount: number;
159
+ productId?: string | undefined;
160
+ }[] | undefined;
161
+ }[]>;
162
+ listSubscriptions(internalCustomerId: string): Promise<{
163
+ id: string;
164
+ subscriptionId: string;
165
+ customerId: string;
166
+ productId: string;
167
+ amount: number;
168
+ currency: "usd";
169
+ interval: SubscriptionInterval;
170
+ quantity: number;
171
+ status: Stripe.Subscription["status"] | "deleted";
172
+ currentPeriodStart: string;
173
+ currentPeriodEnd: string;
174
+ canceledAt?: string | undefined;
175
+ createdAt: string;
176
+ updatedAt: string;
177
+ metadata?: {
178
+ [x: string]: string;
179
+ } | undefined;
180
+ }[]>;
181
+ /**
182
+ * Produces a handler for incoming webhooks from Stripe.
183
+ */
184
+ private handleStripeWebhook;
185
+ }
package/dist/index.js ADDED
@@ -0,0 +1,466 @@
1
+ import { DistributedTable, Endpoint, KeyValueStore, PassThruParser, Resource, Setting } from 'wirejs-resources';
2
+ import Stripe from 'stripe';
3
+ function httpNotFound(context, message) {
4
+ context.responseCode = 404;
5
+ return message ? `Not Found - ${message}` : 'Not found';
6
+ }
7
+ function sum(values) {
8
+ let t = 0;
9
+ for (const v of values)
10
+ t += v;
11
+ return t;
12
+ }
13
+ export class PaymentService extends Resource {
14
+ devMode = new Setting(this, 'dev-mode', {
15
+ private: false,
16
+ description: 'If "yes", skips Stripe checkout and immediately records successful payments.',
17
+ init: () => 'no',
18
+ options: ['yes', 'no'],
19
+ });
20
+ stripeKey = new Setting(this, 'stripe-key', {
21
+ private: true,
22
+ description: 'The API key issued by Stripe.'
23
+ });
24
+ stripeEndpointSecret = new Setting(this, 'stripe-endpoint-secret', {
25
+ private: true,
26
+ description: "The verification secret issued by Stripe for this endpoint."
27
+ });
28
+ prices = new Map();
29
+ transactions = new DistributedTable(this, 'transactions', {
30
+ parse: (PassThruParser),
31
+ key: {
32
+ partition: { field: 'id', type: 'string' },
33
+ },
34
+ indexes: [
35
+ {
36
+ partition: { field: 'customerId', type: 'string' },
37
+ sort: { field: 'createdAt', type: 'string' }
38
+ },
39
+ ]
40
+ });
41
+ subscriptionLines = new DistributedTable(this, 'subscriptions', {
42
+ parse: (PassThruParser),
43
+ key: {
44
+ partition: { field: 'id', type: 'string' }
45
+ },
46
+ indexes: [
47
+ {
48
+ partition: { field: 'customerId', type: 'string' },
49
+ sort: { field: 'status', type: 'string' }
50
+ },
51
+ {
52
+ partition: { field: 'subscriptionId', type: 'string' },
53
+ sort: { field: 'status', type: 'string' }
54
+ }
55
+ ]
56
+ });
57
+ customerInternalIdToExternalId = new KeyValueStore(this, 'customerInternalIdToExternalId');
58
+ customerExternalIdToInternalId = new KeyValueStore(this, 'customerExternalIdToInternalId');
59
+ webhookEndpoint = new Endpoint(this, 'webhook', {
60
+ handle: async (context) => this.handleStripeWebhook(context),
61
+ description: "The webhook Stripe needs to inform this software of payment and subscription changes."
62
+ });
63
+ _stripe = undefined;
64
+ _endpointSecretData = undefined;
65
+ constructor(scope, id) {
66
+ super(scope, id);
67
+ }
68
+ /**
69
+ * Creates a raw Stripe client.
70
+ *
71
+ * @returns
72
+ */
73
+ getClient() {
74
+ this._stripe = this._stripe || new Promise(resolve => {
75
+ this.stripeKey.read().then(key => {
76
+ if (!!key) {
77
+ resolve(new Stripe(key.trim()));
78
+ }
79
+ else {
80
+ resolve(undefined);
81
+ }
82
+ });
83
+ });
84
+ return this._stripe;
85
+ }
86
+ getEndpointSecret() {
87
+ this._endpointSecretData = this._endpointSecretData || new Promise(resolve => {
88
+ this.stripeEndpointSecret.read().then(v => resolve(v || ''));
89
+ });
90
+ return this._endpointSecretData;
91
+ }
92
+ getPrice(product) {
93
+ const interval = (product.type === 'recurring' && product.interval)
94
+ ? product.interval
95
+ : 'one_time';
96
+ const lookupKey = `${product.id}-${product.unitAmount}-${product.currency}-${interval}`;
97
+ if (!this.prices.has(product)) {
98
+ this.prices.set(product, new Promise(async (resolve) => {
99
+ const client = (await this.getClient());
100
+ const existing = await client.prices.list({
101
+ active: true,
102
+ lookup_keys: [lookupKey],
103
+ });
104
+ const existingPrice = existing.data[0];
105
+ if (existingPrice)
106
+ return resolve(existingPrice);
107
+ try {
108
+ await client.products.retrieve(product.id);
109
+ }
110
+ catch (error) {
111
+ if (error.code !== 'resource_missing')
112
+ throw error;
113
+ await client.products.create({
114
+ id: product.id,
115
+ name: product.name
116
+ });
117
+ }
118
+ const newPrice = await client.prices.create({
119
+ currency: product.currency,
120
+ unit_amount: product.unitAmount,
121
+ lookup_key: lookupKey,
122
+ recurring: (product.type === 'recurring' && product.interval) ? {
123
+ interval: product.interval,
124
+ } : undefined,
125
+ metadata: product.metadata,
126
+ product: product.id,
127
+ });
128
+ resolve(newPrice);
129
+ }));
130
+ }
131
+ return this.prices.get(product);
132
+ }
133
+ getProductFromPrice(price) {
134
+ let id;
135
+ let name;
136
+ if (typeof price.product === 'string') {
137
+ id = price.product;
138
+ name = id;
139
+ }
140
+ else {
141
+ id = price.product.id;
142
+ name = price.product.name;
143
+ }
144
+ if (!id)
145
+ throw new Error("Product ID missing.");
146
+ return {
147
+ id,
148
+ name,
149
+ type: price.recurring ? 'recurring' : 'one_time',
150
+ interval: price.recurring ? price.recurring.interval : undefined,
151
+ currency: price.currency,
152
+ unitAmount: price.unit_amount,
153
+ metadata: price.metadata
154
+ };
155
+ }
156
+ async getStripeCustomer(internalId) {
157
+ const existing = await this.customerInternalIdToExternalId.get(internalId);
158
+ if (existing)
159
+ return existing;
160
+ const client = await this.getClient();
161
+ const newCustomer = await client?.customers.create();
162
+ if (!newCustomer)
163
+ throw new Error("Could not create Stripe customer");
164
+ await this.customerInternalIdToExternalId.set(internalId, newCustomer.id);
165
+ await this.customerExternalIdToInternalId.set(newCustomer.id, internalId);
166
+ return newCustomer.id;
167
+ }
168
+ async getInternalCustomer(customer) {
169
+ const externalId = typeof customer === 'string' ? customer : customer.id;
170
+ const internalId = await this.customerExternalIdToInternalId.get(externalId);
171
+ if (!internalId)
172
+ throw new Error("No internal customer ID found for Stripe ID.");
173
+ return internalId;
174
+ }
175
+ async recordInvoicePayment(invoice) {
176
+ // Should always be populated, because we always attach customers at checkout.
177
+ const internalCustomerId = await this.getInternalCustomer(invoice.customer);
178
+ await this.transactions.save({
179
+ amount: invoice.amount_paid,
180
+ createdAt: new Date().toISOString(),
181
+ currency: invoice.currency,
182
+ customerId: internalCustomerId,
183
+ id: invoice.id,
184
+ type: 'payment',
185
+ items: invoice.lines.data.map(line => ({
186
+ id: line.id,
187
+ description: line.description,
188
+ quantity: line.quantity,
189
+ amount: line.amount,
190
+ productId: line.pricing?.price_details?.product
191
+ }))
192
+ });
193
+ }
194
+ async recordCheckoutPayment(session, items) {
195
+ const internalCustomerId = await this.getInternalCustomer(session.customer);
196
+ await this.transactions.save({
197
+ amount: session.amount_total,
198
+ createdAt: new Date().toISOString(),
199
+ currency: session.currency,
200
+ customerId: internalCustomerId,
201
+ id: session.id,
202
+ type: 'payment',
203
+ items: items.data.map(line => ({
204
+ id: line.id,
205
+ description: line.description,
206
+ quantity: line.quantity,
207
+ amount: line.amount_total,
208
+ productId: typeof line.price?.product === 'string'
209
+ ? line.price.product
210
+ : line.price?.product.id
211
+ }))
212
+ });
213
+ }
214
+ async recordCustomerSubscriptionLine(customerId, subscription, lineItem) {
215
+ const product = this.getProductFromPrice(lineItem.price);
216
+ const existing = await this.subscriptionLines.get({ id: lineItem.id });
217
+ await this.subscriptionLines.save({
218
+ id: lineItem.id,
219
+ subscriptionId: subscription.id,
220
+ customerId,
221
+ status: subscription.status,
222
+ productId: product.id,
223
+ amount: lineItem.price.unit_amount,
224
+ currency: lineItem.price.currency,
225
+ interval: lineItem.price.recurring?.interval,
226
+ quantity: lineItem.quantity,
227
+ currentPeriodStart: new Date(lineItem.current_period_start * 1000).toISOString(),
228
+ currentPeriodEnd: new Date(lineItem.current_period_end * 1000).toISOString(),
229
+ createdAt: existing?.createdAt || new Date().toISOString(),
230
+ updatedAt: new Date().toISOString(),
231
+ });
232
+ }
233
+ async recordCustomerSubscription(sub) {
234
+ const internalCustomerId = await this.getInternalCustomer(sub.customer);
235
+ const existingSubscriptionLines = await Array.fromAsync(this.subscriptionLines.query({
236
+ by: 'subscriptionId-status',
237
+ where: {
238
+ subscriptionId: { eq: sub.id },
239
+ status: { ne: 'deleted' }
240
+ }
241
+ }));
242
+ const existingSubLineIds = new Map();
243
+ for (const li of existingSubscriptionLines) {
244
+ existingSubLineIds.set(li.id, li);
245
+ }
246
+ for (const item of sub.items.data) {
247
+ await this.recordCustomerSubscriptionLine(internalCustomerId, sub, item);
248
+ existingSubLineIds.delete(item.id);
249
+ }
250
+ // any remaining "existing" lines were absent from the sub event and
251
+ // must therefore be canceled locally
252
+ for (const li of existingSubLineIds.values()) {
253
+ await this.subscriptionLines.save({
254
+ ...li,
255
+ status: 'deleted',
256
+ updatedAt: new Date().toISOString(),
257
+ canceledAt: new Date().toISOString(),
258
+ });
259
+ }
260
+ }
261
+ async recordLocalModeCheckout({ customer, lineItems, mode }) {
262
+ await this.transactions.save({
263
+ amount: sum(lineItems.map(li => li.product.unitAmount * li.quantity)),
264
+ createdAt: new Date().toISOString(),
265
+ currency: 'usd',
266
+ customerId: customer.id,
267
+ id: crypto.randomUUID(),
268
+ type: 'payment',
269
+ items: lineItems.map(li => ({
270
+ id: crypto.randomUUID(),
271
+ description: li.product.name,
272
+ quantity: li.quantity,
273
+ amount: li.product.unitAmount * li.quantity,
274
+ productId: li.product.id
275
+ }))
276
+ });
277
+ if (mode === 'subscription') {
278
+ const subscriptionId = crypto.randomUUID();
279
+ for (const li of lineItems) {
280
+ const now = new Date();
281
+ let currentPeriodEnd;
282
+ switch (li.product.interval) {
283
+ case 'day':
284
+ currentPeriodEnd = new Date(now);
285
+ currentPeriodEnd.setDate(now.getDate() + 1);
286
+ break;
287
+ case 'week':
288
+ currentPeriodEnd = new Date(now);
289
+ currentPeriodEnd.setDate(now.getDate() + 7);
290
+ break;
291
+ case 'month':
292
+ currentPeriodEnd = new Date(now);
293
+ currentPeriodEnd.setMonth(now.getMonth() + 1);
294
+ break;
295
+ case 'year':
296
+ currentPeriodEnd = new Date(now);
297
+ currentPeriodEnd.setFullYear(now.getFullYear() + 1);
298
+ break;
299
+ default:
300
+ currentPeriodEnd = new Date(now);
301
+ }
302
+ await this.subscriptionLines.save({
303
+ id: crypto.randomUUID(),
304
+ customerId: customer.id,
305
+ createdAt: now.toISOString(),
306
+ updatedAt: now.toISOString(),
307
+ productId: li.product.id,
308
+ amount: li.product.unitAmount,
309
+ currency: li.product.currency,
310
+ interval: li.product.interval,
311
+ quantity: li.quantity,
312
+ status: 'active',
313
+ subscriptionId: subscriptionId,
314
+ currentPeriodStart: now.toISOString(),
315
+ currentPeriodEnd: currentPeriodEnd.toISOString()
316
+ });
317
+ }
318
+ }
319
+ }
320
+ async createCheckoutUrl({ customer, lineItems, successUrl, cancelUrl }) {
321
+ if (lineItems.length === 0) {
322
+ throw new Error("`lineItems` is empty.");
323
+ }
324
+ const billingType = lineItems[0].product.type;
325
+ if (!lineItems.every(li => li.product.type === billingType)) {
326
+ throw new Error("`lineItems` contains mixed `type` values.");
327
+ }
328
+ const mode = billingType === 'one_time' ? 'payment' : 'subscription';
329
+ if (await this.devMode.read() === 'yes') {
330
+ await this.recordLocalModeCheckout({ customer, lineItems, mode });
331
+ return new URL(successUrl).href;
332
+ }
333
+ const stripe = await this.getClient();
334
+ if (!stripe) {
335
+ throw new Error("Stripe client could not be created. Ensure Stripe settings are populated.");
336
+ }
337
+ const lines = [];
338
+ for (const item of lineItems) {
339
+ lines.push({
340
+ price: (await this.getPrice(item.product)).id,
341
+ quantity: item.quantity,
342
+ adjustable_quantity: {
343
+ enabled: false
344
+ }
345
+ });
346
+ }
347
+ const stripeCustomerId = await this.getStripeCustomer(customer.id);
348
+ const session = await stripe.checkout.sessions.create({
349
+ mode,
350
+ customer: stripeCustomerId,
351
+ line_items: lines,
352
+ allow_promotion_codes: true,
353
+ success_url: new URL(successUrl).href,
354
+ cancel_url: new URL(cancelUrl).href
355
+ });
356
+ return session.url;
357
+ }
358
+ async cancelSubscriptionLine(id) {
359
+ const existingLine = await this.subscriptionLines.get({ id });
360
+ if (!existingLine) {
361
+ throw new Error(`Subscription Line doesn't exist: ${id}.`);
362
+ }
363
+ if (await this.devMode.read() === 'yes') {
364
+ await this.subscriptionLines.save({
365
+ ...existingLine,
366
+ status: 'deleted',
367
+ updatedAt: new Date().toISOString(),
368
+ canceledAt: new Date().toISOString(),
369
+ });
370
+ }
371
+ const stripe = await this.getClient();
372
+ if (!stripe) {
373
+ throw new Error("Stripe client could not be created. Ensure Stripe settings are populated.");
374
+ }
375
+ const allSubLines = await Array.fromAsync(this.subscriptionLines.query({
376
+ by: 'subscriptionId-status',
377
+ where: {
378
+ subscriptionId: { eq: existingLine.subscriptionId },
379
+ status: { ne: 'deleted ' }
380
+ }
381
+ }));
382
+ if (allSubLines.length > 1) {
383
+ await stripe.subscriptionItems.del(id);
384
+ }
385
+ else {
386
+ await stripe.subscriptions.cancel(existingLine.subscriptionId);
387
+ }
388
+ }
389
+ async listPayments(internalCustomerId) {
390
+ return Array.fromAsync(this.transactions.query({
391
+ by: 'customerId-createdAt',
392
+ where: {
393
+ customerId: { eq: internalCustomerId }
394
+ },
395
+ }));
396
+ }
397
+ async listSubscriptions(internalCustomerId) {
398
+ return Array.fromAsync(this.subscriptionLines.query({
399
+ by: 'customerId-status',
400
+ where: {
401
+ customerId: { eq: internalCustomerId }
402
+ }
403
+ }));
404
+ }
405
+ /**
406
+ * Produces a handler for incoming webhooks from Stripe.
407
+ */
408
+ async handleStripeWebhook(context) {
409
+ console.log('Stripe callback event');
410
+ const stripe = await this.getClient();
411
+ if (!stripe) {
412
+ throw new Error("Stripe client is not available.");
413
+ }
414
+ const endpointSecret = (await this.getEndpointSecret()).trim();
415
+ if (!endpointSecret) {
416
+ throw new Error("No Stripe endpoint secret configured.");
417
+ }
418
+ let stripeEvent;
419
+ const signature = context.requestHeaders['stripe-signature'];
420
+ try {
421
+ stripeEvent = stripe.webhooks.constructEvent(context.requestBody, signature, endpointSecret);
422
+ console.log("Stripe event validated");
423
+ console.log(stripeEvent);
424
+ }
425
+ catch (err) {
426
+ console.log(`STRIPE FAILURE: Webhook signature verification failed.`, err.message);
427
+ return httpNotFound(context);
428
+ }
429
+ // Handle the event
430
+ switch (stripeEvent.type) {
431
+ case "invoice.payment_succeeded": {
432
+ const invoice = stripeEvent.data.object;
433
+ await this.recordInvoicePayment(invoice);
434
+ break;
435
+ }
436
+ case "checkout.session.completed": {
437
+ const session = stripeEvent.data.object;
438
+ if (session.mode === 'payment') {
439
+ const items = await stripe.checkout.sessions.listLineItems(session.id);
440
+ await this.recordCheckoutPayment(session, items);
441
+ }
442
+ break;
443
+ }
444
+ case 'customer.subscription.trial_will_end':
445
+ // Then define and call a method to handle the subscription trial ending.
446
+ // handleSubscriptionTrialEnding(subscription);
447
+ // nothing to do here for now ... we don't offer trials.
448
+ break;
449
+ case 'customer.subscription.deleted':
450
+ case 'customer.subscription.paused':
451
+ case 'customer.subscription.updated':
452
+ case 'customer.subscription.created':
453
+ case 'customer.subscription.resumed': {
454
+ const sub = stripeEvent.data.object;
455
+ await this.recordCustomerSubscription(sub);
456
+ break;
457
+ }
458
+ default: {
459
+ // Unexpected event type
460
+ console.log(`Unhandled Stripe event type ${stripeEvent.type}.`);
461
+ return httpNotFound(context, "Unhandled Stripe event type ${stripeEvent.type}.");
462
+ }
463
+ }
464
+ return "ok";
465
+ }
466
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "wirejs-module-payments-stripe",
3
+ "version": "0.1.0",
4
+ "description": "Stripe payments module built for wirejs apps.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/svidgen/create-wirejs-app.git",
20
+ "directory": "packages/wirejs-module-payments-stripe"
21
+ },
22
+ "author": "Jon Wire",
23
+ "license": "AGPL-3.0-only",
24
+ "bugs": {
25
+ "url": "https://github.com/svidgen/create-wirejs-app/issues"
26
+ },
27
+ "homepage": "https://github.com/svidgen/create-wirejs-app#readme",
28
+ "dependencies": {
29
+ "stripe": "^18.2.1",
30
+ "wirejs-dom": "^1.0.42",
31
+ "wirejs-resources": "^0.1.106"
32
+ },
33
+ "devDependencies": {
34
+ "typescript": "^5.7.3"
35
+ },
36
+ "files": [
37
+ "package.json",
38
+ "README.md",
39
+ "dist/*"
40
+ ]
41
+ }