feat: mandate flow

This commit is contained in:
Arman
2024-02-13 17:47:12 +01:00
parent b482a7a3da
commit 599afa0f90
4 changed files with 76 additions and 20 deletions
+1
View File
@@ -264,6 +264,7 @@ export enum Submit {
PaymentMethodUpdate = 'submit_payment_method_update',
PaymentMethodDelete = 'submit_payment_method_delete',
RetryPayment = 'submit_retry_payment',
VerifyPayment = 'submit_verify_payment',
BillingAddressCreate = 'submit_billing_address_create',
BillingAddressUpdate = 'submit_billing_address_update',
BillingAddressDelete = 'submit_billing_address_delete',
@@ -2,28 +2,25 @@
import { page } from '$app/stores';
import { Button } from '$lib/elements/forms';
import { HeaderAlert } from '$lib/layout';
import { actionRequiredInvoices } from '$lib/stores/billing';
import { paymentMissingMandate } from '$lib/stores/billing';
import { organization } from '$lib/stores/organization';
import { VARS } from '$lib/system';
import { sdk } from '$lib/stores/sdk';
import { confirmSetup } from '$lib/stores/stripe';
const endpoint = VARS.APPWRITE_ENDPOINT ?? `${$page.url.origin}/v1`;
async function verifyPaymentMethod() {
const method = await sdk.forConsole.billing.setupPaymentMandate(
$organization.$id,
$paymentMissingMandate.$id
);
await confirmSetup(method.clientSecret, method.$id);
}
</script>
{#if $actionRequiredInvoices && $actionRequiredInvoices?.invoices?.length && !$page.url.pathname.includes('/console/account')}
<HeaderAlert title="Authorization required" type="error">
Please authorize your upcoming payment for {$organization.name}. Your bank requires this
security measure to proceed with payment.
{#if $paymentMissingMandate && $paymentMissingMandate.country === 'in' && $paymentMissingMandate.mandateId === null && !$page.url.pathname.includes('/console/account')}
<HeaderAlert title="Authorization required" type="info">
The payment method for {$organization.name} needs to be verified.
<svelte:fragment slot="buttons">
<Button
text
href={`${endpoint}/organizations/${$organization.$id}/invoices/${$actionRequiredInvoices.invoices[0].$id}/view`}>
View invoice
</Button>
<Button
secondary
href={`/console/organization-${$organization.$id}/billing?type=confirmation&invoice=${$actionRequiredInvoices.invoices[0].$id}`}>
Authorize payment
</Button>
<Button secondary on:click={verifyPaymentMethod}>Verify payment method</Button>
</svelte:fragment>
</HeaderAlert>
{/if}
+19 -2
View File
@@ -2,7 +2,14 @@ import { page } from '$app/stores';
import { derived, get, writable } from 'svelte/store';
import { sdk } from './sdk';
import { organization, type Organization } from './organization';
import type { InvoiceList, AddressesList, Invoice, PaymentList, PlansMap } from '$lib/sdk/billing';
import type {
InvoiceList,
AddressesList,
Invoice,
PaymentList,
PlansMap,
PaymentMethodData
} from '$lib/sdk/billing';
import { isCloud } from '$lib/system';
import { cachedStore } from '$lib/helpers/cache';
import { Query } from '@appwrite.io/console';
@@ -14,6 +21,7 @@ import { base } from '$app/paths';
import { activeHeaderAlert } from '$routes/console/store';
import MarkedForDeletion from '$lib/components/billing/alerts/markedForDeletion.svelte';
import { BillingPlan } from '$lib/constants';
import PaymentMandate from '$lib/components/billing/alerts/paymentMandate.svelte';
export type Tier = 'tier-0' | 'tier-1' | 'tier-2';
@@ -266,10 +274,19 @@ export function checkForMarkedForDeletion(org: Organization) {
}
}
export const paymentMissingMandate = writable<PaymentMethodData>(null);
export async function checkForMandate(org: Organization) {
const paymentId = org.paymentMethodId ?? org.backupPaymentMethodId;
const paymentMethod = await sdk.forConsole.billing.getPaymentMethod(paymentId);
if (paymentMethod.mandateId === null && paymentMethod.country === 'in') {
const method = sdk.forConsole.billing.setupPaymentMandate(org.$id, paymentId);
headerAlert.add({
id: 'paymentMandate',
component: PaymentMandate,
show: true,
importance: 8
});
activeHeaderAlert.set(headerAlert.get());
paymentMissingMandate.set(paymentMethod);
}
}
+42 -1
View File
@@ -5,6 +5,7 @@ import { get, writable } from 'svelte/store';
import type { PaymentMethodData } from '$lib/sdk/billing';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { addNotification } from './notifications';
import { organization } from './organization';
export const stripe = writable<Stripe>();
let paymentMethod: PaymentMethodData;
@@ -63,7 +64,8 @@ export async function submitStripeCard(name: string, urlRoute?: string) {
clientSecret,
confirmParams: {
return_url: `${baseUrl}${
urlRoute ?? `organization/billing?clientSecret=${clientSecret}`
urlRoute ??
`organization-${get(organization).$id}/billing?clientSecret=${clientSecret}`
}`,
payment_method_data: {
billing_details: {
@@ -133,6 +135,45 @@ export async function confirmPayment(
}
}
export async function confirmSetup(
clientSecret: string,
paymentMethodId: string,
urlRoute?: string
) {
const baseUrl = 'https://cloud.appwrite.io/console/';
const { setupIntent, error } = await get(stripe).confirmCardSetup(clientSecret, {
payment_method: paymentMethodId,
return_url: `${baseUrl}${
urlRoute ?? `organization-${get(organization).$id}/billing?clientSecret=${clientSecret}`
}`
});
if (error) {
const e = new Error(error.message);
trackError(e, Submit.VerifyPayment);
addNotification({
message: error.message,
type: 'error'
});
}
if (setupIntent && setupIntent.status === 'succeeded') {
trackEvent(Submit.VerifyPayment);
addNotification({
message: 'Payment method verified successfully',
type: 'success'
});
} else {
const e = new Error('Something went wrong. Please try again later.');
trackError(e, Submit.VerifyPayment);
addNotification({
message: e.message,
type: 'error'
});
}
}
const apperanceLight = {
variables: {
colorPrimary: '#606a7b',