web/satellite: use stripe as ES module
Start using @stripe/stripe-js lib (ES module) instead of regular stripe dependency. Use strict typing for stripe commands/events. This lib makes us able to modify stripe input styling in the future. Change-Id: Iaba4f32a42e87edc85a4fbad82e5107c21bf19b6
This commit is contained in:
parent
f1e8cdfe3e
commit
f0829d5961
File diff suppressed because it is too large
Load Diff
@ -23,13 +23,13 @@
|
|||||||
"@hcaptcha/vue3-hcaptcha": "1.2.1",
|
"@hcaptcha/vue3-hcaptcha": "1.2.1",
|
||||||
"@mdi/font": "7.0.96",
|
"@mdi/font": "7.0.96",
|
||||||
"@smithy/signature-v4": "2.0.1",
|
"@smithy/signature-v4": "2.0.1",
|
||||||
|
"@stripe/stripe-js": "2.1.0",
|
||||||
"bip39-english": "2.5.0",
|
"bip39-english": "2.5.0",
|
||||||
"chart.js": "4.2.1",
|
"chart.js": "4.2.1",
|
||||||
"pinia": "2.0.23",
|
"pinia": "2.0.23",
|
||||||
"pretty-bytes": "5.6.0",
|
"pretty-bytes": "5.6.0",
|
||||||
"qrcode": "1.5.3",
|
"qrcode": "1.5.3",
|
||||||
"stream-browserify": "3.0.0",
|
"stream-browserify": "3.0.0",
|
||||||
"stripe": "8.215.0",
|
|
||||||
"util": "0.12.5",
|
"util": "0.12.5",
|
||||||
"vue": "3.3.2",
|
"vue": "3.3.2",
|
||||||
"vue-datepicker-next": "1.0.3",
|
"vue-datepicker-next": "1.0.3",
|
||||||
|
@ -14,40 +14,36 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onBeforeUnmount, onMounted, ref } from 'vue';
|
import { onBeforeUnmount, onMounted, ref } from 'vue';
|
||||||
|
import { loadStripe } from '@stripe/stripe-js/pure';
|
||||||
|
import {
|
||||||
|
Stripe,
|
||||||
|
StripeCardElement,
|
||||||
|
StripeCardElementChangeEvent,
|
||||||
|
TokenResult,
|
||||||
|
} from '@stripe/stripe-js';
|
||||||
|
|
||||||
import { LoadScript } from '@/utils/loadScript';
|
|
||||||
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||||
import { useNotify } from '@/utils/hooks';
|
import { useNotify } from '@/utils/hooks';
|
||||||
import { useConfigStore } from '@/store/modules/configStore';
|
import { useConfigStore } from '@/store/modules/configStore';
|
||||||
|
|
||||||
interface StripeResponse {
|
|
||||||
error: string
|
|
||||||
token: {
|
|
||||||
id: unknown
|
|
||||||
card: {
|
|
||||||
funding : string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
onStripeResponseCallback: (tokenId: unknown) => void,
|
onStripeResponseCallback: (tokenId: unknown) => Promise<void>,
|
||||||
}>(), {
|
}>(), {
|
||||||
onStripeResponseCallback: () => console.error('onStripeResponse is not reinitialized'),
|
onStripeResponseCallback: () => Promise.reject('onStripeResponse is not reinitialized'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const isLoading = ref<boolean>(false);
|
const isLoading = ref<boolean>(false);
|
||||||
/**
|
/**
|
||||||
* Stripe elements is using to create 'Add Card' form.
|
* Stripe elements is used to create 'Add Card' form.
|
||||||
*/
|
*/
|
||||||
const cardElement = ref<any>(); // eslint-disable-line @typescript-eslint/no-explicit-any
|
const cardElement = ref<StripeCardElement>();
|
||||||
/**
|
/**
|
||||||
* Stripe library.
|
* Stripe library.
|
||||||
*/
|
*/
|
||||||
const stripe = ref<any>(); // eslint-disable-line @typescript-eslint/no-explicit-any
|
const stripe = ref<Stripe | null>(null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stripe initialization.
|
* Stripe initialization.
|
||||||
@ -55,26 +51,32 @@ const stripe = ref<any>(); // eslint-disable-line @typescript-eslint/no-explicit
|
|||||||
async function initStripe(): Promise<void> {
|
async function initStripe(): Promise<void> {
|
||||||
const stripePublicKey = configStore.state.config.stripePublicKey;
|
const stripePublicKey = configStore.state.config.stripePublicKey;
|
||||||
|
|
||||||
stripe.value = window['Stripe'](stripePublicKey);
|
try {
|
||||||
if (!stripe.value) {
|
stripe.value = await loadStripe(stripePublicKey);
|
||||||
await notify.error('Unable to initialize stripe', AnalyticsErrorEventSource.BILLING_STRIPE_CARD_INPUT);
|
} catch (error) {
|
||||||
|
notify.error(error.message, AnalyticsErrorEventSource.BILLING_STRIPE_CARD_INPUT);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const elements = stripe.value.elements();
|
if (!stripe.value) {
|
||||||
|
notify.error('Unable to initialize stripe', AnalyticsErrorEventSource.BILLING_STRIPE_CARD_INPUT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const elements = stripe.value?.elements();
|
||||||
if (!elements) {
|
if (!elements) {
|
||||||
await notify.error('Unable to instantiate elements', AnalyticsErrorEventSource.BILLING_STRIPE_CARD_INPUT);
|
notify.error('Unable to instantiate elements', AnalyticsErrorEventSource.BILLING_STRIPE_CARD_INPUT);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cardElement.value = elements.create('card');
|
cardElement.value = elements.create('card');
|
||||||
if (!cardElement.value) {
|
if (!cardElement.value) {
|
||||||
await notify.error('Unable to create card', AnalyticsErrorEventSource.BILLING_STRIPE_CARD_INPUT);
|
notify.error('Unable to create card element', AnalyticsErrorEventSource.BILLING_STRIPE_CARD_INPUT);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cardElement.value.mount('#card-element');
|
cardElement.value?.mount('#card-element');
|
||||||
cardElement.value.addEventListener('change', function (event): void {
|
cardElement.value?.on('change', (event: StripeCardElementChangeEvent) => {
|
||||||
const displayError: HTMLElement = document.getElementById('card-errors') as HTMLElement;
|
const displayError: HTMLElement = document.getElementById('card-errors') as HTMLElement;
|
||||||
if (event.error) {
|
if (event.error) {
|
||||||
displayError.textContent = event.error.message;
|
displayError.textContent = event.error.message;
|
||||||
@ -90,24 +92,29 @@ async function initStripe(): Promise<void> {
|
|||||||
*
|
*
|
||||||
* @param result stripe response
|
* @param result stripe response
|
||||||
*/
|
*/
|
||||||
async function onStripeResponse(result: StripeResponse): Promise<void> {
|
async function onStripeResponse(result: TokenResult): Promise<void> {
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
throw result.error;
|
throw result.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.token.card.funding === 'prepaid') {
|
if (result.token.card?.funding === 'prepaid') {
|
||||||
notify.error('Prepaid cards are not supported', AnalyticsErrorEventSource.BILLING_STRIPE_CARD_INPUT);
|
notify.error('Prepaid cards are not supported', AnalyticsErrorEventSource.BILLING_STRIPE_CARD_INPUT);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await props.onStripeResponseCallback(result.token.id);
|
await props.onStripeResponseCallback(result.token.id);
|
||||||
cardElement.value.clear();
|
cardElement.value?.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires stripe event after all inputs are filled.
|
* Fires stripe event after all inputs are filled.
|
||||||
*/
|
*/
|
||||||
async function onSubmit(): Promise<void> {
|
async function onSubmit(): Promise<void> {
|
||||||
|
if (!(stripe.value && cardElement.value)) {
|
||||||
|
notify.error('Stripe is not initialized', AnalyticsErrorEventSource.BILLING_STRIPE_CARD_INPUT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isLoading.value) return;
|
if (isLoading.value) return;
|
||||||
|
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
@ -124,18 +131,7 @@ async function onSubmit(): Promise<void> {
|
|||||||
/**
|
/**
|
||||||
* Stripe library loading and initialization.
|
* Stripe library loading and initialization.
|
||||||
*/
|
*/
|
||||||
onMounted(async (): Promise<void> => {
|
onMounted(() => {
|
||||||
if (!window['Stripe']) {
|
|
||||||
const script = new LoadScript('https://js.stripe.com/v3/',
|
|
||||||
() => { initStripe(); },
|
|
||||||
() => { notify.error('Stripe library not loaded', AnalyticsErrorEventSource.BILLING_STRIPE_CARD_INPUT);
|
|
||||||
script.remove();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
initStripe();
|
initStripe();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -143,7 +139,7 @@ onMounted(async (): Promise<void> => {
|
|||||||
* Clears listeners.
|
* Clears listeners.
|
||||||
*/
|
*/
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
cardElement.value?.removeEventListener('change');
|
cardElement.value?.off('change');
|
||||||
});
|
});
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
@ -178,4 +174,10 @@ defineExpose({
|
|||||||
.form-row {
|
.form-row {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#card-errors {
|
||||||
|
text-align: left;
|
||||||
|
font-family: 'font-medium', sans-serif;
|
||||||
|
color: var(--c-red-2);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
// Copyright (C) 2020 Storj Labs, Inc.
|
|
||||||
// See LICENSE for copying information.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LoadScript is an utility for loading scripts.
|
|
||||||
*/
|
|
||||||
export class LoadScript {
|
|
||||||
public readonly head : HTMLHeadElement = document.head;
|
|
||||||
public readonly script: HTMLScriptElement = document.createElement('script');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create script element with some predefined attributes, appends it to a DOM and start loading script.
|
|
||||||
* @param url - script url.
|
|
||||||
* @param onSuccess - this callback will be fired when load finished.
|
|
||||||
* @param onError - this callback will be fired when error occurred.
|
|
||||||
*/
|
|
||||||
public constructor(url: string, onSuccess: LoadScriptOnSuccessCallback, onError: LoadScriptOnErrorCallback) {
|
|
||||||
this.head = document.head;
|
|
||||||
this.script = document.createElement('script');
|
|
||||||
|
|
||||||
this.script.type = 'text/javascript';
|
|
||||||
this.script.charset = 'utf8';
|
|
||||||
this.script.async = true;
|
|
||||||
this.script.src = url;
|
|
||||||
|
|
||||||
this.script.onload = () => {
|
|
||||||
this.script.onerror = null;
|
|
||||||
onSuccess();
|
|
||||||
};
|
|
||||||
this.script.onerror = () => {
|
|
||||||
this.script.onerror = null;
|
|
||||||
onError(new Error('Failed to load ' + this.script.src));
|
|
||||||
};
|
|
||||||
|
|
||||||
this.head.appendChild(this.script);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes script element from DOM.
|
|
||||||
*/
|
|
||||||
public remove(): void {
|
|
||||||
this.head.removeChild(this.script);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LoadScriptOnSuccessCallback describes signature of onSuccess callback.
|
|
||||||
*/
|
|
||||||
export type LoadScriptOnSuccessCallback = () => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LoadScriptOnErrorCallback describes signature of onError callback.
|
|
||||||
* @param err - error occurred during script loading.
|
|
||||||
*/
|
|
||||||
export type LoadScriptOnErrorCallback = (err: Error) => void;
|
|
Loading…
Reference in New Issue
Block a user