@autumnsgrove/groveengine 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.
Files changed (219) hide show
  1. package/README.md +163 -0
  2. package/dist/auth/jwt.d.ts +14 -0
  3. package/dist/auth/jwt.js +109 -0
  4. package/dist/auth/session.d.ts +42 -0
  5. package/dist/auth/session.js +105 -0
  6. package/dist/components/admin/GutterManager.svelte +910 -0
  7. package/dist/components/admin/GutterManager.svelte.d.ts +15 -0
  8. package/dist/components/admin/MarkdownEditor.svelte +3114 -0
  9. package/dist/components/admin/MarkdownEditor.svelte.d.ts +43 -0
  10. package/dist/components/custom/CollapsibleSection.svelte +74 -0
  11. package/dist/components/custom/CollapsibleSection.svelte.d.ts +15 -0
  12. package/dist/components/custom/ContentWithGutter.svelte +646 -0
  13. package/dist/components/custom/ContentWithGutter.svelte.d.ts +19 -0
  14. package/dist/components/custom/GutterItem.svelte +201 -0
  15. package/dist/components/custom/GutterItem.svelte.d.ts +11 -0
  16. package/dist/components/custom/LeftGutter.svelte +271 -0
  17. package/dist/components/custom/LeftGutter.svelte.d.ts +17 -0
  18. package/dist/components/custom/MobileTOC.svelte +273 -0
  19. package/dist/components/custom/MobileTOC.svelte.d.ts +11 -0
  20. package/dist/components/custom/TableOfContents.svelte +163 -0
  21. package/dist/components/custom/TableOfContents.svelte.d.ts +11 -0
  22. package/dist/components/gallery/ImageGallery.svelte +681 -0
  23. package/dist/components/gallery/ImageGallery.svelte.d.ts +11 -0
  24. package/dist/components/gallery/Lightbox.svelte +107 -0
  25. package/dist/components/gallery/Lightbox.svelte.d.ts +19 -0
  26. package/dist/components/gallery/LightboxCaption.svelte +25 -0
  27. package/dist/components/gallery/LightboxCaption.svelte.d.ts +11 -0
  28. package/dist/components/gallery/ZoomableImage.svelte +163 -0
  29. package/dist/components/gallery/ZoomableImage.svelte.d.ts +17 -0
  30. package/dist/components/ui/Accordion.svelte +74 -0
  31. package/dist/components/ui/Accordion.svelte.d.ts +42 -0
  32. package/dist/components/ui/Badge.svelte +48 -0
  33. package/dist/components/ui/Badge.svelte.d.ts +26 -0
  34. package/dist/components/ui/Button.svelte +74 -0
  35. package/dist/components/ui/Button.svelte.d.ts +34 -0
  36. package/dist/components/ui/Card.svelte +102 -0
  37. package/dist/components/ui/Card.svelte.d.ts +46 -0
  38. package/dist/components/ui/Dialog.svelte +91 -0
  39. package/dist/components/ui/Dialog.svelte.d.ts +43 -0
  40. package/dist/components/ui/Input.svelte +81 -0
  41. package/dist/components/ui/Input.svelte.d.ts +35 -0
  42. package/dist/components/ui/Select.svelte +69 -0
  43. package/dist/components/ui/Select.svelte.d.ts +36 -0
  44. package/dist/components/ui/Sheet.svelte +98 -0
  45. package/dist/components/ui/Sheet.svelte.d.ts +45 -0
  46. package/dist/components/ui/Skeleton.svelte +31 -0
  47. package/dist/components/ui/Skeleton.svelte.d.ts +26 -0
  48. package/dist/components/ui/Table.svelte +59 -0
  49. package/dist/components/ui/Table.svelte.d.ts +44 -0
  50. package/dist/components/ui/Tabs.svelte +76 -0
  51. package/dist/components/ui/Tabs.svelte.d.ts +41 -0
  52. package/dist/components/ui/Textarea.svelte +81 -0
  53. package/dist/components/ui/Textarea.svelte.d.ts +35 -0
  54. package/dist/components/ui/Toast.svelte +18 -0
  55. package/dist/components/ui/Toast.svelte.d.ts +7 -0
  56. package/dist/components/ui/accordion/accordion-content.svelte +24 -0
  57. package/dist/components/ui/accordion/accordion-content.svelte.d.ts +4 -0
  58. package/dist/components/ui/accordion/accordion-item.svelte +12 -0
  59. package/dist/components/ui/accordion/accordion-item.svelte.d.ts +4 -0
  60. package/dist/components/ui/accordion/accordion-trigger.svelte +29 -0
  61. package/dist/components/ui/accordion/accordion-trigger.svelte.d.ts +7 -0
  62. package/dist/components/ui/accordion/index.d.ts +6 -0
  63. package/dist/components/ui/accordion/index.js +8 -0
  64. package/dist/components/ui/badge/badge.svelte +50 -0
  65. package/dist/components/ui/badge/badge.svelte.d.ts +60 -0
  66. package/dist/components/ui/badge/index.d.ts +2 -0
  67. package/dist/components/ui/badge/index.js +2 -0
  68. package/dist/components/ui/button/button.svelte +82 -0
  69. package/dist/components/ui/button/button.svelte.d.ts +132 -0
  70. package/dist/components/ui/button/index.d.ts +2 -0
  71. package/dist/components/ui/button/index.js +4 -0
  72. package/dist/components/ui/card/card-content.svelte +16 -0
  73. package/dist/components/ui/card/card-content.svelte.d.ts +5 -0
  74. package/dist/components/ui/card/card-description.svelte +16 -0
  75. package/dist/components/ui/card/card-description.svelte.d.ts +5 -0
  76. package/dist/components/ui/card/card-footer.svelte +16 -0
  77. package/dist/components/ui/card/card-footer.svelte.d.ts +5 -0
  78. package/dist/components/ui/card/card-header.svelte +16 -0
  79. package/dist/components/ui/card/card-header.svelte.d.ts +5 -0
  80. package/dist/components/ui/card/card-title.svelte +25 -0
  81. package/dist/components/ui/card/card-title.svelte.d.ts +8 -0
  82. package/dist/components/ui/card/card.svelte +20 -0
  83. package/dist/components/ui/card/card.svelte.d.ts +5 -0
  84. package/dist/components/ui/card/index.d.ts +7 -0
  85. package/dist/components/ui/card/index.js +9 -0
  86. package/dist/components/ui/dialog/dialog-content.svelte +38 -0
  87. package/dist/components/ui/dialog/dialog-content.svelte.d.ts +9 -0
  88. package/dist/components/ui/dialog/dialog-description.svelte +16 -0
  89. package/dist/components/ui/dialog/dialog-description.svelte.d.ts +4 -0
  90. package/dist/components/ui/dialog/dialog-footer.svelte +20 -0
  91. package/dist/components/ui/dialog/dialog-footer.svelte.d.ts +5 -0
  92. package/dist/components/ui/dialog/dialog-header.svelte +20 -0
  93. package/dist/components/ui/dialog/dialog-header.svelte.d.ts +5 -0
  94. package/dist/components/ui/dialog/dialog-overlay.svelte +19 -0
  95. package/dist/components/ui/dialog/dialog-overlay.svelte.d.ts +4 -0
  96. package/dist/components/ui/dialog/dialog-title.svelte +16 -0
  97. package/dist/components/ui/dialog/dialog-title.svelte.d.ts +4 -0
  98. package/dist/components/ui/dialog/index.d.ts +12 -0
  99. package/dist/components/ui/dialog/index.js +14 -0
  100. package/dist/components/ui/index.d.ts +26 -0
  101. package/dist/components/ui/index.js +29 -0
  102. package/dist/components/ui/input/index.d.ts +2 -0
  103. package/dist/components/ui/input/index.js +4 -0
  104. package/dist/components/ui/input/input.svelte +46 -0
  105. package/dist/components/ui/input/input.svelte.d.ts +13 -0
  106. package/dist/components/ui/select/index.d.ts +11 -0
  107. package/dist/components/ui/select/index.js +13 -0
  108. package/dist/components/ui/select/select-content.svelte +39 -0
  109. package/dist/components/ui/select/select-content.svelte.d.ts +7 -0
  110. package/dist/components/ui/select/select-group-heading.svelte +16 -0
  111. package/dist/components/ui/select/select-group-heading.svelte.d.ts +4 -0
  112. package/dist/components/ui/select/select-item.svelte +37 -0
  113. package/dist/components/ui/select/select-item.svelte.d.ts +4 -0
  114. package/dist/components/ui/select/select-scroll-down-button.svelte +19 -0
  115. package/dist/components/ui/select/select-scroll-down-button.svelte.d.ts +4 -0
  116. package/dist/components/ui/select/select-scroll-up-button.svelte +19 -0
  117. package/dist/components/ui/select/select-scroll-up-button.svelte.d.ts +4 -0
  118. package/dist/components/ui/select/select-separator.svelte +13 -0
  119. package/dist/components/ui/select/select-separator.svelte.d.ts +4 -0
  120. package/dist/components/ui/select/select-trigger.svelte +24 -0
  121. package/dist/components/ui/select/select-trigger.svelte.d.ts +4 -0
  122. package/dist/components/ui/separator/index.d.ts +2 -0
  123. package/dist/components/ui/separator/index.js +4 -0
  124. package/dist/components/ui/separator/separator.svelte +22 -0
  125. package/dist/components/ui/separator/separator.svelte.d.ts +4 -0
  126. package/dist/components/ui/sheet/index.d.ts +12 -0
  127. package/dist/components/ui/sheet/index.js +14 -0
  128. package/dist/components/ui/sheet/sheet-content.svelte +53 -0
  129. package/dist/components/ui/sheet/sheet-content.svelte.d.ts +62 -0
  130. package/dist/components/ui/sheet/sheet-description.svelte +16 -0
  131. package/dist/components/ui/sheet/sheet-description.svelte.d.ts +4 -0
  132. package/dist/components/ui/sheet/sheet-footer.svelte +20 -0
  133. package/dist/components/ui/sheet/sheet-footer.svelte.d.ts +5 -0
  134. package/dist/components/ui/sheet/sheet-header.svelte +20 -0
  135. package/dist/components/ui/sheet/sheet-header.svelte.d.ts +5 -0
  136. package/dist/components/ui/sheet/sheet-overlay.svelte +21 -0
  137. package/dist/components/ui/sheet/sheet-overlay.svelte.d.ts +6 -0
  138. package/dist/components/ui/sheet/sheet-title.svelte +16 -0
  139. package/dist/components/ui/sheet/sheet-title.svelte.d.ts +4 -0
  140. package/dist/components/ui/skeleton/index.d.ts +2 -0
  141. package/dist/components/ui/skeleton/index.js +4 -0
  142. package/dist/components/ui/skeleton/skeleton.svelte +17 -0
  143. package/dist/components/ui/skeleton/skeleton.svelte.d.ts +5 -0
  144. package/dist/components/ui/table/index.d.ts +9 -0
  145. package/dist/components/ui/table/index.js +11 -0
  146. package/dist/components/ui/table/table-body.svelte +16 -0
  147. package/dist/components/ui/table/table-body.svelte.d.ts +5 -0
  148. package/dist/components/ui/table/table-caption.svelte +16 -0
  149. package/dist/components/ui/table/table-caption.svelte.d.ts +5 -0
  150. package/dist/components/ui/table/table-cell.svelte +20 -0
  151. package/dist/components/ui/table/table-cell.svelte.d.ts +5 -0
  152. package/dist/components/ui/table/table-footer.svelte +16 -0
  153. package/dist/components/ui/table/table-footer.svelte.d.ts +5 -0
  154. package/dist/components/ui/table/table-head.svelte +23 -0
  155. package/dist/components/ui/table/table-head.svelte.d.ts +5 -0
  156. package/dist/components/ui/table/table-header.svelte +16 -0
  157. package/dist/components/ui/table/table-header.svelte.d.ts +5 -0
  158. package/dist/components/ui/table/table-row.svelte +23 -0
  159. package/dist/components/ui/table/table-row.svelte.d.ts +5 -0
  160. package/dist/components/ui/table/table.svelte +18 -0
  161. package/dist/components/ui/table/table.svelte.d.ts +5 -0
  162. package/dist/components/ui/tabs/index.d.ts +6 -0
  163. package/dist/components/ui/tabs/index.js +8 -0
  164. package/dist/components/ui/tabs/tabs-content.svelte +19 -0
  165. package/dist/components/ui/tabs/tabs-content.svelte.d.ts +4 -0
  166. package/dist/components/ui/tabs/tabs-list.svelte +19 -0
  167. package/dist/components/ui/tabs/tabs-list.svelte.d.ts +4 -0
  168. package/dist/components/ui/tabs/tabs-trigger.svelte +19 -0
  169. package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +4 -0
  170. package/dist/components/ui/textarea/index.d.ts +2 -0
  171. package/dist/components/ui/textarea/index.js +4 -0
  172. package/dist/components/ui/textarea/textarea.svelte +24 -0
  173. package/dist/components/ui/textarea/textarea.svelte.d.ts +6 -0
  174. package/dist/components/ui/toast.d.ts +86 -0
  175. package/dist/components/ui/toast.js +99 -0
  176. package/dist/db/schema.sql +238 -0
  177. package/dist/index.d.ts +14 -0
  178. package/dist/index.js +20 -0
  179. package/dist/payments/index.d.ts +33 -0
  180. package/dist/payments/index.js +47 -0
  181. package/dist/payments/shop.d.ts +165 -0
  182. package/dist/payments/shop.js +588 -0
  183. package/dist/payments/stripe/client.d.ts +231 -0
  184. package/dist/payments/stripe/client.js +198 -0
  185. package/dist/payments/stripe/index.d.ts +18 -0
  186. package/dist/payments/stripe/index.js +17 -0
  187. package/dist/payments/stripe/provider.d.ts +50 -0
  188. package/dist/payments/stripe/provider.js +530 -0
  189. package/dist/payments/types.d.ts +355 -0
  190. package/dist/payments/types.js +7 -0
  191. package/dist/server/logger.d.ts +53 -0
  192. package/dist/server/logger.js +252 -0
  193. package/dist/styles/content.css +514 -0
  194. package/dist/styles/tokens.css +175 -0
  195. package/dist/utils/api.d.ts +20 -0
  196. package/dist/utils/api.js +109 -0
  197. package/dist/utils/cn.d.ts +15 -0
  198. package/dist/utils/cn.js +18 -0
  199. package/dist/utils/csrf.d.ts +22 -0
  200. package/dist/utils/csrf.js +72 -0
  201. package/dist/utils/debounce.d.ts +7 -0
  202. package/dist/utils/debounce.js +14 -0
  203. package/dist/utils/gallery.d.ts +66 -0
  204. package/dist/utils/gallery.js +181 -0
  205. package/dist/utils/gutter.d.ts +54 -0
  206. package/dist/utils/gutter.js +169 -0
  207. package/dist/utils/imageProcessor.d.ts +58 -0
  208. package/dist/utils/imageProcessor.js +205 -0
  209. package/dist/utils/json.d.ts +17 -0
  210. package/dist/utils/json.js +26 -0
  211. package/dist/utils/markdown.d.ts +101 -0
  212. package/dist/utils/markdown.js +947 -0
  213. package/dist/utils/sanitize.d.ts +25 -0
  214. package/dist/utils/sanitize.js +127 -0
  215. package/dist/utils/validation.d.ts +46 -0
  216. package/dist/utils/validation.js +169 -0
  217. package/dist/utils.d.ts +5 -0
  218. package/dist/utils.js +5 -0
  219. package/package.json +129 -0
