feat: stripe

This commit is contained in:
Arman
2023-02-22 17:31:00 +01:00
parent a9f0da8f2d
commit 8e7df46f2f
9 changed files with 178 additions and 14 deletions
+35 -10
View File
@@ -14,10 +14,12 @@
"@popperjs/core": "^2.11.6",
"@sentry/svelte": "^7.36.0",
"@sentry/tracing": "^7.36.0",
"@stripe/stripe-js": "^1.47.0",
"analytics": "^0.8.1",
"echarts": "^5.4.1",
"pretty-bytes": "^6.1.0",
"prismjs": "^1.29.0",
"stripe": "^11.11.0",
"tippy.js": "^6.3.7",
"web-vitals": "^3.1.1"
},
@@ -1927,6 +1929,11 @@
"@sinonjs/commons": "^2.0.0"
}
},
"node_modules/@stripe/stripe-js": {
"version": "1.47.0",
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-1.47.0.tgz",
"integrity": "sha512-jKSClqEIKS2MbPCXlSsseDSZyJ3dVrfUrYMz5LBY1o9iS2tfKbpTZACt8r2g+xyQozI+uHr76pVTyFsmBKA4Mg=="
},
"node_modules/@sveltejs/adapter-static": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-1.0.6.tgz",
@@ -2267,8 +2274,7 @@
"node_modules/@types/node": {
"version": "18.11.19",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.19.tgz",
"integrity": "sha512-YUgMWAQBWLObABqrvx8qKO1enAvBUdjZOAWQ5grBAkp5LQv45jBvYKZ3oFS9iKRCQyFjqw6iuEa1vmFqtxYLZw==",
"dev": true
"integrity": "sha512-YUgMWAQBWLObABqrvx8qKO1enAvBUdjZOAWQ5grBAkp5LQv45jBvYKZ3oFS9iKRCQyFjqw6iuEa1vmFqtxYLZw=="
},
"node_modules/@types/prettier": {
"version": "2.7.2",
@@ -3035,7 +3041,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
@@ -4311,8 +4316,7 @@
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"node_modules/functions-have-names": {
"version": "1.2.3",
@@ -4356,7 +4360,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
"integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
@@ -4496,7 +4499,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1"
},
@@ -4538,7 +4540,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
@@ -6597,7 +6598,6 @@
"version": "1.12.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -7182,6 +7182,20 @@
"node": ">=6"
}
},
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"dependencies": {
"side-channel": "^1.0.4"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
@@ -7575,7 +7589,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
@@ -7913,6 +7926,18 @@
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/stripe": {
"version": "11.11.0",
"resolved": "https://registry.npmjs.org/stripe/-/stripe-11.11.0.tgz",
"integrity": "sha512-CT8TZJ+bUaMmqCOljHdKwHTWym0bscrCHrKmvK2KoTaz/9gDIzG+lXTJOFUOb3K7Jwaoaori4yEVzOgA7U46+g==",
"dependencies": {
"@types/node": ">=8.1.0",
"qs": "^6.11.0"
},
"engines": {
"node": ">=12.*"
}
},
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+2
View File
@@ -25,10 +25,12 @@
"@popperjs/core": "^2.11.6",
"@sentry/svelte": "^7.36.0",
"@sentry/tracing": "^7.36.0",
"@stripe/stripe-js": "^1.47.0",
"analytics": "^0.8.1",
"echarts": "^5.4.1",
"pretty-bytes": "^6.1.0",
"prismjs": "^1.29.0",
"stripe": "^11.11.0",
"tippy.js": "^6.3.7",
"web-vitals": "^3.1.1"
},
+1
View File
@@ -9,6 +9,7 @@ export const VARS = {
| string
| undefined,
CONSOLE_MODE: import.meta.env?.VITE_CONSOLE_MODE?.toString() as string | undefined,
CONSOLE_TIER: import.meta.env?.VITE_CONSOLE_TIER?.toString() as string | undefined,
VERCEL_ENV: import.meta.env?.VITE_VERCEL_ENV?.toString() as string | undefined,
GOOGLE_ANALYTICS: import.meta.env?.VITE_GA_PROJECT?.toString() as string | undefined
};
@@ -1,16 +1,21 @@
<script>
<script lang="ts">
import { Container } from '$lib/layout';
import AvailableCredit from './availableCredit.svelte';
import BillingAddress from './billingAddress.svelte';
import PaymentHistory from './paymentHistory.svelte';
import PaymentMethods from './paymentMethods.svelte';
import PaymentModal from './paymentModal.svelte';
import PaymentSummary from './paymentSummary.svelte';
let showPayment = false;
</script>
<Container>
<PaymentSummary />
<PaymentHistory />
<PaymentMethods />
<PaymentMethods bind:showPayment />
<BillingAddress />
<AvailableCredit />
</Container>
<PaymentModal bind:show={showPayment} />
@@ -6,7 +6,7 @@
let coupon: string = null;
async function redeem() {
const response = await fetch('/v1/account/credit', {
const response = await fetch('/v1/billing/credit', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@@ -1,5 +1,6 @@
<script lang="ts">
import { CardGrid, Empty, Heading } from '$lib/components';
export let showPayment = false;
</script>
<CardGrid>
@@ -7,7 +8,7 @@
<p class="text">View or update your organization payment methods here.</p>
<svelte:fragment slot="aside">
<Empty>
<Empty on:click={() => (showPayment = true)}>
<p class="text">Add a payment method</p>
</Empty>
</svelte:fragment>
@@ -0,0 +1,90 @@
<script lang="ts">
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import { Modal } from '$lib/components';
import { InputText, Button, FormList } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { publicKey } from './stripe';
import { loadStripe } from '@stripe/stripe-js';
import { onMount } from 'svelte';
import FormItem from '$lib/elements/forms/formItem.svelte';
import { app } from '$lib/stores/app';
export let show = false;
let name: string;
let error: string;
let isCreating = false;
let stripe;
let elements;
let card: HTMLDivElement;
let address: HTMLDivElement;
let complete = false;
const appearance = {
theme: $app.themeInUse === 'dark' ? 'night' : 'stripe',
variables: {
colorPrimary: $app.themeInUse === 'dark' ? '#ffffff' : '#000000'
}
};
let paymentIntent;
let clientSecret;
onMount(async () => {
stripe = await loadStripe(publicKey);
elements = stripe.elements(appearance);
// paymentIntent = await createIntent();
// clientSecret = paymentIntent.client_secret;
createForm();
});
async function createForm() {
const cardElement = elements.create('card');
cardElement.mount(card);
const addressElement = elements.create('address');
addressElement.mount(address);
cardElement.on('change', (e) => (complete = e.complete));
}
async function handleSubmit() {
try {
isCreating = true;
trackEvent(Submit.ProjectCreate);
addNotification({
type: 'success',
message: `${name} has been created`
});
} catch (e) {
isCreating = false;
error = e.message;
trackError(e, Submit.ProjectCreate);
}
}
</script>
<Modal {error} on:submit={handleSubmit} size="big" bind:show>
<svelte:fragment slot="header">Add Payment Method</svelte:fragment>
This payment method will be added to the Acme Org billing details.
<FormList>
<InputText
id="name"
label="Cardholder name"
bind:value={name}
required
autofocus={true}
disabled={isCreating} />
<FormItem>
<div class="input-text-wrapper">
<div class="input-text" bind:this={card} />
<div bind:this={address} />
</div>
</FormItem>
</FormList>
<svelte:fragment slot="footer">
<Button secondary on:click={() => (show = false)}>Cancel</Button>
<Button submit disabled={isCreating}>Create</Button>
</svelte:fragment>
</Modal>
@@ -1,5 +1,6 @@
<script lang="ts">
import { Box, CardGrid, Heading } from '$lib/components';
import { VARS } from '$lib/system';
</script>
<CardGrid>
@@ -13,5 +14,11 @@
</svelte:fragment>
<p class="u-trim-1">X projects</p>
</Box>
{#if VARS.CONSOLE_TIER && VARS.CONSOLE_TIER !== 'base'}
<div class="u-flex">
<p class="u-bold">Next billing data</p>
<p class="text u-margin-inline-start-auto">XX DATE</p>
</div>
{/if}
</svelte:fragment>
</CardGrid>
@@ -0,0 +1,33 @@
import { writable } from 'svelte/store';
import Stripe from 'stripe';
export const publicKey = import.meta.env?.VITE_STRIPE_PUBLIC_KEY?.toString() as string | undefined;
export const secretKey = import.meta.env?.VITE_STRIPE_SECRET_KEY?.toString() as string | undefined;
export const stripe = new Stripe(secretKey, undefined);
function createCustomerStore() {
const { subscribe, set } = writable<Stripe.Customer | Stripe.DeletedCustomer>(undefined);
return {
subscribe,
set,
load: async (id: string) => {
const customer: Stripe.Customer | Stripe.DeletedCustomer =
await stripe.customers.retrieve(id);
set(customer);
},
create: async (params: Stripe.CustomerCreateParams) => {
const customer: Stripe.Customer = await stripe.customers.create(params);
set(customer);
},
update: async (id: string, params: Stripe.CustomerUpdateParams) => {
const customer: Stripe.Customer = await stripe.customers.update(id, params);
set(customer);
},
remove: async (id: string) => {
const customer: Stripe.DeletedCustomer = await stripe.customers.del(id);
set(customer);
}
};
}
export const customer = createCustomerStore();