@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 +4 -7
- package/adapter.html.js +65 -24
- package/adapter.js +42 -39
- package/package.json +2 -3
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
|
-
|
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:
|
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
|
-
//
|
77
|
-
|
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
|
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
|
87
|
-
|
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
|
-
|
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:
|
210
|
+
font-size: 10px;
|
162
211
|
-webkit-font-smoothing: antialiased;
|
212
|
+
height: content;
|
213
|
+
background-color: white;
|
163
214
|
display: flex;
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
width: 100vw;
|
215
|
+
flex-direction: column;
|
216
|
+
justify-content: start;
|
217
|
+
align-items: center;
|
168
218
|
}
|
169
219
|
|
170
220
|
form {
|
171
|
-
width:
|
172
|
-
|
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},
|
24
|
-
* storage where we store the
|
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
|
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
|
-
|
74
|
+
app.env[Stripe.EnvConfig.publishable_key];
|
75
|
+
|
73
76
|
this.config.secret_key ??=
|
74
|
-
|
77
|
+
app.env[Stripe.EnvConfig.secret_key];
|
78
|
+
|
75
79
|
this.config.webhook_endpoint_secret ??=
|
76
|
-
|
80
|
+
app.env[Stripe.EnvConfig.webhook_endpoint_secret];
|
77
81
|
}
|
78
82
|
|
79
83
|
get stripe() {
|
80
|
-
const is_valid = this.config.publishable_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: `
|
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 ?
|
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 &&
|
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 {
|
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.
|
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(`
|
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
|
-
*
|
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
|
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"
|