refactor: payment settings

This commit is contained in:
Jannat Patel
2025-09-18 18:29:08 +05:30
parent 9b893b56f4
commit bcfd3bb636
10 changed files with 3126 additions and 4244 deletions

View File

@@ -0,0 +1,153 @@
<template>
<Dialog
v-model="show"
:options="{
title:
gatewayID === 'new'
? __('New Payment Gateway')
: __('Edit Payment Gateway'),
size: '3xl',
}"
>
<template #body-content>
<SettingFields
v-if="gatewayID != 'new'"
:fields="paymentGateway.data.fields"
:data="paymentGateway.data.data"
class="pt-5 my-0"
/>
<div v-else>
<FormControl
v-model="newGateway"
:label="__('Select Payment Gateway')"
type="select"
:options="allGatewayOptions"
:required="true"
/>
<SettingFields
v-if="newGateway"
:fields="newGatewayFields"
:data="null"
class="pt-5 my-0"
/>
</div>
</template>
<template #actions="{ close }">
<div class="pb-5 float-right">
<Button variant="solid" @click="saveSettings(close)">
{{ __('Save') }}
</Button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import {
Button,
call,
createListResource,
createResource,
Dialog,
FormControl,
} from 'frappe-ui'
import { computed, ref, watch } from 'vue'
import SettingFields from '@/components/Settings/SettingFields.vue'
const show = defineModel()
const paymentGateways = defineModel<any>('paymentGateways')
const newGateway = ref(null)
const props = defineProps<{
gatewayID: string | null
}>()
const paymentGateway = createResource({
url: 'lms.lms.api.get_payment_gateway_details',
makeParams(values: any) {
return {
payment_gateway: props.gatewayID,
}
},
transform(data: any) {
arrangeFields(data.fields)
return data
},
})
const allGateways = createListResource({
doctype: 'DocType',
filters: {
module: 'Payment Gateways',
},
fields: ['name', 'issingle', 'fields'],
})
const arrangeFields = (fields: any[]) => {
fields = fields.sort((a, b) => {
if (a.type === 'Upload' && b.type !== 'Upload') {
return 1
} else if (a.type !== 'Upload' && b.type === 'Upload') {
return -1
}
return 0
})
fields.splice(3, 0, {
type: 'Column Break',
})
}
watch(
() => props.gatewayID,
() => {
if (props.gatewayID && props.gatewayID !== 'new') {
paymentGateway.reload()
} else if (props.gatewayID == 'new') {
allGateways.reload()
console.log(allGateways.data)
}
}
)
const saveSettings = (close: () => void) => {
call('frappe.client.set_value', {
doctype: paymentGateway.data.doctype,
name: paymentGateway.data.docname,
fieldname: Object.keys(paymentGateway.data.data).reduce(
(fields: any, key: string) => {
if (
paymentGateway.data.data[key] &&
typeof paymentGateway.data.data[key] === 'object'
) {
fields[key] = paymentGateway.data.data[key].file_url
} else {
fields[key] = paymentGateway.data.data[key]
}
return fields
},
{}
),
}).then(() => {
paymentGateway.reload()
close()
})
}
const allGatewayOptions = computed(() => {
let options: string[] = []
let gatewayList = allGateways.data?.map((gateway: any) => gateway.name) || []
gatewayList.forEach((gateway: any) => {
console.log(gateway)
let gatewayName = gateway.split(' ')[0]
let existingGateways =
paymentGateways.value?.data?.map((pg: any) => pg.name) || []
if (
!options.includes(gatewayName) &&
!existingGateways.includes(gatewayName)
) {
options.push(gatewayName)
}
})
return options.map((gateway: string) => ({ label: gateway, value: gateway }))
})
</script>

View File

