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