@storecraft/payments-stripe 1.0.17 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -71,13 +71,10 @@ const app = new App(config)
71
71
  .withPlatform(new NodePlatform())
72
72
  .withDatabase(new MongoDB())
73
73
  .withStorage(new GoogleStorage())
74
- .withPaymentGateways(
75
- {
76
- 'stripe': new Stripe() // config can be inferred from env variables
77
- }
78
- );
79
-
80
- await app.init();
74
+ .withPaymentGateways({
75
+ stripe: new Stripe() // config can be inferred from env variables
76
+ })
77
+ .init();
81
78
 
82
79
  ```
83
80
 
package/adapter.html.js CHANGED
@@ -13,7 +13,7 @@
13
13
  * Or use the following dummy details:
14
14
  * https://docs.stripe.com/testing?testing-method=card-numbers
15
15
  * - Card number: 4242424242424242
16
- * - Expiry date: 12/2027
16
+ * - Expiry date:
17
17
  * - CVC code: 897
18
18
  *
19
19
  * @param {Config} config
@@ -37,7 +37,33 @@ export default function html_buy_ui(config, order_data) {
37
37
  <script defer>
38
38
  // This is your test publishable API key.
39
39
  const stripe = new Stripe("${config.publishable_key}");
40
+ const dispatchEvent = (event, data) => {
41
+ window?.parent?.postMessage(
42
+ {
43
+ who: "storecraft",
44
+ event,
45
+ data
46
+ },
47
+ "*"
48
+ );
49
+ }
50
+ // Override console.log to send messages to the parent window
51
+ console.log = function(...args) {
52
+ // Log to console
53
+ dispatchEvent(
54
+ "storecraft/checkout-log",
55
+ args
56
+ );
57
+ };
40
58
 
59
+ console.error = function(...args) {
60
+ // Log to console
61
+ dispatchEvent(
62
+ "storecraft/checkout-error",
63
+ args
64
+ );
65
+ };
66
+
41
67
  let elements;
42
68
 
43
69
  window.onload = function() {
@@ -73,20 +99,34 @@ export default function html_buy_ui(config, order_data) {
73
99
  const { error } = await stripe.confirmPayment({
74
100
  elements,
75
101
  confirmParams: {
76
- // Make sure to change this to your payment completion page
77
- return_url: "https://storecraft.app/",
102
+ // return to this same page, which can also interpret the
103
+ // payment intent status from the URL parameters
104
+ return_url: window.location.href,
78
105
  },
106
+ redirect: 'if_required'
79
107
  });
80
108
 
81
109
  // This point will only be reached if there is an immediate error when
82
110
  // confirming the payment. Otherwise, your customer will be redirected to
83
- // your \`return_url\`. For some payment methods like iDEAL, your customer will
111
+ // your \`return_url\` unless 'if_required' ise set. For some payment methods like iDEAL, your customer will
84
112
  // be redirected to an intermediate site first to authorize the payment, then
85
113
  // redirected to the \`return_url\`.
86
- if (error.type === "card_error" || error.type === "validation_error") {
87
- showMessage(error.message);
114
+ if(error) {
115
+ dispatchEvent("storecraft/checkout-error", {
116
+ order_id: "${order_data.id}"
117
+ });
118
+
119
+ if (error.type === "card_error" || error.type === "validation_error") {
120
+ showMessage(error.message);
121
+ } else if(error) {
122
+ showMessage("An unexpected error occurred.");
123
+ }
88
124
  } else {
89
- showMessage("An unexpected error occurred.");
125
+ // if 'if_required' is set and no redirect is needed,
126
+ // Payment succeeded, you can show a success message to your customer
127
+ dispatchEvent("storecraft/checkout-complete", {
128
+ order_id: "${order_data.id}"
129
+ });
90
130
  }
91
131
 
92
132
  setLoading(false);
@@ -107,6 +147,10 @@ export default function html_buy_ui(config, order_data) {
107
147
  switch (paymentIntent.status) {
108
148
  case "succeeded":
109
149
  showMessage("Payment succeeded!");
150
+ dispatchEvent("storecraft/checkout-complete", {
151
+ order_id: "${order_data.id}"
152
+ });
153
+
110
154
  break;
111
155
  case "processing":
112
156
  showMessage("Your payment is processing.");
@@ -116,6 +160,9 @@ export default function html_buy_ui(config, order_data) {
116
160
  break;
117
161
  default:
118
162
  showMessage("Something went wrong.");
163
+ dispatchEvent("storecraft/checkout-error", {
164
+ order_id: "${order_data.id}"
165
+ });
119
166
  break;
120
167
  }
121
168
  }
@@ -128,6 +175,8 @@ export default function html_buy_ui(config, order_data) {
128
175
  messageContainer.classList.remove("hidden");
129
176
  messageContainer.textContent = messageText;
130
177
 
178
+ console.log(messageText);
179
+
131
180
  setTimeout(function () {
132
181
  messageContainer.classList.add("hidden");
133
182
  messageContainer.textContent = "";
@@ -158,23 +207,21 @@ export default function html_buy_ui(config, order_data) {
158
207
 
159
208
  body {
160
209
  font-family: -apple-system, BlinkMacSystemFont, sans-serif;
161
- font-size: 16px;
210
+ font-size: 10px;
162
211
  -webkit-font-smoothing: antialiased;
212
+ height: content;
213
+ background-color: white;
163
214
  display: flex;
164
- justify-content: center;
165
- align-content: center;
166
- height: 100vh;
167
- width: 100vw;
215
+ flex-direction: column;
216
+ justify-content: start;
217
+ align-items: center;
168
218
  }
169
219
 
170
220
  form {
171
- width: 30vw;
172
- min-width: 500px;
221
+ width: 100%;
222
+ max-width: 500px;
223
+ mmmin-width: 500px;
173
224
  align-self: center;
174
- box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1),
175
- 0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07);
176
- border-radius: 7px;
177
- padding: 40px;
178
225
  }
179
226
 
180
227
  .hidden {
@@ -287,12 +334,6 @@ export default function html_buy_ui(config, order_data) {
287
334
  }
288
335
  }
289
336
 
290
- @media only screen and (max-width: 600px) {
291
- form {
292
- width: 80vw;
293
- min-width: initial;
294
- }
295
- }
296
337
 
297
338
  </style>
298
339
  </head>
package/adapter.js CHANGED
@@ -20,20 +20,22 @@ import { Stripe as StripeCls } from 'stripe'
20
20
  */
21
21
 
22
22
  /**
23
- * @description in a {@link StripeCls.PaymentIntent}, is a `metadata` key-value
24
- * storage where we store the `order_id` of the `storecraft` order.
23
+ * @description in a {@link StripeCls.PaymentIntent},
24
+ * is a `metadata` key-value storage where we store the
25
+ * `order_id` of the `storecraft` order.
25
26
  */
26
27
  export const metadata_storecraft_order_id = 'storecraft_order_id'
27
28
 
28
29
  /**
29
30
  * @implements {Impl}
30
31
  *
31
- * @description **Stripe** gateway (https://docs.stripe.com/payments/place-a-hold-on-a-payment-method)
32
+ * @description **Stripe** gateway
33
+ * (https://docs.stripe.com/payments/place-a-hold-on-a-payment-method)
32
34
  */
33
35
  export class Stripe {
34
36
 
35
37
  /** @satisfies {ENV<Config>} */
36
- static EnvConfig = /** @type{const} */ ({
38
+ static EnvConfig = /** @type {const} */ ({
37
39
  publishable_key: 'STRIPE_PUBLISHABLE_KEY',
38
40
  secret_key: 'STRIPE_SECRET_KEY',
39
41
  webhook_endpoint_secret: 'STRIPE_WEBHOOK_SECRET',
@@ -43,7 +45,6 @@ export class Stripe {
43
45
  /** @type {StripeCls} */ #stripe;
44
46
 
45
47
  /**
46
- *
47
48
  * @param {Config} config
48
49
  */
49
50
  constructor(config={}) {
@@ -56,6 +57,7 @@ export class Stripe {
56
57
  automatic_payment_methods: {
57
58
  enabled: true,
58
59
  },
60
+ capture_method: 'manual',
59
61
  payment_method_options: {
60
62
  card: {
61
63
  capture_method: 'manual',
@@ -69,15 +71,18 @@ export class Stripe {
69
71
  /** @type {Impl["onInit"]} */
70
72
  onInit = (app) => {
71
73
  this.config.publishable_key ??=
72
- app.platform.env[Stripe.EnvConfig.publishable_key];
74
+ app.env[Stripe.EnvConfig.publishable_key];
75
+
73
76
  this.config.secret_key ??=
74
- app.platform.env[Stripe.EnvConfig.secret_key];
77
+ app.env[Stripe.EnvConfig.secret_key];
78
+
75
79
  this.config.webhook_endpoint_secret ??=
76
- app.platform.env[Stripe.EnvConfig.webhook_endpoint_secret];
80
+ app.env[Stripe.EnvConfig.webhook_endpoint_secret];
77
81
  }
78
82
 
79
83
  get stripe() {
80
- const is_valid = this.config.publishable_key && this.config.secret_key;
84
+ const is_valid = this.config.publishable_key &&
85
+ this.config.secret_key;
81
86
 
82
87
  if(!is_valid) {
83
88
  throw new StorecraftError(
@@ -96,7 +101,7 @@ export class Stripe {
96
101
  get info() {
97
102
  return {
98
103
  name: 'Stripe',
99
- description: `Stripe powers online and in-person payment processing and financial solutions for businesses of all sizes.`,
104
+ description: `Pay with Credit Card and other payment methods`,
100
105
  url: 'https://docs.stripe.com/payments/place-a-hold-on-a-payment-method',
101
106
  logo_url: 'https://images.ctfassets.net/fzn2n1nzq965/HTTOloNPhisV9P4hlMPNA/cacf1bb88b9fc492dfad34378d844280/Stripe_icon_-_square.svg?q=80&w=256'
102
107
  }
@@ -127,7 +132,6 @@ export class Stripe {
127
132
  }
128
133
 
129
134
  /**
130
- *
131
135
  * @type {Impl["invokeAction"]}
132
136
  */
133
137
  invokeAction(action_handle) {
@@ -146,7 +150,6 @@ export class Stripe {
146
150
 
147
151
  /**
148
152
  * @description (Optional) buy link ui
149
- *
150
153
  * @type {Impl["onBuyLinkHtml"]}
151
154
  */
152
155
  async onBuyLinkHtml(order) {
@@ -158,7 +161,6 @@ export class Stripe {
158
161
 
159
162
  /**
160
163
  * @description on checkout create `hook`
161
- *
162
164
  * @type {Impl["onCheckoutCreate"]}
163
165
  */
164
166
  async onCheckoutCreate(order) {
@@ -182,7 +184,6 @@ export class Stripe {
182
184
  * They advocate async flows where confirmation happens async from
183
185
  * client side into their servers, and then you are notified via a
184
186
  * webhook.
185
- *
186
187
  * @type {Impl["onCheckoutComplete"]}
187
188
  */
188
189
  async onCheckoutComplete(create_result) {
@@ -226,7 +227,6 @@ export class Stripe {
226
227
 
227
228
  /**
228
229
  * @description Fetch the order and analyze it's status
229
- *
230
230
  * @type {Impl["status"]}
231
231
  */
232
232
  async status(create_result) {
@@ -255,7 +255,10 @@ export class Stripe {
255
255
  );
256
256
  }
257
257
  if(lc?.refunded) {
258
- const date = lc?.refunds?.data?.[0]?.created ? (new Date(lc?.refunds?.data?.[0]?.created).toUTCString()) : 'unknown time';
258
+ const date = lc?.refunds?.data?.[0]?.created ?
259
+ (new Date(lc?.refunds?.data?.[0]?.created).toUTCString()) :
260
+ 'unknown time';
261
+
259
262
  stat.messages.push(
260
263
  `**${fmt(lc.amount_refunded)}** was \`REFUNDED\` at \`${date}\``,