@@ -0,0 +1,140 @@
<template>
<div class="flex min-h-0 flex-col text-base">
<div class="flex items-center justify-between mb-5">
<div>
<div class="text-xl font-semibold mb-1 text-ink-gray-9">
{{ __(label) }}
</div>
<div class="text-ink-gray-6 leading-5">
{{ __(description) }}
</div>
</div>
<Button @click="openForm('new')">
<template #prefix>
<Plus class="h-3 w-3 stroke-1.5" />
</template>
{{ __('New') }}
</Button>
</div>
<div v-if="paymentGateways.data?.length" class="overflow-y-scroll">
<ListView
:columns="columns"
:rows="paymentGateways.data"
row-key="name"
:options="{
showTooltip: false,
onRowClick: (row) => {
openForm(row.name)
},
}"
>
<ListHeader
class="mb-2 grid items-center space-x-4 rounded bg-surface-gray-2 p-2"
>
<ListHeaderItem :item="item" v-for="item in columns">
<template #prefix="{ item }">
<FeatherIcon
v-if="item.icon"
:name="item.icon"
class="h-4 w-4 stroke-1.5"
/>
</template>
</ListHeaderItem>
</ListHeader>
<ListRows>
<ListRow :row="row" v-for="row in paymentGateways.data">
<template #default="{ column, item }">
<ListRowItem :item="row[column.key]" :align="column.align">
<div v-if="column.key == 'enabled'">
<Badge v-if="row[column.key]" theme="green">
{{ __('Enabled') }}
</Badge>
<Badge v-else theme="gray">
{{ __('Disabled') }}
</Badge>
</div>
<div v-else class="leading-5 text-sm">
{{ row[column.key] }}
</div>
</ListRowItem>
</template>
</ListRow>
</ListRows>
<ListSelectBanner>
<template #actions="{ unselectAll, selections }">
<div class="flex gap-2">
<Button
variant="ghost"
@click="removeAccount(selections, unselectAll)"
>
<Trash2 class="h-4 w-4 stroke-1.5" />
</Button>
</div>
</template>
</ListSelectBanner>
</ListView>
</div>
</div>
<PaymentGatewayDetails
v-model="showForm"
:gatewayID="currentGateway"
v-model:paymentGateways="paymentGateways"
/>
</template>
<script setup>
import {
Badge,
Button,
createListResource,
FeatherIcon,
ListView,
ListHeader,
ListHeaderItem,
ListRows,
ListRow,
ListRowItem,
ListSelectBanner,
} from 'frappe-ui'
import { computed, ref } from 'vue'
import { Plus, Trash2 } from 'lucide-vue-next'
import PaymentGatewayDetails from '@/components/Settings/PaymentGatewayDetails.vue'
const showForm = ref(false)
const currentGateway = ref(null)
const props = defineProps({
label: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
})
const paymentGateways = createListResource({
doctype: 'Payment Gateway',
fields: ['name', 'gateway_settings', 'gateway_controller'],
auto: true,
orderBy: 'modified desc',
})
const openForm = (gatewayID) => {
currentGateway.value = gatewayID
showForm.value = true
}
const columns = computed(() => {
return [
{
label: __('Gateway'),
key: 'name',
icon: 'credit-card',
},
]
})
</script>

View File

