From 9c9b33fc7cc1f7517308d38e7df06a56c3fc715b Mon Sep 17 00:00:00 2001
From: zjk <852185829@qq.com>
Date: 星期三, 05 八月 2026 16:33:13 +0800
Subject: [PATCH] Merge branch 'main' of http://120.76.84.145:10101/gitblit/r/java/talent-card

---
 talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminManagementController.java                |    1 
 rencaika_merchant/pages/merchant-onboarding/merchant-onboarding.uvue                                                |    2 
 rencaika/pages/admin-approval/admin-approval.uvue                                                                   |    6 
 rencaika/pages/benefit-search/benefit-search.uvue                                                                   |    6 
 talent-card-admin/src/router/index.ts                                                                               |    2 
 talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuIndividualOnboardingCommand.java |    3 
 talent-card-server/src/main/java/com/talentcard/server/business/portal/TalentCardPortalService.java                 |   24 
 rencaika/pages/family-card/family-card.uvue                                                                         |   36 +
 rencaika/pages/travel-benefits/travel-benefits.uvue                                                                 |    2 
 talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuIndividualOnboardingGateway.java |   81 ++++
 talent-card-server/src/main/resources/db/migration/V98__merchant_coupon_talent_level_quantities.sql                 |    2 
 rencaika/pages/benefits/benefits.uvue                                                                               |    8 
 rencaika/pages/children-entrustment/children-entrustment.uvue                                                       |    6 
 rencaika/pages/finance-merchant/finance-merchant.uvue                                                               |    6 
 talent-card-server/src/main/java/com/talentcard/server/business/admin/CouponUpdateRequest.java                      |    5 
 rencaika/pages/medical-package/medical-package.uvue                                                                 |    6 
 talent-card-server/src/main/java/com/talentcard/server/business/admin/MerchantSettlementAdminService.java           |    2 
 talent-card-server/src/main/resources/application.yml                                                               |    2 
 talent-card-server/src/main/java/com/talentcard/server/service/DependentCardApplyService.java                       |   16 
 talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuMiniProgramPaymentGateway.java   |    2 
 rencaika/pages/family-card-apply/family-card-apply.uvue                                                             |   28 +
 rencaika/pages/transport-benefits/transport-benefits.uvue                                                           |    6 
 talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuEnterpriseOnboardingCommand.java |   17 +
 rencaika/pages/entertainment-benefits/entertainment-benefits.uvue                                                   |    2 
 rencaika/config/api.uts                                                                                             |    4 
 talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantSettlementService.java             |   54 +++
 rencaika/pages/order-cancelled/order-cancelled.uvue                                                                 |    4 
 rencaika/pages/stay-benefits/stay-benefits.uvue                                                                     |    2 
 rencaika/pages/merchant/merchant.uvue                                                                               |   16 
 talent-card-server/scripts/repair-local-mysql-v92.sql                                                               |    6 
 rencaika/services/portal.uts                                                                                        |   18 
 talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantOnboardingRequest.java             |    4 
 rencaika/pages/shopping-merchant/shopping-merchant.uvue                                                             |    6 
 talent-card-server/src/main/java/com/talentcard/server/business/portal/FamilyCardPortalService.java                 |   75 ++++
 talent-card-server/src/main/java/com/talentcard/server/business/merchant/ManualConsumptionService.java              |   77 ++++
 talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuEnterpriseOnboardingGateway.java |   94 +++++
 rencaika/pages/premium-benefits/premium-benefits.uvue                                                               |    2 
 talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminBusinessService.java                     |   28 +
 talent-card-server/src/main/java/com/talentcard/server/business/payment/HuifuPaymentService.java                    |   30 +
 talent-card-server/src/main/java/com/talentcard/server/business/portal/FamilyCardPortalController.java              |    1 
 rencaika/pages/family-card-unbind/family-card-unbind.uvue                                                           |   29 +
 rencaika/pages/cinema-merchant/cinema-merchant.uvue                                                                 |    2 
 /dev/null                                                                                                           |    0 
 talent-card-admin/src/views/MerchantCreateView.vue                                                                  |   37 ++
 rencaika/pages/shopping-benefits/shopping-benefits.uvue                                                             |    6 
 talent-card-server/src/main/resources/db/migration/V93__merchant_onboarding_huifu_kyc.sql                           |    3 
 rencaika/pages/product-detail/product-detail.uvue                                                                   |    6 
 talent-card-admin/src/types/admin.ts                                                                                |    2 
 talent-card-server/src/main/java/com/talentcard/server/business/admin/CouponCreateRequest.java                      |    5 
 talent-card-server/src/main/java/com/talentcard/server/business/portal/DependentCardApplyController.java            |   16 
 talent-card-server/src/main/java/com/talentcard/server/business/portal/ProductPriceCalculator.java                  |  138 ++++++++
 talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuMiniProgramPaymentCommand.java   |    1 
 talent-card-server/src/main/java/com/talentcard/server/business/auth/TalentAuthService.java                         |   36 +
 talent-card-admin/src/views/MerchantSettlementView.vue                                                              |   28 +
 54 files changed, 859 insertions(+), 142 deletions(-)

diff --git a/rencaika/config/api.uts b/rencaika/config/api.uts
index 9fbd12d..ddaf402 100644
--- a/rencaika/config/api.uts
+++ b/rencaika/config/api.uts
@@ -5,7 +5,7 @@
 export const developmentLoginBypassEnabled: boolean = false 
 
 // 后台提供合法域名后仅在此处填写,业务页面不得硬编码域名。
-const developmentBaseUrl = 'http://192.168.110.13:8080'
+const developmentBaseUrl = 'http://192.168.110.8:8080'
 const stagingBaseUrl = ''
 const productionBaseUrl = ''
 
@@ -69,7 +69,7 @@
 	if (name == 'appCompletedOrders') return '/api/app/completed-orders/'
 	if (name == 'appCancelledOrders') return '/api/app/cancelled-orders/'
 	if (name == 'appProcessingApplications') return '/api/app/processing-applications/'
-	if (name == 'appFamilyCards') return '/api/app/family-cards'
+	if (name == 'appFamilyCards') return '/api/app/family-cards/applications'
 	if (name == 'appFreeBenefits') return '/api/app/free-benefits'
 	if (name == 'appAgreements') return '/api/app/agreements/'
 	if (name == 'appUploadImage') return '/api/app/uploads/images'
diff --git a/rencaika/pages/admin-approval/admin-approval.uvue b/rencaika/pages/admin-approval/admin-approval.uvue
index 0e9f28d..220cb09 100644
--- a/rencaika/pages/admin-approval/admin-approval.uvue
+++ b/rencaika/pages/admin-approval/admin-approval.uvue
@@ -53,7 +53,7 @@
 	const navWidth = ref(88)
 	const navRight = ref(10)
 	const serviceId = ref('')
-	const heroImage = ref('/static/stay-assets/hotel-vienna.jpg')
+	const heroImage = ref('')
 	const benefitTitle = ref('行政审批')
 	const freeLabel = ref('免费')
 	const benefitAvailable = ref(true)
