Skip to content

Commit

Permalink
chore: wip
Browse files Browse the repository at this point in the history
  • Loading branch information
glennmichael123 committed Nov 13, 2024
1 parent 488e383 commit 462d946
Show file tree
Hide file tree
Showing 31 changed files with 144 additions and 133 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ MAIL_ENCRYPTION=null
MAIL_FROM_NAME="${APP_NAME}"
MAIL_FROM_ADDRESS="[email protected]"

STRIPE_SECRET_KEY=
STRIPE_PUBLIC_KEY=

SEARCH_ENGINE_DRIVER=meilisearch
MEILISEARCH_HOST=http://127.0.0.1:7700
MEILISEARCH_KEY=masterKey
Expand Down
6 changes: 2 additions & 4 deletions app/Actions/Payment/CreateSetupIntentAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ export default new Action({
description: 'Create Setup Intent for stripe',
method: 'POST',
async handle(request: RequestInstance) {
const amount = Number(request.get('amount'))

const user = await User.find(1)

const paymentIntent = await user?.createSetupIntent()
const setupIntent = await user?.createSetupIntent()

return paymentIntent
return setupIntent
},
})
10 changes: 5 additions & 5 deletions app/Actions/Payment/FetchPaymentMethodsAction.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { RequestInstance } from '@stacksjs/types'
import { Action } from '@stacksjs/actions'
import User from '../../../storage/framework/orm/src/models/User.ts'

export default new Action({
name: 'CreateSubscriptionAction',
description: 'Create Subscription for stripe',
method: 'POST',
async handle(request: RequestInstance) {
name: 'FetchPaymentMethodsAction',
description: 'Fetch the user payment methods',
method: 'GET',
async handle() {
const user = await User.find(1)

const paymentMethods = await user?.paymentMethods()

return paymentMethods
Expand Down
Binary file modified bun.lockb
Binary file not shown.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,8 @@
"storage/framework/libs/components/*",
"storage/framework/views/*",
"storage/framework/server"
]
],
"devDependencies": {
"@stacksjs/eslint-plugin": "^0.1.3"
}
}
91 changes: 47 additions & 44 deletions resources/functions/billing/payments.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,65 @@
export const publishableKey = import.meta.env.FRONTEND_STRIPE_PUBLIC_KEY
import { loadStripe } from '@stripe/stripe-js'

export const publishableKey = import.meta.env.FRONTEND_STRIPE_PUBLIC_KEY

const stripe = ref(null as any)
const elements = ref(null as any)

export function useBillable() {
async function fetchSetupIntent(): Promise<string> {
const url = 'http://localhost:3008/stripe/create-setup-intent'

const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
})
async function fetchSetupIntent(): Promise<string> {
const url = 'http://localhost:3008/stripe/create-setup-intent'

const client: any = await response.json()
const clientSecret = client.client_secret
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
})

return clientSecret
}
const client: any = await response.json()
const clientSecret = client.client_secret

async function loadStripeElement(clientSecret: string): Promise<boolean> {
stripe.value = await loadStripe(publishableKey)
return clientSecret
}