@@ -1,128 +0,0 @@
<template>
<div class="flex flex-col h-full">
<div class="flex items-center justify-between">
<div class="text-xl font-semibold mb-1 text-ink-gray-9">
{{ label }}
</div>
<!-- <Badge
v-if="isDirty"
:label="__('Not Saved')"
variant="subtle"
theme="orange"
/> -->
</div>
<div class="overflow-y-scroll">
<div class="flex flex-col divide-y">
<SettingFields :fields="fields" :data="data.doc" />
<SettingFields
v-if="paymentGateway.data"
:fields="paymentGateway.data.fields"
:data="paymentGateway.data.data"
class="pt-5 my-0"
/>
</div>
</div>
<div class="flex flex-row-reverse mt-auto">
<Button variant="solid" @click="update">
{{ __('Update') }}
</Button>
</div>
</div>
</template>
<script setup>
import SettingFields from '@/components/Settings/SettingFields.vue'
import { createResource, Badge, Button } from 'frappe-ui'
import { watch } from 'vue'
const props = defineProps({
label: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
data: {
type: Object,
required: true,
},
fields: {
type: Array,
required: true,
},
})
const paymentGateway = createResource({
url: 'lms.lms.api.get_payment_gateway_details',
makeParams(values) {
return {
payment_gateway: props.data.doc.payment_gateway,
}
},
transform(data) {
arrangeFields(data.fields)
return data
},
auto: true,
})
const arrangeFields = (fields) => {
fields = fields.sort((a, b) => {
if (a.type === 'Upload' && b.type !== 'Upload') {
return 1
} else if (a.type !== 'Upload' && b.type === 'Upload') {
return -1
}
return 0
})
fields.splice(3, 0, {
type: 'Column Break',
})
}
const saveSettings = createResource({
url: 'frappe.client.set_value',
makeParams(values) {
let fields = {}
Object.keys(paymentGateway.data.data).forEach((key) => {
if (
paymentGateway.data.data[key] &&
typeof paymentGateway.data.data[key] === 'object'
) {
fields[key] = paymentGateway.data.data[key].file_url
} else {
fields[key] = paymentGateway.data.data[key]
}
})
return {
doctype: paymentGateway.data.doctype,
name: paymentGateway.data.docname,
fieldname: fields,
}
},
auto: false,
onSuccess(data) {
paymentGateway.reload()
},
})
const update = () => {
props.fields.forEach((f) => {
if (f.type != 'Column Break') {
props.data.doc[f.name] = f.value
}
})
props.data.save.submit()
saveSettings.submit()
}
watch(
() => props.data.doc.payment_gateway,
() => {
paymentGateway.reload()
}
)
</script>

View File

@@ -30,12 +30,9 @@
</CodeEditor>
</div>
<div
v-else-if="field.type == 'Upload'"
class="grid grid-cols-2 gap-10"
>
<div class="space-y-2">
<div class="text-sm text-ink-gray-8 font-medium mb-1">
<div v-else-if="field.type == 'Upload'">
<div class="space-y-1 mb-2">
<div class="text-sm text-ink-gray-5 font-medium">
{{ __(field.label) }}
</div>
<div class="text-sm text-ink-gray-5 leading-5">

View File

@@ -41,18 +41,18 @@
v-bind="{
label: activeTab.label,
description: activeTab.description,
...(activeTab.label === 'Branding'
...(activeTab.label == 'Branding'
? { fields: activeTab.fields }
: {}),
}"
/>
<PaymentSettings
v-else-if="activeTab.label === 'Payment Gateway'"
<!-- <PaymentSettings
v-else-if="activeTab.label === 'Gateways'"
:label="activeTab.label"
:description="activeTab.description"
:data="data"
:fields="activeTab.fields"
/>
/> -->
<SettingDetails
v-else
:fields="activeTab.fields"
@@ -76,7 +76,7 @@ import Evaluators from '@/components/Settings/Evaluators.vue'
import Categories from '@/components/Settings/Categories.vue'
import EmailTemplates from '@/components/Settings/EmailTemplates.vue'
import BrandSettings from '@/components/Settings/BrandSettings.vue'
import PaymentSettings from '@/components/Settings/PaymentSettings.vue'
import PaymentGateways from '@/components/Settings/PaymentGateways.vue'
import ZoomSettings from '@/components/Settings/ZoomSettings.vue'
import Badges from '@/components/Settings/Badges.vue'
@@ -159,12 +159,12 @@ const tabsStructure = computed(() => {
],
},
{
label: 'Settings',
hideLabel: true,
label: 'Payment',
hideLabel: false,
items: [
{
label: 'Payment Gateway',
icon: 'DollarSign',
label: 'Configuration',
icon: 'CreditCard',
description:
'Configure the payment gateway and other payment related settings',
fields: [
@@ -200,6 +200,12 @@ const tabsStructure = computed(() => {
},
],
},
{
label: 'Gateways',
icon: 'DollarSign',
template: markRaw(PaymentGateways),
description: 'Add and manage all your payment gateways',
},
],
},
{
@@ -275,7 +281,7 @@ const tabsStructure = computed(() => {
name: 'favicon',
type: 'Upload',
description:
'Appears in the browser tab next to the page title, bookmarks, and shortcuts to help users quickly identify the application.',
'Appears in the browser tab next to the page title to help users quickly identify the application.',
},
],
},