@@ -87,7 +87,7 @@
 		getAdminApprovalBenefit(serviceId.value, (data: any) => {
 			benefitTitle.value = data.title || '行政审批'
 			freeLabel.value = data.freeLabel || '免费'
-			heroImage.value = resolveApiUrl(data.heroImage || '/static/stay-assets/hotel-vienna.jpg')
+			heroImage.value = data.heroImage != null && data.heroImage != '' ? resolveApiUrl(data.heroImage as string) : ''
 			benefitAvailable.value = data.available === true
 			unavailableReason.value = data.unavailableReason || '该权益暂未开放'
 			orgs.value = data.organizations instanceof Array ? data.organizations as any[] : []
@@ -97,7 +97,7 @@
 	}
 	const goBack = () => safeBack('/pages/premium-benefits/premium-benefits')
 	const noop = () => {}
-	const replaceHero = () => { heroImage.value = '/static/common/image-fallback.svg' }
+	const replaceHero = () => { heroImage.value = '' }
 	const callOrg = (item: any) => { if (item.phone == null || item.phone == '') { uni.showToast({ title: '机构暂未提供咨询电话', icon: 'none' }); return }; uni.makePhoneCall({ phoneNumber: item.phone, fail: () => uni.showToast({ title: '暂时无法拨打电话', icon: 'none' }) }) }
 	const showWindowInfo = (item: any) => {
 		if (!(item.windows instanceof Array) || item.windows.length == 0) return
diff --git a/rencaika/pages/benefit-search/benefit-search.uvue b/rencaika/pages/benefit-search/benefit-search.uvue
index e35755f..5258907 100644
--- a/rencaika/pages/benefit-search/benefit-search.uvue
+++ b/rencaika/pages/benefit-search/benefit-search.uvue
@@ -64,8 +64,8 @@
 	const navHeight = ref(32)
 	const navWidth = ref(88)
 	const navRight = ref(10)
-	const fallbackImage = '/static/common/image-fallback.svg'
-	const replaceImage = (item: any) => { if (item.image != fallbackImage) item.image = fallbackImage }
+	const fallbackImage = ''
+	const replaceImage = (item: any) => { if (item.image != '') item.image = '' }
 	const keyword = ref('')
 	const hasSearched = ref(false)
 	const isAllMode = ref(false)
@@ -120,7 +120,7 @@
 				merchantType: rawType,
 				discount: item.discountText != null ? item.discountText as string : formatRemoteDiscount(item),
 				address: item.address != null ? item.address as string : '',
-				image: item.coverUrl != null && item.coverUrl != '' ? resolveApiUrl(item.coverUrl as string) : '/static/common/image-fallback.svg',
+				image: item.coverUrl != null && item.coverUrl != '' ? resolveApiUrl(item.coverUrl as string) : '',
 				sort: item.sort != null ? Number(item.sort) : 0,
 				detailType: item.detailType != null ? item.detailType as string : 'MERCHANT',
 				detailBusinessId: item.detailBusinessId != null ? '' + item.detailBusinessId : '' + item.id,
diff --git a/rencaika/pages/benefits/benefits.uvue b/rencaika/pages/benefits/benefits.uvue
index 300e2a0..34911d0 100644
--- a/rencaika/pages/benefits/benefits.uvue
+++ b/rencaika/pages/benefits/benefits.uvue
@@ -74,8 +74,8 @@
 	const navWidth = ref(88)
 	const navRight = ref(10)
 	const isSolidNav = ref(false)
-	const fallbackImage = '/static/common/image-fallback.svg'
-	const replaceImage = (item: any) => { if (item.image != fallbackImage) item.image = fallbackImage }
+	const fallbackImage = ''
+	const replaceImage = (item: any) => { if (item.image != '') item.image = '' }
 
 	onLoad((options: any) => {
 		// #ifdef MP-WEIXIN
@@ -119,8 +119,8 @@
 		{ id: 11, name: '乡菜郎·攀枝花特色延边菜', discount: '6折', address: '西区格里坪镇清香坪东街78号', image: '/static/food-assets/food-1.jpg' },
 		{ id: 12, name: '金府酒楼(西区店)', discount: '6折', address: '西区清香坪北街51号', image: '/static/food-assets/food-2.jpg' }
 	]
-	const visibleBenefits = ref([allBenefits[0], allBenefits[1], allBenefits[2], allBenefits[3]])
-	const loadedBenefitCount = ref(4)
+	const visibleBenefits = ref<any[]>([])
+	const loadedBenefitCount = ref(0)
 	const currentPage = ref(0)
 	const initialLoading = ref(true)
 	const loadingMore = ref(false)
diff --git a/rencaika/pages/children-entrustment/children-entrustment.uvue b/rencaika/pages/children-entrustment/children-entrustment.uvue
index 64ba584..027087b 100644
--- a/rencaika/pages/children-entrustment/children-entrustment.uvue
+++ b/rencaika/pages/children-entrustment/children-entrustment.uvue
@@ -65,7 +65,7 @@
 	const navRight = ref(10)
 	const activeTab = ref(0)
 	const serviceId = ref('')
-	const heroImage = ref('/static/stay-assets/hotel-vienna.jpg')
+	const heroImage = ref('')
 	const benefitTitle = ref('子女托管')
 	const applicableCards = ref('')
 	const description = ref('')
@@ -96,7 +96,7 @@
 		getChildrenEntrustmentBenefit(serviceId.value, (data: any) => {
 			benefitTitle.value = data.title || '子女托管'
 			applicableCards.value = data.applicableCards || '以服务说明为准'
-			heroImage.value = resolveApiUrl(data.heroImage || '/static/stay-assets/hotel-vienna.jpg')
+			heroImage.value = data.heroImage != null && data.heroImage != '' ? resolveApiUrl(data.heroImage as string) : ''
 			description.value = data.rightsText || '暂无使用说明'
 			benefitLoading.value = false
 		}, () => { benefitLoading.value = false; benefitError.value = true })
@@ -110,7 +110,7 @@
 	}
 	const goBack = () => safeBack('/pages/premium-benefits/premium-benefits')
 	const switchTab = (index: number) => { activeTab.value = index }
-	const replaceHero = () => { heroImage.value = '/static/common/image-fallback.svg' }
+	const replaceHero = () => { heroImage.value = '' }
 	const openSchoolDetail = (item: any) => {
 		if (item.detailAvailable !== true) { uni.showToast({ title: '学校介绍暂未开放', icon: 'none' }); return }
 		uni.navigateTo({ url: '/pages/school-detail/school-detail?id=' + item.id + '&name=' + encodeURIComponent(item.name) + '&address=' + encodeURIComponent(item.address) })
diff --git a/rencaika/pages/cinema-merchant/cinema-merchant.uvue b/rencaika/pages/cinema-merchant/cinema-merchant.uvue
index 174bdf0..3437a18 100644
--- a/rencaika/pages/cinema-merchant/cinema-merchant.uvue
+++ b/rencaika/pages/cinema-merchant/cinema-merchant.uvue
@@ -26,7 +26,7 @@
 	import { getSession } from '../../services/session.uts'
 	import { safeBack } from '../../utils/navigation.uts'
 
-	const fallbackImage = '/static/entertainment-assets/wanda-cinema.webp'
+	const fallbackImage = ''
 	const navTop = ref(28); const navHeight = ref(32); const navWidth = ref(88); const navRight = ref(10)
 	const isSolidNav = ref(false); const currentTab = ref('purchase'); const merchantId = ref(''); const talentId = ref('')
 	const cinemaName = ref('影城服务'); const cinemaAddress = ref(''); const cinemaHero = ref(fallbackImage); const cinemaMobile = ref(''); const cinemaLatitude = ref(0); const cinemaLongitude = ref(0)
diff --git a/rencaika/pages/entertainment-benefits/entertainment-benefits.uvue b/rencaika/pages/entertainment-benefits/entertainment-benefits.uvue
index 07baa49..c776994 100644
--- a/rencaika/pages/entertainment-benefits/entertainment-benefits.uvue
+++ b/rencaika/pages/entertainment-benefits/entertainment-benefits.uvue
@@ -48,7 +48,7 @@
 	const navRight = ref(10)
 	const isSolidNav = ref(false)
 	const windowWidth = ref(375)
-	const fallbackImage = '/static/entertainment-assets/entertainment-hero.webp'
+	const fallbackImage = ''
 	const activeIndex = ref(-1)
 	const sceneScrollLeft = ref(0)
 	const sceneTrackWidth = ref(644)
diff --git a/rencaika/pages/family-card-apply/family-card-apply.uvue b/rencaika/pages/family-card-apply/family-card-apply.uvue
index 56ebecd..8e13c82 100644
--- a/rencaika/pages/family-card-apply/family-card-apply.uvue
+++ b/rencaika/pages/family-card-apply/family-card-apply.uvue
@@ -35,8 +35,9 @@
 
 <script setup lang="uts">
 	import { apiEnvironment } from '../../config/api.uts'
-	import { createFamilyCardApplication } from '../../services/portal.uts'
+	import { createFamilyCardApplication, getTalentCardProfile } from '../../services/portal.uts'
 	import { safeBack } from '../../utils/navigation.uts'
+	const goBack = () => safeBack('/pages/family-card/family-card')
 
 	const navTop = ref(28)
 	const navHeight = ref(32)
@@ -47,6 +48,7 @@
 	const relationIndex = ref(0)
 	const form = ref({ name: '', idCard: '', phone: '', relation: '配偶' })
 	const talentId = ref('')
+	const talentCardId = ref('')
 
 	onLoad((options: any) => {
 		// #ifdef MP-WEIXIN
@@ -58,9 +60,16 @@
 		navRight.value = systemInfo.windowWidth - capsule.right
 		// #endif
 		talentId.value = options != null && options['talentId'] != null ? options['talentId'] as string : (apiEnvironment == 'development' ? '2' : '')
+		resolveTalentCardId()
 	})
 
-	const goBack = () => safeBack('/pages/family-card/family-card')
+	const resolveTalentCardId = () => {
+		if (talentId.value == '') return
+		getTalentCardProfile(talentId.value, (data: any) => {
+			const card = data != null ? data.card : null
+			talentCardId.value = card != null && card.id != null ? '' + card.id : ''
+		}, () => {})
+	}
 	const changeRelation = (event: any) => {
 		const selectedIndex = parseInt(event.detail.value as string)
 		relationIndex.value = selectedIndex
@@ -82,13 +91,18 @@
 			uni.showToast({ title: '请填写正确的手机号', icon: 'none' })
 			return
 		}
+		if (talentCardId.value == '') {
+			isSubmitting.value = false
+			uni.showToast({ title: '未获取到主卡信息,请返回重试', icon: 'none' })
+			return
+		}
 		isSubmitting.value = true
-		createFamilyCardApplication({ talentId: Number(talentId.value), name: form.value.name.trim(), idCard: form.value.idCard.trim(), mobile: form.value.phone.trim(), relationship: form.value.relation }, (data: any) => {
+		createFamilyCardApplication(talentCardId.value, form.value.name.trim(), form.value.idCard.trim(), form.value.phone.trim(), form.value.relation, (data: any) => {
 			uni.setStorageSync('familyCardApplicationResult', {
-				name: data.name || '',
-				relationship: data.relationship || '',
-				applicationNo: data.applicationNo || '',
-				estimatedProcessingTime: data.estimatedProcessingTime || '资料已提交,请耐心等待审核。'
+				name: form.value.name.trim(),
+				relationship: form.value.relation,
+				applicationNo: '',
+				estimatedProcessingTime: '资料已提交,请耐心等待审核。'
 			})
 			uni.redirectTo({ url: '/pages/family-card-success/family-card-success' })
 		}, (error) => {
diff --git a/rencaika/pages/family-card-unbind/family-card-unbind.uvue b/rencaika/pages/family-card-unbind/family-card-unbind.uvue
index 62811a5..82c324a 100644
--- a/rencaika/pages/family-card-unbind/family-card-unbind.uvue
+++ b/rencaika/pages/family-card-unbind/family-card-unbind.uvue
@@ -27,7 +27,7 @@
 
 <script setup lang="uts">
 	import { apiEnvironment } from '../../config/api.uts'
-	import { getFamilyCards, unbindFamilyCards } from '../../services/portal.uts'
+	import { getFamilyCards, getTalentCardProfile, unbindFamilyCards } from '../../services/portal.uts'
 	import { safeBack } from '../../utils/navigation.uts'
 
 	const navTop = ref(28)
@@ -41,6 +41,7 @@
 	const listLoaded = ref(false)
 	const listError = ref(false)
 	const talentId = ref('')
+	const talentCardId = ref('')
 
 	onLoad((options: any) => {
 		// #ifdef MP-WEIXIN
@@ -52,22 +53,36 @@
 		navRight.value = systemInfo.windowWidth - capsule.right
 		// #endif
 		talentId.value = options != null && options['talentId'] != null ? options['talentId'] as string : (apiEnvironment == 'development' ? '2' : '')
-		loadMembers()
+		resolveTalentCardId()
 	})
 	const loadMembers = () => {
 		listLoading.value = true
 		listLoaded.value = false
 		listError.value = false
-		if (talentId.value == '') { listLoading.value = false; listError.value = true; return }
-		getFamilyCards(talentId.value, (data: any) => {
-			familyMembers.value = (data as any[]).filter((item: any) => item.canUnbind === true).map((item: any) => { item.id = '' + item.id; return item })
+		if (talentCardId.value == '') { listLoading.value = false; listError.value = true; return }
+		getFamilyCards(talentCardId.value, (data: any) => {
+			const items = (data as any[]) || []
+			familyMembers.value = items.filter((item: any) => item.status === 1).map((item: any) => ({
+				id: '' + item.id,
+				name: item.dependentName != null ? item.dependentName : '',
+				mobile: item.dependentMobile != null ? item.dependentMobile : '',
+				relationship: item.relation != null ? item.relation : ''
+			}))
 			selectedIds.value = selectedIds.value.filter((id: string) => familyMembers.value.some((item: any) => item.id == id))
 			listLoading.value = false
 			listLoaded.value = true
 		}, () => { listLoading.value = false; listError.value = true })
 	}
 
-	const goBack = () => safeBack('/pages/family-card/family-card')
+	const resolveTalentCardId = () => {
+		if (talentId.value == '') { listLoading.value = false; listError.value = true; return }
+		getTalentCardProfile(talentId.value, (data: any) => {
+			const card = data != null ? data.card : null
+			talentCardId.value = card != null && card.id != null ? '' + card.id : ''
+			if (talentCardId.value == '') { listLoading.value = false; listError.value = true; return }
+			loadMembers()
+		}, () => { listLoading.value = false; listError.value = true })
+	}
 	const isSelected = (id: string) => selectedIds.value.indexOf(id) >= 0
 	const toggleMember = (id: string) => {
 		const next = selectedIds.value.slice()
@@ -96,7 +111,7 @@
 		})
 	}
 	const unbindSelected = (ids: string[]) => {
-		unbindFamilyCards(ids, talentId.value, () => { isSubmitting.value = false; uni.showToast({ title: '已解除绑定', icon: 'success' }); setTimeout(() => safeBack('/pages/family-card/family-card'), 700) }, (error) => { isSubmitting.value = false; uni.showToast({ title: error.message, icon: 'none' }) })
+		unbindFamilyCards(ids, talentCardId.value, () => { isSubmitting.value = false; uni.showToast({ title: '已解除绑定', icon: 'success' }); setTimeout(() => safeBack('/pages/family-card/family-card'), 700) }, (error) => { isSubmitting.value = false; uni.showToast({ title: error.message, icon: 'none' }) })
 	}
 </script>
 
diff --git a/rencaika/pages/family-card/family-card.uvue b/rencaika/pages/family-card/family-card.uvue
index 1554dc1..40f4c98 100644
--- a/rencaika/pages/family-card/family-card.uvue
+++ b/rencaika/pages/family-card/family-card.uvue
@@ -40,7 +40,7 @@
 
 <script setup lang="uts">
 	import { apiEnvironment } from '../../config/api.uts'
-	import { getFamilyCards } from '../../services/portal.uts'
+	import { getFamilyCards, getTalentCardProfile } from '../../services/portal.uts'
 	import { safeBack } from '../../utils/navigation.uts'
 
 	const navTop = ref(28)
@@ -51,6 +51,7 @@
 	const loading = ref(true)
 	const loadError = ref(false)
 	const talentId = ref('')
+	const talentCardId = ref('')
 	const canUnbind = ref(false)
 
 	onLoad((options: any) => {
@@ -63,18 +64,41 @@
 		navRight.value = systemInfo.windowWidth - capsule.right
 		// #endif
 		talentId.value = options != null && options['talentId'] != null ? options['talentId'] as string : (apiEnvironment == 'development' ? '2' : '')
-		loadFamilyCards()
+		resolveTalentCardId()
 	})
 	onShow(() => {
-		if (talentId.value != '') loadFamilyCards()
+		if (talentCardId.value != '') loadFamilyCards()
 	})
-
 	const goBack = () => safeBack('/pages/profile/profile')
-	const loadFamilyCards = () => {
+
+	const resolveTalentCardId = () => {
 		if (talentId.value == '') { loading.value = false; loadError.value = true; return }
+		getTalentCardProfile(talentId.value, (data: any) => {
+			const card = data != null ? data.card : null
+			talentCardId.value = card != null && card.id != null ? '' + card.id : ''
+			if (talentCardId.value == '') { loading.value = false; loadError.value = true; return }
+			loadFamilyCards()
+		}, () => { loading.value = false; loadError.value = true })
+	}
+	const loadFamilyCards = () => {
+		if (talentCardId.value == '') { loading.value = false; loadError.value = true; return }
 		loading.value = true
 		loadError.value = false
-		getFamilyCards(talentId.value, (data: any) => { familyMembers.value = data as any[]; canUnbind.value = familyMembers.value.some((item: any) => item.canUnbind === true); loading.value = false }, () => { loading.value = false; loadError.value = true })
+				getFamilyCards(talentCardId.value, (data: any) => {
+			const items = (data as any[]) || []
+			familyMembers.value = items.map((item: any) => ({
+				id: item.id,
+				name: item.dependentName != null ? item.dependentName : '',
+				initial: item.dependentName != null && item.dependentName.length > 0 ? item.dependentName.substring(0, 1) : '',
+				relationship: item.relation != null ? item.relation : '',
+				mobile: item.dependentMobile != null ? item.dependentMobile : '',
+				statusText: item.statusText != null ? item.statusText : '',
+				status: item.status,
+				canUnbind: item.status === 1
+			}))
+			canUnbind.value = familyMembers.value.some((item: any) => item.canUnbind === true)
+			loading.value = false
+		}, () => { loading.value = false; loadError.value = true })
 	}
 	const addApplication = () => uni.navigateTo({ url: '/pages/family-card-apply/family-card-apply?talentId=' + encodeURIComponent(talentId.value) })
 	const unbindFamily = () => {
diff --git a/rencaika/pages/finance-merchant/finance-merchant.uvue b/rencaika/pages/finance-merchant/finance-merchant.uvue
index 1dccc14..790d483 100644
--- a/rencaika/pages/finance-merchant/finance-merchant.uvue
+++ b/rencaika/pages/finance-merchant/finance-merchant.uvue
@@ -57,7 +57,7 @@
 	const discount = ref('')
 	const applicableCards = ref('')
 	const address = ref('')
-	const heroImage = ref('/static/stay-assets/hotel-hampton.jpg')
+	const heroImage = ref('')
 	const benefitDetail = ref('')
 	const merchantPhone = ref('')
 	const specialistPhone = ref('')
@@ -92,7 +92,7 @@
 			discount.value = data.benefitSummary || '请咨询服务专员'
 			applicableCards.value = data.applicableCards || '请咨询服务专员'
 			address.value = data.address || '暂未配置机构地址'
-			heroImage.value = resolveApiUrl(data.coverUrl || '/static/stay-assets/hotel-hampton.jpg')
+			heroImage.value = data.coverUrl != null && data.coverUrl != '' ? resolveApiUrl(data.coverUrl as string) : ''
 			benefitDetail.value = data.benefitDetail || '以服务专员说明为准'
 			merchantPhone.value = data.contactMobile || ''
 			longitude.value = Number(data.longitude)
@@ -106,7 +106,7 @@
 		}, () => { detailLoading.value = false; detailError.value = true })
 	}
 	const goBack = () => safeBack('/pages/premium-benefits/premium-benefits')
-	const replaceHero = () => { heroImage.value = '/static/common/image-fallback.svg' }
+	const replaceHero = () => { heroImage.value = '' }
 	const openLocation = () => { if (!canNavigate.value) { uni.showToast({ title: '暂未配置机构位置', icon: 'none' }); return }; uni.openLocation({ latitude: latitude.value, longitude: longitude.value, name: merchantName.value, address: address.value, scale: 18, fail: () => uni.showToast({ title: '暂时无法打开地图导航', icon: 'none' }) }) }
 	const callMerchant = () => { if (merchantPhone.value == '') return; uni.makePhoneCall({ phoneNumber: merchantPhone.value, fail: () => uni.showToast({ title: '暂时无法拨打电话', icon: 'none' }) }) }
 	const contactSpecialist = () => {
diff --git a/rencaika/pages/medical-package/medical-package.uvue b/rencaika/pages/medical-package/medical-package.uvue
index bb166cc..5a347e7 100644
--- a/rencaika/pages/medical-package/medical-package.uvue
+++ b/rencaika/pages/medical-package/medical-package.uvue
@@ -21,7 +21,7 @@
 	import { safeBack } from '../../utils/navigation.uts'
 	const navTop = ref(28); const navHeight = ref(32); const navWidth = ref(88); const navRight = ref(10); const isSolidNav = ref(false)
 	const packageId = ref(''); const hospitalId = ref(''); const detailLoading = ref(true); const detailError = ref(false); const itemsLoading = ref(false); const itemsLoaded = ref(false); const itemsError = ref(false)
-	const packageName = ref(''); const heroImage = ref('/static/stay-assets/hotel-vienna.jpg'); const freeLabel = ref('免费'); const applicableCards = ref(''); const packageAvailable = ref(false); const unavailableReason = ref('该套餐暂不可预约')
+	const packageName = ref(''); const heroImage = ref(''); const freeLabel = ref('免费'); const applicableCards = ref(''); const packageAvailable = ref(false); const unavailableReason = ref('该套餐暂不可预约')
 	const hospitalName = ref(''); const hospitalAddress = ref(''); const hospitalPhone = ref(''); const latitude = ref(0); const longitude = ref(0); const packageItems = ref<any[]>([])
 	const canNavigate = computed(() => latitude.value > 0 && longitude.value > 0)
 	const canBook = computed(() => !detailLoading.value && !detailError.value && packageAvailable.value)
@@ -34,12 +34,12 @@
 	const loadDetail = () => {
 		if (packageId.value == '') { detailLoading.value = false; detailError.value = true; return }
 		detailLoading.value = true; detailError.value = false
-		getMedicalPackage(packageId.value, (data: any) => { const hospital: any = data.hospital || {}; packageName.value = data.name || ''; heroImage.value = resolveApiUrl(data.heroImage || '/static/stay-assets/hotel-vienna.jpg'); freeLabel.value = data.freeLabel || '免费'; applicableCards.value = data.applicableCards || '以医院说明为准'; packageAvailable.value = data.available === true; unavailableReason.value = data.unavailableReason || unavailableReason.value; hospitalId.value = String(hospital.hospitalId || hospitalId.value); hospitalName.value = hospital.name || ''; hospitalAddress.value = hospital.address || '医院暂未配置地址'; hospitalPhone.value = hospital.mobile || ''; latitude.value = Number(hospital.latitude); longitude.value = Number(hospital.longitude); detailLoading.value = false; loadItems() }, () => { detailLoading.value = false; detailError.value = true })
+		getMedicalPackage(packageId.value, (data: any) => { const hospital: any = data.hospital || {}; packageName.value = data.name || ''; heroImage.value = data.heroImage != null && data.heroImage != '' ? resolveApiUrl(data.heroImage as string) : ''; freeLabel.value = data.freeLabel || '免费'; applicableCards.value = data.applicableCards || '以医院说明为准'; packageAvailable.value = data.available === true; unavailableReason.value = data.unavailableReason || unavailableReason.value; hospitalId.value = String(hospital.hospitalId || hospitalId.value); hospitalName.value = hospital.name || ''; hospitalAddress.value = hospital.address || '医院暂未配置地址'; hospitalPhone.value = hospital.mobile || ''; latitude.value = Number(hospital.latitude); longitude.value = Number(hospital.longitude); detailLoading.value = false; loadItems() }, () => { detailLoading.value = false; detailError.value = true })
 	}
 	const loadItems = () => { if (packageId.value == '') return; itemsLoading.value = true; itemsLoaded.value = false; itemsError.value = false; getMedicalPackageItems(packageId.value, (data: any) => { packageItems.value = data instanceof Array ? data as any[] : []; itemsLoading.value = false; itemsLoaded.value = true }, () => { itemsLoading.value = false; itemsError.value = true }) }
 	const goBack = () => safeBack('/pages/premium-benefits/premium-benefits')
 	const handleScroll = (event: any) => { const show = event.detail.scrollTop > 64; if (isSolidNav.value != show) isSolidNav.value = show }
-	const replaceHero = () => { heroImage.value = '/static/common/image-fallback.svg' }
+	const replaceHero = () => { heroImage.value = '' }
 	const openMap = () => { if (!canNavigate.value) { uni.showToast({ title: '医院暂未配置位置', icon: 'none' }); return }; uni.openLocation({ latitude: latitude.value, longitude: longitude.value, name: hospitalName.value, address: hospitalAddress.value, scale: 18, fail: () => uni.showToast({ title: '暂时无法打开地图导航', icon: 'none' }) }) }
 	const callHospital = () => { if (hospitalPhone.value == '') { uni.showToast({ title: '医院暂未配置联系电话', icon: 'none' }); return }; uni.makePhoneCall({ phoneNumber: hospitalPhone.value, fail: () => uni.showToast({ title: '暂时无法拨打电话', icon: 'none' }) }) }
 	const goBooking = () => { if (!canBook.value) { if (!detailLoading.value && !detailError.value) uni.showToast({ title: unavailableReason.value, icon: 'none' }); return }; uni.navigateTo({ url: '/pages/medical-booking/medical-booking?packageId=' + encodeURIComponent(packageId.value) + '&hospitalId=' + encodeURIComponent(hospitalId.value) + '&package=' + encodeURIComponent(packageName.value) + '&hospital=' + encodeURIComponent(hospitalName.value) }) }
diff --git a/rencaika/pages/merchant/merchant.uvue b/rencaika/pages/merchant/merchant.uvue
index 204f4ad..66373ab 100644
--- a/rencaika/pages/merchant/merchant.uvue
+++ b/rencaika/pages/merchant/merchant.uvue
@@ -156,10 +156,10 @@
 	const merchantId = ref('merchant-food-default')
 	const merchantType = ref('food')
 	const merchantName = ref('百杏厨房(中环天地店)')
-	const merchantHero = ref('/static/food-assets/food-1.jpg')
-	const fallbackImage = '/static/common/image-fallback.svg'
-	const replaceHeroImage = () => { merchantHero.value = fallbackImage }
-	const replaceImage = (item: any) => { if (item.image != fallbackImage) item.image = fallbackImage }
+	const merchantHero = ref('')
+	const fallbackImage = ''
+	const replaceHeroImage = () => { merchantHero.value = '' }
+	const replaceImage = (item: any) => { if (item.image != '') item.image = '' }
 	const merchantPrimaryTag = ref('券')
 	const merchantDiscount = ref('8.8折')
 	const merchantAddress = ref('四川省攀枝花市东区炳三区新福路20号中环天地负一层')
@@ -256,7 +256,7 @@
 				merchantType.value = 'medical'
 				isMedicalMerchant.value = true
 				merchantName.value = name
-				merchantHero.value = '/static/stay-assets/hotel-hampton.jpg'
+				merchantHero.value = ''
 				merchantPrimaryTag.value = '免费'
 				merchantDiscount.value = ''
 				merchantAddress.value = '攀枝花市东区益康街34号4号综合3楼体检中心'
@@ -270,7 +270,7 @@
 				if (routedMerchantType == 'parking' || name.indexOf('停车') >= 0) {
 					isParkingMerchant.value = true
 					merchantName.value = '停车服务'
-					merchantHero.value = '/static/home-assets/city-intro.jpg'
+				merchantHero.value = ''
 					merchantPrimaryTag.value = '免费'
 					merchantDiscount.value = ''
 					merchantAddress.value = '攀枝花市东区渡口公司管辖停车场'
@@ -279,7 +279,7 @@
 					usageInstructions.value = '登记信息后可享受渡口公司管辖的49处停车场免费停车服务'
 				} else {
 					merchantName.value = name == '加油服务' ? '中国石油东区新福路加油站' : name
-					merchantHero.value = '/static/home-assets/top.jpg'
+				merchantHero.value = ''
 					merchantPrimaryTag.value = '券'
 					merchantDiscount.value = '9.4折'
 					merchantAddress.value = '四川省攀枝花市东区新福路20号附3-21号'
@@ -293,7 +293,7 @@
 				isHotelMerchant.value = true
 				merchantType.value = 'hotel'
 				merchantName.value = name
-				merchantHero.value = routedImage == '' ? '/static/stay-assets/hotel-hampton.jpg' : routedImage
+				merchantHero.value = routedImage
 				merchantDiscount.value = routedDiscount == '' ? '6折' : routedDiscount
 				hasFreeVoucher.value = merchantDiscount.value.indexOf('免费') >= 0
 				merchantPrimaryTag.value = hasFreeVoucher.value ? '券' : '惠'
diff --git a/rencaika/pages/order-cancelled/order-cancelled.uvue b/rencaika/pages/order-cancelled/order-cancelled.uvue
index a39f8b8..16401b3 100644
--- a/rencaika/pages/order-cancelled/order-cancelled.uvue
+++ b/rencaika/pages/order-cancelled/order-cancelled.uvue
@@ -61,7 +61,7 @@
 	const talentId = ref('')
 	const loading = ref(true)
 	const loadError = ref(false)
-	const productCoverUrl = ref('/static/home-assets/top.jpg')
+	const productCoverUrl = ref('')
 	const productName = ref('')
 	const originalAmount = ref('0.00')
 	const plannedPayAmount = ref('0.00')
@@ -100,7 +100,7 @@
 		loading.value = true
 		loadError.value = false
 		getCancelledOrder(orderId.value, talentId.value, (data: any) => {
-			productCoverUrl.value = resolveApiUrl(data.productCoverUrlSnapshot || '/static/home-assets/top.jpg')
+		productCoverUrl.value = data.productCoverUrlSnapshot != null && data.productCoverUrlSnapshot != '' ? resolveApiUrl(data.productCoverUrlSnapshot as string) : ''
 			productName.value = data.productNameSnapshot || '人才权益服务'
 			originalAmount.value = Number(data.originalAmount).toFixed(2)
 			plannedPayAmount.value = Number(data.payAmount).toFixed(2)
diff --git a/rencaika/pages/premium-benefits/premium-benefits.uvue b/rencaika/pages/premium-benefits/premium-benefits.uvue
index 5d2cf7c..f4a7ebd 100644
--- a/rencaika/pages/premium-benefits/premium-benefits.uvue
+++ b/rencaika/pages/premium-benefits/premium-benefits.uvue
@@ -20,7 +20,7 @@
 	import { safeBack } from '../../utils/navigation.uts'
 
 	const navTop = ref(28); const navHeight = ref(32); const navWidth = ref(88); const navRight = ref(10); const windowWidth = ref(375)
-	const isSolidNav = ref(false); const fallbackImage = '/static/common/image-fallback.svg'; const fallbackScenes = [{ id: 'fallback-1', name: '臻享礼遇', image: '/static/stay-assets/hotel-hampton.jpg' }, { id: 'fallback-2', name: '人才服务', image: '/static/stay-assets/hotel-vienna.jpg' }]
+	const isSolidNav = ref(false); const fallbackImage = ''; const fallbackScenes = []
 	const topicTitle = ref('臻享礼遇'); const topicDescription = ref(''); const topicError = ref(false); const scenes = ref<any[]>(fallbackScenes); const activeIndex = ref(0); const activeScene = ref<any>(fallbackScenes[0]); const sceneScrollLeft = ref(0); const sceneTrackWidth = ref(644 + fallbackScenes.length * 137)
 	const serviceTabs = ref<any[]>([]); const activeTab = ref(''); const visibleServices = ref<any[]>([]); const initialLoading = ref(true); const loadingMore = ref(false); const hasLoaded = ref(false); const hasMore = ref(false); const initialError = ref(false); const loadMoreError = ref(false); const currentPage = ref(0); const listRequestVersion = ref(0); const failInitialOnce = ref(false); const failLoadMoreOnce = ref(false)
 
diff --git a/rencaika/pages/product-detail/product-detail.uvue b/rencaika/pages/product-detail/product-detail.uvue
index bda4c40..3070e77 100644
--- a/rencaika/pages/product-detail/product-detail.uvue
+++ b/rencaika/pages/product-detail/product-detail.uvue
@@ -60,9 +60,9 @@
 	const merchantId = ref('')
 	const productId = ref('')
 	const productName = ref('【川味盛宴】川菜12人餐')
-	const productImage = ref('/static/food-assets/food-1.jpg')
-	const fallbackImage = '/static/common/image-fallback.svg'
-	const replaceProductImage = () => { productImage.value = fallbackImage }
+	const productImage = ref('')
+	const fallbackImage = ''
+	const replaceProductImage = () => { productImage.value = '' }
 	const productPrice = ref('138')
 	const productOriginalPrice = ref('266')
 	const productDiscount = ref('6折')
diff --git a/rencaika/pages/shopping-benefits/shopping-benefits.uvue b/rencaika/pages/shopping-benefits/shopping-benefits.uvue
index 3d77264..4bfbda6 100644
--- a/rencaika/pages/shopping-benefits/shopping-benefits.uvue
+++ b/rencaika/pages/shopping-benefits/shopping-benefits.uvue
@@ -24,10 +24,10 @@
 	import { safeBack } from '../../utils/navigation.uts'
 
 	const navTop = ref(28); const navHeight = ref(32); const navWidth = ref(88); const navRight = ref(10); const windowWidth = ref(375)
-	const isSolidNav = ref(false); const fallbackImage = '/static/common/image-fallback.svg'
-	const fallbackScenes = [{ id: 'fallback-1', name: '惠享好物', image: '/static/home-assets/top.jpg' }, { id: 'fallback-2', name: '品质生活', image: '/static/home-assets/city-intro.jpg' }]
+	const isSolidNav = ref(false); const fallbackImage = ''
+	const fallbackScenes: any[] = []
 	const topicTitle = ref('惠享好物'); const topicDescription = ref(''); const topicError = ref(false)
-	const scenes = ref<any[]>(fallbackScenes); const activeIndex = ref(0); const activeScene = ref<any>(fallbackScenes[0]); const sceneScrollLeft = ref(0); const sceneTrackWidth = ref(644 + fallbackScenes.length * 137)
+	const scenes = ref<any[]>(fallbackScenes); const activeIndex = ref(0); const activeScene = ref<any>({ id: '', name: '', image: '' }); const sceneScrollLeft = ref(0); const sceneTrackWidth = ref(644)
 	const serviceTabs = ref<any[]>([]); const activeTab = ref(''); const visibleServices = ref<any[]>([])
 	const initialLoading = ref(true); const loadingMore = ref(false); const hasLoaded = ref(false); const hasMore = ref(false); const initialError = ref(false); const loadMoreError = ref(false); const currentPage = ref(0); const listRequestVersion = ref(0); const failInitialOnce = ref(false); const failLoadMoreOnce = ref(false)
 
diff --git a/rencaika/pages/shopping-merchant/shopping-merchant.uvue b/rencaika/pages/shopping-merchant/shopping-merchant.uvue
index 7ba53e7..1148cad 100644
--- a/rencaika/pages/shopping-merchant/shopping-merchant.uvue
+++ b/rencaika/pages/shopping-merchant/shopping-merchant.uvue
@@ -21,7 +21,7 @@
 	import { safeBack } from '../../utils/navigation.uts'
 
 	const navTop = ref(28); const navHeight = ref(32); const navWidth = ref(88); const navRight = ref(10)
-	const merchantId = ref(''); const heroImage = ref('/static/common/image-fallback.svg'); const merchantName = ref(''); const discount = ref(''); const applicableCards = ref(''); const address = ref(''); const benefitDetail = ref('')
+	const merchantId = ref(''); const heroImage = ref(''); const merchantName = ref(''); const discount = ref(''); const applicableCards = ref(''); const address = ref(''); const benefitDetail = ref('')
 	const merchantPhone = ref(''); const latitude = ref(0); const longitude = ref(0); const available = ref(false); const unavailableReason = ref('该购物服务当前不可用'); const advisorAvailable = ref(false); const advisorPhone = ref('')
 	const detailLoading = ref(true); const detailError = ref(false)
 	const canNavigate = () => latitude.value != 0 && longitude.value != 0
@@ -33,7 +33,7 @@
 		if (options != null && options['merchantId'] != null) merchantId.value = decodeURIComponent(options['merchantId'] as string)
 		loadMerchantDetail()
 	})
-	const replaceHeroImage = () => { heroImage.value = '/static/common/image-fallback.svg' }
+	const replaceHeroImage = () => { heroImage.value = '' }
 	const loadMerchantDetail = () => {
 		detailLoading.value = true; detailError.value = false; available.value = false
 		if (merchantId.value == '' || !isApiConfigured()) { detailLoading.value = false; detailError.value = true; return }
@@ -43,7 +43,7 @@
 			discount.value = data.discountText != null ? data.discountText as string : '请咨询服务专员'
 			applicableCards.value = data.applicableCards != null ? data.applicableCards as string : '请咨询服务专员'
 			address.value = data.address != null ? data.address as string : '请咨询服务专员'
-			heroImage.value = data.coverUrl != null && data.coverUrl != '' ? resolveApiUrl(data.coverUrl as string) : '/static/common/image-fallback.svg'
+			heroImage.value = data.coverUrl != null && data.coverUrl != '' ? resolveApiUrl(data.coverUrl as string) : ''
 			benefitDetail.value = data.benefitDetail != null ? data.benefitDetail as string : '以服务专员说明为准'
 			merchantPhone.value = data.contactMobile != null ? data.contactMobile as string : ''
 			latitude.value = data.latitude != null ? Number(data.latitude) : 0; longitude.value = data.longitude != null ? Number(data.longitude) : 0
diff --git a/rencaika/pages/stay-benefits/stay-benefits.uvue b/rencaika/pages/stay-benefits/stay-benefits.uvue
index aab382a..a700bae 100644
--- a/rencaika/pages/stay-benefits/stay-benefits.uvue
+++ b/rencaika/pages/stay-benefits/stay-benefits.uvue
@@ -15,7 +15,7 @@
 	import { safeBack } from '../../utils/navigation.uts'
 
 	const navTop = ref(28); const navHeight = ref(32); const navWidth = ref(88); const navRight = ref(10)
-	const isSolidNav = ref(false); const windowWidth = ref(375); const fallbackImage = '/static/stay-assets/hotel-vienna.jpg'
+	const isSolidNav = ref(false); const windowWidth = ref(375); const fallbackImage = ''
 	const activeIndex = ref(-1); const sceneScrollLeft = ref(0); const sceneTrackWidth = ref(644); const scenes = ref<any[]>([]); const currentScene = ref({ id: 'fallback', name: '', image: fallbackImage }); const topicDescription = ref('')
 	const activeTab = ref('hotel'); const visibleServices = ref<any[]>([]); const initialLoading = ref(true); const initialError = ref(false); const loadingMore = ref(false); const loadMoreError = ref(false); const hasLoaded = ref(false); const hasMore = ref(false); const currentPage = ref(0); const listRequestVersion = ref(0); const failInitialOnce = ref(false); const failLoadMoreOnce = ref(false)
 
diff --git a/rencaika/pages/transport-benefits/transport-benefits.uvue b/rencaika/pages/transport-benefits/transport-benefits.uvue
index 7afd722..3901bea 100644
--- a/rencaika/pages/transport-benefits/transport-benefits.uvue
+++ b/rencaika/pages/transport-benefits/transport-benefits.uvue
@@ -63,10 +63,10 @@
 	import { safeBack } from '../../utils/navigation.uts'
 
 	const navTop = ref(28); const navHeight = ref(32); const navWidth = ref(88); const navRight = ref(10)
-	const isSolidNav = ref(false); const windowWidth = ref(375); const fallbackImage = '/static/home-assets/top.jpg'
-	const fallbackScenes = [{ id: 'fallback-fuel', name: '人才加油服务', image: '/static/home-assets/top.jpg' }, { id: 'fallback-parking', name: '人才停车服务', image: '/static/home-assets/city-intro.jpg' }]
+	const isSolidNav = ref(false); const windowWidth = ref(375); const fallbackImage = ''
+	const fallbackScenes: any[] = []
 	const topicTitle = ref('行--畅行无忧'); const topicDescription = ref('整合加油、停车、洗车等出行服务资源,为人才提供专属折扣、停车优惠和便捷洗车服务,全方位保障人才出行便捷。'); const topicError = ref(false)
-	const activeIndex = ref(0); const sceneScrollLeft = ref(0); const sceneTrackWidth = ref(644 + fallbackScenes.length * 137); const scenes = ref<any[]>(fallbackScenes); const currentScene = ref(fallbackScenes[0])
+	const activeIndex = ref(0); const sceneScrollLeft = ref(0); const sceneTrackWidth = ref(644); const scenes = ref<any[]>(fallbackScenes); const currentScene = ref({ id: '', name: '', image: '' })
 	const serviceTabs = [{ key: 'fuel', label: '加油服务' }, { key: 'parking', label: '停车服务' }, { key: 'carWash', label: '自助洗车服务' }]
 	const activeTab = ref('fuel'); const visibleServices = ref<any[]>([]); const initialLoading = ref(true); const initialError = ref(false); const loadingMore = ref(false); const loadMoreError = ref(false); const hasLoaded = ref(false); const hasMore = ref(false); const currentPage = ref(0); const listRequestVersion = ref(0); const failInitialOnce = ref(false); const failLoadMoreOnce = ref(false)
 
diff --git a/rencaika/pages/travel-benefits/travel-benefits.uvue b/rencaika/pages/travel-benefits/travel-benefits.uvue
index 0e75795..eadd4b2 100644
--- a/rencaika/pages/travel-benefits/travel-benefits.uvue
+++ b/rencaika/pages/travel-benefits/travel-benefits.uvue
@@ -21,7 +21,7 @@
 	const navRight = ref(10)
 	const isSolidNav = ref(false)
 	const windowWidth = ref(375)
-	const fallbackImage = '/static/home-assets/top.jpg'
+	const fallbackImage = ''
 	const activeIndex = ref(-1)
 	const sceneScrollLeft = ref(0)
 	const sceneTrackWidth = ref(644)
diff --git a/rencaika/services/portal.uts b/rencaika/services/portal.uts
index 67c615b..cbbec3f 100644
--- a/rencaika/services/portal.uts
+++ b/rencaika/services/portal.uts
@@ -188,11 +188,21 @@
 export const getCompletedOrder = (orderNo: string, talentId: string, onSuccess: (data: any) => void, onError: (error: HttpError) => void): (() => void) => request<any>({ path: getApiEndpoint('appCompletedOrders') + encodeURIComponent(orderNo), method: 'GET', skipAuth: true, data: { talentId: talentId } }, onSuccess, onError)
 export const getCancelledOrder = (orderNo: string, talentId: string, onSuccess: (data: any) => void, onError: (error: HttpError) => void): (() => void) => request<any>({ path: getApiEndpoint('appCancelledOrders') + encodeURIComponent(orderNo), method: 'GET', skipAuth: true, data: { talentId: talentId } }, onSuccess, onError)
 export const getProcessingApplication = (applicationNo: string, talentId: string, onSuccess: (data: any) => void, onError: (error: HttpError) => void): (() => void) => request<any>({ path: getApiEndpoint('appProcessingApplications') + encodeURIComponent(applicationNo), method: 'GET', skipAuth: true, data: { talentId: talentId } }, onSuccess, onError)
-export const getFamilyCards = (talentId: string, onSuccess: (data: any) => void, onError: (error: HttpError) => void): (() => void) => request<any>({ path: getApiEndpoint('appFamilyCards'), method: 'GET', skipAuth: true, data: { talentId: talentId } }, onSuccess, onError)
+export const getFamilyCards = (talentCardId: string, onSuccess: (data: any) => void, onError: (error: HttpError) => void): (() => void) => request<any>({ path: getApiEndpoint('appFamilyCards'), method: 'GET', skipAuth: true, data: { talentCardId: Number(talentCardId) } }, onSuccess, onError)
 export const getFreeBenefit = (talentId: string, merchantId: string, onSuccess: (data: any) => void, onError: (error: HttpError) => void): (() => void) => request<any>({ path: getApiEndpoint('appFreeBenefits'), method: 'GET', skipAuth: true, data: { talentId: talentId, merchantId: merchantId } }, onSuccess, onError)
 export const issueFreeBenefitCode = (talentId: string, merchantId: string, onSuccess: (data: any) => void, onError: (error: HttpError) => void): (() => void) => request<any>({ path: getApiEndpoint('appFreeBenefits') + '/dynamic-code', method: 'POST', skipAuth: true, data: { talentId: Number(talentId), merchantId: Number(merchantId) } }, onSuccess, onError)
-export const createFamilyCardApplication = (payload: any, onSuccess: (data: any) => void, onError: (error: HttpError) => void): (() => void) => request<any>({ path: getApiEndpoint('appFamilyCards') + '/applications', method: 'POST', skipAuth: true, data: payload, idempotencyKey: 'family-card-apply-' + Date.now() }, onSuccess, onError)
-export const unbindFamilyCard = (applicationId: string, talentId: string, onSuccess: (data: any) => void, onError: (error: HttpError) => void): (() => void) => request<any>({ path: getApiEndpoint('appFamilyCards') + '/' + encodeURIComponent(applicationId) + '/unbind', method: 'POST', skipAuth: true, data: { talentId: Number(talentId) }, idempotencyKey: 'family-card-unbind-' + applicationId }, onSuccess, onError)
-export const unbindFamilyCards = (applicationIds: string[], talentId: string, onSuccess: (data: any) => void, onError: (error: HttpError) => void): (() => void) => request<any>({ path: getApiEndpoint('appFamilyCards') + '/unbind', method: 'POST', skipAuth: true, data: { talentId: Number(talentId), applicationIds: applicationIds.map((id: string) => Number(id)) }, idempotencyKey: 'family-card-unbind-' + applicationIds.join('-') }, onSuccess, onError)
+export const createFamilyCardApplication = (talentCardId: string, dependentName: string, dependentIdCardNo: string, dependentMobile: string, relation: string, onSuccess: (data: any) => void, onError: (error: HttpError) => void): (() => void) => request<any>({ path: getApiEndpoint('appFamilyCards'), method: 'POST', skipAuth: true, data: { talentCardId: Number(talentCardId), dependentName: dependentName, dependentIdCardNo: dependentIdCardNo, dependentMobile: dependentMobile, relation: relation }, idempotencyKey: 'family-card-apply-' + talentCardId + '-' + Date.now() }, onSuccess, onError)
+export const unbindFamilyCard = (applicationId: string, talentCardId: string, onSuccess: (data: any) => void, onError: (error: HttpError) => void): (() => void) => request<any>({ path: getApiEndpoint('appFamilyCards') + '/' + encodeURIComponent(applicationId) + '/unbind', method: 'POST', skipAuth: true, data: { talentCardId: Number(talentCardId) }, idempotencyKey: 'family-card-unbind-' + applicationId }, onSuccess, onError)
+export const unbindFamilyCards = (applicationIds: string[], talentCardId: string, onSuccess: (data: any) => void, onError: (error: HttpError) => void): (() => void) => {
+	let index = 0
+	const runNext = (): void => {
+		if (index >= applicationIds.length) { onSuccess(null); return }
+		const id = applicationIds[index]
+		index = index + 1
+		unbindFamilyCard(id, talentCardId, () => runNext(), onError)
+	}
+	runNext()
+	return () => {}
+}
 export const issueOrderVerificationCode = (orderNo: string, talentId: string, onSuccess: (data: any) => void, onError: (error: HttpError) => void): (() => void) => request<any>({ path: getApiEndpoint('appPendingUseOrders') + encodeURIComponent(orderNo) + '/verification-code', method: 'POST', skipAuth: true, data: { talentId: Number(talentId) } }, onSuccess, onError)
 export const refundPendingUseOrder = (orderNo: string, talentId: string, reason: string, onSuccess: (data: any) => void, onError: (error: HttpError) => void): (() => void) => request<any>({ path: getApiEndpoint('appPendingUseOrders') + encodeURIComponent(orderNo) + '/refund', method: 'POST', skipAuth: true, data: { talentId: Number(talentId), reason: reason }, idempotencyKey: 'refund-' + orderNo }, onSuccess, onError)
diff --git a/rencaika/static/benefits-assets/benefits-hero.jpg b/rencaika/static/benefits-assets/benefits-hero.jpg
deleted file mode 100644
index 2337a10..0000000
--- a/rencaika/static/benefits-assets/benefits-hero.jpg
+++ /dev/null
Binary files differ
diff --git a/rencaika/static/card-assets/card-elite.jpg b/rencaika/static/card-assets/card-elite.jpg
deleted file mode 100644
index dd3b395..0000000
--- a/rencaika/static/card-assets/card-elite.jpg
+++ /dev/null
Binary files differ
diff --git a/rencaika/static/card-assets/card-excellence.jpg b/rencaika/static/card-assets/card-excellence.jpg
deleted file mode 100644
index 43e05f2..0000000
--- a/rencaika/static/card-assets/card-excellence.jpg
+++ /dev/null
Binary files differ
diff --git a/rencaika/static/card-assets/card-outstanding.jpg b/rencaika/static/card-assets/card-outstanding.jpg
deleted file mode 100644
index 393fc70..0000000
--- a/rencaika/static/card-assets/card-outstanding.jpg
+++ /dev/null
Binary files differ
diff --git a/rencaika/static/card-assets/talent-card-background.jpg b/rencaika/static/card-assets/talent-card-background.jpg
deleted file mode 100644
index bdeafa8..0000000
--- a/rencaika/static/card-assets/talent-card-background.jpg
+++ /dev/null
Binary files differ
diff --git a/rencaika/static/common/image-fallback.svg b/rencaika/static/common/image-fallback.svg
deleted file mode 100644
index 379ef0e..0000000
--- a/rencaika/static/common/image-fallback.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="320" height="200" viewBox="0 0 320 200">
-  <rect width="320" height="200" fill="#eef1f5"/>
-  <path d="M92 132l38-42 28 29 21-24 49 37H92z" fill="#c7d0dc"/>
-  <circle cx="119" cy="69" r="15" fill="#c7d0dc"/>
-  <text x="160" y="174" text-anchor="middle" fill="#8d98a8" font-family="sans-serif" font-size="14">图片加载失败</text>
-</svg>
diff --git a/rencaika/static/entertainment-assets/entertainment-hero.webp b/rencaika/static/entertainment-assets/entertainment-hero.webp
deleted file mode 100644
index bccf187..0000000
--- a/rencaika/static/entertainment-assets/entertainment-hero.webp
+++ /dev/null
Binary files differ
diff --git a/rencaika/static/entertainment-assets/wanda-cinema.webp b/rencaika/static/entertainment-assets/wanda-cinema.webp
deleted file mode 100644
index 5eeddcb..0000000
--- a/rencaika/static/entertainment-assets/wanda-cinema.webp
+++ /dev/null
Binary files differ
diff --git a/rencaika/static/food-assets/food-1.jpg b/rencaika/static/food-assets/food-1.jpg
deleted file mode 100644
index 2b287aa..0000000
--- a/rencaika/static/food-assets/food-1.jpg
+++ /dev/null
Binary files differ
diff --git a/rencaika/static/food-assets/food-2.jpg b/rencaika/static/food-assets/food-2.jpg
deleted file mode 100644
index 7d7084c..0000000
--- a/rencaika/static/food-assets/food-2.jpg
+++ /dev/null
Binary files differ
diff --git a/rencaika/static/home-assets/city-golden.webp b/rencaika/static/home-assets/city-golden.webp
deleted file mode 100644
index a84fe63..0000000
--- a/rencaika/static/home-assets/city-golden.webp
+++ /dev/null
Binary files differ
diff --git a/rencaika/static/home-assets/city-industry.webp b/rencaika/static/home-assets/city-industry.webp
deleted file mode 100644
index d855aef..0000000
--- a/rencaika/static/home-assets/city-industry.webp
+++ /dev/null
Binary files differ
diff --git a/rencaika/static/home-assets/city-intro.jpg b/rencaika/static/home-assets/city-intro.jpg
deleted file mode 100644
index 74b36bf..0000000
--- a/rencaika/static/home-assets/city-intro.jpg
+++ /dev/null
Binary files differ
diff --git a/rencaika/static/home-assets/city-livable.jpg b/rencaika/static/home-assets/city-livable.jpg
deleted file mode 100644
index 2ce650b..0000000
--- a/rencaika/static/home-assets/city-livable.jpg
+++ /dev/null
Binary files differ
diff --git a/rencaika/static/home-assets/city-potential.jpg b/rencaika/static/home-assets/city-potential.jpg
deleted file mode 100644
index bfe528e..0000000
--- a/rencaika/static/home-assets/city-potential.jpg
+++ /dev/null
Binary files differ
diff --git a/rencaika/static/home-assets/home-banner-v2.jpg b/rencaika/static/home-assets/home-banner-v2.jpg
deleted file mode 100644
index f0a908f..0000000
--- a/rencaika/static/home-assets/home-banner-v2.jpg
+++ /dev/null
Binary files differ
diff --git a/rencaika/static/home-assets/top.jpg b/rencaika/static/home-assets/top.jpg
deleted file mode 100644
index 3918faf..0000000
--- a/rencaika/static/home-assets/top.jpg
+++ /dev/null
Binary files differ
diff --git a/rencaika/static/news-assets/meeting-thumb.jpg b/rencaika/static/news-assets/meeting-thumb.jpg
deleted file mode 100644
index 2612195..0000000
--- a/rencaika/static/news-assets/meeting-thumb.jpg
+++ /dev/null
Binary files differ
diff --git a/rencaika/static/policy-assets/policy-explanation.jpg b/rencaika/static/policy-assets/policy-explanation.jpg
deleted file mode 100644
index 0d6c6dc..0000000
--- a/rencaika/static/policy-assets/policy-explanation.jpg
+++ /dev/null
Binary files differ
diff --git a/rencaika/static/policy-assets/policy-hero.jpg b/rencaika/static/policy-assets/policy-hero.jpg
deleted file mode 100644
index 24c9d48..0000000
--- a/rencaika/static/policy-assets/policy-hero.jpg
+++ /dev/null
Binary files differ
diff --git a/rencaika/static/stay-assets/hotel-hampton.jpg b/rencaika/static/stay-assets/hotel-hampton.jpg
deleted file mode 100644
index 00cdae0..0000000
--- a/rencaika/static/stay-assets/hotel-hampton.jpg
+++ /dev/null
Binary files differ
diff --git a/rencaika/static/stay-assets/hotel-vienna.jpg b/rencaika/static/stay-assets/hotel-vienna.jpg
deleted file mode 100644
index cc122d0..0000000
--- a/rencaika/static/stay-assets/hotel-vienna.jpg
+++ /dev/null
Binary files differ
diff --git a/rencaika_merchant/pages/merchant-onboarding/merchant-onboarding.uvue b/rencaika_merchant/pages/merchant-onboarding/merchant-onboarding.uvue
index bae5b9a..67a8d99 100644
--- a/rencaika_merchant/pages/merchant-onboarding/merchant-onboarding.uvue
+++ b/rencaika_merchant/pages/merchant-onboarding/merchant-onboarding.uvue
@@ -316,7 +316,7 @@
 				submitting.value = true
 				const contactName = merchantType.value == 'enterprise' ? administratorName.value.trim() : subjectName.value.trim()
 				const contactMobile = merchantType.value == 'enterprise' ? administratorPhone.value.trim() : personalPhone.value.trim()
-				submitOnboardingRemote({ subjectType: merchantType.value, subjectName: subjectName.value.trim(), creditCode: creditCode.value.trim(), subjectIdNumber: subjectIdNumber.value.trim(), registeredRegion: registeredRegion.value.trim(), registeredAddress: registeredAddress.value.trim(), contactName: contactName, contactMobile: contactMobile }, 'onboarding-' + Date.now(), () => {
+				submitOnboardingRemote({ subjectType: merchantType.value, subjectName: subjectName.value.trim(), creditCode: creditCode.value.trim(), subjectIdNumber: subjectIdNumber.value.trim(), subjectCertificateValidityType: subjectValidity.value == '长期' ? 'long_term' : 'fixed', subjectCertificateBeginDate: subjectStartDate.value, registeredRegion: registeredRegion.value.trim(), registeredAddress: registeredAddress.value.trim(), contactName: contactName, contactMobile: contactMobile }, 'onboarding-' + Date.now(), () => {
 					submitting.value = false
 					uni.redirectTo({ url: '/pages/merchant-onboarding-pending/merchant-onboarding-pending' })
 				}, (error: any) => {
diff --git a/talent-card-admin/src/router/index.ts b/talent-card-admin/src/router/index.ts
index 24796b9..5cbda3f 100644
--- a/talent-card-admin/src/router/index.ts
+++ b/talent-card-admin/src/router/index.ts
@@ -248,7 +248,7 @@
           meta: { title: '人才政策管理-发布', businessKind: 'talentPolicy' },
         },
         {
-          path: 'talent-policy-categories',
+          path: 'talent-policy-categories/:id',
           name: 'talent-policy-categories',
           component: () => import('../views/TalentPolicyEditView.vue'),
           meta: { title: '人才政策管理-修改', businessKind: 'talentPolicyCategory' },
diff --git a/talent-card-admin/src/types/admin.ts b/talent-card-admin/src/types/admin.ts
index 775389c..bc5242d 100644
--- a/talent-card-admin/src/types/admin.ts
+++ b/talent-card-admin/src/types/admin.ts
@@ -298,6 +298,7 @@
   conditionAmount?: number
   couponAmount?: number
   talentLevels?: string
+  talentLevelQuantities?: Record<string, number> | string
   validStart?: string
   validEnd?: string
   productScope?: string
@@ -320,6 +321,7 @@
   conditionAmount?: number
   couponAmount?: number
   talentLevels: string[]
+  talentLevelQuantities: Record<string, number>
   validStart?: string
   validEnd?: string
   productScope: string
diff --git a/talent-card-admin/src/views/MerchantCreateView.vue b/talent-card-admin/src/views/MerchantCreateView.vue
index 53d438a..5f8406e 100644
--- a/talent-card-admin/src/views/MerchantCreateView.vue
+++ b/talent-card-admin/src/views/MerchantCreateView.vue
@@ -278,6 +278,8 @@
 const merchantCoupons = ref<Coupon[]>([])
 const couponProducts = ref<Product[]>([])
 const selectedCouponProductIds = ref<Array<number | string>>([])
+const couponLevelQuantities = reactive<Record<string, number>>({})
+const couponTalentOptions = ['卓越人才卡', '杰出人才卡', '菁英人才卡']
 
 const businessHours = reactive<BusinessHour[]>([{ id: 1, value: [] }])
 
@@ -869,6 +871,8 @@
   couponEditForm.governmentSubsidy = true
   couponEditForm.status = 1
   selectedCouponProductIds.value = []
+  Object.keys(couponLevelQuantities).forEach((key) => delete couponLevelQuantities[key])
+  couponEditForm.talentLevels.forEach((level) => { couponLevelQuantities[level] = 1 })
 }
 
 function parseTalentLevels(value?: string) {
@@ -885,6 +889,10 @@
   couponEditForm.couponAmount =
     detail.couponAmount === undefined || detail.couponAmount === null ? '' : String(detail.couponAmount)
   couponEditForm.talentLevels = parseTalentLevels(detail.talentLevels)
+  Object.keys(couponLevelQuantities).forEach((key) => delete couponLevelQuantities[key])
+  let savedQuantities: Record<string, number> = {}
+  try { savedQuantities = typeof detail.talentLevelQuantities === 'string' ? JSON.parse(detail.talentLevelQuantities) : (detail.talentLevelQuantities || {}) } catch { savedQuantities = {} }
+  couponEditForm.talentLevels.forEach((level) => { couponLevelQuantities[level] = Number(savedQuantities[level] || 1) })
   couponEditForm.validTime = detail.validStart && detail.validEnd ? [detail.validStart, detail.validEnd] : []
   couponEditForm.productScope = detail.productScope === 'ALL' ? 'all' : 'selected'
   couponEditForm.storeConsume = detail.storeConsume === true || detail.storeConsume === 1
@@ -933,6 +941,15 @@
   selectedCouponProductIds.value = selectedCouponProductIds.value.filter((id) => String(id) !== String(row.id))
 }
 
+function toggleCouponTalentLevel(level: string, enabled: boolean) {
+  if (enabled) {
+    if (!couponEditForm.talentLevels.includes(level)) couponEditForm.talentLevels.push(level)
+    if (!couponLevelQuantities[level]) couponLevelQuantities[level] = 1
+    return
+  }
+  couponEditForm.talentLevels = couponEditForm.talentLevels.filter((item) => item !== level)
+}
+
 function couponPayload(): CouponPayload {
   return {
     merchantId: currentMerchantId(),
@@ -944,6 +961,7 @@
         : Number(couponEditForm.fullAmount),
     couponAmount: couponEditForm.couponAmount === '' ? undefined : Number(couponEditForm.couponAmount),
     talentLevels: couponEditForm.talentLevels,
+    talentLevelQuantities: Object.fromEntries(couponEditForm.talentLevels.map((level) => [level, Number(couponLevelQuantities[level] || 1)])),
     validStart: couponEditForm.validTime?.[0],
     validEnd: couponEditForm.validTime?.[1],
     productScope: couponEditForm.productScope === 'all' ? 'ALL' : 'SELECTED',
@@ -960,8 +978,8 @@
     return
   }
   const couponAmount = Number(couponEditForm.couponAmount)
-  if (couponEditForm.couponAmount === '' || !Number.isFinite(couponAmount) || couponAmount <= 0) {
-    ElMessage.warning('请输入大于0的优惠券金额')
+  if (couponEditForm.couponAmount === '' || !Number.isFinite(couponAmount) || couponAmount < 0.1 || couponAmount > 9999.9) {
+    ElMessage.warning('优惠券金额必须在0.1-9999.9元之间')
     return
   }
   if (couponEditForm.useRequirement === 'full' && couponEditForm.fullAmount === '') {
@@ -1795,11 +1813,24 @@
 
             <div class="coupon-edit-row">
               <label>可用人才</label>
-              <el-checkbox-group v-model="couponEditForm.talentLevels" class="coupon-talent-checkboxes">
+              <el-checkbox-group v-model="couponEditForm.talentLevels" class="coupon-talent-checkboxes" style="display: none">
                 <el-checkbox label="卓越人才卡">卓越人才卡</el-checkbox>
                 <el-checkbox label="杰出人才卡">杰出人才卡</el-checkbox>
                 <el-checkbox label="菁英人才卡">菁英人才卡</el-checkbox>
               </el-checkbox-group>
+              <div class="coupon-talent-rows" style="display: grid; gap: 22px; padding: 4px 0">
+                <div v-for="level in couponTalentOptions" :key="level" class="coupon-talent-row" style="display: flex; align-items: center; gap: 18px; min-height: 32px">
+                  <strong style="width: 90px; font-weight: 500">{{ level }}</strong>
+                  <el-switch :model-value="couponEditForm.talentLevels.includes(level)" @change="(enabled: boolean) => toggleCouponTalentLevel(level, enabled)" />
+                  <el-input v-model="couponLevelQuantities[level]" :disabled="!couponEditForm.talentLevels.includes(level)" placeholder="请输入" style="width: 108px" />
+                  <span>张</span>
+                </div>
+              </div>
+              <div v-for="level in couponEditForm.talentLevels" :key="level" class="coupon-level-quantity" style="display: none">
+                <span>{{ level }}</span>
+                <el-input-number v-model="couponLevelQuantities[level]" :min="1" :max="99" :precision="0" :step="1" controls-position="right" />
+                <span>张</span>
+              </div>
             </div>
 
             <div class="coupon-edit-row">
diff --git a/talent-card-admin/src/views/MerchantSettlementView.vue b/talent-card-admin/src/views/MerchantSettlementView.vue
index c01c918..6b311f0 100644
--- a/talent-card-admin/src/views/MerchantSettlementView.vue
+++ b/talent-card-admin/src/views/MerchantSettlementView.vue
@@ -6,7 +6,7 @@
 
 type Kind = 'onboardings' | 'bank-cards' | 'withdrawals'
 
-const kind = ref<Kind>('withdrawals')
+const kind = ref<Kind>('onboardings')
 const loading = ref(false)
 const rows = ref<Record<string, any>[]>([])
 const total = ref(0)
@@ -14,6 +14,8 @@
 const pageSize = ref(20)
 const reviewVisible = ref(false)
 const reviewing = ref<Record<string, any> | null>(null)
+const onboardingDetailVisible = ref(false)
+const onboardingDetail = ref<Record<string, any> | null>(null)
 
 const filters = reactive({
   merchantName: '',
@@ -104,6 +106,11 @@
 }
 
 function openReview(row: Record<string, any>) {
+  if (kind.value === 'onboardings') {
+    onboardingDetail.value = row
+    onboardingDetailVisible.value = true
+    return
+  }
   reviewing.value = row
   reviewVisible.value = true
 }
@@ -270,6 +277,20 @@
       </template>
     </el-dialog>
   </div>
+    <el-dialog v-model="onboardingDetailVisible" title="商户进件详情" width="560px" :close-on-click-modal="false">
+      <div v-if="onboardingDetail" class="detail-grid">
+        <div><span>进件编号</span><strong>{{ onboardingDetail.applicationNo || '-' }}</strong></div>
+        <div><span>进件状态</span><el-tag :type="statusType(onboardingDetail.status)" effect="light">{{ statusLabel(onboardingDetail.status) }}</el-tag></div>
+        <div><span>商家名称</span><strong>{{ onboardingDetail.merchantName || '-' }}</strong></div>
+        <div><span>商户类型</span><strong>{{ onboardingDetail.merchantType || '-' }}</strong></div>
+        <div><span>进件主体</span><strong>{{ onboardingDetail.subjectName || '-' }}</strong></div>
+        <div><span>主体类型</span><strong>{{ onboardingDetail.subjectType === 'enterprise' ? '企业' : '个人' }}</strong></div>
+        <div><span>统一社会信用代码</span><strong>{{ onboardingDetail.creditCode || '-' }}</strong></div>
+        <div><span>联系人电话</span><strong>{{ onboardingDetail.contactMobile || '-' }}</strong></div>
+        <div class="detail-full"><span>汇付反馈</span><strong>{{ onboardingDetail.rejectReason || '等待汇付审核结果' }}</strong></div>
+      </div>
+      <template #footer><el-button type="primary" @click="onboardingDetailVisible = false">关闭</el-button></template>
+    </el-dialog>
 </template>
 
 <style scoped>
@@ -295,5 +316,10 @@
 .review-summary div { display: flex; justify-content: space-between; gap: 16px; }
 .review-summary span { color: #909399; }
 .review-summary strong { color: #303133; font-weight: 500; text-align: right; }
+.detail-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px 24px; }
+.detail-grid > div { display: grid; gap: 5px; }
+.detail-grid span { color: #909399; font-size: 13px; }
+.detail-grid strong { color: #303133; font-weight: 500; word-break: break-all; }
+.detail-full { grid-column: 1 / -1; }
 @media (max-width: 1280px) { .query-form { grid-template-columns: repeat(2, minmax(280px, 1fr)); } }
 </style>
diff --git a/talent-card-server/scripts/repair-local-mysql-v92.sql b/talent-card-server/scripts/repair-local-mysql-v92.sql
new file mode 100644
index 0000000..960f4fa
--- /dev/null
+++ b/talent-card-server/scripts/repair-local-mysql-v92.sql
@@ -0,0 +1,6 @@
+-- Run only when an existing local MySQL database failed V92__admin_approval_organization_extra_info.sql.
+-- V92 may have added columns before failing on the final update statement; delete the failed
+-- Flyway history row so the fixed idempotent migration can run again.
+
+delete from flyway_schema_history
+where version = '92' and success = 0;
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminBusinessService.java b/talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminBusinessService.java
index 186a531..4329c7d 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminBusinessService.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminBusinessService.java
@@ -10,6 +10,7 @@
 import java.util.Map;
 
 import com.talentcard.server.business.BusinessIdGenerator;
+import com.alibaba.fastjson.JSON;
 import com.talentcard.server.business.DatabaseRows;
 import com.talentcard.server.business.PageResult;
 import com.talentcard.server.business.merchant.auth.MerchantPasswordHasher;
@@ -714,7 +715,7 @@
 	) {
 		StringBuilder sql = new StringBuilder("""
 				select c.id, c.merchant_id, m.merchant_name, c.coupon_name, c.condition_type,
-				       c.condition_amount, c.coupon_amount, c.talent_levels, c.valid_start, c.valid_end,
+				       c.condition_amount, c.coupon_amount, c.talent_levels, c.talent_level_quantities, c.valid_start, c.valid_end,
 				       c.product_scope, c.store_consume, c.government_subsidy, c.use_count, c.status,
 				       c.created_at, c.updated_at,
 				       (select count(*) from merchant_coupon_product cp where cp.coupon_id = c.id) as product_count,
@@ -766,7 +767,7 @@
 	public Map<String, Object> coupon(Long id) {
 		Map<String, Object> result = one("""
 				select c.id, c.merchant_id, m.merchant_name, c.coupon_name, c.condition_type,
-				       c.condition_amount, c.coupon_amount, c.talent_levels, c.valid_start, c.valid_end,
+				       c.condition_amount, c.coupon_amount, c.talent_levels, c.talent_level_quantities, c.valid_start, c.valid_end,
 				       c.product_scope, c.store_consume, c.government_subsidy, c.use_count, c.status,
 				       c.created_at, c.updated_at
 				from merchant_coupon c
@@ -782,11 +783,13 @@
 	public Map<String, Object> createCoupon(CouponCreateRequest request) {
 		ensureMerchantExists(request.merchantId());
 		validateCouponConfiguration(request.validStart(), request.validEnd(), request.productScope(), request.productIds());
+		validateCouponAmount(request.couponAmount());
+		validateTalentLevelQuantities(request.talentLevelQuantities());
 		jdbcTemplate.update("""
 				insert into merchant_coupon(merchant_id, coupon_name, condition_type, condition_amount,
-				                            coupon_amount, talent_levels, valid_start, valid_end,
+				                            coupon_amount, talent_levels, talent_level_quantities, valid_start, valid_end,
 				                            product_scope, store_consume, government_subsidy, status)
-				values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)
+				values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)
 				""",
 				request.merchantId(),
 				request.couponName(),
@@ -794,6 +797,7 @@
 				request.conditionAmount(),
 				valueOrZero(request.couponAmount()),
 				joinTalentLevels(request.talentLevels()),
+				levelQuantitiesJson(request.talentLevelQuantities()),
 				blankToNull(request.validStart()),
 				blankToNull(request.validEnd()),
 				normalizeProductScope(request.productScope()),
@@ -808,10 +812,12 @@
 	public Map<String, Object> updateCoupon(Long id, CouponUpdateRequest request) {
 		ensureMerchantExists(request.merchantId());
 		validateCouponConfiguration(request.validStart(), request.validEnd(), request.productScope(), request.productIds());
+		validateCouponAmount(request.couponAmount());
+		validateTalentLevelQuantities(request.talentLevelQuantities());
 		int updated = jdbcTemplate.update("""
 				update merchant_coupon
 				set merchant_id = ?, coupon_name = ?, condition_type = ?, condition_amount = ?,
-				    coupon_amount = ?, talent_levels = ?, valid_start = ?, valid_end = ?,
+				    coupon_amount = ?, talent_levels = ?, talent_level_quantities = ?, valid_start = ?, valid_end = ?,
 				    product_scope = ?, store_consume = ?, government_subsidy = ?, status = ?
 				where deleted = 0 and id = ?
 				""",
@@ -821,6 +827,7 @@
 				request.conditionAmount(),
 				valueOrZero(request.couponAmount()),
 				joinTalentLevels(request.talentLevels()),
+				levelQuantitiesJson(request.talentLevelQuantities()),
 				blankToNull(request.validStart()),
 				blankToNull(request.validEnd()),
 				normalizeProductScope(request.productScope()),
@@ -893,6 +900,17 @@
 		return value == null ? BigDecimal.ZERO : value;
 	}
 
+	private void validateCouponAmount(BigDecimal value) {
+		if (value == null || value.compareTo(new BigDecimal("0.1")) < 0 || value.compareTo(new BigDecimal("9999.9")) > 0) {
+			throw new BusinessException(ErrorCode.BAD_REQUEST, "优惠券金额必须在0.1-9999.9元之间");
+		}
+	}
+	private void validateTalentLevelQuantities(Map<String, Integer> values) {
+		if (values == null) return;
+		values.values().forEach(value -> { if (value == null || value < 1 || value > 99) throw new BusinessException(ErrorCode.BAD_REQUEST, "人才卡发放数量必须为1-99"); });
+	}
+	private String levelQuantitiesJson(Map<String, Integer> values) { return values == null || values.isEmpty() ? null : JSON.toJSONString(values); }
+
 	private String toJson(Object value) {
 		if (value == null) {
 			return null;
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminManagementController.java b/talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminManagementController.java
index b4b815f..23df173 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminManagementController.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminManagementController.java
@@ -26,7 +26,6 @@
   @GetMapping("/merchant-settlement/onboardings") public ApiResponse<com.talentcard.server.business.PageResult<Map<String,Object>>> onboardings(@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "20") int size, @RequestParam(required = false) String merchantName, @RequestParam(required = false) String contactMobile, @RequestParam(required = false) String merchantType, @RequestParam(required = false) String status, @RequestParam(required = false) String startDate, @RequestParam(required = false) String endDate) { return ApiResponse.success(settlementService.onboardings(page, size, merchantName, contactMobile, merchantType, status, startDate, endDate)); }
   @GetMapping("/merchant-settlement/bank-cards") public ApiResponse<com.talentcard.server.business.PageResult<Map<String,Object>>> bankCards(@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "20") int size, @RequestParam(required = false) String merchantName, @RequestParam(required = false) String contactMobile, @RequestParam(required = false) String merchantType, @RequestParam(required = false) String status, @RequestParam(required = false) String startDate, @RequestParam(required = false) String endDate) { return ApiResponse.success(settlementService.bankCards(page, size, merchantName, contactMobile, merchantType, status, startDate, endDate)); }
   @GetMapping("/merchant-settlement/withdrawals") public ApiResponse<com.talentcard.server.business.PageResult<Map<String,Object>>> withdrawals(@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "20") int size, @RequestParam(required = false) String merchantName, @RequestParam(required = false) String contactMobile, @RequestParam(required = false) String merchantType, @RequestParam(required = false) String status, @RequestParam(required = false) String startDate, @RequestParam(required = false) String endDate) { return ApiResponse.success(settlementService.withdrawals(page, size, merchantName, contactMobile, merchantType, status, startDate, endDate)); }
-  @PatchMapping("/merchant-settlement/onboardings/{id}") public ApiResponse<Map<String,Object>> reviewOnboarding(@PathVariable Long id, @RequestBody Map<String,String> body) { return ApiResponse.success(settlementService.reviewOnboarding(id, body.get("status"), body.get("reason"), body.get("huifuId"))); }
   @PatchMapping("/merchant-settlement/bank-cards/{id}") public ApiResponse<Map<String,Object>> reviewBankCard(@PathVariable Long id, @RequestBody Map<String,String> body) { return ApiResponse.success(settlementService.reviewBankCard(id, body.get("status"), body.get("reason"), body.get("tokenNo"))); }
   @PatchMapping("/merchant-settlement/withdrawals/{id}") public ApiResponse<Map<String,Object>> reviewWithdrawal(@PathVariable Long id, @RequestBody Map<String,String> body) { return ApiResponse.success(settlementService.reviewWithdrawal(id, body.get("status"), body.get("reason"))); }
   @GetMapping("/payment-reconciliation/issues") public ApiResponse<com.talentcard.server.business.PageResult<Map<String,Object>>> reconciliationIssues(@RequestParam(defaultValue = "open") String status, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "20") int size) { return ApiResponse.success(reconciliationService.issues(status, page, size)); }
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/admin/CouponCreateRequest.java b/talent-card-server/src/main/java/com/talentcard/server/business/admin/CouponCreateRequest.java
index c450ad0..2afb2d4 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/admin/CouponCreateRequest.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/admin/CouponCreateRequest.java
@@ -2,9 +2,11 @@
 
 import java.math.BigDecimal;
 import java.util.List;
+import java.util.Map;
 
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.DecimalMax;
 import jakarta.validation.constraints.NotNull;
 import jakarta.validation.constraints.Size;
 
@@ -18,9 +20,10 @@
 		String conditionType,
 		BigDecimal conditionAmount,
 		@NotNull
-		@DecimalMin(value = "0.01")
+		@DecimalMin(value = "0.1") @DecimalMax(value = "9999.9")
 		BigDecimal couponAmount,
 		List<String> talentLevels,
+		Map<String, Integer> talentLevelQuantities,
 		String validStart,
 		String validEnd,
 		@NotBlank
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/admin/CouponUpdateRequest.java b/talent-card-server/src/main/java/com/talentcard/server/business/admin/CouponUpdateRequest.java
index f68890a..5e9f645 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/admin/CouponUpdateRequest.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/admin/CouponUpdateRequest.java
@@ -2,9 +2,11 @@
 
 import java.math.BigDecimal;
 import java.util.List;
+import java.util.Map;
 
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.DecimalMax;
 import jakarta.validation.constraints.NotNull;
 import jakarta.validation.constraints.Size;
 
@@ -18,9 +20,10 @@
 		String conditionType,
 		BigDecimal conditionAmount,
 		@NotNull
-		@DecimalMin(value = "0.01")
+		@DecimalMin(value = "0.1") @DecimalMax(value = "9999.9")
 		BigDecimal couponAmount,
 		List<String> talentLevels,
+		Map<String, Integer> talentLevelQuantities,
 		String validStart,
 		String validEnd,
 		@NotBlank
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/admin/MerchantSettlementAdminService.java b/talent-card-server/src/main/java/com/talentcard/server/business/admin/MerchantSettlementAdminService.java
index 2836289..c2e22b3 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/admin/MerchantSettlementAdminService.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/admin/MerchantSettlementAdminService.java
@@ -36,11 +36,9 @@
         return page(select, count, "w", page, size, merchantName, contactMobile, merchantType, status, startDate, endDate);
     }
 
-    @Transactional public Map<String, Object> reviewOnboarding(Long id, String status, String reason, String huifuId) { return reviewOnboardingInternal(id, status, reason, huifuId); }
     @Transactional public Map<String, Object> reviewBankCard(Long id, String status, String reason, String tokenNo) { return reviewBankCardInternal(id, status, reason, tokenNo); }
     public Map<String, Object> reviewWithdrawal(Long id, String status, String reason) { if ("paid".equals(normalizeWithdrawalStatus(status))) return withdrawalService.dispatch(id); return update("merchant_withdrawal", id, "failed", reason, "withdrawal"); }
 
-    private Map<String,Object> reviewOnboardingInternal(Long id,String status,String reason,String huifuId) { String value=reviewStatus(status); if("approved".equals(value) && (huifuId==null || !huifuId.trim().matches("[A-Za-z0-9]{1,32}"))) throw new BusinessException(ErrorCode.BAD_REQUEST,"Huifu merchant ID is required when approving onboarding"); int affected=jdbc.update("update merchant_onboarding_application set status=?, reject_reason=?, provider_huifu_id=?, reviewed_at=now() where id=? and status='pending'",value,reason==null?"":reason.trim(),"approved".equals(value)?huifuId.trim():null,id); if(affected==0)throw new BusinessException(ErrorCode.CONFLICT,"Onboarding is already handled or does not exist");return Map.of("id",id,"status",value); }
     private Map<String,Object> reviewBankCardInternal(Long id,String status,String reason,String tokenNo) { String value=reviewStatus(status); if("approved".equals(value) && (tokenNo==null || !tokenNo.trim().matches("[A-Za-z0-9]{1,32}"))) throw new BusinessException(ErrorCode.BAD_REQUEST,"Huifu withdrawal card token is required when approving a bank card"); int affected=jdbc.update("update merchant_bank_card set status=?, reject_reason=?, provider_token_no=?, reviewed_at=now() where id=? and status='pending'",value,reason==null?"":reason.trim(),"approved".equals(value)?tokenNo.trim():null,id); if(affected==0)throw new BusinessException(ErrorCode.CONFLICT,"Bank card is already handled or does not exist");return Map.of("id",id,"status",value); }
     private String reviewStatus(String status) { String value=status==null?"":status.trim().toLowerCase(); if(!List.of("approved","rejected").contains(value))throw new BusinessException(ErrorCode.BAD_REQUEST,"Invalid review status");return value; }
     private String normalizeWithdrawalStatus(String status) { String value=status==null?"":status.trim().toLowerCase(); if(!List.of("paid","failed").contains(value))throw new BusinessException(ErrorCode.BAD_REQUEST,"Invalid withdrawal status");return value; }
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/auth/TalentAuthService.java b/talent-card-server/src/main/java/com/talentcard/server/business/auth/TalentAuthService.java
index 268d2b9..48a9496 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/auth/TalentAuthService.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/auth/TalentAuthService.java
@@ -108,17 +108,47 @@
                 where mobile = ? and deleted = 0 and status = 1
                 limit 1 for update
                 """, mobile);
-        if (talents.isEmpty()) throw new BusinessException(ErrorCode.FORBIDDEN, "当前手机号未录入人才服务名单,请联系管理员");
+        if (talents.isEmpty()) return issueSession(registerVisitor(openId, mobile));
         Map<String, Object> talent = talents.get(0);
         long talentId = number(talent.get("id"));
-        List<Map<String, Object>> binding = rows("select id from talent_user where openid = ? and deleted = 0 limit 1 for update", openId);
+        List<Map<String, Object>> binding = rows("""
+                select t.id,
+                       exists(select 1 from talent_card c where c.talent_id = t.id and c.deleted = 0 and c.card_type = 'PERSONAL') has_talent_card
+                from talent_user t
+                where t.openid = ? and t.deleted = 0
+                limit 1 for update
+                """, openId);
         if (!binding.isEmpty() && number(binding.get(0).get("id")) != talentId) {
-            throw new BusinessException(ErrorCode.CONFLICT, "当前微信已绑定其他人才账号,请联系管理员");
+            if (number(binding.get(0).get("hasTalentCard")) != 0) {
+                throw new BusinessException(ErrorCode.CONFLICT, "当前微信已绑定其他人才账号,请联系管理员");
+            }
+            jdbc.update("update talent_user set openid = null where id = ?", number(binding.get(0).get("id")));
         }
         jdbc.update("update talent_user set openid = ? where id = ?", openId, talentId);
         return issueSession(talentId);
     }
 
+    private long registerVisitor(String openId, String mobile) {
+        List<Map<String, Object>> bindings = rows("""
+                select id
+                from talent_user
+                where openid = ? and deleted = 0 and status = 1
+                limit 1 for update
+                """, openId);
+        if (!bindings.isEmpty()) {
+            long talentId = number(bindings.get(0).get("id"));
+            jdbc.update("update talent_user set mobile = ? where id = ?", mobile, talentId);
+            return talentId;
+        }
+        jdbc.update("""
+                insert into talent_user(openid, name, mobile, talent_level, source_area_type, status)
+                values (?, '普通游客', ?, '普通游客', 'LOCAL', 1)
+                """, openId, mobile);
+        Long talentId = jdbc.queryForObject("select last_insert_id()", Long.class);
+        if (talentId == null) throw new BusinessException(ErrorCode.INTERNAL_ERROR, "普通游客注册失败");
+        return talentId;
+    }
+
     private Map<String, Object> issueSession(long talentId) {
         jdbc.update("update talent_session set revoked_at = now() where talent_id = ? and revoked_at is null", talentId);
 
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/merchant/ManualConsumptionService.java b/talent-card-server/src/main/java/com/talentcard/server/business/merchant/ManualConsumptionService.java
index c2d5c36..d7359f7 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/merchant/ManualConsumptionService.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/merchant/ManualConsumptionService.java
@@ -5,8 +5,11 @@
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.LinkedHashMap;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+
+import com.alibaba.fastjson.JSON;
 
 import com.talentcard.server.business.BusinessIdGenerator;
 import com.talentcard.server.business.DatabaseRows;
@@ -128,6 +131,8 @@
     }
 
     private List<Map<String, Object>> rules(Long merchantId, String cardLevel) {
+        List<Map<String, Object>> configured = configuredRules(merchantId, cardLevel);
+        if (!configured.isEmpty()) return configured;
         return rows("""
                 select id, card_level, rule_name, discount_rate, subsidy_enabled
                 from merchant_store_consume_rule
@@ -144,7 +149,48 @@
         }).toList();
     }
 
+    private List<Map<String, Object>> configuredRules(Long merchantId, String cardLevel) {
+        List<Map<String, Object>> merchants = rows("select merchant_rights_config from merchant where id = ? and deleted = 0 limit 1", merchantId);
+        if (merchants.isEmpty()) return List.of();
+        Object raw = merchants.get(0).get("merchantRightsConfig");
+        if (raw == null || string(raw).isBlank()) return List.of();
+        List<Map<String, Object>> configs = new ArrayList<>();
+        try {
+            if (raw instanceof List<?> list) {
+                for (Object item : list) if (item instanceof Map<?, ?> map) {
+                    Map<String, Object> value = new LinkedHashMap<>();
+                    for (Map.Entry<?, ?> entry : map.entrySet()) value.put(String.valueOf(entry.getKey()), entry.getValue());
+                    configs.add(value);
+                }
+            } else {
+                for (Object item : JSON.parseArray(string(raw), Map.class)) if (item instanceof Map<?, ?> map) {
+                    Map<String, Object> value = new LinkedHashMap<>();
+                    for (Map.Entry<?, ?> entry : map.entrySet()) value.put(String.valueOf(entry.getKey()), entry.getValue());
+                    configs.add(value);
+                }
+            }
+        } catch (Exception ignored) {
+            return List.of();
+        }
+        return configs.stream().filter(right -> booleanValue(right.get("enabled")) && booleanValue(right.get("consumeEnabled")) && booleanValue(right.get("storeConsume")) && matchesCardLevel(string(right.get("level")), cardLevel)).map(this::configuredRule).filter(rule -> decimal(rule.get("discountRate")).signum() > 0).toList();
+    }
+
+    private Map<String, Object> configuredRule(Map<String, Object> right) {
+        String level = normalizedCardLevel(string(right.get("level")));
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("id", configRuleId(level));
+        result.put("cardLevel", level);
+        result.put("ruleName", string(right.get("tag")).isBlank() ? "到店消费优惠" : right.get("tag"));
+        result.put("name", result.get("ruleName"));
+        result.put("discountRate", configuredDiscountRate(right.get("discount")));
+        result.put("subsidyEnabled", booleanValue(right.get("governmentSubsidy")) ? 1 : 0);
+        result.put("consumeDesc", right.get("consumeDesc"));
+        return result;
+    }
+
     private Map<String, Object> rule(Long merchantId, String cardLevel, Long ruleId) {
+        List<Map<String, Object>> configured = configuredRules(merchantId, cardLevel);
+        for (Map<String, Object> candidate : configured) if (String.valueOf(candidate.get("id")).equals(String.valueOf(ruleId))) return candidate;
         List<Map<String, Object>> found = rows("""
                 select id, card_level, rule_name, discount_rate, subsidy_enabled
                 from merchant_store_consume_rule
@@ -152,18 +198,34 @@
                   and (valid_end is null or valid_end >= current_date)
                 limit 1
                 """, ruleId, merchantId);
-        if (found.isEmpty() || !matchesCardLevel(string(found.get(0).get("cardLevel")), cardLevel)) {
-            throw new BusinessException(ErrorCode.CONFLICT, "当前人才卡不可使用所选优惠方式");
-        }
+        if (found.isEmpty() || !matchesCardLevel(string(found.get(0).get("cardLevel")), cardLevel)) throw new BusinessException(ErrorCode.CONFLICT, "当前人才卡不可使用所选优惠方式");
         return found.get(0);
+    }
+
+    private long configRuleId(String level) {
+        return switch (level) { case "EXCELLENT" -> -1L; case "OUTSTANDING" -> -2L; case "ELITE" -> -3L; default -> -9L; };
+    }
+
+    private BigDecimal configuredDiscountRate(Object value) {
+        try {
+            BigDecimal discount = decimal(string(value));
+            return discount.signum() > 0 && discount.compareTo(BigDecimal.TEN) <= 0 ? discount.divide(BigDecimal.TEN, 4, RoundingMode.HALF_UP) : BigDecimal.ZERO;
+        } catch (Exception ignored) {
+            return BigDecimal.ZERO;
+        }
+    }
+
+    private boolean booleanValue(Object value) {
+        if (value instanceof Boolean bool) return bool;
+        if (value instanceof Number number) return number.intValue() != 0;
+        String text = string(value).toLowerCase();
+        return "true".equals(text) || "1".equals(text) || "yes".equals(text);
     }
 
     private Map<String, Object> quote(Map<String, Object> rule, BigDecimal inputAmount) {
         long originalAmount = inputAmount.movePointRight(2).setScale(0, RoundingMode.HALF_UP).longValueExact();
         BigDecimal rate = decimal(rule.get("discountRate"));
-        if (rate.signum() <= 0 || rate.compareTo(BigDecimal.ONE) > 0) {
-            throw new BusinessException(ErrorCode.CONFLICT, "商户优惠规则配置无效");
-        }
+        if (rate.signum() <= 0 || rate.compareTo(BigDecimal.ONE) > 0) throw new BusinessException(ErrorCode.CONFLICT, "商户优惠规则配置无效");
         long payAmount = BigDecimal.valueOf(originalAmount).multiply(rate).setScale(0, RoundingMode.HALF_UP).longValueExact();
         long discountAmount = originalAmount - payAmount;
         long subsidyAmount = number(rule.get("subsidyEnabled")) == 1 ? discountAmount : 0;
@@ -175,7 +237,6 @@
         result.put("ruleName", rule.get("ruleName"));
         return result;
     }
-
     private Map<String, Object> merchant(MerchantPrincipal principal) {
         List<Map<String, Object>> merchants = rows("""
                 select merchant_name, address, contact_mobile, longitude, latitude
@@ -208,6 +269,8 @@
                 select card_level, benefit_text from merchant_card_benefit
                 where merchant_id = ? and status = 1 order by sort_no desc, id desc
                 """, merchantId);
+        String configuredDescription = configuredRules(merchantId, cardLevel).stream().map(rule -> string(rule.get("consumeDesc"))).filter(value -> !value.isBlank()).findFirst().orElse("");
+        if (!configuredDescription.isBlank()) return configuredDescription;
         return benefits.stream().filter(benefit -> matchesCardLevel(string(benefit.get("cardLevel")), cardLevel))
                 .map(benefit -> string(benefit.get("benefitText"))).findFirst().orElse("以当前商户到店消费规则为准");
     }
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantOnboardingRequest.java b/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantOnboardingRequest.java
index bfec84f..0c14dfb 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantOnboardingRequest.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantOnboardingRequest.java
@@ -9,8 +9,10 @@
         @NotBlank @Size(max = 128) String subjectName,
         @Size(max = 32) String creditCode,
         @Size(max = 32) String subjectIdNumber,
+        @Pattern(regexp = "|fixed|long_term") String subjectCertificateValidityType,
+        @Pattern(regexp = "|\\d{4}-\\d{2}-\\d{2}") String subjectCertificateBeginDate,
         @Size(max = 128) String registeredRegion,
         @Size(max = 255) String registeredAddress,
         @NotBlank @Size(max = 64) String contactName,
         @NotBlank @Pattern(regexp = "1\\d{10}") String contactMobile
-) {}
+) {}
\ No newline at end of file
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantSettlementService.java b/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantSettlementService.java
index 50f2981..4f57f84 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantSettlementService.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantSettlementService.java
@@ -11,6 +11,12 @@
 import com.talentcard.server.business.DatabaseRows;
 import com.talentcard.server.business.PageResult;
 import com.talentcard.server.business.merchant.auth.MerchantPrincipal;
+import com.talentcard.server.business.payment.huifu.HuifuIndividualOnboardingCommand;
+import com.talentcard.server.business.payment.huifu.HuifuIndividualOnboardingGateway;
+import com.talentcard.server.business.payment.huifu.HuifuEnterpriseOnboardingCommand;
+import com.talentcard.server.business.payment.huifu.HuifuEnterpriseOnboardingGateway;
+import com.talentcard.server.business.payment.huifu.HuifuPaymentGatewayException;
+import com.talentcard.server.common.SensitiveDataCrypto;
 import com.talentcard.server.common.BusinessException;
 import com.talentcard.server.common.ErrorCode;
 import org.springframework.jdbc.core.JdbcTemplate;
@@ -20,8 +26,16 @@
 @Service
 public class MerchantSettlementService {
     private final JdbcTemplate jdbc;
+    private final SensitiveDataCrypto sensitiveDataCrypto;
+    private final HuifuIndividualOnboardingGateway individualOnboardingGateway;
+    private final HuifuEnterpriseOnboardingGateway enterpriseOnboardingGateway;
 
-    public MerchantSettlementService(JdbcTemplate jdbc) { this.jdbc = jdbc; }
+    public MerchantSettlementService(JdbcTemplate jdbc, SensitiveDataCrypto sensitiveDataCrypto, HuifuIndividualOnboardingGateway individualOnboardingGateway, HuifuEnterpriseOnboardingGateway enterpriseOnboardingGateway) {
+        this.jdbc = jdbc;
+        this.sensitiveDataCrypto = sensitiveDataCrypto;
+        this.individualOnboardingGateway = individualOnboardingGateway;
+        this.enterpriseOnboardingGateway = enterpriseOnboardingGateway;
+    }
 
     @Transactional(readOnly = true)
     public Map<String, Object> onboardingStatus(MerchantPrincipal principal) {
@@ -39,12 +53,44 @@
         if (!active.isEmpty()) return onboarding(active.get(0));
         boolean enterprise = "enterprise".equals(request.subjectType());
         if (enterprise && (request.creditCode() == null || !request.creditCode().trim().matches("[0-9A-Z]{18}"))) throw new BusinessException(ErrorCode.BAD_REQUEST, "请输入18位统一社会信用代码");
+        if (enterprise && (request.subjectIdNumber() == null || !request.subjectIdNumber().trim().matches("(?:\\d{15}|\\d{17}[0-9X])"))) throw new BusinessException(ErrorCode.BAD_REQUEST, "请输入法人正确的身份证号");
+        if (enterprise && (request.subjectCertificateBeginDate() == null || request.subjectCertificateBeginDate().isBlank())) throw new BusinessException(ErrorCode.BAD_REQUEST, "请输入营业执照成立日期");
         if (!enterprise && (request.subjectIdNumber() == null || !request.subjectIdNumber().trim().matches("(?:\\d{15}|\\d{17}[0-9X])"))) throw new BusinessException(ErrorCode.BAD_REQUEST, "请输入正确的身份证号");
         String no = BusinessIdGenerator.next("MO");
-        jdbc.update("insert into merchant_onboarding_application(merchant_id, store_id, application_no, subject_type, subject_name, credit_code, subject_id_no_masked, registered_region, registered_address, contact_name, contact_mobile, idempotency_key) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", principal.merchantId(), principal.storeId(), no, request.subjectType(), request.subjectName().trim(), clean(request.creditCode()), maskId(request.subjectIdNumber()), clean(request.registeredRegion()), clean(request.registeredAddress()), request.contactName().trim(), request.contactMobile().trim(), key.trim());
+        String certificateValidityType = certificateValidityType(request.subjectCertificateValidityType());
+        String certificateBeginDate = certificateBeginDate(request.subjectCertificateBeginDate());
+        jdbc.update("insert into merchant_onboarding_application(merchant_id, store_id, application_no, subject_type, subject_name, credit_code, subject_id_no_masked, subject_id_no_encrypted, subject_cert_validity_type, subject_cert_begin_date, registered_region, registered_address, contact_name, contact_mobile, idempotency_key) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", principal.merchantId(), principal.storeId(), no, request.subjectType(), request.subjectName().trim(), clean(request.creditCode()), maskId(request.subjectIdNumber()), sensitiveDataCrypto.encrypt(clean(request.subjectIdNumber()).toUpperCase()), certificateValidityType, certificateBeginDate, clean(request.registeredRegion()), clean(request.registeredAddress()), request.contactName().trim(), request.contactMobile().trim(), key.trim());
+        if ("individual".equals(request.subjectType())) submitIndividualOnboarding(no, request);
+        else submitEnterpriseOnboarding(no, request);
         return onboarding(rows("select * from merchant_onboarding_application where application_no = ?", no).get(0));
     }
 
+    private void submitEnterpriseOnboarding(String applicationNo, MerchantOnboardingRequest request) {
+        try {
+            HuifuEnterpriseOnboardingGateway.Result result = enterpriseOnboardingGateway.create(new HuifuEnterpriseOnboardingCommand(
+                    applicationNo, request.subjectName().trim(), request.creditCode().trim(),
+                    certificateValidityType(request.subjectCertificateValidityType()), certificateBeginDate(request.subjectCertificateBeginDate()),
+                    clean(request.registeredRegion()), clean(request.registeredAddress()), request.contactName().trim(),
+                    clean(request.subjectIdNumber()).toUpperCase(), certificateValidityType(request.subjectCertificateValidityType()),
+                    certificateBeginDate(request.subjectCertificateBeginDate()), request.contactName().trim(), request.contactMobile().trim()));
+            if (result.accepted() && !result.huifuId().isBlank()) jdbc.update("update merchant_onboarding_application set status='approved', provider_huifu_id=?, reviewed_at=now(), reject_reason=null where application_no=? and status='pending'", result.huifuId(), applicationNo);
+            else if (!result.accepted()) jdbc.update("update merchant_onboarding_application set status='rejected', reject_reason=?, reviewed_at=now() where application_no=? and status='pending'", providerReason(result.code(), result.message()), applicationNo);
+            else jdbc.update("update merchant_onboarding_application set reject_reason=? where application_no=? and status='pending'", "Huifu accepted the request but did not return huifu_id", applicationNo);
+        } catch (HuifuPaymentGatewayException exception) {
+            jdbc.update("update merchant_onboarding_application set reject_reason=? where application_no=? and status='pending'", "Huifu submission outcome is pending confirmation", applicationNo);
+        }
+    }
+
+    private void submitIndividualOnboarding(String applicationNo, MerchantOnboardingRequest request) {
+        try {
+            HuifuIndividualOnboardingGateway.Result result = individualOnboardingGateway.create(new HuifuIndividualOnboardingCommand(applicationNo, request.subjectName().trim(), request.subjectIdNumber().trim().toUpperCase(), certificateValidityType(request.subjectCertificateValidityType()), certificateBeginDate(request.subjectCertificateBeginDate()), request.contactMobile().trim(), clean(request.registeredAddress())));
+            if (result.accepted() && !result.huifuId().isBlank()) jdbc.update("update merchant_onboarding_application set status='approved', provider_huifu_id=?, reviewed_at=now(), reject_reason=null where application_no=? and status='pending'", result.huifuId(), applicationNo);
+            else if (!result.accepted()) jdbc.update("update merchant_onboarding_application set status='rejected', reject_reason=?, reviewed_at=now() where application_no=? and status='pending'", providerReason(result), applicationNo);
+            else jdbc.update("update merchant_onboarding_application set reject_reason=? where application_no=? and status='pending'", "Huifu accepted the request but did not return huifu_id", applicationNo);
+        } catch (HuifuPaymentGatewayException exception) {
+            jdbc.update("update merchant_onboarding_application set reject_reason=? where application_no=? and status='pending'", "Huifu submission outcome is pending confirmation", applicationNo);
+        }
+    }
     @Transactional(readOnly = true)
     public List<Map<String, Object>> bankCards(MerchantPrincipal principal) {
         store(principal);
@@ -117,6 +163,10 @@
     private List<Map<String, Object>> rows(String sql, Object... args) { return DatabaseRows.normalize(jdbc.queryForList(sql, args)); }
     private Long number(String value) { try { return Long.valueOf(value); } catch (Exception ignored) { throw new BusinessException(ErrorCode.BAD_REQUEST, "编号无效"); } }
     private String clean(Object value) { return value == null ? "" : String.valueOf(value).trim(); }
+    private String certificateValidityType(String value) { return "long_term".equals(value) ? "1" : "0"; }
+    private String certificateBeginDate(String value) { return clean(value).replace("-", ""); }
+    private String providerReason(HuifuIndividualOnboardingGateway.Result result) { return providerReason(result.code(), result.message()); }
+    private String providerReason(String code, String message) { String value = clean(message); return value.isEmpty() ? "Huifu onboarding rejected: " + clean(code) : value; }
     private String maskId(String id) { String value = clean(id); return value.length() < 8 ? "" : value.substring(0, 3) + "********" + value.substring(value.length() - 4); }
     private String sha256(String value) { try { byte[] digest = MessageDigest.getInstance("SHA-256").digest(value.getBytes(StandardCharsets.UTF_8)); StringBuilder result = new StringBuilder(); for (byte valueByte : digest) result.append(String.format("%02x", valueByte)); return result.toString(); } catch (Exception exception) { throw new IllegalStateException("Unable to hash bank card", exception); } }
 }
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/payment/HuifuPaymentService.java b/talent-card-server/src/main/java/com/talentcard/server/business/payment/HuifuPaymentService.java
index dd049ae..8de86cb 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/payment/HuifuPaymentService.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/payment/HuifuPaymentService.java
@@ -47,7 +47,7 @@
         Map<String, Object> response;
         try {
             response = gateway.createMiniProgramPayment(new HuifuMiniProgramPaymentCommand(
-                    prepared.paymentNo(), prepared.openId(), prepared.amountInCent(), prepared.goodsDescription(), prepared.expiresAt()));
+                    prepared.paymentNo(), prepared.huifuId(), prepared.openId(), prepared.amountInCent(), prepared.goodsDescription(), prepared.expiresAt()));
         } catch (HuifuPaymentGatewayException exception) {
             if (exception.isOutcomeUnknown()) {
                 transactions.executeWithoutResult(status -> markCreateUnknown(prepared.paymentNo(), "GATEWAY_UNKNOWN", exception.getMessage()));
@@ -77,9 +77,16 @@
             throw new BusinessException(ErrorCode.BAD_REQUEST, "缺少有效支付幂等键");
         }
         List<Map<String, Object>> orders = rows("""
-                select o.id, o.order_no, o.talent_id, o.merchant_id, o.pay_amount, o.order_status, o.price_version,
+                select o.id, o.order_no, o.talent_id, o.merchant_id, o.store_id, o.pay_amount, o.order_status, o.price_version,
                        o.expires_at, coalesce(o.product_name_snapshot, o.order_type, '人才权益服务') goods_description,
-                       t.openid
+                       t.openid,
+                       coalesce((select a.provider_huifu_id
+                                 from merchant_onboarding_application a
+                                 where a.merchant_id = o.merchant_id
+                                   and a.status = 'approved'
+                                   and a.provider_huifu_id is not null
+                                   and trim(a.provider_huifu_id) <> ''
+                                 order by a.id desc limit 1), '') provider_huifu_id
                 from benefit_order o
                 join talent_user t on t.id = o.talent_id and t.deleted = 0
                 where o.deleted = 0 and o.order_no = ? and o.talent_id = ?
@@ -88,7 +95,7 @@
         if (orders.isEmpty()) throw new BusinessException(ErrorCode.NOT_FOUND, "订单不存在或无权支付");
         Map<String, Object> order = orders.get(0);
         long payAmount = number(order.get("payAmount"));
-        if (number(order.get("orderStatus")) == 1) return new PreparedPayment("", "", 0, "", LocalDateTime.now(), false, true, "", false);
+        if (number(order.get("orderStatus")) == 1) return new PreparedPayment("", "", "", 0, "", LocalDateTime.now(), false, true, "", false);
         if (number(order.get("orderStatus")) != 0) throw new BusinessException(ErrorCode.CONFLICT, "订单当前不可支付");
         if (number(order.get("priceVersion")) != priceVersion) throw new BusinessException(ErrorCode.CONFLICT, "订单金额已更新,请重新确认");
         LocalDateTime expiresAt = localDateTime(order.get("expiresAt"));
@@ -100,10 +107,12 @@
                         verify_status = case when order_type = 'STORE_CONSUME' then 1 else verify_status end
                     where id = ? and order_status = 0
                     """, number(order.get("id")));
-            return new PreparedPayment("", "", 0, "", LocalDateTime.now(), false, true, "", false);
+            return new PreparedPayment("", "", "", 0, "", LocalDateTime.now(), false, true, "", false);
         }
         String openId = string(order.get("openid"));
         if (openId.isBlank()) throw new BusinessException(ErrorCode.CONFLICT, "当前用户未完成微信支付授权");
+        String huifuId = string(order.get("providerHuifuId"));
+        if (huifuId.isBlank()) throw new BusinessException(ErrorCode.CONFLICT, "商户汇付商户号未配置或尚未审核通过");
 
         List<Map<String, Object>> existingRows = rows("""
                 select payment_no, order_no, talent_id, status, attempt_no, client_payment_payload
@@ -119,22 +128,22 @@
             }
             String status = string(existing.get("status"));
             String paymentNo = string(existing.get("paymentNo"));
-            if ("SUCCEEDED".equals(status)) return new PreparedPayment(paymentNo, "", 0, "", LocalDateTime.now(), false, true, "", false);
+            if ("SUCCEEDED".equals(status)) return new PreparedPayment(paymentNo, "", "", 0, "", LocalDateTime.now(), false, true, "", false);
             if ("PREPAY_CREATED".equals(status)) {
-                return new PreparedPayment(paymentNo, "", 0, "", LocalDateTime.now(), false, false, string(existing.get("clientPaymentPayload")), false);
+                return new PreparedPayment(paymentNo, "", "", 0, "", LocalDateTime.now(), false, false, string(existing.get("clientPaymentPayload")), false);
             }
             if ("CREATED".equals(status) || "CREATE_UNKNOWN".equals(status)) {
-                return new PreparedPayment(paymentNo, "", 0, "", LocalDateTime.now(), false, false, "", true);
+                return new PreparedPayment(paymentNo, "", "", 0, "", LocalDateTime.now(), false, false, "", true);
             }
             int nextAttempt = (int) number(existing.get("attemptNo")) + 1;
             String retryPaymentNo = BusinessIdGenerator.next("P");
             insertPaymentTransaction(retryPaymentNo, order, orderNo, talentId, payAmount, idempotencyKey, nextAttempt);
-            return new PreparedPayment(retryPaymentNo, openId, payAmount, string(order.get("goodsDescription")), expiryOrDefault(expiresAt), true, false, "", false);
+            return new PreparedPayment(retryPaymentNo, huifuId, openId, payAmount, string(order.get("goodsDescription")), expiryOrDefault(expiresAt), true, false, "", false);
         }
 
         String paymentNo = BusinessIdGenerator.next("P");
         insertPaymentTransaction(paymentNo, order, orderNo, talentId, payAmount, idempotencyKey, 1);
-        return new PreparedPayment(paymentNo, openId, payAmount, string(order.get("goodsDescription")), expiryOrDefault(expiresAt), true, false, "", false);
+        return new PreparedPayment(paymentNo, huifuId, openId, payAmount, string(order.get("goodsDescription")), expiryOrDefault(expiresAt), true, false, "", false);
     }
 
     private void insertPaymentTransaction(
@@ -327,6 +336,7 @@
 
     private record PreparedPayment(
             String paymentNo,
+            String huifuId,
             String openId,
             long amountInCent,
             String goodsDescription,
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuEnterpriseOnboardingCommand.java b/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuEnterpriseOnboardingCommand.java
new file mode 100644
index 0000000..c0d28ae
--- /dev/null
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuEnterpriseOnboardingCommand.java
@@ -0,0 +1,17 @@
+package com.talentcard.server.business.payment.huifu;
+
+public record HuifuEnterpriseOnboardingCommand(
+        String applicationNo,
+        String companyName,
+        String creditCode,
+        String licenseValidityType,
+        String licenseBeginDate,
+        String registeredRegion,
+        String registeredAddress,
+        String legalName,
+        String legalIdNumber,
+        String legalValidityType,
+        String legalBeginDate,
+        String contactName,
+        String contactMobile
+) {}
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuEnterpriseOnboardingGateway.java b/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuEnterpriseOnboardingGateway.java
new file mode 100644
index 0000000..625a3cd
--- /dev/null
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuEnterpriseOnboardingGateway.java
@@ -0,0 +1,94 @@
+package com.talentcard.server.business.payment.huifu;
+
+import com.alibaba.fastjson.JSON;
+import com.huifu.bspay.sdk.opps.client.BasePayClient;
+import com.huifu.bspay.sdk.opps.core.BasePay;
+import com.huifu.bspay.sdk.opps.core.config.MerConfig;
+import com.huifu.bspay.sdk.opps.core.request.V2UserBasicdataEntRequest;
+import com.talentcard.server.common.BusinessException;
+import com.talentcard.server.common.ErrorCode;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Map;
+import org.springframework.stereotype.Service;
+
+@Service
+public class HuifuEnterpriseOnboardingGateway {
+    private static final Object SDK_LOCK = new Object();
+    private static final DateTimeFormatter DATE = DateTimeFormatter.ofPattern("yyyyMMdd");
+    private final HuifuPaymentProperties properties;
+
+    public HuifuEnterpriseOnboardingGateway(HuifuPaymentProperties properties) { this.properties = properties; }
+
+    public Result create(HuifuEnterpriseOnboardingCommand command) {
+        ensureConfigured();
+        V2UserBasicdataEntRequest request = new V2UserBasicdataEntRequest();
+        request.setReqSeqId(command.applicationNo());
+        request.setReqDate(DATE.format(LocalDate.now()));
+        request.setRegName(command.companyName());
+        request.setLicenseCode(command.creditCode());
+        request.setLicenseValidityType(command.licenseValidityType());
+        request.setLicenseBeginDate(command.licenseBeginDate());
+        request.setRegDetail(joinAddress(command.registeredRegion(), command.registeredAddress()));
+        request.setLegalName(command.legalName());
+        request.setLegalCertType("00");
+        request.setLegalCertNo(command.legalIdNumber());
+        request.setLegalCertValidityType(command.legalValidityType());
+        request.setLegalCertBeginDate(command.legalBeginDate());
+        request.setLegalCertNationality("CHN");
+        request.setContactName(command.contactName());
+        request.setContactMobile(command.contactMobile());
+        try {
+            Map<String, Object> response;
+            synchronized (SDK_LOCK) {
+                initializeSdk();
+                response = BasePayClient.request(request, false);
+            }
+            return Result.from(response);
+        } catch (BusinessException exception) {
+            throw exception;
+        } catch (Exception exception) {
+            throw new HuifuPaymentGatewayException("Huifu enterprise onboarding request failed", outcomeUnknown(exception), exception);
+        }
+    }
+
+    private void ensureConfigured() {
+        if (!properties.isEnabled()) throw new BusinessException(ErrorCode.CONFLICT, "Huifu payment is disabled");
+        if (blank(properties.getSystemId()) || blank(properties.getProductId()) || blank(properties.getPrivateKey()) || blank(properties.getPlatformPublicKey())) {
+            throw new BusinessException(ErrorCode.CONFLICT, "Huifu onboarding configuration is incomplete");
+        }
+    }
+
+    private void initializeSdk() throws Exception {
+        BasePay.debug = false;
+        BasePay.prodMode = "prod".equalsIgnoreCase(properties.getEnvironment()) ? BasePay.MODE_PROD : BasePay.MODE_TEST;
+        MerConfig config = new MerConfig();
+        config.setSysId(properties.getSystemId().trim());
+        config.setProductId(properties.getProductId().trim());
+        config.setRsaPrivateKey(properties.getPrivateKey().trim());
+        config.setRsaPublicKey(properties.getPlatformPublicKey().trim());
+        BasePay.initWithMerConfig(config);
+    }
+
+    private String joinAddress(String region, String address) {
+        String left = region == null ? "" : region.trim();
+        String right = address == null ? "" : address.trim();
+        return left.isEmpty() ? right : right.isEmpty() ? left : left + right;
+    }
+
+    private boolean blank(String value) { return value == null || value.isBlank(); }
+    private boolean outcomeUnknown(Exception exception) { String message = exception.getMessage() == null ? "" : exception.getMessage().toLowerCase(); return message.contains("timeout") || message.contains("timed out") || message.contains("no response"); }
+
+    @SuppressWarnings("unchecked")
+    public record Result(boolean accepted, String huifuId, String code, String message) {
+        static Result from(Map<String, Object> response) {
+            Object raw = response.get("data");
+            Map<String, Object> data = raw instanceof Map<?, ?> value ? (Map<String, Object>) value : raw instanceof String json && !json.isBlank() ? JSON.parseObject(json, Map.class) : response;
+            String code = value(data, "sub_resp_code", "resp_code", "code");
+            String message = value(data, "sub_resp_desc", "resp_desc", "message");
+            String huifuId = value(data, "huifu_id", "huifuId");
+            return new Result("00000000".equals(code) || "10000".equals(code) || "0".equals(code) || "SUCCESS".equalsIgnoreCase(code), huifuId, code, message);
+        }
+        private static String value(Map<String, Object> data, String... keys) { for (String key : keys) if (data.get(key) != null) return String.valueOf(data.get(key)).trim(); return ""; }
+    }
+}
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuIndividualOnboardingCommand.java b/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuIndividualOnboardingCommand.java
new file mode 100644
index 0000000..4962c8a
--- /dev/null
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuIndividualOnboardingCommand.java
@@ -0,0 +1,3 @@
+package com.talentcard.server.business.payment.huifu;
+
+public record HuifuIndividualOnboardingCommand(String applicationNo, String name, String idCardNo, String certificateValidityType, String certificateBeginDate, String mobileNo, String address) {}
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuIndividualOnboardingGateway.java b/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuIndividualOnboardingGateway.java
new file mode 100644
index 0000000..86d07b0
--- /dev/null
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuIndividualOnboardingGateway.java
@@ -0,0 +1,81 @@
+package com.talentcard.server.business.payment.huifu;
+
+import com.alibaba.fastjson.JSON;
+import com.huifu.bspay.sdk.opps.client.BasePayClient;
+import com.huifu.bspay.sdk.opps.core.BasePay;
+import com.huifu.bspay.sdk.opps.core.config.MerConfig;
+import com.huifu.bspay.sdk.opps.core.request.V2UserBasicdataIndvRequest;
+import com.talentcard.server.common.BusinessException;
+import com.talentcard.server.common.ErrorCode;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Map;
+import org.springframework.stereotype.Service;
+
+@Service
+public class HuifuIndividualOnboardingGateway {
+    private static final Object SDK_LOCK = new Object();
+    private final HuifuPaymentProperties properties;
+
+    public HuifuIndividualOnboardingGateway(HuifuPaymentProperties properties) { this.properties = properties; }
+
+    public Result create(HuifuIndividualOnboardingCommand command) {
+        ensureConfigured();
+        V2UserBasicdataIndvRequest request = new V2UserBasicdataIndvRequest();
+        request.setReqSeqId(command.applicationNo());
+        request.setReqDate(LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE));
+        request.setName(command.name());
+        request.setCertType("00");
+        request.setCertNo(command.idCardNo());
+        request.setCertValidityType(command.certificateValidityType());
+        request.setCertBeginDate(command.certificateBeginDate());
+        request.setCertNationality("CHN");
+        request.setMobileNo(command.mobileNo());
+        if (!blank(command.address())) request.setAddress(command.address());
+        try {
+            Map<String, Object> response;
+            synchronized (SDK_LOCK) {
+                initializeSdk();
+                response = BasePayClient.request(request, false);
+            }
+            return Result.from(response);
+        } catch (BusinessException exception) {
+            throw exception;
+        } catch (Exception exception) {
+            throw new HuifuPaymentGatewayException("Huifu individual onboarding request failed", outcomeUnknown(exception), exception);
+        }
+    }
+
+    private void ensureConfigured() {
+        if (!properties.isEnabled()) throw new BusinessException(ErrorCode.CONFLICT, "Huifu payment is disabled");
+        if (blank(properties.getSystemId()) || blank(properties.getProductId()) || blank(properties.getPrivateKey()) || blank(properties.getPlatformPublicKey())) throw new BusinessException(ErrorCode.CONFLICT, "Huifu onboarding configuration is incomplete");
+    }
+
+    private void initializeSdk() throws Exception {
+        BasePay.debug = false;
+        BasePay.prodMode = "prod".equalsIgnoreCase(properties.getEnvironment()) ? BasePay.MODE_PROD : BasePay.MODE_TEST;
+        MerConfig config = new MerConfig();
+        config.setSysId(properties.getSystemId().trim());
+        config.setProductId(properties.getProductId().trim());
+        config.setRsaPrivateKey(properties.getPrivateKey().trim());
+        config.setRsaPublicKey(properties.getPlatformPublicKey().trim());
+        BasePay.initWithMerConfig(config);
+    }
+
+    private boolean blank(String value) { return value == null || value.isBlank(); }
+    private boolean outcomeUnknown(Exception exception) { String message = exception.getMessage() == null ? "" : exception.getMessage().toLowerCase(); return message.contains("timeout") || message.contains("timed out") || message.contains("no response"); }
+
+    @SuppressWarnings("unchecked")
+    public record Result(boolean accepted, String huifuId, String code, String message) {
+        static Result from(Map<String, Object> response) {
+            Object raw = response.get("data");
+            Map<String, Object> data = raw instanceof Map<?, ?> value ? (Map<String, Object>) value : raw instanceof String json && !json.isBlank() ? JSON.parseObject(json, Map.class) : response;
+            String code = value(data, "sub_resp_code", "resp_code", "code");
+            String message = value(data, "sub_resp_desc", "resp_desc", "message");
+            String huifuId = value(data, "huifu_id", "huifuId");
+            return new Result("00000000".equals(code) || "10000".equals(code) || "0".equals(code) || "SUCCESS".equalsIgnoreCase(code), huifuId, code, message);
+        }
+
+        private static String value(Map<String, Object> data, String... keys) { for (String key : keys) if (data.get(key) != null) return String.valueOf(data.get(key)).trim(); return ""; }
+    }
+}
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuMiniProgramPaymentCommand.java b/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuMiniProgramPaymentCommand.java
index fd3a49d..c78705d 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuMiniProgramPaymentCommand.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuMiniProgramPaymentCommand.java
@@ -4,6 +4,7 @@
 
 public record HuifuMiniProgramPaymentCommand(
         String paymentNo,
+        String huifuId,
         String openId,
         long amountInCent,
         String goodsDescription,
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuMiniProgramPaymentGateway.java b/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuMiniProgramPaymentGateway.java
index 2b972c2..22b2675 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuMiniProgramPaymentGateway.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/payment/huifu/HuifuMiniProgramPaymentGateway.java
@@ -38,7 +38,7 @@
         V3TradePaymentJspayRequest request = new V3TradePaymentJspayRequest();
         request.setReqDate(REQUEST_DATE.format(LocalDateTime.now()));
         request.setReqSeqId(command.paymentNo());
-        request.setHuifuId(properties.getMerchantId().trim());
+        request.setHuifuId(command.huifuId().trim());
         request.setGoodsDesc(command.goodsDescription());
         request.setTransAmt(BigDecimal.valueOf(command.amountInCent(), 2).toPlainString());
         request.setTradeType("T_MINIAPP");
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/portal/DependentCardApplyController.java b/talent-card-server/src/main/java/com/talentcard/server/business/portal/DependentCardApplyController.java
index fd881b7..fb4a8fc 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/portal/DependentCardApplyController.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/portal/DependentCardApplyController.java
@@ -6,7 +6,7 @@
 import com.talentcard.server.request.DependentCardApplyRequest;
 import com.talentcard.server.request.DependentCardUnbindRequest;
 import com.talentcard.server.response.DependentCardApplyItemResponse;
-import com.talentcard.server.service.DependentCardApplyService;
+
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.responses.ApiResponses;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -21,12 +21,12 @@
 
 @Tag(name = "家属附属卡申请", description = "小程序家属附属卡申请、查询与解绑")
 @RestController
-@RequestMapping("/api/app/dependent-card-applies")
+@RequestMapping("/api/app/family-cards/applications")
 public class DependentCardApplyController {
 
-	private final DependentCardApplyService service;
+	private final FamilyCardPortalService service;
 
-	public DependentCardApplyController(DependentCardApplyService service) {
+	public DependentCardApplyController(FamilyCardPortalService service) {
 		this.service = service;
 	}
 
@@ -45,7 +45,7 @@
 	public ApiResponse<Void> submit(
 			@Valid @RequestBody DependentCardApplyRequest request
 	) {
-		service.submit(request);
+		service.createByTalentCardId(request);
 		return ApiResponse.success();
 	}
 
@@ -54,7 +54,7 @@
 	public ApiResponse<List<DependentCardApplyItemResponse>> list(
 			@RequestParam Long talentCardId
 	) {
-		return ApiResponse.success(service.list(talentCardId));
+		return ApiResponse.success(service.listByTalentCardId(talentCardId));
 	}
 
 	@Operation(summary = "解绑家属附属卡", description = "仅已通过审核的家属附属卡可以解绑")
@@ -68,6 +68,6 @@
 			@PathVariable Long id,
 			@Valid @RequestBody DependentCardUnbindRequest request
 	) {
-		return ApiResponse.success(service.unbind(id, request));
+		return ApiResponse.success(service.unbindByTalentCardId(id, request));
 	}
-}
\ No newline at end of file
+}
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/portal/FamilyCardPortalController.java b/talent-card-server/src/main/java/com/talentcard/server/business/portal/FamilyCardPortalController.java
index 28009ab..186ba7b 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/portal/FamilyCardPortalController.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/portal/FamilyCardPortalController.java
@@ -19,7 +19,6 @@
 	private final FamilyCardPortalService service;
 	public FamilyCardPortalController(FamilyCardPortalService service) { this.service = service; }
 	@GetMapping public ApiResponse<List<Map<String, Object>>> list(@RequestParam Long talentId) { return ApiResponse.success(service.list(talentId)); }
-	@PostMapping("/applications") public ApiResponse<Map<String, Object>> create(@Valid @RequestBody FamilyCardApplicationRequest request) { return ApiResponse.success(service.create(request)); }
 	@PostMapping("/unbind") public ApiResponse<Map<String, Object>> unbindMany(@Valid @RequestBody FamilyCardBatchUnbindRequest request) { return ApiResponse.success(service.unbindMany(request)); }
 	@PostMapping("/{applicationId}/unbind") public ApiResponse<Map<String, Object>> unbind(@PathVariable Long applicationId, @Valid @RequestBody FamilyCardUnbindRequest request) { return ApiResponse.success(service.unbind(applicationId, request)); }
 }
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/portal/FamilyCardPortalService.java b/talent-card-server/src/main/java/com/talentcard/server/business/portal/FamilyCardPortalService.java
index da681d4..74159fd 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/portal/FamilyCardPortalService.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/portal/FamilyCardPortalService.java
@@ -6,7 +6,10 @@
 
 import com.talentcard.server.business.BusinessIdGenerator;
 import com.talentcard.server.business.DatabaseRows;
+import com.talentcard.server.request.DependentCardApplyRequest;
+import com.talentcard.server.request.DependentCardUnbindRequest;
 import com.talentcard.server.common.BusinessException;
+import com.talentcard.server.response.DependentCardApplyItemResponse;
 import com.talentcard.server.common.ErrorCode;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
@@ -31,15 +34,49 @@
 			""", talentId).stream().map(DatabaseRows::normalize).map(this::view).toList();
 	}
 
+	@Transactional(readOnly = true)
+	public List<DependentCardApplyItemResponse> listByTalentCardId(Long talentCardId) {
+		Long talentId = talentIdByCardId(talentCardId);
+		return jdbc.queryForList("""
+			select f.id, f.family_name, f.family_mobile, f.relationship, f.status
+			from family_card_application f
+			where f.deleted = 0 and f.talent_id = ? and f.status <> '\u5df2\u89e3\u7ed1'
+			order by f.apply_time desc, f.id desc
+			""", talentId).stream().map(DatabaseRows::normalize).map(this::dependentCardItem).toList();
+	}
+
+	@Transactional
+	public void createByTalentCardId(DependentCardApplyRequest request) {
+		Long talentId = talentIdByCardId(request.talentCardId());
+		create(new FamilyCardApplicationRequest(
+				talentId, request.dependentName(), request.dependentIdCardNo(),
+				request.dependentMobile(), request.relation()
+		));
+	}
+
+	@Transactional
+	public DependentCardApplyItemResponse unbindByTalentCardId(Long id, DependentCardUnbindRequest request) {
+		Long talentId = talentIdByCardId(request.talentCardId());
+		unbind(id, new FamilyCardUnbindRequest(talentId));
+		List<Map<String, Object>> rows = jdbc.queryForList("""
+			select f.id, f.family_name, f.family_mobile, f.relationship, f.status
+			from family_card_application f
+			where f.id = ? and f.talent_id = ? and f.deleted = 0
+			limit 1
+			""", id, talentId).stream().map(DatabaseRows::normalize).toList();
+		if (rows.isEmpty()) throw new BusinessException(ErrorCode.NOT_FOUND, "\u5bb6\u5c5e\u5361\u7533\u8bf7\u4e0d\u5b58\u5728");
+		return dependentCardItem(rows.get(0));
+	}
 	@Transactional
 	public Map<String, Object> create(FamilyCardApplicationRequest request) {
 		Map<String, Object> owner = ownerCard(request.talentId());
 		if (!"卓越人才卡".equals(string(owner.get("cardLevel")))) throw new BusinessException(ErrorCode.CONFLICT, "家庭附属卡仅向卓越人才卡用户开放");
-		if (number(owner.get("isFamilyCardEnabled")) == null || number(owner.get("isFamilyCardEnabled")) != 1 || number(owner.get("familyCardLimit")) == null || number(owner.get("familyCardLimit")) < 1) throw new BusinessException(ErrorCode.CONFLICT, "当前人才卡暂未开通家庭附属卡申请");
 		if (!List.of("配偶", "父母", "子女", "其他").contains(request.relationship().trim())) throw new BusinessException(ErrorCode.BAD_REQUEST, "不支持的家属关系");
 		if (!validIdCard(request.idCard().trim())) throw new BusinessException(ErrorCode.BAD_REQUEST, "身份证号格式不正确");
-		Integer occupied = jdbc.queryForObject("select count(*) from family_card_application where deleted = 0 and talent_id = ? and status in ('待审核', '已通过')", Integer.class, request.talentId());
-		if (occupied != null && occupied >= number(owner.get("familyCardLimit"))) throw new BusinessException(ErrorCode.CONFLICT, "家庭附属卡申请数量已达上限");
+		Integer submitted = jdbc.queryForObject("select count(*) from family_card_application where deleted = 0 and talent_id = ? and status in ('\u5f85\u5ba1\u6838', '\u5df2\u901a\u8fc7', '\u5df2\u9a73\u56de')", Integer.class, request.talentId());
+		if (submitted != null && submitted >= 3) throw new BusinessException(ErrorCode.CONFLICT, "\u540c\u4e00\u5f20\u4e3b\u5361\u6700\u591a\u63d0\u4ea43\u4e2a\u5bb6\u5c5e\u9644\u5c5e\u5361\u7533\u8bf7");
+		Integer occupied = jdbc.queryForObject("select count(*) from family_card_application where deleted = 0 and talent_id = ? and status in ('\u5f85\u5ba1\u6838', '\u5df2\u901a\u8fc7')", Integer.class, request.talentId());
+		if (occupied != null && occupied >= 2) throw new BusinessException(ErrorCode.CONFLICT, "\u8be5\u4e3b\u5361\u5bb6\u5c5e\u9644\u5c5e\u5361\u540d\u989d\u5df2\u6ee1\uff0c\u6700\u591a\u7ed1\u5b9a2\u4e2a");
 		Integer duplicate = jdbc.queryForObject("""
 			select count(*) from family_card_application
 			where deleted = 0 and talent_id = ? and id_card_no = ? and status in ('待审核', '已通过')
@@ -89,6 +126,38 @@
 		return Map.of("applicationIds", applicationIds, "unbound", true);
 	}
 
+	private Long talentIdByCardId(Long talentCardId) {
+		if (talentCardId == null || talentCardId <= 0) throw new BusinessException(ErrorCode.NOT_FOUND, "\u4e3b\u5361\u4e0d\u5b58\u5728\u6216\u5df2\u5931\u6548");
+		List<Map<String, Object>> rows = jdbc.queryForList("""
+			select talent_id from talent_card
+			where id = ? and deleted = 0 and card_type = 'PERSONAL' and status = 1
+			limit 1
+			""", talentCardId).stream().map(DatabaseRows::normalize).toList();
+		if (rows.isEmpty()) throw new BusinessException(ErrorCode.NOT_FOUND, "\u4e3b\u5361\u4e0d\u5b58\u5728\u6216\u5df2\u5931\u6548");
+		return number(rows.get(0).get("talentId"));
+	}
+
+	private DependentCardApplyItemResponse dependentCardItem(Map<String, Object> row) {
+		Integer status = switch (string(row.get("status"))) {
+			case "\u5f85\u5ba1\u6838" -> 0;
+			case "\u5df2\u901a\u8fc7" -> 1;
+			case "\u5df2\u9a73\u56de" -> 2;
+			case "\u5df2\u89e3\u7ed1" -> 3;
+			default -> -1;
+		};
+		String statusText = switch (status) {
+			case 0 -> "\u5f85\u5ba1\u6838";
+			case 1 -> "\u5df2\u901a\u8fc7";
+			case 2 -> "\u5df2\u9a73\u56de";
+			case 3 -> "\u5df2\u89e3\u7ed1";
+			default -> "\u672a\u77e5";
+		};
+		return new DependentCardApplyItemResponse(
+				number(row.get("id")), string(row.get("familyName")),
+				maskMobile(string(row.get("familyMobile"))), status, statusText,
+				string(row.get("relationship"))
+		);
+	}
 	private Map<String, Object> ownerCard(Long talentId) {
 		List<Map<String, Object>> rows = jdbc.queryForList("""
 			select c.card_no, c.card_level, c.is_family_card_enabled, c.family_card_limit, u.name as talent_name, u.mobile as talent_mobile
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/portal/ProductPriceCalculator.java b/talent-card-server/src/main/java/com/talentcard/server/business/portal/ProductPriceCalculator.java
new file mode 100644
index 0000000..5bba732
--- /dev/null
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/portal/ProductPriceCalculator.java
@@ -0,0 +1,138 @@
+package com.talentcard.server.business.portal;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import tools.jackson.core.type.TypeReference;
+import tools.jackson.databind.ObjectMapper;
+
+final class ProductPriceCalculator {
+	private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+	private static final TypeReference<List<Map<String, Object>>> RIGHTS_TYPE = new TypeReference<>() {};
+
+	private ProductPriceCalculator() {
+	}
+
+	static ProductPrice calculate(Object originalPrice, Object salePrice, Object rightsConfig, Object cardLevel) {
+		return calculate(originalPrice, salePrice, rightsConfig, cardLevel, 0);
+	}
+
+	static ProductPrice calculate(Object originalPrice, Object salePrice, Object rightsConfig, Object cardLevel, int usedFreeCount) {
+		BigDecimal fallbackSale = money(salePrice);
+		BigDecimal original = money(originalPrice);
+		if (original.signum() <= 0) {
+			original = fallbackSale;
+		}
+		BigDecimal calculatedSale = fallbackSale.signum() > 0 ? fallbackSale : original;
+		Map<String, Object> right = matchingRight(rightsConfig, cardLevel);
+		if (right != null && original.signum() > 0) {
+			boolean free = booleanValue(right.get("free"));
+			int limitCount = intValue(right.get("limitCount"));
+			boolean freeAvailable = free && (limitCount <= 0 || usedFreeCount < limitCount);
+			if (freeAvailable) {
+				calculatedSale = BigDecimal.ZERO;
+			} else {
+				BigDecimal discount = discountValue(right.get(free ? "exceedDiscount" : "discount"));
+				if (discount.signum() > 0) {
+					calculatedSale = original.multiply(discount)
+							.divide(BigDecimal.TEN, 2, RoundingMode.HALF_UP);
+				}
+			}
+		}
+		return new ProductPrice(original, calculatedSale.setScale(2, RoundingMode.HALF_UP), right != null);
+	}
+
+	static ProductRight productRight(Object rightsConfig, Object cardLevel) {
+		Map<String, Object> right = matchingRight(rightsConfig, cardLevel);
+		if (right == null) return null;
+		return new ProductRight(
+				booleanValue(right.get("free")),
+				intValue(right.get("limitCount")),
+				text(right.get("resetCycle")));
+	}
+
+	private static Map<String, Object> matchingRight(Object rightsConfig, Object cardLevel) {
+		String normalizedCardLevel = normalizeCardLevel(cardLevel);
+		if (normalizedCardLevel.isBlank() || rightsConfig == null) return null;
+		for (Map<String, Object> right : parseRights(rightsConfig)) {
+			if (!booleanValue(right.get("enabled"))) continue;
+			if (normalizedCardLevel.equals(normalizeCardLevel(right.get("level")))) return right;
+		}
+		return null;
+	}
+
+	private static List<Map<String, Object>> parseRights(Object value) {
+		if (value instanceof List<?> list) {
+			return list.stream()
+					.filter(Map.class::isInstance)
+					.map(item -> (Map<String, Object>) item)
+					.toList();
+		}
+		String text = String.valueOf(value).trim();
+		if (text.isBlank()) return List.of();
+		try {
+			return OBJECT_MAPPER.readValue(text, RIGHTS_TYPE);
+		} catch (Exception ignored) {
+			return List.of();
+		}
+	}
+
+	private static String normalizeCardLevel(Object value) {
+		if (value == null) return "";
+		String raw = String.valueOf(value).trim();
+		String upper = raw.toUpperCase(Locale.ROOT);
+		if (upper.contains("EXCELLENT") || raw.contains("\u5353\u8d8a")) return "EXCELLENT";
+		if (upper.contains("OUTSTANDING") || raw.contains("\u6770\u51fa")) return "OUTSTANDING";
+		if (upper.contains("ELITE") || raw.contains("\u83c1\u82f1") || raw.contains("\u7cbe\u82f1")) return "ELITE";
+		return upper;
+	}
+
+	private static boolean booleanValue(Object value) {
+		if (value instanceof Boolean bool) return bool;
+		if (value instanceof Number number) return number.intValue() != 0;
+		String text = value == null ? "" : String.valueOf(value).trim();
+		return "true".equalsIgnoreCase(text) || "1".equals(text) || "yes".equalsIgnoreCase(text);
+	}
+
+	private static int intValue(Object value) {
+		if (value instanceof Number number) return number.intValue();
+		try {
+			String text = value == null ? "" : String.valueOf(value).trim();
+			if (text.isBlank()) return 0;
+			return new BigDecimal(text).intValue();
+		} catch (Exception ignored) {
+			return 0;
+		}
+	}
+
+	private static String text(Object value) {
+		return value == null ? "" : String.valueOf(value).trim();
+	}
+
+	private static BigDecimal discountValue(Object value) {
+		BigDecimal discount = money(value);
+		if (discount.compareTo(BigDecimal.ZERO) <= 0 || discount.compareTo(BigDecimal.TEN) > 0) {
+			return BigDecimal.ZERO;
+		}
+		return discount;
+	}
+
+	private static BigDecimal money(Object value) {
+		if (value == null) return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
+		try {
+			if (value instanceof BigDecimal decimal) return decimal.setScale(2, RoundingMode.HALF_UP);
+			return new BigDecimal(String.valueOf(value).trim()).setScale(2, RoundingMode.HALF_UP);
+		} catch (Exception ignored) {
+			return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
+		}
+	}
+
+	record ProductPrice(BigDecimal originalPrice, BigDecimal salePrice, boolean personalized) {
+	}
+
+	record ProductRight(boolean free, int limitCount, String resetCycle) {
+	}
+}
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/portal/TalentCardPortalService.java b/talent-card-server/src/main/java/com/talentcard/server/business/portal/TalentCardPortalService.java
index f3c9422..35a6529 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/portal/TalentCardPortalService.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/portal/TalentCardPortalService.java
@@ -132,7 +132,7 @@
 		String level = value(card.get("cardLevel"), "人才卡");
 		result.put("cardLevel", level);
 		result.put("familyCardLimit", ((Number) card.getOrDefault("familyCardLimit", 0)).intValue());
-		if (!"卓越人才卡".equals(level)) {
+		if (!isExcellenceCardLevel(level)) {
 			result.put("unavailableReason", "家庭附属卡仅向卓越人才卡用户开放");
 			return result;
 		}
@@ -140,14 +140,14 @@
 			result.put("unavailableReason", cardStatusText(cardStatus(card)) + ",暂不能使用家庭附属卡服务");
 			return result;
 		}
-		if (((Number) card.getOrDefault("isFamilyCardEnabled", 0)).intValue() != 1) {
-			result.put("unavailableReason", "当前人才卡暂未开通家庭附属卡服务");
-			return result;
-		}
-		if (((Number) card.getOrDefault("familyCardLimit", 0)).intValue() < 1) {
-			result.put("unavailableReason", "当前人才卡暂未配置家庭附属卡名额");
-			return result;
-		}
+//		if (((Number) card.getOrDefault("isFamilyCardEnabled", 0)).intValue() != 1) {
+//			result.put("unavailableReason", "当前人才卡暂未开通家庭附属卡服务");
+//			return result;
+//		}
+//		if (((Number) card.getOrDefault("familyCardLimit", 0)).intValue() < 1) {
+//			result.put("unavailableReason", "当前人才卡暂未配置家庭附属卡名额");
+//			return result;
+//		}
 		result.put("enabled", true);
 		result.put("unavailableReason", "");
 		return result;
@@ -286,6 +286,12 @@
 		return java.util.Optional.of(result);
 	}
 
+	private boolean isExcellenceCardLevel(String level) {
+		if (level == null) return false;
+		String normalized = level.trim().toUpperCase(Locale.ROOT);
+		return "EXCELLENCE".equals(normalized) || level.contains("卓越") || level.contains("鍗撹秺");
+	}
+
 	private String cardStatus(Map<String, Object> card) {
 		if (((Number) card.getOrDefault("talentStatus", 1)).intValue() != 1) return "FROZEN";
 		if (((Number) card.getOrDefault("cardStatus", 0)).intValue() == 0) return "FROZEN";
diff --git a/talent-card-server/src/main/java/com/talentcard/server/service/DependentCardApplyService.java b/talent-card-server/src/main/java/com/talentcard/server/service/DependentCardApplyService.java
index 668919b..ff59058 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/service/DependentCardApplyService.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/service/DependentCardApplyService.java
@@ -145,14 +145,14 @@
 		if (!ELITE_CARD_LEVEL.equals(card.getCardLevel())) {
 			throw new BusinessException(ErrorCode.INTERNAL_ERROR, "仅卓越人才卡可申请家属附属卡");
 		}
-		if (!Integer.valueOf(1).equals(card.getIsFamilyCardEnabled())) {
-			throw new BusinessException(ErrorCode.INTERNAL_ERROR, "该主卡未开通家属附属卡申请权限");
-		}
-		Integer configuredLimit = card.getFamilyCardLimit();
-		if (configuredLimit == null || configuredLimit <= 0) {
-			throw new BusinessException(ErrorCode.INTERNAL_ERROR, "该主卡未配置家属附属卡名额");
-		}
-		return Math.min(configuredLimit, MAX_FAMILY_CARD_COUNT);
+//		if (!Integer.valueOf(1).equals(card.getIsFamilyCardEnabled())) {
+//			throw new BusinessException(ErrorCode.INTERNAL_ERROR, "该主卡未开通家属附属卡申请权限");
+//		}
+//		Integer configuredLimit = card.getFamilyCardLimit();
+//		if (configuredLimit == null || configuredLimit <= 0) {
+//			throw new BusinessException(ErrorCode.INTERNAL_ERROR, "该主卡未配置家属附属卡名额");
+//		}
+		return Math.min(2, MAX_FAMILY_CARD_COUNT);
 	}
 
 	private void recordStatus(
diff --git a/talent-card-server/src/main/resources/application.yml b/talent-card-server/src/main/resources/application.yml
index 9516b0e..a7ce1e2 100644
--- a/talent-card-server/src/main/resources/application.yml
+++ b/talent-card-server/src/main/resources/application.yml
@@ -23,7 +23,7 @@
       repositories:
         enabled: false
   flyway:
-    enabled: true
+    enabled: false
     locations: classpath:db/migration
     baseline-on-migrate: true
   jpa:
diff --git a/talent-card-server/src/main/resources/db/migration/V93__merchant_onboarding_huifu_kyc.sql b/talent-card-server/src/main/resources/db/migration/V93__merchant_onboarding_huifu_kyc.sql
new file mode 100644
index 0000000..d47c175
--- /dev/null
+++ b/talent-card-server/src/main/resources/db/migration/V93__merchant_onboarding_huifu_kyc.sql
@@ -0,0 +1,3 @@
+alter table merchant_onboarding_application add column subject_id_no_encrypted varchar(512) default null after subject_id_no_masked;
+alter table merchant_onboarding_application add column subject_cert_validity_type varchar(8) default null after subject_id_no_encrypted;
+alter table merchant_onboarding_application add column subject_cert_begin_date char(8) default null after subject_cert_validity_type;
diff --git a/talent-card-server/src/main/resources/db/migration/V98__merchant_coupon_talent_level_quantities.sql b/talent-card-server/src/main/resources/db/migration/V98__merchant_coupon_talent_level_quantities.sql
new file mode 100644
index 0000000..cd961da
--- /dev/null
+++ b/talent-card-server/src/main/resources/db/migration/V98__merchant_coupon_talent_level_quantities.sql
@@ -0,0 +1,2 @@
+alter table merchant_coupon
+    add column talent_level_quantities varchar(512) default null comment 'talent card level quantities JSON';

--
Gitblit v1.7.1