2
0

♻️ (js) Implement Payment input in bot v2

Closes #193
This commit is contained in:
Baptiste Arnaud
2023-01-04 11:05:35 +01:00
parent b105bf8b8e
commit 61eff4c36d
3 changed files with 53 additions and 224 deletions

View File

@ -13,7 +13,6 @@
},
"license": "MIT",
"dependencies": {
"@power-elements/stripe-elements": "3.3.0",
"@stripe/stripe-js": "1.46.0",
"models": "workspace:*",
"phone": "3.1.32",

View File

@ -1,18 +1,9 @@
import { SendButton } from '@/components/SendButton'
import { createEffect, createSignal, Show } from 'solid-js'
import { Stripe, StripeElements } from '@stripe/stripe-js'
import { createSignal, onMount, Show } from 'solid-js'
import { loadStripe } from '@stripe/stripe-js/pure'
import type { Stripe, StripeElements } from '@stripe/stripe-js'
import { BotContext } from '@/types'
import { PaymentInputOptions, RuntimeOptions } from 'models'
import '@power-elements/stripe-elements'
declare module 'solid-js' {
namespace JSX {
interface IntrinsicElements {
'stripe-payment-request': unknown
}
}
}
// TODO: Implement support for payment input. (WIP)
type Props = {
context: BotContext
@ -20,38 +11,37 @@ type Props = {
onSuccess: () => void
}
let stripe: Stripe | undefined
let elements: StripeElements | undefined
let ignoreFirstPaymentIntentCall = true
const slotName = 'stripe-payment-form'
let paymentElementSlot: HTMLSlotElement
let stripe: Stripe | null = null
let elements: StripeElements | null = null
export const StripePaymentForm = (props: Props) => {
const [message, setMessage] = createSignal<string>()
const [isMounted, setIsMounted] = createSignal(false)
const [isLoading, setIsLoading] = createSignal(false)
createEffect(() => {
onMount(async () => {
initShadowMountPoint(paymentElementSlot)
stripe = await loadStripe(props.options.publicKey)
if (!stripe) return
if (ignoreFirstPaymentIntentCall)
return (ignoreFirstPaymentIntentCall = false)
stripe
.retrievePaymentIntent(props.options.paymentIntentSecret)
.then(({ paymentIntent }) => {
switch (paymentIntent?.status) {
case 'succeeded':
setMessage('Payment succeeded!')
break
case 'processing':
setMessage('Your payment is processing.')
break
case 'requires_payment_method':
setMessage('Your payment was not successful, please try again.')
break
default:
setMessage('Something went wrong.')
break
}
elements = stripe.elements({
appearance: {
theme: 'stripe',
variables: {
colorPrimary: getComputedStyle(paymentElementSlot).getPropertyValue(
'--typebot-button-bg-color'
),
},
},
clientSecret: props.options.paymentIntentSecret,
})
const paymentElement = elements.create('payment', {
layout: 'tabs',
})
paymentElement.mount('#payment-element')
setTimeout(() => setIsMounted(true), 1000)
})
const handleSubmit = async (event: Event & { submitter: HTMLElement }) => {
@ -89,31 +79,34 @@ export const StripePaymentForm = (props: Props) => {
onSubmit={handleSubmit}
class="flex flex-col rounded-lg p-4 typebot-input w-full items-center"
>
{/* <stripe-payment-request
publishable-key={props.options.publicKey}
client-secret={props.options.paymentIntentSecret}
generate="source"
amount="125"
label="Double Double"
country="CA"
currency={props.options.currency}
/> */}
<slot name={slotName} ref={paymentElementSlot} />
<Show when={isMounted()}>
<SendButton
isLoading={isLoading() || !elements}
class="mt-4 w-full max-w-lg"
isLoading={isLoading()}
class="mt-4 w-full max-w-lg animate-fade-in"
disableIcon
>
{props.options.labels.button} {props.options.amountLabel}
</SendButton>
</Show>
<Show when={message()}>
<div
id="payment-message"
class="typebot-input-error-message mt-4 text-center"
>
<div class="typebot-input-error-message mt-4 text-center animate-fade-in">
{message()}
</div>
</Show>
</form>
)
}
const initShadowMountPoint = (element: HTMLElement) => {
const rootNode = element.getRootNode() as ShadowRoot
const host = rootNode.host
const slotPlaceholder = document.createElement('div')
slotPlaceholder.style.width = '100%'
slotPlaceholder.slot = slotName
host.appendChild(slotPlaceholder)
const paymentElementContainer = document.createElement('div')
paymentElementContainer.id = 'payment-element'
slotPlaceholder.appendChild(paymentElementContainer)
}

167
pnpm-lock.yaml generated
View File

@ -571,7 +571,6 @@ importers:
packages/js:
specifiers:
'@power-elements/stripe-elements': 3.3.0
'@rollup/plugin-babel': 6.0.3
'@rollup/plugin-node-resolve': 15.0.1
'@rollup/plugin-replace': 5.0.2
@ -603,7 +602,6 @@ importers:
vite: 4.0.3
vite-plugin-solid: 2.5.0
dependencies:
'@power-elements/stripe-elements': 3.3.0
'@stripe/stripe-js': 1.46.0
models: link:../models
phone: 3.1.32
@ -4958,14 +4956,6 @@ packages:
'@jridgewell/resolve-uri': 3.1.0
'@jridgewell/sourcemap-codec': 1.4.14
/@lavadrop/camel-case/0.4.0:
resolution: {integrity: sha512-1h8sU1aQ0Ms1aAenfRxC45AXAvayqtxyTOt/QNB2dpLQnyaTkT3ejJFNdoucBREmCZRZSZtCFU08Wq3QoMyJaQ==}
dev: false
/@lavadrop/kebab-case/3.0.0:
resolution: {integrity: sha512-16m/BEvXl+e37jHUuIkeynwBbxNlsGvnl0CKwPD37A4V7XhPmOyld0XnY302TEx8+N4D/DonjScrXF91D5JzQw==}
dev: false
/@leichtgewicht/ip-codec/2.0.4:
resolution: {integrity: sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==}
dev: false
@ -5015,10 +5005,6 @@ packages:
'@lezer/common': 1.0.2
dev: false
/@lit/reactive-element/1.5.0:
resolution: {integrity: sha512-fQh9FDK0LPTwDk+0HhSZEtb8K0LTN1wXerwpGrWA+a8tWulYRDLI4vQDWp4GOIsewn0572KYV/oZ3+492D7osA==}
dev: false
/@mdx-js/mdx/1.6.22:
resolution: {integrity: sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA==}
dependencies:
@ -5267,10 +5253,6 @@ packages:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.14.0
/@pacote/memoize/1.1.8:
resolution: {integrity: sha512-LeMfu2X9A3PyUTya+UZp9BaLMht+K8hmCyLLPhZc+2hh1F+udHUymJUsbXFucdzN7ZrVrvr0mna4p/umYuEUuQ==}
dev: false
/@panva/hkdf/1.0.2:
resolution: {integrity: sha512-MSAs9t3Go7GUkMhpKC44T58DJ5KGk2vBo+h1cqQeqlMfdGkxaVB78ZWpv9gYi/g2fa4sopag9gJsNvS8XGgWJA==}
dev: false
@ -5303,19 +5285,6 @@ packages:
resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==}
dev: false
/@power-elements/stripe-elements/3.3.0:
resolution: {integrity: sha512-O5BUVOzo9BUykQptBnLKAR59cAFViJU4PpyAddQamPRyK3nreNzGi1b4zIlzsbbuArrn/u2KNPcFZAkYqX9N2A==}
requiresBuild: true
dependencies:
'@lavadrop/camel-case': 0.4.0
'@lavadrop/kebab-case': 3.0.0
'@pacote/memoize': 1.1.8
'@stripe/stripe-js': 1.46.0
lit: 2.5.0
patch-package: 6.5.0
tslib: 2.4.1
dev: false
/@prettier/plugin-php/0.19.2_prettier@2.8.1:
resolution: {integrity: sha512-j4Io//Js9ajKP7vgjKpS0rS8B/HKIJxutUshlbCpXvsP8Cz28MYp2VFJytpS9zyLEfsMPXordNWjkBVvHRL7ow==}
peerDependencies:
@ -6516,10 +6485,6 @@ packages:
resolution: {integrity: sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==}
dev: true
/@types/trusted-types/2.0.2:
resolution: {integrity: sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==}
dev: false
/@types/unist/2.0.6:
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
dev: false
@ -7208,10 +7173,6 @@ packages:
/@xtuc/long/4.2.2:
resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
/@yarnpkg/lockfile/1.1.0:
resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==}
dev: false
/@zag-js/element-size/0.3.0:
resolution: {integrity: sha512-5/hEI+0c6ZNCx6KHlOS5/WeHsd6+I7gk7Y/b/zATp4Rp3tHirs/tu1frq+iy5BmfaG9hbQtfHfUJTjOcI5jnoQ==}
dev: false
@ -8963,17 +8924,6 @@ packages:
- encoding
dev: false
/cross-spawn/6.0.5:
resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==}
engines: {node: '>=4.8'}
dependencies:
nice-try: 1.0.5
path-key: 2.0.1
semver: 5.7.1
shebang-command: 1.2.0
which: 1.3.1
dev: false
/cross-spawn/7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
@ -10805,12 +10755,6 @@ packages:
locate-path: 6.0.0
path-exists: 4.0.0
/find-yarn-workspace-root/2.0.0:
resolution: {integrity: sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==}
dependencies:
micromatch: 4.0.5
dev: false
/flat-cache/3.0.4:
resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==}
engines: {node: ^10.12.0 || >=12.0.0}
@ -10960,15 +10904,6 @@ packages:
universalify: 2.0.0
dev: false
/fs-extra/7.0.1:
resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==}
engines: {node: '>=6 <7 || >=8'}
dependencies:
graceful-fs: 4.2.10
jsonfile: 4.0.0
universalify: 0.1.2
dev: false
/fs-extra/9.1.0:
resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==}
engines: {node: '>=10'}
@ -13185,12 +13120,6 @@ packages:
engines: {node: '>=6'}
hasBin: true
/jsonfile/4.0.0:
resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
optionalDependencies:
graceful-fs: 4.2.10
dev: false
/jsonfile/6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
dependencies:
@ -13276,12 +13205,6 @@ packages:
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
engines: {node: '>=0.10.0'}
/klaw-sync/6.0.0:
resolution: {integrity: sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==}
dependencies:
graceful-fs: 4.2.10
dev: false
/kleur/3.0.3:
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
engines: {node: '>=6'}
@ -13357,27 +13280,6 @@ packages:
engines: {node: '>=4'}
dev: false
/lit-element/3.2.2:
resolution: {integrity: sha512-6ZgxBR9KNroqKb6+htkyBwD90XGRiqKDHVrW/Eh0EZ+l+iC+u+v+w3/BA5NGi4nizAVHGYvQBHUDuSmLjPp7NQ==}
dependencies:
'@lit/reactive-element': 1.5.0
lit-html: 2.5.0
dev: false
/lit-html/2.5.0:
resolution: {integrity: sha512-bLHosg1XL3JRUcKdSVI0sLCs0y1wWrj2sqqAN3cZ7bDDPNgmDHH29RV48x6Wz3ZmkxIupaE+z7uXSZ/pXWAO1g==}
dependencies:
'@types/trusted-types': 2.0.2
dev: false
/lit/2.5.0:
resolution: {integrity: sha512-DtnUP6vR3l4Q8nRPPNBD+UxbAhwJPeky+OVbi3pdgMqm0g57xFSl1Sj64D1rIB+nVNdiVVg8YxB0hqKjvdadZA==}
dependencies:
'@lit/reactive-element': 1.5.0
lit-element: 3.2.2
lit-html: 2.5.0
dev: false
/load-tsconfig/0.2.3:
resolution: {integrity: sha512-iyT2MXws+dc2Wi6o3grCFtGXpeMvHmJqS27sMPGtV2eUu4PeFnG+33I8BlFK1t1NWMjOpcx9bridn5yxLDX2gQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@ -14422,10 +14324,6 @@ packages:
next: 13.1.1_7xlrwlvvs7cv2obrs6a5y6oxxq
dev: false
/nice-try/1.0.5:
resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
dev: false
/no-case/2.3.2:
resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==}
dependencies:
@ -14702,14 +14600,6 @@ packages:
dependencies:
mimic-fn: 2.1.0
/open/7.4.2:
resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==}
engines: {node: '>=8'}
dependencies:
is-docker: 2.2.1
is-wsl: 2.2.0
dev: false
/open/8.4.0:
resolution: {integrity: sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==}
engines: {node: '>=12'}
@ -14778,6 +14668,7 @@ packages:
/os-tmpdir/1.0.2:
resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==}
engines: {node: '>=0.10.0'}
dev: true
/p-cancelable/1.1.0:
resolution: {integrity: sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==}
@ -14970,27 +14861,6 @@ packages:
tslib: 2.4.1
dev: false
/patch-package/6.5.0:
resolution: {integrity: sha512-tC3EqJmo74yKqfsMzELaFwxOAu6FH6t+FzFOsnWAuARm7/n2xB5AOeOueE221eM9gtMuIKMKpF9tBy/X2mNP0Q==}
engines: {node: '>=10', npm: '>5'}
hasBin: true
dependencies:
'@yarnpkg/lockfile': 1.1.0
chalk: 4.1.2
cross-spawn: 6.0.5
find-yarn-workspace-root: 2.0.0
fs-extra: 7.0.1
is-ci: 2.0.0
klaw-sync: 6.0.0
minimist: 1.2.7
open: 7.4.2
rimraf: 2.7.1
semver: 5.7.1
slash: 2.0.0
tmp: 0.0.33
yaml: 1.10.2
dev: false
/path-browserify/1.0.1:
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
dev: false
@ -15012,11 +14882,6 @@ packages:
resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==}
dev: false
/path-key/2.0.1:
resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==}
engines: {node: '>=4'}
dev: false
/path-key/3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
@ -16789,13 +16654,6 @@ packages:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
/rimraf/2.7.1:
resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
hasBin: true
dependencies:
glob: 7.2.3
dev: false
/rimraf/3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
hasBin: true
@ -17242,24 +17100,12 @@ packages:
/shallowequal/1.1.0:
resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==}
/shebang-command/1.2.0:
resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==}
engines: {node: '>=0.10.0'}
dependencies:
shebang-regex: 1.0.0
dev: false
/shebang-command/2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
dependencies:
shebang-regex: 3.0.0
/shebang-regex/1.0.0:
resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==}
engines: {node: '>=0.10.0'}
dev: false
/shebang-regex/3.0.0:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
@ -17322,11 +17168,6 @@ packages:
sax: 1.2.4
dev: false
/slash/2.0.0:
resolution: {integrity: sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==}
engines: {node: '>=6'}
dev: false
/slash/3.0.0:
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
engines: {node: '>=8'}
@ -18129,6 +17970,7 @@ packages:
engines: {node: '>=0.6.0'}
dependencies:
os-tmpdir: 1.0.2
dev: true
/tmpl/1.0.5:
resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
@ -18642,11 +18484,6 @@ packages:
unist-util-visit-parents: 3.1.1
dev: false
/universalify/0.1.2:
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
engines: {node: '>= 4.0.0'}
dev: false
/universalify/0.2.0:
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
engines: {node: '>= 4.0.0'}