if (stripe) {
elements.value = stripe.value.elements({ clientSecret })
async function loadStripeElement(clientSecret: string): Promise<boolean> {
stripe.value = await loadStripe(publishableKey)

const paymentElement = elements.value.create('payment', {
fields: { billingDetails: 'auto' },
})
if (stripe) {
elements.value = stripe.value.elements({ clientSecret })

paymentElement.mount('#payment-element')
const paymentElement = elements.value.create('payment', {
fields: { billingDetails: 'auto' },
})

return true
}
paymentElement.mount('#payment-element')

return false
return true
}

async function handleAddPaymentMethod() {
if (!stripe.value || !elements.value) return

const { setupIntent, error } = await stripe.value.confirmSetup({
elements: elements.value,
confirmParams: {
return_url: 'http://localhost:5173/settings/billing'
},
})

if (error) {
console.error(error.message) // Display or handle error for the user
} else {
console.log('Setup Intent successful:', setupIntent)
// You might save setupIntent.id to your database here
}
return false
}

async function handleAddPaymentMethod() {
if (!stripe.value || !elements.value)
return

const { setupIntent, error } = await stripe.value.confirmSetup({
elements: elements.value,
confirmParams: {
return_url: 'http://localhost:5173/settings/billing',
},
})

if (error) {
console.error(error.message) // Display or handle error for the user
}
else {
console.log('Setup Intent successful:', setupIntent)
// You might save setupIntent.id to your database here
}
}

return { fetchSetupIntent, loadStripeElement, handleAddPaymentMethod }
}
return { fetchSetupIntent, loadStripeElement, handleAddPaymentMethod }
}
72 changes: 46 additions & 26 deletions resources/views/dashboard/components/billing/payment-method.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const showStripe = ref(false)
const { fetchSetupIntent, loadStripeElement, handleAddPaymentMethod } = useBillable()
async function loadWebElement() {
const clientSecret = await fetchSetupIntent()
const clientSecret = await fetchSetupIntent()
showStripe.value = await loadStripeElement(clientSecret)
Expand All @@ -21,52 +21,72 @@ function cancelPaymentForm() {
</script>

<template>

<div class="mt-16 w-2/3 bg-white px-8 py-6 shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg">
<h2 class="text-lg text-gray-900 font-medium">
Payment Info
</h2>

<div class="pt-4 space-x-2" v-if="stripeLoading || !showStripe">
<div class="h-12 w-12">
<img src="/images/logos/mastercard.svg" alt="Mastercard Logo">
</div>
<ul v-if="stripeLoading || !showStripe" role="list" class="grid grid-cols-1 mt-8 gap-6 lg:grid-cols-1 sm:grid-cols-1">
<li class="col-span-1 border rounded-lg bg-white shadow divide-y divide-gray-200">
<div class="w-full p-4">
<div class="flex space-x-4">
<div class="h-24 w-24">
<img src="/images/logos/mastercard.svg" alt="Mastercard Logo" class="border">
</div>
<h2 class="text-xl text-gray-600">
Mastercard •••• 4242 <br>
<span class="text-xs text-gray-500 italic">Expires 10/30</span>
</h2>
</div>

<h2 class="text-xl text-gray-600">
•••• •••• •••• ••••
</h2>

<h2 class="text-xl text-gray-600">
No payment method added yet.
</h2>
</div>
<div class="flex justify-end space-x-4">
<button
type="button"
class="border rounded-md bg-white px-2 py-1 text-sm text-white font-semibold shadow-sm hover:bg-blue-gray-50 focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2 focus-visible:outline"
@click="handleAddPaymentMethod()"
>
<svg class="h-4 w-4 text-gray-700" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
</svg>
</button>
<button
type="button"
class="border rounded-md bg-white px-2 py-1 text-sm text-white font-semibold shadow-sm hover:bg-blue-gray-50 focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2 focus-visible:outline"
@click="handleAddPaymentMethod()"
>
<svg class="h-4 w-4 text-gray-700" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
</button>
</div>
</div>
</li>
</ul>

<div v-show="!stripeLoading || showStripe">
<form id="payment-form">
<div id="payment-element">
</div>
<div id="payment-element" />
<div class="flex pt-4 space-x-2">
<button
type="button"
class="rounded-md bg-blue-600 px-2.5 py-1.5 text-sm text-white font-semibold shadow-sm hover:bg-blue-gray-500 focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2 focus-visible:outline"
@click="handleAddPaymentMethod()"
type="button"
class="rounded-md bg-blue-600 px-2.5 py-1.5 text-sm text-white font-semibold shadow-sm hover:bg-blue-gray-500 focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2 focus-visible:outline"
@click="handleAddPaymentMethod()"
>
Save Payment Method
</button>

<button
type="button"
class="rounded-md bg-white px-2.5 py-1.5 text-sm text-gray-700 font-semibold shadow-sm hover:bg-gray-50 focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2 focus-visible:outline"
@click="cancelPaymentForm()"
type="button"
class="rounded-md bg-white px-2.5 py-1.5 text-sm text-gray-700 font-semibold shadow-sm hover:bg-gray-50 focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2 focus-visible:outline"
@click="cancelPaymentForm()"
>
Cancel
Cancel
</button>

</div>
</form>
</div>

<div class="mt-8 flex" v-if="stripeLoading || !showStripe">
<div v-if="stripeLoading || !showStripe" class="mt-8 flex">
<button
type="button"
class="rounded-md bg-blue-600 px-2.5 py-1.5 text-sm text-white font-semibold shadow-sm hover:bg-blue-gray-500 focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2 focus-visible:outline"
Expand Down
35 changes: 0 additions & 35 deletions resources/views/dashboard/settings/billing.vue
Original file line number Diff line number Diff line change
@@ -1,41 +1,6 @@
<script setup lang="ts">
import PaymentMethod from '../components/billing/payment-method.vue'
// import { loadStripe, publishableKey } from '../../../functions/billing/payments'
import Plans from '../components/billing/plans.vue'
// let elements
// let stripe
// const loading = ref(true)
// // TODO: learn about subscriptions
// async function initialize() {
// stripe = await loadStripe(publishableKey)
// const { clientSecret } = await fetch('http://localhost:3008/stripe/create-setup-intent').then(res => res.json()) as any
// if (stripe) {
// elements = stripe.elements({ clientSecret })
// const paymentElement = elements.create('payment')
// paymentElement.mount('#payment-element')
// const linkAuthenticationElement = elements.create('linkAuthentication')
// linkAuthenticationElement.mount('#link-authentication-element')
// }
// loading.value = false
// }
// async function payPlan() {
// await initialize()
// }
// function calculateOrderAmount() {
// // Replace this constant with a calculation of the order's amount
// // Calculate the order total on the server to prevent
// // people from directly manipulating the amount on the client
// return 2000
// }
</script>

<template>
Expand Down
2 changes: 1 addition & 1 deletion routes/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ route.get('/install', 'Actions/InstallAction')
route.post('/ai/ask', 'Actions/AI/AskAction')
route.post('/ai/summary', 'Actions/AI/SummaryAction')

route.get('/stripe/user-payment-methods', 'Actions/Payment/CreateSetupIntentAction')
route.get('/stripe/user-payment-methods', 'Actions/Payment/FetchPaymentMethodsAction')
route.get('/stripe/create-setup-intent', 'Actions/Payment/CreateSetupIntentAction')
route.post('/stripe/create-payment-intent', 'Actions/Payment/CreatePaymentIntentAction')
route.post('/stripe/create-subscription', 'Actions/Payment/CreateSubscriptionAction')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { TransitionRoot, TransitionChild } from '@headlessui/vue'
import { TransitionChild, TransitionRoot } from '@headlessui/vue'

export {
TransitionChild,
TransitionRoot
TransitionRoot,
}
2 changes: 1 addition & 1 deletion storage/framework/core/components/transition/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Plugin } from 'vue'
import { TransitionRoot, TransitionChild } from './components'
import { TransitionChild, TransitionRoot } from './components'

const plugin: Plugin = {
install(app) {
Expand Down
7 changes: 5 additions & 2 deletions storage/framework/core/orm/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1168,7 +1168,10 @@ export async function generateModelString(
async isIncomplete(type: string): Promise<boolean> {
return await manageSubscription.isIncomplete(this, type)
}
async paymentMethods(cardType?: string): Promise<Stripe.Response<Stripe.ApiList<Stripe.PaymentMethod>>> {
return await managePaymentMethod.listPaymentMethods(this, cardType)
}
async newSubscriptionInvoice(
type: string,
lookupKey: string,
Expand Down Expand Up @@ -1363,7 +1366,7 @@ export async function generateModelString(
return `import type { Generated, Insertable, Selectable, Updateable } from 'kysely'
import { manageCharge, manageCheckout, manageCustomer, managePaymentMethod, manageSubscription, managePrice, manageSetupIntent, type Stripe } from '@stacksjs/payments'
import { db, sql } from '@stacksjs/database'
import type { CheckoutLineItem, CheckoutOptions, StripeCustomerOptions, SubscriptionOptions } from '@stacksjs/types'
import type { CheckoutLineItem, CheckoutOptions, StripeCustomerOptions } from '@stacksjs/types'
import { HttpError } from '@stacksjs/error-handling'
import { dispatch } from '@stacksjs/events'
import { generateTwoFactorSecret } from '@stacksjs/auth'
Expand Down
16 changes: 8 additions & 8 deletions storage/framework/core/payments/src/billable/payment-method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,19 @@ export const managePaymentMethod: ManagePaymentMethod = (() => {

async function listPaymentMethods(user: UserModel, cardType?: string): Promise<Stripe.Response<Stripe.ApiList<Stripe.PaymentMethod>>> {
if (!user.hasStripeId()) {
throw new Error('Customer does not exist in Stripe');
throw new Error('Customer does not exist in Stripe')
}

const paymentMethods = await stripe.paymentMethod.list({
customer: user.stripe_id,
type: 'card',
});
type: 'card',
})

if (cardType) {
paymentMethods.data = paymentMethods.data.filter((method) => method.card?.brand === cardType);
paymentMethods.data = paymentMethods.data.filter(method => method.card?.brand === cardType)
}
return paymentMethods;

return paymentMethods
}

async function retrievePaymentMethod(user: UserModel, paymentMethodId: string): Promise<Stripe.Response<Stripe.PaymentMethod>> {
Expand Down
Loading

0 comments on commit 462d946

Please sign in to comment.