@@ -0,0 +1,530 @@
1
+ /**
2
+ * Stripe Payment Provider Implementation
3
+ *
4
+ * Implements the PaymentProvider interface using Stripe's API.
5
+ * Supports Stripe Connect for marketplace functionality.
6
+ */
7
+ import { StripeClient, StripeAPIError, } from './client.js';
8
+ // =============================================================================
9
+ // STRIPE PROVIDER
10
+ // =============================================================================
11
+ export class StripeProvider {
12
+ name = 'stripe';
13
+ client;
14
+ webhookSecret;
15
+ connectWebhookSecret;
16
+ constructor(config) {
17
+ this.client = new StripeClient({ secretKey: config.secretKey });
18
+ this.webhookSecret = config.webhookSecret;
19
+ this.connectWebhookSecret = config.webhookSecret; // Can be separate if needed
20
+ }
21
+ // ==========================================================================
22
+ // PRODUCTS & PRICES
23
+ // ==========================================================================
24
+ async syncProduct(product) {
25
+ // Check if product already exists in Stripe
26
+ // For now, always create new. In production, you'd want to update existing.
27
+ const stripeProduct = await this.client.request('products', {
28
+ method: 'POST',
29
+ params: {
30
+ name: product.name,
31
+ description: product.description,
32
+ images: product.images.slice(0, 8), // Stripe allows max 8 images
33
+ active: product.status === 'active',
34
+ metadata: {
35
+ grove_product_id: product.id,
36
+ grove_tenant_id: product.tenantId,
37
+ product_type: product.type,
38
+ ...product.metadata,
39
+ },
40
+ },
41
+ });
42
+ return { providerProductId: stripeProduct.id };
43
+ }
44
+ async syncPrice(variant, providerProductId) {
45
+ const params = {
46
+ product: providerProductId,
47
+ currency: variant.price.currency,
48
+ unit_amount: variant.price.amount,
49
+ metadata: {
50
+ grove_variant_id: variant.id,
51
+ grove_product_id: variant.productId,
52
+ variant_name: variant.name,
53
+ },
54
+ };
55
+ // Add recurring config for subscriptions
56
+ if (variant.pricingType === 'recurring' && variant.recurring) {
57
+ params.recurring = {
58
+ interval: variant.recurring.interval,
59
+ interval_count: variant.recurring.intervalCount,
60
+ };
61
+ }
62
+ const stripePrice = await this.client.request('prices', {
63
+ method: 'POST',
64
+ params,
65
+ });
66
+ return { providerPriceId: stripePrice.id };
67
+ }
68
+ async archiveProduct(providerProductId) {
69
+ await this.client.request(`products/${providerProductId}`, {
70
+ method: 'POST',
71
+ params: { active: false },
72
+ });
73
+ }
74
+ // ==========================================================================
75
+ // CHECKOUT
76
+ // ==========================================================================
77
+ async createCheckoutSession(items, options, resolveVariant) {
78
+ // Build line items from cart
79
+ const lineItems = [];
80
+ for (const item of items) {
81
+ const variant = await resolveVariant(item.variantId);
82
+ if (!variant) {
83
+ throw new Error(`Variant not found: ${item.variantId}`);
84
+ }
85
+ if (variant.providerPriceId) {
86
+ // Use existing Stripe price
87
+ lineItems.push({
88
+ price: variant.providerPriceId,
89
+ quantity: item.quantity,
90
+ });
91
+ }
92
+ else {
93
+ // Create inline price data
94
+ lineItems.push({
95
+ price_data: {
96
+ currency: variant.price.currency,
97
+ unit_amount: variant.price.amount,
98
+ product_data: {
99
+ name: variant.name,
100
+ metadata: {
101
+ grove_variant_id: variant.id,
102
+ },
103
+ },
104
+ ...(variant.pricingType === 'recurring' && variant.recurring
105
+ ? {
106
+ recurring: {
107
+ interval: variant.recurring.interval,
108
+ interval_count: variant.recurring.intervalCount,
109
+ },
110
+ }
111
+ : {}),
112
+ },
113
+ quantity: item.quantity,
114
+ });
115
+ }
116
+ }
117
+ // Build checkout session params
118
+ const params = {
119
+ mode: options.mode,
120
+ success_url: options.successUrl,
121
+ cancel_url: options.cancelUrl,
122
+ line_items: lineItems,
123
+ metadata: options.metadata || {},
124
+ };
125
+ // Customer
126
+ if (options.customerId) {
127
+ params.customer = options.customerId;
128
+ }
129
+ else if (options.customerEmail) {
130
+ params.customer_email = options.customerEmail;
131
+ }
132
+ // Stripe Tax
133
+ if (options.automaticTax) {
134
+ params.automatic_tax = { enabled: true };
135
+ }
136
+ // Tax ID collection
137
+ if (options.taxIdCollection) {
138
+ params.tax_id_collection = { enabled: true };
139
+ }
140
+ // Billing address
141
+ if (options.billingAddressCollection) {
142
+ params.billing_address_collection = options.billingAddressCollection;
143
+ }
144
+ // Shipping address (physical products)
145
+ if (options.shippingAddressCollection) {
146
+ params.shipping_address_collection = {
147
+ allowed_countries: options.shippingAddressCollection.allowedCountries,
148
+ };
149
+ }
150
+ // Subscription trial
151
+ if (options.mode === 'subscription' && options.trialPeriodDays) {
152
+ params.subscription_data = {
153
+ trial_period_days: options.trialPeriodDays,
154
+ };
155
+ }
156
+ // Promo codes
157
+ if (options.allowPromotionCodes) {
158
+ params.allow_promotion_codes = true;
159
+ }
160
+ // Stripe Connect: application fee and connected account
161
+ const requestOptions = {
162
+ method: 'POST',
163
+ params,
164
+ };
165
+ if (options.connectedAccountId) {
166
+ requestOptions.stripeAccount = options.connectedAccountId;
167
+ // Application fee (platform commission)
168
+ if (options.applicationFeeAmount) {
169
+ if (options.mode === 'subscription') {
170
+ params.subscription_data = {
171
+ ...params.subscription_data,
172
+ application_fee_percent: undefined, // Use fixed amount instead
173
+ };
174
+ // For subscriptions, we need to use transfer_data instead
175
+ params.payment_intent_data = {
176
+ application_fee_amount: options.applicationFeeAmount,
177
+ };
178
+ }
179
+ else {
180
+ params.payment_intent_data = {
181
+ application_fee_amount: options.applicationFeeAmount,
182
+ };
183
+ }
184
+ }
185
+ }
186
+ const session = await this.client.request('checkout/sessions', requestOptions);
187
+ return this.mapCheckoutSession(session);
188
+ }
189
+ async getCheckoutSession(sessionId) {
190
+ try {
191
+ const session = await this.client.request(`checkout/sessions/${sessionId}`);
192
+ return this.mapCheckoutSession(session);
193
+ }
194
+ catch (err) {
195
+ if (err instanceof StripeAPIError && err.statusCode === 404) {
196
+ return null;
197
+ }
198
+ throw err;
199
+ }
200
+ }
201
+ mapCheckoutSession(session) {
202
+ return {
203
+ id: session.id,
204
+ url: session.url,
205
+ status: session.status,
206
+ mode: session.mode,
207
+ customerId: session.customer,
208
+ customerEmail: session.customer_email,
209
+ amountTotal: session.amount_total
210
+ ? { amount: session.amount_total, currency: session.currency }
211
+ : undefined,
212
+ paymentStatus: session.payment_status,
213
+ metadata: session.metadata,
214
+ expiresAt: new Date(session.expires_at * 1000),
215
+ };
216
+ }
217
+ // ==========================================================================
218
+ // PAYMENTS
219
+ // ==========================================================================
220
+ async getPaymentStatus(providerPaymentId) {
221
+ const intent = await this.client.request(`payment_intents/${providerPaymentId}`);
222
+ const statusMap = {
223
+ requires_payment_method: 'pending',
224
+ requires_confirmation: 'pending',
225
+ requires_action: 'pending',
226
+ processing: 'processing',
227
+ requires_capture: 'processing',
228
+ canceled: 'canceled',
229
+ succeeded: 'succeeded',
230
+ };
231
+ return statusMap[intent.status] || 'pending';
232
+ }
233
+ async refund(request, providerPaymentId) {
234
+ const params = {
235
+ payment_intent: providerPaymentId,
236
+ };
237
+ if (request.amount) {
238
+ params.amount = request.amount;
239
+ }
240
+ if (request.reason) {
241
+ params.reason = request.reason;
242
+ }
243
+ const refund = await this.client.request('refunds', {
244
+ method: 'POST',
245
+ params,
246
+ });
247
+ return {
248
+ id: refund.id,
249
+ orderId: request.orderId,
250
+ amount: { amount: refund.amount, currency: refund.currency },
251
+ status: refund.status,
252
+ reason: refund.reason,
253
+ providerRefundId: refund.id,
254
+ createdAt: new Date(refund.created * 1000),
255
+ };
256
+ }
257
+ // ==========================================================================
258
+ // SUBSCRIPTIONS
259
+ // ==========================================================================
260
+ async getSubscription(providerSubscriptionId) {
261
+ try {
262
+ const sub = await this.client.request(`subscriptions/${providerSubscriptionId}`);
263
+ return this.mapSubscription(sub);
264
+ }
265
+ catch (err) {
266
+ if (err instanceof StripeAPIError && err.statusCode === 404) {
267
+ return null;
268
+ }
269
+ throw err;
270
+ }
271
+ }
272
+ async cancelSubscription(providerSubscriptionId, cancelImmediately = false) {
273
+ if (cancelImmediately) {
274
+ await this.client.request(`subscriptions/${providerSubscriptionId}`, {
275
+ method: 'DELETE',
276
+ });
277
+ }
278
+ else {
279
+ await this.client.request(`subscriptions/${providerSubscriptionId}`, {
280
+ method: 'POST',
281
+ params: { cancel_at_period_end: true },
282
+ });
283
+ }
284
+ }
285
+ async resumeSubscription(providerSubscriptionId) {
286
+ await this.client.request(`subscriptions/${providerSubscriptionId}`, {
287
+ method: 'POST',
288
+ params: { cancel_at_period_end: false },
289
+ });
290
+ }
291
+ mapSubscription(sub) {
292
+ const statusMap = {
293
+ trialing: 'trialing',
294
+ active: 'active',
295
+ past_due: 'past_due',
296
+ paused: 'paused',
297
+ canceled: 'canceled',
298
+ unpaid: 'unpaid',
299
+ incomplete: 'unpaid',
300
+ incomplete_expired: 'canceled',
301
+ };
302
+ const item = sub.items.data[0];
303
+ return {
304
+ id: sub.id,
305
+ tenantId: sub.metadata.grove_tenant_id || '',
306
+ customerId: sub.customer,
307
+ customerEmail: '', // Would need to fetch customer to get email
308
+ productId: sub.metadata.grove_product_id || '',
309
+ variantId: sub.metadata.grove_variant_id || '',
310
+ quantity: item?.quantity || 1,
311
+ status: statusMap[sub.status] || 'active',
312
+ currentPeriodStart: new Date(sub.current_period_start * 1000),
313
+ currentPeriodEnd: new Date(sub.current_period_end * 1000),
314
+ cancelAtPeriodEnd: sub.cancel_at_period_end,
315
+ canceledAt: sub.canceled_at ? new Date(sub.canceled_at * 1000) : undefined,
316
+ trialStart: sub.trial_start ? new Date(sub.trial_start * 1000) : undefined,
317
+ trialEnd: sub.trial_end ? new Date(sub.trial_end * 1000) : undefined,
318
+ providerSubscriptionId: sub.id,
319
+ createdAt: new Date(sub.created * 1000),
320
+ updatedAt: new Date(sub.created * 1000), // Stripe doesn't have updated_at
321
+ };
322
+ }
323
+ // ==========================================================================
324
+ // CUSTOMERS
325
+ // ==========================================================================
326
+ async syncCustomer(customer) {
327
+ const params = {
328
+ metadata: {
329
+ grove_customer_id: customer.id,
330
+ grove_tenant_id: customer.tenantId,
331
+ ...customer.metadata,
332
+ },
333
+ };
334
+ if (customer.email)
335
+ params.email = customer.email;
336
+ if (customer.name)
337
+ params.name = customer.name;
338
+ if (customer.phone)
339
+ params.phone = customer.phone;
340
+ // Create or update
341
+ if (customer.providerCustomerId) {
342
+ const updated = await this.client.request(`customers/${customer.providerCustomerId}`, { method: 'POST', params });
343
+ return { providerCustomerId: updated.id };
344
+ }
345
+ else {
346
+ const created = await this.client.request('customers', {
347
+ method: 'POST',
348
+ params,
349
+ });
350
+ return { providerCustomerId: created.id };
351
+ }
352
+ }
353
+ async getCustomer(providerCustomerId) {
354
+ try {
355
+ const cust = await this.client.request(`customers/${providerCustomerId}`);
356
+ return {
357
+ id: cust.metadata.grove_customer_id || cust.id,
358
+ tenantId: cust.metadata.grove_tenant_id || '',
359
+ email: cust.email || '',
360
+ name: cust.name,
361
+ phone: cust.phone,
362
+ providerCustomerId: cust.id,
363
+ metadata: cust.metadata,
364
+ createdAt: new Date(cust.created * 1000),
365
+ updatedAt: new Date(cust.created * 1000),
366
+ };
367
+ }
368
+ catch (err) {
369
+ if (err instanceof StripeAPIError && err.statusCode === 404) {
370
+ return null;
371
+ }
372
+ throw err;
373
+ }
374
+ }
375
+ async createBillingPortalSession(providerCustomerId, returnUrl) {
376
+ const session = await this.client.request('billing_portal/sessions', {
377
+ method: 'POST',
378
+ params: {
379
+ customer: providerCustomerId,
380
+ return_url: returnUrl,
381
+ },
382
+ });
383
+ return { url: session.url };
384
+ }
385
+ // ==========================================================================
386
+ // WEBHOOKS
387
+ // ==========================================================================
388
+ async handleWebhook(request) {
389
+ const signature = request.headers.get('stripe-signature');
390
+ if (!signature) {
391
+ return { received: false, error: 'Missing Stripe-Signature header' };
392
+ }
393
+ if (!this.webhookSecret) {
394
+ return { received: false, error: 'Webhook secret not configured' };
395
+ }
396
+ const payload = await request.text();
397
+ const result = await this.client.verifyWebhookSignature(payload, signature, this.webhookSecret);
398
+ if (!result.valid) {
399
+ return { received: false, error: result.error };
400
+ }
401
+ const stripeEvent = result.event;
402
+ const event = {
403
+ id: crypto.randomUUID(),
404
+ type: this.mapEventType(stripeEvent.type),
405
+ data: stripeEvent.data.object,
406
+ createdAt: new Date(stripeEvent.created * 1000),
407
+ providerEventId: stripeEvent.id,
408
+ };
409
+ return { received: true, event };
410
+ }
411
+ mapEventType(stripeType) {
412
+ const typeMap = {
413
+ 'checkout.session.completed': 'checkout.session.completed',
414
+ 'checkout.session.expired': 'checkout.session.expired',
415
+ 'payment_intent.succeeded': 'payment.succeeded',
416
+ 'payment_intent.payment_failed': 'payment.failed',
417
+ 'customer.subscription.created': 'subscription.created',
418
+ 'customer.subscription.updated': 'subscription.updated',
419
+ 'customer.subscription.deleted': 'subscription.canceled',
420
+ 'customer.subscription.trial_will_end': 'subscription.trial_will_end',
421
+ 'invoice.paid': 'invoice.paid',
422
+ 'invoice.payment_failed': 'invoice.payment_failed',
423
+ 'charge.refunded': 'refund.created',
424
+ 'refund.updated': 'refund.updated',
425
+ 'account.updated': 'account.updated',
426
+ 'payout.paid': 'payout.paid',
427
+ };
428
+ return typeMap[stripeType] || 'payment.succeeded';
429
+ }
430
+ // ==========================================================================
431
+ // STRIPE CONNECT
432
+ // ==========================================================================
433
+ async createConnectAccount(options) {
434
+ // Create the Connect account
435
+ const account = await this.client.request('accounts', {
436
+ method: 'POST',
437
+ params: {
438
+ type: options.type || 'express',
439
+ country: options.country || 'US',
440
+ email: options.email,
441
+ business_type: options.businessType,
442
+ capabilities: {
443
+ card_payments: { requested: true },
444
+ transfers: { requested: true },
445
+ },
446
+ metadata: {
447
+ grove_tenant_id: options.tenantId,
448
+ },
449
+ },
450
+ });
451
+ // Create account link for onboarding
452
+ const accountLink = await this.client.request('account_links', {
453
+ method: 'POST',
454
+ params: {
455
+ account: account.id,
456
+ refresh_url: options.refreshUrl,
457
+ return_url: options.returnUrl,
458
+ type: 'account_onboarding',
459
+ },
460
+ });
461
+ return {
462
+ accountId: account.id,
463
+ onboardingUrl: accountLink.url,
464
+ expiresAt: new Date(accountLink.expires_at * 1000),
465
+ };
466
+ }
467
+ async getConnectAccount(providerAccountId) {
468
+ try {
469
+ const account = await this.client.request(`accounts/${providerAccountId}`);
470
+ return this.mapConnectAccount(account);
471
+ }
472
+ catch (err) {
473
+ if (err instanceof StripeAPIError && err.statusCode === 404) {
474
+ return null;
475
+ }
476
+ throw err;
477
+ }
478
+ }
479
+ async createConnectAccountLink(providerAccountId, options) {
480
+ const accountLink = await this.client.request('account_links', {
481
+ method: 'POST',
482
+ params: {
483
+ account: providerAccountId,
484
+ refresh_url: options.refreshUrl,
485
+ return_url: options.returnUrl,
486
+ type: 'account_onboarding',
487
+ },
488
+ });
489
+ return {
490
+ url: accountLink.url,
491
+ expiresAt: new Date(accountLink.expires_at * 1000),
492
+ };
493
+ }
494
+ async createConnectLoginLink(providerAccountId) {
495
+ const loginLink = await this.client.request(`accounts/${providerAccountId}/login_links`, { method: 'POST' });
496
+ return { url: loginLink.url };
497
+ }
498
+ mapConnectAccount(account) {
499
+ let status = 'pending';
500
+ if (account.charges_enabled && account.payouts_enabled) {
501
+ status = 'enabled';
502
+ }
503
+ else if (account.details_submitted) {
504
+ status = 'restricted';
505
+ }
506
+ else if (!account.charges_enabled && account.details_submitted) {
507
+ status = 'disabled';
508
+ }
509
+ return {
510
+ id: account.id,
511
+ tenantId: '', // Would need to look up from metadata
512
+ providerAccountId: account.id,
513
+ status,
514
+ chargesEnabled: account.charges_enabled,
515
+ payoutsEnabled: account.payouts_enabled,
516
+ detailsSubmitted: account.details_submitted,
517
+ email: account.email,
518
+ country: account.country,
519
+ defaultCurrency: account.default_currency,
520
+ createdAt: new Date(account.created * 1000),
521
+ updatedAt: new Date(account.created * 1000),
522
+ };
523
+ }
524
+ }
525
+ // =============================================================================
526
+ // FACTORY FUNCTION
527
+ // =============================================================================
528
+ export function createStripeProvider(config) {
529
+ return new StripeProvider(config);
530
+ }