261
264
  );
@@ -266,7 +269,8 @@ export class Stripe {
266
269
  stat.messages.push(
267
270
  ...[
268
271
  `Intent was \`CANCELLED\` at \`${date}\`.`,
269
- o.cancellation_reason && `Cancellation reason is ${o.cancellation_reason}`
272
+ o.cancellation_reason &&
273
+ `Cancellation reason is ${o.cancellation_reason}`
270
274
  ].filter(Boolean)
271
275
  );
272
276
  }
@@ -276,24 +280,20 @@ export class Stripe {
276
280
 
277
281
  /**
278
282
  * @description [https://docs.stripe.com/webhooks](https://docs.stripe.com/webhooks)
279
- *
280
283
  * @type {Impl["webhook"]}
281
284
  */
282
285
  async webhook(request, response) {
283
286
  const sig = request.headers.get('Stripe-Signature');
284
287
 
285
- let event;
286
-
287
- event = await this.stripe.webhooks.constructEventAsync(
288
+ let event = await this.stripe.webhooks.constructEventAsync(
288
289
  request.rawBody, sig, this.config.webhook_endpoint_secret, undefined,
289
290
  StripeCls.createSubtleCryptoProvider()
290
291
  );
291
-
292
- let order_id;
293
- /** @type {StripeCls.PaymentIntent} */
294
- let payment_intent;
295
292
 
296
- /** @type {PaymentOptionsEnum[keyof PaymentOptionsEnum]} */
293
+ /** @type {string} */
294
+ let order_id = undefined;
295
+
296
+ /** @type {typeof PaymentOptionsEnum[keyof typeof PaymentOptionsEnum]} */
297
297
  let payment_status = PaymentOptionsEnum.unpaid;
298
298
 
299
299
  // Handle the event
@@ -302,34 +302,41 @@ export class Stripe {
302
302
  case 'payment_intent.payment_failed':
303
303
  case 'payment_intent.requires_action':
304
304
  case 'payment_intent.amount_capturable_updated':
305
- case 'payment_intent.canceled':
306
- payment_intent = event.data.object;
305
+ case 'payment_intent.canceled': {
306
+ const payment_intent = event.data.object;
307
307
  order_id = payment_intent.metadata[metadata_storecraft_order_id];
308
308
 
309
309
  if(payment_intent.status==='requires_capture')
310
310
  payment_status = PaymentOptionsEnum.authorized;
311
311
  else if(payment_intent.status==='canceled')
312
- payment_status = PaymentOptionsEnum.unpaid;
312
+ payment_status = PaymentOptionsEnum.cancelled;
313
313
  else if(payment_intent.status==='processing')
314
314
  payment_status = PaymentOptionsEnum.unpaid;
315
315
  else if(payment_intent.status==='requires_action')
316
316
  payment_status = PaymentOptionsEnum.unpaid;
317
317
  else if(payment_intent.status==='succeeded')
318
318
  payment_status = PaymentOptionsEnum.captured;
319
-
319
+
320
320
  break;
321
+ }
321
322
  case 'charge.refunded':
322
323
  case 'charge.refund.updated':
324
+ const payment_intent = event.data.object;
325
+ order_id = payment_intent.metadata[metadata_storecraft_order_id];
323
326
  payment_status = PaymentOptionsEnum.refunded;
324
327
  break;
325
328
  default: {
326
- console.log(`Unhandled event type ${event.type}`);
327
-
328
-
329
+ console.log(`Stripe:: We don't handle event of type ${event.type}`);
329
330
 
330
331
  return undefined;
331
332
  }
332
333
  }
334
+
335
+ if(!order_id) {
336
+ throw new Error(
337
+ `No 'storecraft' 'order_id' found in metadata for event type ${event.type}`
338
+ )
339
+ }
333
340
 
334
341
  // Return a response to acknowledge receipt of the event
335
342
  response.sendJson({received: true});
@@ -345,9 +352,8 @@ export class Stripe {
345
352
 
346
353
  /**
347
354
  * @description Retrieve latest order payload
348
- *
349
- * @param {CheckoutCreateResult} create_result first create result, holds `Stripe` intent
350
- *
355
+ * @param {CheckoutCreateResult} create_result
356
+ * first create result, holds `Stripe` intent
351
357
  */
352
358
  retrieve_order = (create_result) => {
353
359
  return this.stripe.paymentIntents.retrieve(
@@ -362,7 +368,6 @@ export class Stripe {
362
368
 
363
369
  /**
364
370
  * @description todo: logic for if user wanted capture at approval
365
- *
366
371
  * @param {CheckoutCreateResult} create_result
367
372
  */
368
373
  async cancel(create_result) {
@@ -378,7 +383,6 @@ export class Stripe {
378
383
 
379
384
  /**
380
385
  * @description todo: logic for if user wanted capture at approval
381
- *
382
386
  * @param {CheckoutCreateResult} create_result
383
387
  */
384
388
  async capture(create_result) {
@@ -394,7 +398,6 @@ export class Stripe {
394
398
 
395
399
  /**
396
400
  * @description todo: logic for if user wanted capture at approval
397
- *
398
401
  * @param {CheckoutCreateResult} create_result
399
402
  */
400
403
  async refund(create_result) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storecraft/payments-stripe",
3
- "version": "1.0.17",
3
+ "version": "1.3.0",
4
4
  "description": "Official Storecraft <-> Stripe integration",
5
5
  "license": "MIT",
6
6
  "author": "Tomer Shalev (https://github.com/store-craft)",
@@ -21,8 +21,7 @@
21
21
  "types": "types.public.d.ts",
22
22
  "scripts": {
23
23
  "payments-stripe:test": "uvu -c",
24
- "test": "npm run payments-stripe:test",
25
- "prepublishOnly": "npm version patch --force"
24
+ "test": "npm run payments-stripe:test"
26
25
  },
27
26
  "dependencies": {
28
27
  "stripe": "^16.6.0"