emulate 0.4.0 → 0.5.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 (50) hide show
  1. package/README.md +59 -10
  2. package/dist/api.d.ts +2 -1
  3. package/dist/api.js +232 -96
  4. package/dist/api.js.map +1 -1
  5. package/dist/{chunk-TEPNEZ63.js → chunk-AQ2CLRU3.js} +26 -23
  6. package/dist/chunk-AQ2CLRU3.js.map +1 -0
  7. package/dist/chunk-WVQMFHQM.js +83 -0
  8. package/dist/chunk-WVQMFHQM.js.map +1 -0
  9. package/dist/{dist-RDFBZ5O6.js → dist-4X2KPMAJ.js} +212 -47
  10. package/dist/dist-4X2KPMAJ.js.map +1 -0
  11. package/dist/{dist-OTJZRQ3Q.js → dist-5JVGPOL3.js} +217 -75
  12. package/dist/dist-5JVGPOL3.js.map +1 -0
  13. package/dist/{dist-G7WQPZ3Y.js → dist-CE6BUCWQ.js} +211 -60
  14. package/dist/dist-CE6BUCWQ.js.map +1 -0
  15. package/dist/{dist-6JFNJPUU.js → dist-CFST4X4K.js} +172 -22
  16. package/dist/dist-CFST4X4K.js.map +1 -0
  17. package/dist/{dist-YOVM5HEY.js → dist-ENKE2S7V.js} +521 -60
  18. package/dist/dist-ENKE2S7V.js.map +1 -0
  19. package/dist/{dist-RMK3BS5M.js → dist-ETHHYBGF.js} +197 -33
  20. package/dist/dist-ETHHYBGF.js.map +1 -0
  21. package/dist/{dist-QMOJM6DV.js → dist-IBXD3O6A.js} +239 -54
  22. package/dist/dist-IBXD3O6A.js.map +1 -0
  23. package/dist/dist-J6LHUR52.js +1899 -0
  24. package/dist/dist-J6LHUR52.js.map +1 -0
  25. package/dist/{dist-6EW7SSOZ.js → dist-KKTYBE5S.js} +391 -222
  26. package/dist/dist-KKTYBE5S.js.map +1 -0
  27. package/dist/{dist-VVXVP5EZ.js → dist-LDUHEJAN.js} +553 -91
  28. package/dist/dist-LDUHEJAN.js.map +1 -0
  29. package/dist/{dist-B674PYKV.js → dist-PWGOAQC6.js} +22 -43
  30. package/dist/dist-PWGOAQC6.js.map +1 -0
  31. package/dist/{dist-H6JYGQM4.js → dist-REDHDZ3V.js} +272 -157
  32. package/dist/dist-REDHDZ3V.js.map +1 -0
  33. package/dist/fonts/favicon.ico +0 -0
  34. package/dist/helpers-LXLP3DFE-LBOTATT5.js +17 -0
  35. package/dist/helpers-LXLP3DFE-LBOTATT5.js.map +1 -0
  36. package/dist/index.js +365 -108
  37. package/dist/index.js.map +1 -1
  38. package/package.json +17 -14
  39. package/dist/chunk-TEPNEZ63.js.map +0 -1
  40. package/dist/dist-6EW7SSOZ.js.map +0 -1
  41. package/dist/dist-6JFNJPUU.js.map +0 -1
  42. package/dist/dist-B674PYKV.js.map +0 -1
  43. package/dist/dist-G7WQPZ3Y.js.map +0 -1
  44. package/dist/dist-H6JYGQM4.js.map +0 -1
  45. package/dist/dist-OTJZRQ3Q.js.map +0 -1
  46. package/dist/dist-QMOJM6DV.js.map +0 -1
  47. package/dist/dist-RDFBZ5O6.js.map +0 -1
  48. package/dist/dist-RMK3BS5M.js.map +0 -1
  49. package/dist/dist-VVXVP5EZ.js.map +0 -1
  50. package/dist/dist-YOVM5HEY.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import "./chunk-TEPNEZ63.js";
1
+ import "./chunk-AQ2CLRU3.js";
2
2
 
3
3
  // ../@emulators/stripe/dist/index.js
4
4
  import { randomBytes } from "crypto";
@@ -190,12 +190,26 @@ function customerRoutes({ app, store, webhooks }) {
190
190
  });
191
191
  app.get("/v1/customers/:id", (c) => {
192
192
  const customer = ss.customers.findOneBy("stripe_id", c.req.param("id"));
193
- if (!customer) return stripeError(c, 404, "invalid_request_error", `No such customer: '${c.req.param("id")}'`, "resource_missing");
193
+ if (!customer)
194
+ return stripeError(
195
+ c,
196
+ 404,
197
+ "invalid_request_error",
198
+ `No such customer: '${c.req.param("id")}'`,
199
+ "resource_missing"
200
+ );
194
201
  return c.json(formatCustomer(customer));
195
202
  });
196
203
  app.post("/v1/customers/:id", async (c) => {
197
204
  const customer = ss.customers.findOneBy("stripe_id", c.req.param("id"));
198
- if (!customer) return stripeError(c, 404, "invalid_request_error", `No such customer: '${c.req.param("id")}'`, "resource_missing");
205
+ if (!customer)
206
+ return stripeError(
207
+ c,
208
+ 404,
209
+ "invalid_request_error",
210
+ `No such customer: '${c.req.param("id")}'`,
211
+ "resource_missing"
212
+ );
199
213
  const body = await parseStripeBody(c);
200
214
  const updated = ss.customers.update(customer.id, {
201
215
  ...body.email !== void 0 && { email: body.email },
@@ -213,7 +227,14 @@ function customerRoutes({ app, store, webhooks }) {
213
227
  });
214
228
  app.delete("/v1/customers/:id", async (c) => {
215
229
  const customer = ss.customers.findOneBy("stripe_id", c.req.param("id"));
216
- if (!customer) return stripeError(c, 404, "invalid_request_error", `No such customer: '${c.req.param("id")}'`, "resource_missing");
230
+ if (!customer)
231
+ return stripeError(
232
+ c,
233
+ 404,
234
+ "invalid_request_error",
235
+ `No such customer: '${c.req.param("id")}'`,
236
+ "resource_missing"
237
+ );
217
238
  for (const pi of ss.paymentIntents.findBy("customer_id", customer.stripe_id)) {
218
239
  ss.paymentIntents.update(pi.id, { customer_id: null });
219
240
  }
@@ -250,10 +271,24 @@ function paymentIntentRoutes({ app, store, webhooks }) {
250
271
  app.post("/v1/payment_intents", async (c) => {
251
272
  const body = await parseStripeBody(c);
252
273
  if (!body.amount || !body.currency) {
253
- return stripeError(c, 400, "invalid_request_error", "Missing required param: amount and currency are required.", void 0, "amount");
274
+ return stripeError(
275
+ c,
276
+ 400,
277
+ "invalid_request_error",
278
+ "Missing required param: amount and currency are required.",
279
+ void 0,
280
+ "amount"
281
+ );
254
282
  }
255
283
  if (body.customer && !ss.customers.findOneBy("stripe_id", body.customer)) {
256
- return stripeError(c, 400, "invalid_request_error", `No such customer: '${body.customer}'`, "resource_missing", "customer");
284
+ return stripeError(
285
+ c,
286
+ 400,
287
+ "invalid_request_error",
288
+ `No such customer: '${body.customer}'`,
289
+ "resource_missing",
290
+ "customer"
291
+ );
257
292
  }
258
293
  const status = body.payment_method ? "requires_confirmation" : "requires_payment_method";
259
294
  const pi = ss.paymentIntents.insert({
@@ -276,14 +311,28 @@ function paymentIntentRoutes({ app, store, webhooks }) {
276
311
  });
277
312
  app.get("/v1/payment_intents/:id", (c) => {
278
313
  const pi = ss.paymentIntents.findOneBy("stripe_id", c.req.param("id"));
279
- if (!pi) return stripeError(c, 404, "invalid_request_error", `No such payment_intent: '${c.req.param("id")}'`, "resource_missing");
314
+ if (!pi)
315
+ return stripeError(
316
+ c,
317
+ 404,
318
+ "invalid_request_error",
319
+ `No such payment_intent: '${c.req.param("id")}'`,
320
+ "resource_missing"
321
+ );
280
322
  const expand = parseExpand(c);
281
323
  const result = applyExpand(formatPaymentIntent(pi), expand, expandResolvers);
282
324
  return c.json(result);
283
325
  });
284
326
  app.post("/v1/payment_intents/:id", async (c) => {
285
327
  const pi = ss.paymentIntents.findOneBy("stripe_id", c.req.param("id"));
286
- if (!pi) return stripeError(c, 404, "invalid_request_error", `No such payment_intent: '${c.req.param("id")}'`, "resource_missing");
328
+ if (!pi)
329
+ return stripeError(
330
+ c,
331
+ 404,
332
+ "invalid_request_error",
333
+ `No such payment_intent: '${c.req.param("id")}'`,
334
+ "resource_missing"
335
+ );
287
336
  const body = await parseStripeBody(c);
288
337
  const updates = {};
289
338
  if (body.amount !== void 0) updates.amount = body.amount;
@@ -301,10 +350,23 @@ function paymentIntentRoutes({ app, store, webhooks }) {
301
350
  });
302
351
  app.post("/v1/payment_intents/:id/confirm", async (c) => {
303
352
  const pi = ss.paymentIntents.findOneBy("stripe_id", c.req.param("id"));
304
- if (!pi) return stripeError(c, 404, "invalid_request_error", `No such payment_intent: '${c.req.param("id")}'`, "resource_missing");
353
+ if (!pi)
354
+ return stripeError(
355
+ c,
356
+ 404,
357
+ "invalid_request_error",
358
+ `No such payment_intent: '${c.req.param("id")}'`,
359
+ "resource_missing"
360
+ );
305
361
  const body = await parseStripeBody(c);
306
362
  if (pi.status !== "requires_confirmation" && pi.status !== "requires_payment_method") {
307
- return stripeError(c, 400, "invalid_request_error", `This PaymentIntent's status is ${pi.status}, which does not allow confirmation.`, "payment_intent_unexpected_state");
363
+ return stripeError(
364
+ c,
365
+ 400,
366
+ "invalid_request_error",
367
+ `This PaymentIntent's status is ${pi.status}, which does not allow confirmation.`,
368
+ "payment_intent_unexpected_state"
369
+ );
308
370
  }
309
371
  if (body.payment_method) {
310
372
  ss.paymentIntents.update(pi.id, { payment_method: body.payment_method });
@@ -329,16 +391,40 @@ function paymentIntentRoutes({ app, store, webhooks }) {
329
391
  await webhooks.dispatch(
330
392
  "charge.succeeded",
331
393
  void 0,
332
- { type: "charge.succeeded", data: { object: { id: charge.stripe_id, object: "charge", amount: charge.amount, currency: charge.currency, status: charge.status } } },
394
+ {
395
+ type: "charge.succeeded",
396
+ data: {
397
+ object: {
398
+ id: charge.stripe_id,
399
+ object: "charge",
400
+ amount: charge.amount,
401
+ currency: charge.currency,
402
+ status: charge.status
403
+ }
404
+ }
405
+ },
333
406
  "stripe"
334
407
  );
335
408
  return c.json(formatPaymentIntent(updated));
336
409
  });
337
410
  app.post("/v1/payment_intents/:id/cancel", async (c) => {
338
411
  const pi = ss.paymentIntents.findOneBy("stripe_id", c.req.param("id"));
339
- if (!pi) return stripeError(c, 404, "invalid_request_error", `No such payment_intent: '${c.req.param("id")}'`, "resource_missing");
412
+ if (!pi)
413
+ return stripeError(
414
+ c,
415
+ 404,
416
+ "invalid_request_error",
417
+ `No such payment_intent: '${c.req.param("id")}'`,
418
+ "resource_missing"
419
+ );
340
420
  if (pi.status === "succeeded" || pi.status === "canceled") {
341
- return stripeError(c, 400, "invalid_request_error", `This PaymentIntent's status is ${pi.status}, which does not allow cancellation.`, "payment_intent_unexpected_state");
421
+ return stripeError(
422
+ c,
423
+ 400,
424
+ "invalid_request_error",
425
+ `This PaymentIntent's status is ${pi.status}, which does not allow cancellation.`,
426
+ "payment_intent_unexpected_state"
427
+ );
342
428
  }
343
429
  const updated = ss.paymentIntents.update(pi.id, { status: "canceled" });
344
430
  await webhooks.dispatch(
@@ -358,6 +444,31 @@ function paymentIntentRoutes({ app, store, webhooks }) {
358
444
  return stripeList(c, items, "/v1/payment_intents", formatPaymentIntent);
359
445
  });
360
446
  }
447
+ function paymentMethodRoutes({ app, store }) {
448
+ const ss = getStripeStore(store);
449
+ app.get("/v1/payment_methods", (c) => {
450
+ const customerId = c.req.query("customer");
451
+ if (customerId && !ss.customers.findOneBy("stripe_id", customerId)) {
452
+ return stripeError(
453
+ c,
454
+ 400,
455
+ "invalid_request_error",
456
+ `No such customer: '${customerId}'`,
457
+ "resource_missing",
458
+ "customer"
459
+ );
460
+ }
461
+ return c.json(
462
+ {
463
+ object: "list",
464
+ url: "/v1/payment_methods",
465
+ has_more: false,
466
+ data: []
467
+ },
468
+ 200
469
+ );
470
+ });
471
+ }
361
472
  function formatCharge(ch) {
362
473
  return {
363
474
  id: ch.stripe_id,
@@ -387,7 +498,8 @@ function chargeRoutes({ app, store }) {
387
498
  };
388
499
  app.get("/v1/charges/:id", (c) => {
389
500
  const charge = ss.charges.findOneBy("stripe_id", c.req.param("id"));
390
- if (!charge) return stripeError(c, 404, "invalid_request_error", `No such charge: '${c.req.param("id")}'`, "resource_missing");
501
+ if (!charge)
502
+ return stripeError(c, 404, "invalid_request_error", `No such charge: '${c.req.param("id")}'`, "resource_missing");
391
503
  const expand = parseExpand(c);
392
504
  const result = applyExpand(formatCharge(charge), expand, expandResolvers);
393
505
  return c.json(result);
@@ -417,7 +529,8 @@ function productRoutes({ app, store, webhooks }) {
417
529
  const ss = getStripeStore(store);
418
530
  app.post("/v1/products", async (c) => {
419
531
  const body = await parseStripeBody(c);
420
- if (!body.name) return stripeError(c, 400, "invalid_request_error", "Missing required param: name.", void 0, "name");
532
+ if (!body.name)
533
+ return stripeError(c, 400, "invalid_request_error", "Missing required param: name.", void 0, "name");
421
534
  const product = ss.products.insert({
422
535
  stripe_id: stripeId("prod"),
423
536
  name: body.name,
@@ -435,7 +548,14 @@ function productRoutes({ app, store, webhooks }) {
435
548
  });
436
549
  app.get("/v1/products/:id", (c) => {
437
550
  const product = ss.products.findOneBy("stripe_id", c.req.param("id"));
438
- if (!product) return stripeError(c, 404, "invalid_request_error", `No such product: '${c.req.param("id")}'`, "resource_missing");
551
+ if (!product)
552
+ return stripeError(
553
+ c,
554
+ 404,
555
+ "invalid_request_error",
556
+ `No such product: '${c.req.param("id")}'`,
557
+ "resource_missing"
558
+ );
439
559
  return c.json(formatProduct(product));
440
560
  });
441
561
  app.get("/v1/products", (c) => {
@@ -460,7 +580,14 @@ function formatPrice(p) {
460
580
  };
461
581
  }
462
582
  function formatProduct2(p) {
463
- return { id: p.stripe_id, object: "product", name: p.name, active: p.active, created: toUnixTimestamp(p.created_at), livemode: false };
583
+ return {
584
+ id: p.stripe_id,
585
+ object: "product",
586
+ name: p.name,
587
+ active: p.active,
588
+ created: toUnixTimestamp(p.created_at),
589
+ livemode: false
590
+ };
464
591
  }
465
592
  function priceRoutes({ app, store, webhooks }) {
466
593
  const ss = getStripeStore(store);
@@ -473,10 +600,24 @@ function priceRoutes({ app, store, webhooks }) {
473
600
  app.post("/v1/prices", async (c) => {
474
601
  const body = await parseStripeBody(c);
475
602
  if (!body.currency || !body.product) {
476
- return stripeError(c, 400, "invalid_request_error", "Missing required param: currency and product are required.", void 0, "currency");
603
+ return stripeError(
604
+ c,
605
+ 400,
606
+ "invalid_request_error",
607
+ "Missing required param: currency and product are required.",
608
+ void 0,
609
+ "currency"
610
+ );
477
611
  }
478
612
  if (!ss.products.findOneBy("stripe_id", body.product)) {
479
- return stripeError(c, 400, "invalid_request_error", `No such product: '${body.product}'`, "resource_missing", "product");
613
+ return stripeError(
614
+ c,
615
+ 400,
616
+ "invalid_request_error",
617
+ `No such product: '${body.product}'`,
618
+ "resource_missing",
619
+ "product"
620
+ );
480
621
  }
481
622
  const price = ss.prices.insert({
482
623
  stripe_id: stripeId("price"),
@@ -497,7 +638,8 @@ function priceRoutes({ app, store, webhooks }) {
497
638
  });
498
639
  app.get("/v1/prices/:id", (c) => {
499
640
  const price = ss.prices.findOneBy("stripe_id", c.req.param("id"));
500
- if (!price) return stripeError(c, 404, "invalid_request_error", `No such price: '${c.req.param("id")}'`, "resource_missing");
641
+ if (!price)
642
+ return stripeError(c, 404, "invalid_request_error", `No such price: '${c.req.param("id")}'`, "resource_missing");
501
643
  const expand = parseExpand(c);
502
644
  const result = applyExpand(formatPrice(price), expand, expandResolvers);
503
645
  return c.json(result);
@@ -526,6 +668,7 @@ var FONTS = {
526
668
  "geist-sans.woff2": readFileSync(join(__dirname, "fonts", "geist-sans.woff2")),
527
669
  "GeistPixel-Square.woff2": readFileSync(join(__dirname, "fonts", "GeistPixel-Square.woff2"))
528
670
  };
671
+ var FAVICON = readFileSync(join(__dirname, "fonts", "favicon.ico"));
529
672
  function escapeHtml(s) {
530
673
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
531
674
  }
@@ -677,6 +820,132 @@ body{
677
820
  .app-link-name{font-weight:600;font-size:.875rem;color:#33ff00;}
678
821
  .app-link-scopes{font-size:.6875rem;color:#1a8c00;margin-top:1px;}
679
822
  .empty{color:#1a8c00;text-align:center;padding:28px 0;font-size:.875rem;}
823
+
824
+ .inspector-layout{max-width:960px;margin:0 auto;padding:28px 20px;}
825
+ .inspector-tabs{display:flex;gap:4px;margin-bottom:20px;}
826
+ .inspector-tabs a{
827
+ padding:7px 16px;border-radius:6px;text-decoration:none;
828
+ font-size:.8125rem;color:#1a8c00;border:1px solid transparent;
829
+ transition:color .15s,border-color .15s;
830
+ }
831
+ .inspector-tabs a:hover{color:#33ff00;}
832
+ .inspector-tabs a.active{color:#33ff00;font-weight:600;border-color:#0a3300;background:#0a3300;}
833
+ .inspector-section{margin-bottom:24px;}
834
+ .inspector-section h2{
835
+ font-family:'Geist Pixel',monospace;
836
+ font-size:1rem;font-weight:600;color:#33ff00;margin-bottom:10px;
837
+ }
838
+ .inspector-section h3{
839
+ font-family:'Geist Pixel',monospace;
840
+ font-size:.875rem;font-weight:600;color:#1a8c00;margin:16px 0 8px;
841
+ }
842
+ .inspector-table{width:100%;border-collapse:collapse;margin-bottom:12px;}
843
+ .inspector-table th,.inspector-table td{
844
+ text-align:left;padding:8px 12px;border-bottom:1px solid #0a3300;
845
+ font-size:.8125rem;
846
+ }
847
+ .inspector-table th{color:#1a8c00;font-weight:600;font-size:.75rem;text-transform:uppercase;letter-spacing:.04em;}
848
+ .inspector-table td{color:#33ff00;}
849
+ .inspector-table tbody tr{transition:background .1s;}
850
+ .inspector-table tbody tr:hover{background:#0a3300;}
851
+ .inspector-empty{color:#1a8c00;text-align:center;padding:20px 0;font-size:.8125rem;}
852
+
853
+ .checkout-layout{
854
+ display:flex;min-height:calc(100vh - 42px);
855
+ }
856
+ .checkout-summary{
857
+ flex:1;background:#020;padding:48px 40px 48px 10%;
858
+ display:flex;flex-direction:column;justify-content:center;
859
+ border-right:1px solid #0a3300;
860
+ }
861
+ .checkout-form-side{
862
+ flex:1;background:#000;padding:48px 10% 48px 40px;
863
+ display:flex;flex-direction:column;justify-content:center;
864
+ }
865
+ .checkout-merchant{
866
+ display:flex;align-items:center;gap:10px;margin-bottom:6px;
867
+ }
868
+ .checkout-merchant-name{
869
+ font-family:'Geist Pixel',monospace;
870
+ font-size:.9375rem;font-weight:600;color:#33ff00;
871
+ }
872
+ .checkout-test-badge{
873
+ font-size:.625rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
874
+ background:#0a3300;color:#1a8c00;padding:2px 8px;border-radius:4px;
875
+ }
876
+ .checkout-total{
877
+ font-family:'Geist Pixel',monospace;
878
+ font-size:2rem;font-weight:700;color:#33ff00;margin:8px 0 28px;
879
+ }
880
+ .checkout-line-item{
881
+ display:flex;align-items:center;gap:14px;padding:14px 0;
882
+ border-bottom:1px solid #0a3300;
883
+ }
884
+ .checkout-line-item:first-child{border-top:1px solid #0a3300;}
885
+ .checkout-item-icon{
886
+ width:42px;height:42px;border-radius:6px;background:#0a3300;
887
+ display:flex;align-items:center;justify-content:center;flex-shrink:0;
888
+ font-family:'Geist Pixel',monospace;font-size:.875rem;font-weight:700;color:#116600;
889
+ }
890
+ .checkout-item-details{flex:1;min-width:0;}
891
+ .checkout-item-name{font-size:.875rem;font-weight:600;color:#33ff00;}
892
+ .checkout-item-qty{font-size:.75rem;color:#1a8c00;margin-top:2px;}
893
+ .checkout-item-price{
894
+ font-size:.875rem;font-weight:600;color:#33ff00;text-align:right;white-space:nowrap;
895
+ }
896
+ .checkout-item-unit{font-size:.6875rem;color:#1a8c00;text-align:right;margin-top:2px;}
897
+ .checkout-totals{margin-top:20px;}
898
+ .checkout-totals-row{
899
+ display:flex;justify-content:space-between;padding:6px 0;
900
+ font-size:.8125rem;color:#1a8c00;
901
+ }
902
+ .checkout-totals-row.total{
903
+ border-top:1px solid #0a3300;margin-top:8px;padding-top:14px;
904
+ font-size:.9375rem;font-weight:600;color:#33ff00;
905
+ }
906
+ .checkout-form-section{margin-bottom:24px;}
907
+ .checkout-form-label{
908
+ font-size:.8125rem;font-weight:600;color:#33ff00;margin-bottom:8px;display:block;
909
+ }
910
+ .checkout-input{
911
+ width:100%;padding:10px 12px;border:1px solid #0a3300;border-radius:6px;
912
+ background:#020;color:#33ff00;font:inherit;font-size:.875rem;
913
+ transition:border-color .15s;outline:none;
914
+ }
915
+ .checkout-input:focus{border-color:#33ff00;}
916
+ .checkout-input::placeholder{color:#116600;}
917
+ .checkout-card-box{
918
+ border:1px solid #0a3300;border-radius:6px;padding:14px;
919
+ background:#020;
920
+ }
921
+ .checkout-card-row{
922
+ display:flex;gap:12px;margin-top:10px;
923
+ }
924
+ .checkout-card-row .checkout-input{flex:1;}
925
+ .checkout-sim-note{
926
+ font-size:.6875rem;color:#1a8c00;margin-top:10px;text-align:center;
927
+ font-style:italic;
928
+ }
929
+ .checkout-pay-btn{
930
+ width:100%;padding:14px;border:none;border-radius:8px;
931
+ background:#33ff00;color:#000;font:inherit;font-size:.9375rem;font-weight:700;
932
+ cursor:pointer;transition:background .15s;
933
+ font-family:'Geist Pixel',monospace;
934
+ }
935
+ .checkout-pay-btn:hover{background:#44ff22;}
936
+ .checkout-cancel{
937
+ text-align:center;margin-top:14px;
938
+ }
939
+ .checkout-cancel a{
940
+ color:#1a8c00;text-decoration:none;font-size:.8125rem;
941
+ transition:color .15s;
942
+ }
943
+ .checkout-cancel a:hover{color:#33ff00;}
944
+ @media(max-width:768px){
945
+ .checkout-layout{flex-direction:column;}
946
+ .checkout-summary{padding:32px 20px;border-right:none;border-bottom:1px solid #0a3300;}
947
+ .checkout-form-side{padding:32px 20px;}
948
+ }
680
949
  `;
681
950
  var POWERED_BY = `<div class="powered-by">Powered by <a href="https://emulate.dev" target="_blank" rel="noopener">emulate</a></div>`;
682
951
  function emuBar(service) {
@@ -696,6 +965,7 @@ function head(title) {
696
965
  <head>
697
966
  <meta charset="utf-8"/>
698
967
  <meta name="viewport" content="width=device-width,initial-scale=1"/>
968
+ <link rel="icon" href="/_emulate/favicon.ico"/>
699
969
  <title>${escapeHtml(title)} | emulate</title>
700
970
  <style>${CSS}</style>
701
971
  </head>`;
@@ -714,6 +984,72 @@ ${emuBar(service)}
714
984
  ${POWERED_BY}
715
985
  </body></html>`;
716
986
  }
987
+ function renderCheckoutPage(opts, service) {
988
+ const fmt = (cents, cur) => `$${(cents / 100).toFixed(2)} ${cur.toUpperCase()}`;
989
+ const fmtShort = (cents) => `$${(cents / 100).toFixed(2)}`;
990
+ const itemsHtml = opts.lineItems.length > 0 ? opts.lineItems.map((li) => {
991
+ const initial = li.name.charAt(0).toUpperCase();
992
+ const unitNote = li.quantity > 1 ? `<div class="checkout-item-unit">${fmtShort(li.unitPrice)} each</div>` : "";
993
+ return `<div class="checkout-line-item">
994
+ <div class="checkout-item-icon">${escapeHtml(initial)}</div>
995
+ <div class="checkout-item-details">
996
+ <div class="checkout-item-name">${escapeHtml(li.name)}</div>
997
+ <div class="checkout-item-qty">Qty ${li.quantity}</div>
998
+ </div>
999
+ <div>
1000
+ <div class="checkout-item-price">${fmtShort(li.totalPrice)}</div>
1001
+ ${unitNote}
1002
+ </div>
1003
+ </div>`;
1004
+ }).join("") : '<p class="empty">No line items</p>';
1005
+ const totalsHtml = `<div class="checkout-totals">
1006
+ <div class="checkout-totals-row">
1007
+ <span>Subtotal</span><span>${fmtShort(opts.subtotal)}</span>
1008
+ </div>
1009
+ <div class="checkout-totals-row total">
1010
+ <span>Total due</span><span>${fmt(opts.total, opts.currency)}</span>
1011
+ </div>
1012
+ </div>`;
1013
+ const cancelHtml = opts.cancelUrl ? `<div class="checkout-cancel"><a href="${escapeAttr(opts.cancelUrl)}">Cancel</a></div>` : "";
1014
+ const merchant = opts.merchantName ? escapeHtml(opts.merchantName) : "Checkout";
1015
+ return `${head("Checkout")}
1016
+ <body>
1017
+ ${emuBar(service)}
1018
+ <div class="checkout-layout">
1019
+ <div class="checkout-summary">
1020
+ <div class="checkout-merchant">
1021
+ <span class="checkout-merchant-name">${merchant}</span>
1022
+ <span class="checkout-test-badge">Test Mode</span>
1023
+ </div>
1024
+ <div class="checkout-total">${fmtShort(opts.total)}</div>
1025
+ ${itemsHtml}
1026
+ ${totalsHtml}
1027
+ </div>
1028
+ <div class="checkout-form-side">
1029
+ <form method="post" action="/checkout/${escapeAttr(opts.sessionId)}/complete">
1030
+ <div class="checkout-form-section">
1031
+ <label class="checkout-form-label">Email</label>
1032
+ <input type="email" name="email" class="checkout-input" placeholder="you@example.com"/>
1033
+ </div>
1034
+ <div class="checkout-form-section">
1035
+ <label class="checkout-form-label">Card information</label>
1036
+ <div class="checkout-card-box">
1037
+ <input type="text" class="checkout-input" placeholder="1234 1234 1234 1234" disabled/>
1038
+ <div class="checkout-card-row">
1039
+ <input type="text" class="checkout-input" placeholder="MM / YY" disabled/>
1040
+ <input type="text" class="checkout-input" placeholder="CVC" disabled/>
1041
+ </div>
1042
+ </div>
1043
+ <div class="checkout-sim-note">Card fields are simulated. Payment will be auto-approved.</div>
1044
+ </div>
1045
+ <button type="submit" class="checkout-pay-btn">Pay ${fmtShort(opts.total)}</button>
1046
+ </form>
1047
+ ${cancelHtml}
1048
+ </div>
1049
+ </div>
1050
+ ${POWERED_BY}
1051
+ </body></html>`;
1052
+ }
717
1053
  var SERVICE_LABEL = "Stripe";
718
1054
  function formatSession(s, baseUrl) {
719
1055
  return {
@@ -735,9 +1071,17 @@ function checkoutSessionRoutes({ app, store, webhooks, baseUrl }) {
735
1071
  const ss = getStripeStore(store);
736
1072
  app.post("/v1/checkout/sessions", async (c) => {
737
1073
  const body = await parseStripeBody(c);
738
- if (!body.mode) return stripeError(c, 400, "invalid_request_error", "Missing required param: mode.", void 0, "mode");
1074
+ if (!body.mode)
1075
+ return stripeError(c, 400, "invalid_request_error", "Missing required param: mode.", void 0, "mode");
739
1076
  if (body.customer && !ss.customers.findOneBy("stripe_id", body.customer)) {
740
- return stripeError(c, 400, "invalid_request_error", `No such customer: '${body.customer}'`, "resource_missing", "customer");
1077
+ return stripeError(
1078
+ c,
1079
+ 400,
1080
+ "invalid_request_error",
1081
+ `No such customer: '${body.customer}'`,
1082
+ "resource_missing",
1083
+ "customer"
1084
+ );
741
1085
  }
742
1086
  const lineItems = [];
743
1087
  if (body.line_items) {
@@ -747,17 +1091,45 @@ function checkoutSessionRoutes({ app, store, webhooks, baseUrl }) {
747
1091
  for (let i = 0; i < body.line_items.length; i++) {
748
1092
  const li = body.line_items[i];
749
1093
  if (!li || typeof li !== "object") {
750
- return stripeError(c, 400, "invalid_request_error", `Invalid line_items[${i}]: must be an object.`, void 0, `line_items[${i}]`);
1094
+ return stripeError(
1095
+ c,
1096
+ 400,
1097
+ "invalid_request_error",
1098
+ `Invalid line_items[${i}]: must be an object.`,
1099
+ void 0,
1100
+ `line_items[${i}]`
1101
+ );
751
1102
  }
752
1103
  if (!li.price || typeof li.price !== "string") {
753
- return stripeError(c, 400, "invalid_request_error", `Missing required param: line_items[${i}][price].`, void 0, `line_items[${i}][price]`);
1104
+ return stripeError(
1105
+ c,
1106
+ 400,
1107
+ "invalid_request_error",
1108
+ `Missing required param: line_items[${i}][price].`,
1109
+ void 0,
1110
+ `line_items[${i}][price]`
1111
+ );
754
1112
  }
755
1113
  if (!ss.prices.findOneBy("stripe_id", li.price)) {
756
- return stripeError(c, 400, "invalid_request_error", `No such price: '${li.price}'`, "resource_missing", `line_items[${i}][price]`);
1114
+ return stripeError(
1115
+ c,
1116
+ 400,
1117
+ "invalid_request_error",
1118
+ `No such price: '${li.price}'`,
1119
+ "resource_missing",
1120
+ `line_items[${i}][price]`
1121
+ );
757
1122
  }
758
1123
  const qty = typeof li.quantity === "number" ? li.quantity : parseInt(li.quantity, 10);
759
1124
  if (!Number.isFinite(qty) || qty < 1) {
760
- return stripeError(c, 400, "invalid_request_error", `Invalid line_items[${i}][quantity]: must be a positive integer.`, void 0, `line_items[${i}][quantity]`);
1125
+ return stripeError(
1126
+ c,
1127
+ 400,
1128
+ "invalid_request_error",
1129
+ `Invalid line_items[${i}][quantity]: must be a positive integer.`,
1130
+ void 0,
1131
+ `line_items[${i}][quantity]`
1132
+ );
761
1133
  }
762
1134
  lineItems.push({ price: li.price, quantity: qty });
763
1135
  }
@@ -777,14 +1149,34 @@ function checkoutSessionRoutes({ app, store, webhooks, baseUrl }) {
777
1149
  });
778
1150
  app.get("/v1/checkout/sessions/:id", (c) => {
779
1151
  const session = ss.checkoutSessions.findOneBy("stripe_id", c.req.param("id"));
780
- if (!session) return stripeError(c, 404, "invalid_request_error", `No such checkout session: '${c.req.param("id")}'`, "resource_missing");
1152
+ if (!session)
1153
+ return stripeError(
1154
+ c,
1155
+ 404,
1156
+ "invalid_request_error",
1157
+ `No such checkout session: '${c.req.param("id")}'`,
1158
+ "resource_missing"
1159
+ );
781
1160
  return c.json(formatSession(session, baseUrl));
782
1161
  });
783
1162
  app.post("/v1/checkout/sessions/:id/expire", async (c) => {
784
1163
  const session = ss.checkoutSessions.findOneBy("stripe_id", c.req.param("id"));
785
- if (!session) return stripeError(c, 404, "invalid_request_error", `No such checkout session: '${c.req.param("id")}'`, "resource_missing");
1164
+ if (!session)
1165
+ return stripeError(
1166
+ c,
1167
+ 404,
1168
+ "invalid_request_error",
1169
+ `No such checkout session: '${c.req.param("id")}'`,
1170
+ "resource_missing"
1171
+ );
786
1172
  if (session.status !== "open") {
787
- return stripeError(c, 400, "invalid_request_error", "Only open sessions can be expired.", "checkout_session_not_open");
1173
+ return stripeError(
1174
+ c,
1175
+ 400,
1176
+ "invalid_request_error",
1177
+ "Only open sessions can be expired.",
1178
+ "checkout_session_not_open"
1179
+ );
788
1180
  }
789
1181
  const updated = ss.checkoutSessions.update(session.id, { status: "expired" });
790
1182
  await webhooks.dispatch(
@@ -808,35 +1200,53 @@ function checkoutSessionRoutes({ app, store, webhooks, baseUrl }) {
808
1200
  app.get("/checkout/:id", (c) => {
809
1201
  const session = ss.checkoutSessions.findOneBy("stripe_id", c.req.param("id"));
810
1202
  if (!session) {
811
- return c.html(renderCardPage("Session Not Found", "This checkout session does not exist.", '<p class="empty">The session ID is invalid or has been removed.</p>', SERVICE_LABEL), 404);
1203
+ return c.html(
1204
+ renderCardPage(
1205
+ "Session Not Found",
1206
+ "This checkout session does not exist.",
1207
+ '<p class="empty">The session ID is invalid or has been removed.</p>',
1208
+ SERVICE_LABEL
1209
+ ),
1210
+ 404
1211
+ );
812
1212
  }
813
1213
  if (session.status !== "open") {
814
- return c.html(renderCardPage("Session Expired", "This checkout session is no longer available.", `<p class="empty">Status: ${escapeHtml(session.status)}</p>`, SERVICE_LABEL));
1214
+ return c.html(
1215
+ renderCardPage(
1216
+ "Session Expired",
1217
+ "This checkout session is no longer available.",
1218
+ `<p class="empty">Status: ${escapeHtml(session.status)}</p>`,
1219
+ SERVICE_LABEL
1220
+ )
1221
+ );
815
1222
  }
816
- const lineItemsHtml = session.line_items.length > 0 ? session.line_items.map((li) => {
1223
+ const lineItems = session.line_items.map((li) => {
817
1224
  const priceObj = ss.prices.findOneBy("stripe_id", li.price);
818
1225
  const product = priceObj ? ss.products.findOneBy("stripe_id", priceObj.product_id) : null;
819
- const name = product?.name ?? li.price;
820
- const amount = priceObj ? `$${(priceObj.unit_amount / 100).toFixed(2)} ${priceObj.currency.toUpperCase()}` : "";
821
- return `<div class="org-row">
822
- <span class="org-icon">$</span>
823
- <span class="org-name">${escapeHtml(name)}</span>
824
- <span class="emu-bar-service">${escapeHtml(amount)} x ${li.quantity}</span>
825
- </div>`;
826
- }).join("") : '<p class="empty">No line items</p>';
827
- const body = `
828
- ${lineItemsHtml}
829
- <form class="user-form" method="post" action="/checkout/${escapeAttr(session.stripe_id)}/complete">
830
- <button type="submit" class="user-btn">
831
- <span class="avatar">$</span>
832
- <span class="user-text">
833
- <span class="user-login">Pay and Complete</span>
834
- </span>
835
- </button>
836
- </form>
837
- ${session.cancel_url ? `<p class="info-text"><a href="${escapeAttr(session.cancel_url)}" class="btn-revoke">Cancel</a></p>` : ""}
838
- `;
839
- return c.html(renderCardPage("Checkout", `Complete your ${escapeHtml(session.mode)} payment.`, body, SERVICE_LABEL));
1226
+ const unitPrice = priceObj?.unit_amount ?? 0;
1227
+ return {
1228
+ name: product?.name ?? li.price,
1229
+ quantity: li.quantity,
1230
+ unitPrice,
1231
+ totalPrice: unitPrice * li.quantity,
1232
+ currency: priceObj?.currency ?? "usd"
1233
+ };
1234
+ });
1235
+ const subtotal = lineItems.reduce((sum, li) => sum + li.totalPrice, 0);
1236
+ const currency = lineItems.length > 0 ? lineItems[0].currency : "usd";
1237
+ return c.html(
1238
+ renderCheckoutPage(
1239
+ {
1240
+ lineItems,
1241
+ subtotal,
1242
+ total: subtotal,
1243
+ currency,
1244
+ sessionId: session.stripe_id,
1245
+ cancelUrl: session.cancel_url
1246
+ },
1247
+ SERVICE_LABEL
1248
+ )
1249
+ );
840
1250
  });
841
1251
  app.post("/checkout/:id/complete", async (c) => {
842
1252
  const session = ss.checkoutSessions.findOneBy("stripe_id", c.req.param("id"));
@@ -851,9 +1261,47 @@ function checkoutSessionRoutes({ app, store, webhooks, baseUrl }) {
851
1261
  "stripe"
852
1262
  );
853
1263
  if (session.success_url) {
854
- return c.redirect(session.success_url);
1264
+ const url = session.success_url.replace("{CHECKOUT_SESSION_ID}", updated.stripe_id);
1265
+ return c.redirect(url);
855
1266
  }
856
- return c.html(renderCardPage("Payment Complete", "Your payment was successful.", '<p class="empty check">Payment received</p>', SERVICE_LABEL));
1267
+ return c.html(
1268
+ renderCardPage(
1269
+ "Payment Complete",
1270
+ "Your payment was successful.",
1271
+ '<p class="empty check">Payment received</p>',
1272
+ SERVICE_LABEL
1273
+ )
1274
+ );
1275
+ });
1276
+ }
1277
+ function customerSessionRoutes({ app, store }) {
1278
+ const ss = getStripeStore(store);
1279
+ app.post("/v1/customer_sessions", async (c) => {
1280
+ const body = await parseStripeBody(c);
1281
+ if (!body.customer)
1282
+ return stripeError(c, 400, "invalid_request_error", "Missing required param: customer.", void 0, "customer");
1283
+ const customer = ss.customers.findOneBy("stripe_id", body.customer);
1284
+ if (!customer)
1285
+ return stripeError(
1286
+ c,
1287
+ 400,
1288
+ "invalid_request_error",
1289
+ `No such customer: '${body.customer}'`,
1290
+ "resource_missing",
1291
+ "customer"
1292
+ );
1293
+ return c.json(
1294
+ {
1295
+ object: "customer_session",
1296
+ client_secret: stripeId("cuss_secret"),
1297
+ components: body.components ?? {},
1298
+ created: Math.floor(Date.now() / 1e3),
1299
+ customer: customer.stripe_id,
1300
+ expires_at: Math.floor(Date.now() / 1e3) + 1800,
1301
+ livemode: false
1302
+ },
1303
+ 200
1304
+ );
857
1305
  });
858
1306
  }
859
1307
  function seedDefaults(store, _baseUrl) {
@@ -866,7 +1314,7 @@ function seedDefaults(store, _baseUrl) {
866
1314
  metadata: {}
867
1315
  });
868
1316
  }
869
- function seedFromConfig(store, _baseUrl, config) {
1317
+ function seedFromConfig(store, _baseUrl, config, webhooks) {
870
1318
  const ss = getStripeStore(store);
871
1319
  if (config.customers) {
872
1320
  for (const c of config.customers) {
@@ -875,7 +1323,7 @@ function seedFromConfig(store, _baseUrl, config) {
875
1323
  if (existing) continue;
876
1324
  }
877
1325
  ss.customers.insert({
878
- stripe_id: stripeId("cus"),
1326
+ stripe_id: c.id ?? stripeId("cus"),
879
1327
  email: c.email ?? null,
880
1328
  name: c.name ?? null,
881
1329
  description: c.description ?? null,
@@ -886,7 +1334,7 @@ function seedFromConfig(store, _baseUrl, config) {
886
1334
  if (config.products) {
887
1335
  for (const p of config.products) {
888
1336
  const product = ss.products.insert({
889
- stripe_id: stripeId("prod"),
1337
+ stripe_id: p.id ?? stripeId("prod"),
890
1338
  name: p.name,
891
1339
  description: p.description ?? null,
892
1340
  active: true,
@@ -895,7 +1343,7 @@ function seedFromConfig(store, _baseUrl, config) {
895
1343
  const matchingPrices = config.prices?.filter((pr) => pr.product_name === p.name) ?? [];
896
1344
  for (const pr of matchingPrices) {
897
1345
  ss.prices.insert({
898
- stripe_id: stripeId("price"),
1346
+ stripe_id: pr.id ?? stripeId("price"),
899
1347
  product_id: product.stripe_id,
900
1348
  currency: pr.currency.toLowerCase(),
901
1349
  unit_amount: pr.unit_amount,
@@ -906,17 +1354,30 @@ function seedFromConfig(store, _baseUrl, config) {
906
1354
  }
907
1355
  }
908
1356
  }
1357
+ if (config.webhooks && webhooks) {
1358
+ for (const wh of config.webhooks) {
1359
+ webhooks.register({
1360
+ url: wh.url,
1361
+ events: wh.events,
1362
+ active: true,
1363
+ secret: wh.secret,
1364
+ owner: "stripe"
1365
+ });
1366
+ }
1367
+ }
909
1368
  }
910
1369
  var stripePlugin = {
911
1370
  name: "stripe",
912
1371
  register(app, store, webhooks, baseUrl, tokenMap) {
913
1372
  const ctx = { app, store, webhooks, baseUrl, tokenMap };
914
1373
  customerRoutes(ctx);
1374
+ paymentMethodRoutes(ctx);
915
1375
  paymentIntentRoutes(ctx);
916
1376
  chargeRoutes(ctx);
917
1377
  productRoutes(ctx);
918
1378
  priceRoutes(ctx);
919
1379
  checkoutSessionRoutes(ctx);
1380
+ customerSessionRoutes(ctx);
920
1381
  },
921
1382
  seed(store, baseUrl) {
922
1383
  seedDefaults(store, baseUrl);
@@ -929,4 +1390,4 @@ export {
929
1390
  seedFromConfig,
930
1391
  stripePlugin
931
1392
  };
932
- //# sourceMappingURL=dist-YOVM5HEY.js.map
1393
+ //# sourceMappingURL=dist-ENKE2S7V.js.map